Broken Link Checker - Version 0.9

Version Description

  • Masquerade as IE 7 when using the Snoopy library to check links. Should prevent some false positives.
  • Fixed relative URL handling (yet again). It'll work this time, honest ;)
  • Fixed post titles being displayed incorrectly on multilingual blogs (props Konstanin Zhilenko)
  • Misc fixes/comments.
  • "Unlink" works properly now.
  • Additional source code comments.
  • Don't try to display icons in email notifications. It didn't work anyway.
  • Use AJAX nonces for additional security.
  • General code cleanup.
  • Email notifications about broken links.
  • "Recheck" bulk action.
  • Check comment links.
  • Suspend checking if the server is overloaded (on by default).
  • Icons for broken links and redirects.
  • Fixed some UI glitches.
  • "Discard" gone, replaced by "Not broken".
  • "Exclude" gone from action links.
  • Better handling of false positives.
  • FTP, mailto:, javascript: and other links with unsupported protocols now show up in the All links list.
Download this release

Release Info

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

Code changes from version 0.8.1 to 0.9

broken-link-checker.php CHANGED
@@ -3,8 +3,8 @@
3
  /*
4
  Plugin Name: Broken Link Checker
5
  Plugin URI: http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/
6
- Description: Checks your posts for broken links and missing images and notifies you on the dashboard if any are found.
7
- Version: 0.8.1
8
  Author: Janis Elsts
9
  Author URI: http://w-shadow.com/blog/
10
  Text Domain: broken-link-checker
@@ -15,44 +15,67 @@ Created by Janis Elsts (email : whiteshadow@w-shadow.com)
15
  MySQL 4.0 compatibility by Jeroen (www.yukka.eu)
16
  */
17
 
 
 
 
 
 
 
18
  /*
19
- //FirePHP for debugging
20
- define('BLC_DEBUG', true);
21
- if ( !class_exists('FB') ) {
22
- require_once 'FirePHPCore/fb.php4';
 
 
23
  }
24
- //FB::setEnabled(false);
25
-
26
  //to comment out all calls : (^[^\/]*)(FB::) -> $1\/\/$2
27
  //to uncomment : \/\/(\s*FB::) -> $1
28
  //*/
29
 
30
- //Make sure some useful constants are defined
31
- if ( ! defined( 'WP_CONTENT_URL' ) )
32
- define( 'WP_CONTENT_URL', get_option( 'siteurl' ) . '/wp-content' );
33
- if ( ! defined( 'WP_CONTENT_DIR' ) )
34
- define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
35
- if ( ! defined( 'WP_PLUGIN_URL' ) )
36
- define( 'WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins' );
37
- if ( ! defined( 'WP_PLUGIN_DIR' ) )
38
- define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
39
-
40
- //The HTTP code of a link record can be set to one of these in some special circumstances
41
- if ( ! defined('BLC_CHECKING') )
42
- define('BLC_CHECKING', 1); //The link is currently being checked. If this state persists, suspect a glitch.
43
- if ( ! defined('BLC_TIMEOUT') )
44
- define('BLC_TIMEOUT', 0); //The code used for links that timed out and didn't return an actual response.
 
 
 
 
 
 
 
 
45
 
46
  //Load and initialize the plugin's configuration
 
47
  $blc_directory = dirname(__FILE__);
48
  require $blc_directory . '/config-manager.php';
 
 
49
  $blc_config_manager = new blcConfigurationManager(
50
  //Save the plugin's configuration into this DB option
51
  'wsblc_options',
52
  //Initialize default settings
53
  array(
54
- 'max_execution_time' => 5*60, //How long the worker instance may run, at most.
55
- 'check_threshold' => 72, //Check each link every 72 hours.
 
 
 
 
 
 
56
 
57
  'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
58
  'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
@@ -60,37 +83,226 @@ $blc_config_manager = new blcConfigurationManager(
60
  'mark_removed_links' => false, //Whether to add the removed_link class when un-linking a link.
61
  'removed_link_css' => ".removed_link, a.removed_link {\n\ttext-decoration: line-through;\n}",
62
 
63
- 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
64
- 'recheck_count' => 3, //[Internal] How many times a broken link should be re-checked (slightly buggy)
 
 
 
 
 
 
 
65
 
66
- //These three are currently ignored. Everything is checked by default.
67
- 'check_posts' => true,
68
- 'check_custom_fields' => true,
69
- 'check_blogroll' => true,
70
-
71
  'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
72
 
73
  'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
74
 
75
- 'need_resynch' => false, //[Internal flag]
76
- 'current_db_version' => 0, //The current version of the plugin's tables
77
 
78
  'custom_tmp_dir' => '', //The lockfile will be stored in this directory.
79
  //If this option is not set, the plugin's own directory or the
80
  //system-wide /tmp directory will be used instead.
81
 
82
- 'timeout' => 30, //Links that take longer than this to respond will be treated as broken.
83
  )
84
  );
85
 
86
- if ( !is_admin() ){
87
- //This is user-side request, so we may need to do is run the broken link highlighter.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  if ( $blc_config_manager->options['mark_broken_links'] ){
89
- //Load some utilities (used by the higlighter) and the highlighter itself
90
  require $blc_directory . '/utility-class.php';
91
- require $blc_directory . '/highlighter-class.php';
92
- $blc_link_highlighter = new blcLinkHighlighter( $blc_config_manager->options['broken_link_css'] );
93
  }
 
94
  //And possibly inject the CSS for removed links
95
  if ( $blc_config_manager->options['mark_removed_links'] && !empty($blc_config_manager->options['removed_link_css']) ){
96
  function blc_print_remove_link_css(){
@@ -99,14 +311,10 @@ if ( !is_admin() ){
99
  }
100
  add_action('wp_head', 'blc_print_remove_link_css');
101
  }
102
- } else {
103
- //Load everything
104
- require $blc_directory . '/utility-class.php';
105
- require $blc_directory . '/instance-classes.php';
106
- require $blc_directory . '/link-classes.php';
107
- require $blc_directory . '/core.php';
108
-
109
- $ws_link_checker = new wsBrokenLinkChecker( __FILE__ , $blc_config_manager );
110
  }
111
 
 
 
 
 
112
  ?>
3
  /*
4
  Plugin Name: Broken Link Checker
5
  Plugin URI: http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/
6
+ Description: Checks your blog for broken links and missing images and notifies you on the dashboard if any are found.
7
+ Version: 0.9
8
  Author: Janis Elsts
9
  Author URI: http://w-shadow.com/blog/
10
  Text Domain: broken-link-checker
15
  MySQL 4.0 compatibility by Jeroen (www.yukka.eu)
16
  */
17
 
18
+ /***********************************************
19
+ Debugging stuff
20
+ ************************************************/
21
+
22
+ define('BLC_DEBUG', false);
23
+
24
  /*
25
+ if ( constant('BLC_DEBUG') ){
26
+ //Load FirePHP for debug logging
27
+ if ( !class_exists('FB') ) {
28
+ require_once 'FirePHPCore/fb.php4';
29
+ }
30
+ //FB::setEnabled(false);
31
  }
 
 
32
  //to comment out all calls : (^[^\/]*)(FB::) -> $1\/\/$2
33
  //to uncomment : \/\/(\s*FB::) -> $1
34
  //*/
35
 
36
+ /***********************************************
37
+ Constants
38
+ ************************************************/
39
+
40
+ /*
41
+ For performance, some internal APIs used for retrieving multiple links, instances or containers
42
+ can take an optional "$purpose" argument. Those APIs will try to use this argument to pre-load
43
+ any DB data required for the specified purpose ahead of time.
44
+
45
+ For example, if you're loading a bunch of link containers for the purposes of parsing them and
46
+ thus set $purpose to BLC_FOR_PARSING, the relevant container managers will (if applicable) precache
47
+ the parse-able fields in each returned container object. Still, setting $purpose to any particular
48
+ value does not *guarantee* any data will be preloaded - it's only a suggestion that it should.
49
+
50
+ The currently supported values for the $purpose argument are :
51
+ */
52
+ define('BLC_FOR_EDITING', 'edit');
53
+ define('BLC_FOR_PARSING', 'parse');
54
+ define('BLC_FOR_DISPLAY', 'display');
55
+
56
+ /***********************************************
57
+ Configuration
58
+ ************************************************/
59
 
60
  //Load and initialize the plugin's configuration
61
+ global $blc_directory;
62
  $blc_directory = dirname(__FILE__);
63
  require $blc_directory . '/config-manager.php';
64
+
65
+ global $blc_config_manager;
66
  $blc_config_manager = new blcConfigurationManager(
67
  //Save the plugin's configuration into this DB option
68
  'wsblc_options',
69
  //Initialize default settings
70
  array(
71
+ 'max_execution_time' => 5*60, //(in seconds) How long the worker instance may run, at most.
72
+ 'check_threshold' => 72, //(in hours) Check each link every 72 hours.
73
+
74
+ 'recheck_count' => 3, //How many times a broken link should be re-checked.
75
+ 'recheck_threshold' => 20*60, //(in seconds) Re-check broken links after 20 minutes.
76
+
77
+ 'run_in_dashboard' => true, //Run the link checker algo. continuously while the Dashboard is open.
78
+ 'run_via_cron' => true, //Run it hourly via WordPress pseudo-cron.
79
 
80
  'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
81
  'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
83
  'mark_removed_links' => false, //Whether to add the removed_link class when un-linking a link.
84
  'removed_link_css' => ".removed_link, a.removed_link {\n\ttext-decoration: line-through;\n}",
85
 
86
+ 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
87
+
88
+ 'send_email_notifications' => false,//Whether to send email notifications about broken links
89
+ 'notification_schedule' => 'daily', //How often (at most) notifications will be sent. Possible values : 'daily', 'weekly'
90
+ 'last_notification_sent' => 0, //When the last email notification was send (Unix timestamp)
91
+
92
+ 'server_load_limit' => 2, //Stop parsing stuff & checking links if the 1-minute load average
93
+ //goes over this value. Only works on Linux servers. 0 = no limit.
94
+ 'enable_load_limit' => true, //Enable/disable load monitoring.
95
 
 
 
 
 
 
96
  'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
97
 
98
  'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
99
 
100
+ 'need_resynch' => false, //[Internal flag] True if there are unparsed items.
101
+ 'current_db_version' => 0, //The currently set-up version of the plugin's tables
102
 
103
  'custom_tmp_dir' => '', //The lockfile will be stored in this directory.
104
  //If this option is not set, the plugin's own directory or the
105
  //system-wide /tmp directory will be used instead.
106
 
107
+ 'timeout' => 30, //(in seconds) Links that take longer than this to respond will be treated as broken.
108
  )
109
  );
110
 
111
+ /***********************************************
112
+ Global functions
113
+ ************************************************/
114
+
115
+ /**
116
+ * Initialize link containers.
117
+ *
118
+ * @uses do_action() on 'blc_init_containers' after all built-in link containers have been loaded.
119
+ * @see blcContainer
120
+ *
121
+ * @return void
122
+ */
123
+ function blc_init_containers(){
124
+ global $blc_directory;
125
+
126
+ //Only init once.
127
+ static $done = false;
128
+ if ( $done ) return;
129
+
130
+ //Load the base container classes
131
+ require $blc_directory . '/includes/containers.php';
132
+
133
+ //Load built-in link containers
134
+ require $blc_directory . '/includes/containers/post.php';
135
+ require $blc_directory . '/includes/containers/blogroll.php';
136
+ require $blc_directory . '/includes/containers/custom_field.php';
137
+ require $blc_directory . '/includes/containers/comment.php';
138
+ require $blc_directory . '/includes/containers/dummy.php';
139
+
140
+ //Notify other plugins that they may register their custom containers now.
141
+ do_action('blc_init_containers');
142
+
143
+ $done = true;
144
+ }
145
+
146
+ /**
147
+ * Initialize link parsers.
148
+ *
149
+ * @uses do_action() on 'blc_init_parsers' after all built-in parsers have been loaded.
150
+ *
151
+ * @return void
152
+ */
153
+ function blc_init_parsers(){
154
+ global $blc_directory;
155
+
156
+ //Only init once.
157
+ static $done = false;
158
+ if ( $done ) return;
159
+
160
+ //Load the base parser classes
161
+ require $blc_directory . '/includes/parsers.php';
162
+
163
+ //Load built-in parsers
164
+ require $blc_directory . '/includes/parsers/html_link.php';
165
+ require $blc_directory . '/includes/parsers/image.php';
166
+ require $blc_directory . '/includes/parsers/metadata.php';
167
+ require $blc_directory . '/includes/parsers/url_field.php';
168
+
169
+ do_action('blc_init_parsers');
170
+ $done = true;
171
+ }
172
+
173
+ /**
174
+ * Initialize link checkers.
175
+ *
176
+ * @uses do_action() on 'blc_init_checkers' after all built-in checker implementations have been loaded.
177
+ *
178
+ * @return void
179
+ */
180
+ function blc_init_checkers(){
181
+ global $blc_directory;
182
+
183
+ //Only init once.
184
+ static $done = false;
185
+ if ( $done ) return;
186
+
187
+ //Load the base classes for link checker algorithms
188
+ require $blc_directory . '/includes/checkers.php';
189
+
190
+ //Load built-in checker implementations (only HTTP at the time)
191
+ require $blc_directory . '/includes/checkers/http.php';
192
+
193
+ do_action('blc_init_checkers');
194
+ $done = true;
195
+ }
196
+
197
+ /**
198
+ * Load and register all containers, parsers and checkers.
199
+ *
200
+ * @return void
201
+ */
202
+ function blc_init_all_components(){
203
+ blc_init_containers();
204
+ blc_init_parsers();
205
+ blc_init_checkers();
206
+ }
207
+
208
+ /**
209
+ * Get the configuration object used by Broken Link Checker.
210
+ *
211
+ * @return blcConfigurationManager
212
+ */
213
+ function blc_get_configuration(){
214
+ return $GLOBALS['blc_config_manager'];
215
+ }
216
+
217
+ /**
218
+ * Notify the link checker that there are unsynched items
219
+ * that might contain links (e.g. a new or edited post).
220
+ *
221
+ * @return void
222
+ */
223
+ function blc_got_unsynched_items(){
224
+ $conf = blc_get_configuration();
225
+
226
+ if ( !$conf->options['need_resynch'] ){
227
+ $conf->options['need_resynch'] = true;
228
+ $conf->save_options();
229
+ }
230
+ }
231
+
232
+ /**
233
+ * (Re)create synchronization records for all containers and mark them all as unparsed.
234
+ *
235
+ * @param bool $forced If true, the plugin will recreate all synch. records from scratch.
236
+ * @return void
237
+ */
238
+ function blc_resynch( $forced = false ){
239
+ global $wpdb;
240
+
241
+ if ( $forced ){
242
+ //Drop all synchronization records
243
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
244
+ }
245
+
246
+ //(Re)create and update synch. records for all container types.
247
+ blc_resynch_containers($forced);
248
+
249
+ //Delete invalid instances
250
+ blc_cleanup_instances();
251
+ //Delete orphaned links
252
+ blc_cleanup_links();
253
+
254
+ //All done.
255
+ blc_got_unsynched_items();
256
+ }
257
+
258
+ /**
259
+ * Add a weekly Cron schedule for email notifications
260
+ *
261
+ * @param array $schedules Existing Cron schedules.
262
+ * @return array
263
+ */
264
+ function blc_cron_schedules($schedules){
265
+ if ( !isset($schedules['weekly']) ){
266
+ $schedules['weekly'] = array(
267
+ 'interval' => 604800,
268
+ 'display' => __('Once Weekly')
269
+ );
270
+ }
271
+ return $schedules;
272
+ }
273
+ add_filter('cron_schedules', 'blc_cron_schedules');
274
+
275
+ /***********************************************
276
+ Main functionality
277
+ ************************************************/
278
+
279
+ if ( is_admin() || defined('DOING_CRON') ){
280
+
281
+ //It's an admin-side or Cron request. Load everything.
282
+ add_action('plugins_loaded', 'blc_init_all_components');
283
+
284
+ require $blc_directory . '/utility-class.php';
285
+ require $blc_directory . '/includes/links.php';
286
+ require $blc_directory . '/includes/instances.php';
287
+
288
+ require $blc_directory . '/core.php';
289
+
290
+ $ws_link_checker = new wsBrokenLinkChecker( __FILE__ , $blc_config_manager );
291
+
292
+ } else {
293
+
294
+ //This is user-side request, so we don't need to load the core.
295
+ //We do need to load containers (for the purposes of catching
296
+ //new comments and such).
297
+ add_action('plugins_loaded', 'blc_init_containers');
298
+
299
+ //If broken links need to be marked, we also need to load parsers
300
+ //(used to find & modify links) and utilities (used by some parsers).
301
  if ( $blc_config_manager->options['mark_broken_links'] ){
 
302
  require $blc_directory . '/utility-class.php';
303
+ add_action('plugins_loaded', 'blc_init_parsers');
 
304
  }
305
+
306
  //And possibly inject the CSS for removed links
307
  if ( $blc_config_manager->options['mark_removed_links'] && !empty($blc_config_manager->options['removed_link_css']) ){
308
  function blc_print_remove_link_css(){
311
  }
312
  add_action('wp_head', 'blc_print_remove_link_css');
313
  }
 
 
 
 
 
 
 
 
314
  }
315
 
316
+
317
+
318
+
319
+
320
  ?>
core.php CHANGED
@@ -1,8 +1,5 @@
1
  <?php
2
 
3
- //The plugin will use Snoopy in case CURL is not available
4
- if (!class_exists('Snoopy')) require_once(ABSPATH. WPINC . '/class-snoopy.php');
5
-
6
  /**
7
  * Simple function to replicate PHP 5 behaviour
8
  */
@@ -22,13 +19,11 @@ class wsBrokenLinkChecker {
22
  var $loader;
23
  var $my_basename = '';
24
 
25
- var $db_version = 3;
26
 
27
  var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
28
  var $lockfile_handle = null;
29
 
30
- var $native_filters = null;
31
-
32
  /**
33
  * wsBrokenLinkChecker::wsBrokenLinkChecker()
34
  * Class constructor
@@ -40,30 +35,17 @@ class wsBrokenLinkChecker {
40
  function wsBrokenLinkChecker ( $loader, $conf ) {
41
  global $wpdb;
42
 
43
- $this->loader = $loader;
44
  $this->conf = $conf;
45
-
46
- add_action('activate_' . plugin_basename( $this->loader ), array(&$this,'activation'));
47
  $this->my_basename = plugin_basename( $this->loader );
 
 
 
48
 
49
  add_action('init', array(&$this,'load_language'));
50
 
51
  add_action('admin_menu', array(&$this,'admin_menu'));
52
 
53
- //These hooks update the plugin's internal records when posts are added, deleted or modified.
54
- add_action('delete_post', array(&$this,'post_deleted'));
55
- add_action('save_post', array(&$this,'post_saved'));
56
- //Treat post trashing/untrashing as delete/save.
57
- add_action('trash_post', array(&$this,'post_deleted'));
58
- add_action('untrash_post', array(&$this,'post_saved'));
59
-
60
- //These do the same for (blogroll) links.
61
- add_action('add_link', array(&$this,'hook_add_link'));
62
- add_action('edit_link', array(&$this,'hook_edit_link'));
63
- add_action('delete_link', array(&$this,'hook_delete_link'));
64
-
65
- //TODO: Hook the delete_post_meta action to detect when custom fields are deleted directly
66
-
67
  //Load jQuery on Dashboard pages (probably redundant as WP already does that)
68
  add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
69
 
@@ -71,15 +53,14 @@ class wsBrokenLinkChecker {
71
  add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
72
 
73
  //AJAXy hooks
74
- //TODO: Check nonces in AJAX hooks
75
  add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
76
  add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
77
  add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
78
  add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
79
  add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
80
  add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
81
- add_action( 'wp_ajax_blc_exclude_link', array(&$this,'ajax_exclude_link') );
82
  add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
 
83
 
84
  //Check if it's possible to create a lockfile and nag the user about it if not.
85
  if ( $this->lockfile_name() ){
@@ -91,14 +72,25 @@ class wsBrokenLinkChecker {
91
  add_action( 'admin_notices', array( &$this, 'lockfile_warning' ) );
92
  }
93
 
94
- //Initialize the built-in link filters
95
- add_action('init', array(&$this,'init_native_filters'));
 
 
 
 
96
  }
97
 
 
 
 
 
 
98
  function admin_footer(){
 
 
 
99
  ?>
100
  <!-- wsblc admin footer -->
101
- <div id='wsblc_updater_div'></div>
102
  <script type='text/javascript'>
103
  (function($){
104
 
@@ -123,6 +115,12 @@ class wsBrokenLinkChecker {
123
  <?php
124
  }
125
 
 
 
 
 
 
 
126
  function is_excluded($url){
127
  if (!is_array($this->conf->options['exclusion_list'])) return false;
128
  foreach($this->conf->options['exclusion_list'] as $excluded_word){
@@ -193,55 +191,23 @@ class wsBrokenLinkChecker {
193
  wp_enqueue_script('jquery');
194
  }
195
 
196
- function load_ui_scripts(){
197
- //jQuery UI is used on the settings page and in the link listings
 
 
 
 
 
198
  wp_enqueue_script('jquery-ui-core');
199
  wp_enqueue_script('jquery-ui-dialog');
 
200
  }
201
 
202
  /**
203
- * ws_broken_link_checker::post_deleted()
204
- * A hook for post_deleted. Remove link instances associated with that post.
205
  *
206
- * @param int $post_id
207
  * @return void
208
  */
209
- function post_deleted($post_id){
210
- global $wpdb;
211
-
212
- //FB::log($post_id, "Post deleted");
213
- //Remove this post's instances
214
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
215
- WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
216
- $q = $wpdb->prepare($q, intval($post_id) );
217
-
218
- //FB::log($q, 'Executing query');
219
-
220
- if ( $wpdb->query( $q ) === false ){
221
- //FB::error($wpdb->last_error, "Database error");
222
- }
223
-
224
- //Remove the synch record
225
- $q = "DELETE FROM {$wpdb->prefix}blc_synch
226
- WHERE source_id = %d AND source_type = 'post'";
227
- $wpdb->query( $wpdb->prepare($q, intval($post_id)) );
228
-
229
- //Remove any dangling link records
230
- $this->cleanup_links();
231
- }
232
-
233
- function post_saved($post_id){
234
- global $wpdb;
235
-
236
- $post = get_post($post_id);
237
- //Only check links in posts, not revisions and attachments
238
- if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return null;
239
- //Only check published posts
240
- if ( $post->post_status != 'publish' ) return null;
241
-
242
- $this->mark_unsynched( $post_id, 'post' );
243
- }
244
-
245
  function initiate_recheck(){
246
  global $wpdb;
247
 
@@ -252,69 +218,24 @@ class wsBrokenLinkChecker {
252
  $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
253
 
254
  //Mark all posts, custom fields and bookmarks for processing.
255
- $this->resynch();
256
  }
257
 
258
- function resynch(){
259
- global $wpdb;
260
-
261
- //Drop all synchronization records
262
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
263
-
264
- //Create new synchronization records for posts
265
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
266
- SELECT id, 'post', 0
267
- FROM {$wpdb->posts}
268
- WHERE
269
- {$wpdb->posts}.post_status = 'publish'
270
- AND {$wpdb->posts}.post_type IN ('post', 'page')";
271
- $wpdb->query( $q );
272
-
273
- //Create new synchronization records for bookmarks (the blogroll)
274
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(source_id, source_type, synched)
275
- SELECT link_id, 'blogroll', 0
276
- FROM {$wpdb->links}
277
- WHERE 1";
278
- $wpdb->query( $q );
279
-
280
- //Delete invalid instances
281
- $this->cleanup_instances();
282
- //Delete orphaned links
283
- $this->cleanup_links();
284
-
285
- $this->conf->options['need_resynch'] = true;
286
- $this->conf->save_options();
287
- }
288
-
289
- function mark_unsynched( $source_id, $source_type ){
290
- global $wpdb;
291
-
292
- $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
293
- VALUES( %d, %s, %d, NOW() )";
294
- $rez = $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 0 ) );
295
-
296
- if ( !$this->conf->options['need_resynch'] ){
297
- $this->conf->options['need_resynch'] = true;
298
- $this->conf->save_options();
299
- }
300
-
301
- return $rez;
302
- }
303
-
304
- function mark_synched( $source_id, $source_type ){
305
- global $wpdb;
306
- //FB::log("Marking $source_type $source_id as synched.");
307
- $q = "REPLACE INTO {$wpdb->prefix}blc_synch( source_id, source_type, synched, last_synch)
308
- VALUES( %d, %s, %d, NOW() )";
309
- return $wpdb->query( $wpdb->prepare( $q, $source_id, $source_type, 1 ) );
310
- }
311
-
312
  function activation(){
 
 
313
  //Prepare the database.
314
  $this->upgrade_database();
315
 
316
- //Clear the instance table and mark all posts and other parse-able objects as unsynchronized.
317
- $this->resynch();
318
 
319
  //Save the default options.
320
  $this->conf->save_options();
@@ -323,112 +244,216 @@ class wsBrokenLinkChecker {
323
  $this->optimize_database();
324
  }
325
 
 
326
  /**
327
- * ws_broken_link_checker::upgrade_database()
328
- * Create and/or upgrade database tables
329
  *
330
- * @param bool $die_on_error Whether the function should stop the script and display an error message if a DB error is encountered.
331
  * @return void
332
  */
333
- function upgrade_database( $die_on_error = true ){
 
 
 
 
 
 
 
 
 
 
 
334
  global $wpdb;
335
 
336
  //Do we need to upgrade?
337
- //[ Disabled for now, was causing issues when the user manually deletes the plugin ]
338
- //if ( $this->db_version == $this->conf->options['current_db_version'] ) return;
339
-
340
- //Delete tables used by older versions of the plugin
341
- $rez = $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}blc_linkdata, {$wpdb->prefix}blc_postdata" );
342
- if ( $rez === false ){
343
- //FB::error($wpdb->last_error, "Database error");
344
- return false;
345
  }
346
 
347
- require_once (ABSPATH . 'wp-admin/includes/upgrade.php');
348
-
349
- //Create the link table if it doesn't exist yet.
350
- $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_links (
351
- link_id int(20) unsigned NOT NULL auto_increment,
352
- url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
353
- last_check datetime NOT NULL default '0000-00-00 00:00:00',
354
- check_count int(2) unsigned NOT NULL default '0',
355
- final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
356
- redirect_count smallint(5) unsigned NOT NULL,
357
- log text NOT NULL,
358
- http_code smallint(6) NOT NULL,
359
- request_duration float NOT NULL default '0',
360
- timeout tinyint(1) unsigned NOT NULL default '0',
361
-
362
- PRIMARY KEY (link_id),
363
- KEY url (url(150)),
364
- KEY final_url (final_url(150)),
365
- KEY http_code (http_code),
366
- KEY timeout (timeout)
367
- )";
368
- if ( $wpdb->query( $q ) === false ){
369
- if ( $die_on_error )
370
- die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
371
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
 
373
- //Fix URL fields so that they are collated as case-sensitive (this can't be done via dbDelta)
374
- $q = "ALTER TABLE {$wpdb->prefix}blc_links
375
- MODIFY url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
376
- MODIFY final_url text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL";
377
- if ( $wpdb->query( $q ) === false ){
378
- if ( $die_on_error )
379
- die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
380
- };
381
 
382
- //Create the instance table
383
- $q = "CREATE TABLE {$wpdb->prefix}blc_instances (
384
- instance_id int(10) unsigned NOT NULL auto_increment,
385
- link_id int(10) unsigned NOT NULL,
386
- source_id int(10) unsigned NOT NULL,
387
- source_type enum('post','blogroll','custom_field') NOT NULL default 'post',
388
- link_text varchar(250) NOT NULL,
389
- instance_type enum('link','image') NOT NULL default 'link',
390
-
391
- PRIMARY KEY (instance_id),
392
- KEY link_id (link_id),
393
- KEY source_id (source_id,source_type),
394
- FULLTEXT KEY link_text (link_text)
395
- ) ENGINE = MYISAM";
396
- dbDelta($q);
397
-
398
- //Create the synchronization table
399
- $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_synch (
400
- source_id int(20) unsigned NOT NULL,
401
- source_type enum('post','blogroll') NOT NULL,
402
- synched tinyint(3) unsigned NOT NULL,
403
- last_synch datetime NOT NULL,
404
- PRIMARY KEY (source_id, source_type),
405
- KEY synched (synched)
406
- )";
407
- if ( $wpdb->query( $q ) === false ){
408
- if ( $die_on_error )
409
- die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
410
- };
411
 
412
- //Create the custom filter table
413
- $q = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}blc_filters (
414
- id int(10) unsigned NOT NULL AUTO_INCREMENT,
 
415
  `name` varchar(100) NOT NULL,
416
- params text NOT NULL,
417
- PRIMARY KEY (id)
418
- )";
419
- if ( $wpdb->query( $q ) === false ){
420
- if ( $die_on_error )
421
- die( sprintf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error) );
422
- };
 
 
 
 
 
 
 
423
 
424
- $this->conf->options['current_db_version'] = $this->db_version;
425
- $this->conf->save_options();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
 
427
  return true;
428
  }
429
 
430
  /**
431
- * wsBrokenLinkChecker::optimize_database()
432
  * Optimize the plugin's tables
433
  *
434
  * @return void
@@ -458,14 +483,13 @@ class wsBrokenLinkChecker {
458
  );
459
 
460
  //Add plugin-specific scripts and CSS only to the it's own pages
461
- //TODO: Use the admin_enqueue_scripts action to enqueue the scripts
462
  add_action( 'admin_print_styles-' . $options_page_hook, array(&$this, 'options_page_css') );
463
  add_action( 'admin_print_styles-' . $links_page_hook, array(&$this, 'links_page_css') );
464
- add_action( 'admin_print_scripts-' . $options_page_hook, array(&$this, 'load_ui_scripts') );
465
- add_action( 'admin_print_scripts-' . $links_page_hook, array(&$this, 'load_ui_scripts') );
466
  }
467
 
468
- /**
469
  * plugin_action_links()
470
  * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
471
  * on the plugin list.
@@ -480,15 +504,23 @@ class wsBrokenLinkChecker {
480
  return $links;
481
  }
482
 
483
- function mytruncate($str, $max_length=50){
484
- if(strlen($str)<=$max_length) return $str;
485
- return (substr($str, 0, $max_length-3).'...');
486
- }
487
-
488
  function options_page(){
 
 
 
 
 
 
 
 
 
 
 
 
489
  if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
490
  $this->initiate_recheck();
491
  }
 
492
  if(isset($_POST['submit'])) {
493
  check_admin_referer('link-checker-options');
494
 
@@ -512,7 +544,6 @@ class wsBrokenLinkChecker {
512
  $new_removed_link_css = trim($_POST['removed_link_css']);
513
  $this->conf->options['removed_link_css'] = $new_removed_link_css;
514
 
515
- //TODO: Maybe update affected links when exclusion list changes (could be expensive resource-wise).
516
  $this->conf->options['exclusion_list'] = array_filter(
517
  preg_split(
518
  '/[\s\r\n]+/', //split on newlines and whitespace
@@ -540,7 +571,36 @@ class wsBrokenLinkChecker {
540
  if( $new_timeout > 0 ){
541
  $this->conf->options['timeout'] = $new_timeout ;
542
  }
543
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  $this->conf->save_options();
545
 
546
  /*
@@ -549,15 +609,26 @@ class wsBrokenLinkChecker {
549
  inefficient.
550
  */
551
  if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
552
- $this->resynch();
 
 
 
 
553
  }
554
 
 
555
  $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
556
- wp_redirect( add_query_arg( array( 'updated' => 1), $base_url ) );
 
 
 
 
 
 
557
  }
558
 
559
  $debug = $this->get_debug_info();
560
-
561
  ?>
562
 
563
  <div class="wrap"><h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
@@ -579,7 +650,6 @@ class wsBrokenLinkChecker {
579
  </th>
580
  <td>
581
 
582
-
583
  <div id='wsblc_full_status'>
584
  <br/><br/><br/>
585
  </div>
@@ -709,6 +779,19 @@ class wsBrokenLinkChecker {
709
  </td>
710
  </tr>
711
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  </table>
713
 
714
  <h3><?php _e('Advanced','broken-link-checker'); ?></h3>
@@ -738,6 +821,53 @@ class wsBrokenLinkChecker {
738
  </td>
739
  </tr>
740
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
 
742
  <tr valign="top">
743
  <th scope="row">
@@ -771,33 +901,66 @@ class wsBrokenLinkChecker {
771
 
772
  </td>
773
  </tr>
774
-
775
- <tr valign="top">
776
- <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
777
  <td>
778
-
779
  <?php
780
 
781
- printf(
782
- __('%s seconds', 'broken-link-checker'),
783
- sprintf(
784
- '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
785
- $this->conf->options['max_execution_time']
786
- )
787
- );
788
 
789
- ?>
790
- <br/><span class="description">
791
- <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
 
793
- _e('The plugin works by periodically creating a background worker instance that parses your posts looking for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the background instance may run each time before stopping.', 'broken-link-checker');
794
-
 
 
 
 
795
  ?>
796
- </span>
797
-
798
  </td>
799
  </tr>
800
-
801
  </table>
802
 
803
  <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
@@ -825,141 +988,22 @@ class wsBrokenLinkChecker {
825
  }
826
 
827
  function options_page_css(){
828
- ?>
829
- <style type='text/css'>
830
- #blc-debug-info-toggle {
831
- font-size: smaller;
832
- }
833
-
834
- .blc-debug-item-ok {
835
- background-color: #d7ffa2;
836
- }
837
- .blc-debug-item-warning {
838
- background-color: #fcffa2;
839
- }
840
- .blc-debug-item-error {
841
- background-color: #ffc4c4;
842
- }
843
-
844
- #blc-debug-info {
845
- display: none;
846
-
847
- text-align: left;
848
-
849
- border-width: 1px;
850
- border-color: gray;
851
- border-style: solid;
852
-
853
- border-spacing: 0px;
854
- border-collapse: collapse;
855
- }
856
-
857
- #blc-debug-info th, #blc-debug-info td {
858
- padding: 6px;
859
- font-weight: normal;
860
- text-shadow: none;
861
-
862
- border-width: 1px ;
863
- border-color: silver;
864
- border-style: solid;
865
-
866
- border-collapse: collapse;
867
- }
868
- </style>
869
- <?php
870
  }
871
 
872
- /**
873
- * wsBrokenLinkChecker::init_native_filters()
874
- * Initializes (if necessary) and returns the list of built-in link filters
875
- *
876
- * @return array
877
- */
878
- function init_native_filters(){
879
- if ( !empty($this->native_filters) ){
880
- return $this->native_filters;
881
- } else {
882
- //Available filters by link type + the appropriate WHERE expressions
883
- $this->native_filters = array(
884
- 'broken' => array(
885
- 'where_expr' => '( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( check_count > 0 ) AND ( http_code <> ' . BLC_CHECKING . ')',
886
- 'name' => __('Broken', 'broken-link-checker'),
887
- 'heading' => __('Broken Links', 'broken-link-checker'),
888
- 'heading_zero' => __('No broken links found', 'broken-link-checker')
889
- ),
890
- 'redirects' => array(
891
- 'where_expr' => '( redirect_count > 0 )',
892
- 'name' => __('Redirects', 'broken-link-checker'),
893
- 'heading' => __('Redirected Links', 'broken-link-checker'),
894
- 'heading_zero' => __('No redirects found', 'broken-link-checker')
895
- ),
896
-
897
- 'all' => array(
898
- 'where_expr' => '1',
899
- 'name' => __('All', 'broken-link-checker'),
900
- 'heading' => __('Detected Links', 'broken-link-checker'),
901
- 'heading_zero' => __('No links found (yet)', 'broken-link-checker')
902
- ),
903
  );
904
-
905
- return $this->native_filters;
906
  }
907
- }
908
-
909
- /**
910
- * wsBrokenLinkChecker::get_custom_filters()
911
- * Returns a list of user-defined link filters.
912
- *
913
- * @return array An array of custom filter definitions. If there are no custom filters defined returns an empty array.
914
- */
915
- function get_custom_filters(){
916
- global $wpdb;
917
-
918
- $filter_data = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}blc_filters ORDER BY name ASC", ARRAY_A);
919
- $filters = array();
920
-
921
- if ( !empty($filter_data) ) {
922
- foreach($filter_data as $data){
923
- $filters[ 'f'.$data['id'] ] = array(
924
- 'name' => $data['name'],
925
- 'params' => $data['params'],
926
- 'is_search' => true,
927
- 'heading' => ucwords($data['name']),
928
- 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
929
- );
930
- }
931
- }
932
-
933
- return $filters;
934
- }
935
-
936
- function get_search_params( $filter = null ){
937
- //If present, the filter's parameters may be saved either as an array or a string.
938
- $params = array();
939
- if ( !empty($filter) && !empty($filter['params']) ){
940
- $params = $filter['params'];
941
- if ( is_string( $params ) ){
942
- wp_parse_str($params, $params);
943
- }
944
- } else {
945
- //If the filter doesn't have it's own search query, use the URL parameters
946
- $params = array_merge($params, $_GET);
947
- }
948
-
949
- //Only leave valid search query parameters
950
- $search_param_names = array( 's_link_text', 's_link_url', 's_http_code', 's_filter', 's_link_type' );
951
- $output = array();
952
- foreach ( $params as $name => $value ){
953
- if ( in_array($name, $search_param_names) ){
954
- $output[$name] = $value;
955
- }
956
- }
957
-
958
- return $output;
959
- }
960
-
961
- function links_page(){
962
- global $wpdb;
963
 
964
  $action = !empty($_POST['action'])?$_POST['action']:'';
965
  if ( intval($action) == -1 ){
@@ -979,315 +1023,40 @@ class wsBrokenLinkChecker {
979
  $message = '';
980
  $msg_class = 'updated';
981
 
 
982
  if ( $action == 'create-custom-filter' ){
983
- //Create a custom filter!
984
-
985
- check_admin_referer( $action );
986
-
987
- //Filter name must be set
988
- if ( empty($_POST['name']) ){
989
- $message = __("You must enter a filter name!", 'broken-link-checker');
990
- $msg_class = 'error';
991
- //Filter parameters (a search query) must also be set
992
- } elseif ( empty($_POST['params']) ){
993
- $message = __("Invalid search query.", 'broken-link-checker');
994
- $msg_class = 'error';
995
- } else {
996
- //Save the new filter
997
- $q = $wpdb->prepare(
998
- "INSERT INTO {$wpdb->prefix}blc_filters(name, params) VALUES (%s, %s)",
999
- $_POST['name'], $_POST['params']
1000
- );
1001
-
1002
- if ( $wpdb->query($q) ){
1003
- //Saved
1004
- $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $_POST['name']);
1005
- //A little hack to make the filter active immediately
1006
- $_GET['filter_id'] = 'f' . $wpdb->insert_id;
1007
- } else {
1008
- //Error
1009
- $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
1010
- $msg_class = 'error';
1011
- }
1012
- }
1013
-
1014
  } elseif ( $action == 'delete-custom-filter' ){
1015
- //Delete an existing custom filter!
1016
-
1017
- check_admin_referer( $action );
1018
-
1019
- //Filter ID must be set
1020
- if ( empty($_POST['filter_id']) ){
1021
- $message = __("Filter ID not specified.", 'broken-link-checker');
1022
- $msg_class = 'error';
1023
- } else {
1024
- //Remove the "f" character from the filter ID to get its database key
1025
- $filter_id = intval(ltrim($_POST['filter_id'], 'f'));
1026
- //Try to delete the filter
1027
- $q = $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_filters WHERE id = %d", $filter_id);
1028
- if ( $wpdb->query($q) ){
1029
- //Success
1030
- $message = __('Filter deleted', 'broken-link-checker');
1031
- } else {
1032
- //Either the ID is wrong or there was some other error
1033
- $message = __('Database error : %s', 'broken-link-checker');
1034
- $msg_class = 'error';
1035
- }
1036
- }
1037
-
1038
  } elseif ($action == 'bulk-delete-sources') {
1039
- //Delete posts and blogroll entries that contain any of the selected links
1040
- //(links inside custom fields count as part of the post for the purposes of this action).
1041
- //
1042
- //Note that once all posts/bookmarks containing a particular link have been deleted,
1043
- //there is no need to explicitly delete the link record itself. The hooks attached to
1044
- //the post_deleted and delete_link actions will take care of that.
1045
-
1046
- check_admin_referer( 'bulk-action' );
1047
-
1048
- if ( count($selected_links) > 0 ) {
1049
- $selected_links_sql = implode(', ', $selected_links);
1050
-
1051
- $messages = array();
1052
-
1053
- //First, fetch the posts that contain any of the selected links,
1054
- //either in the content or in a custom field.
1055
- $q = "
1056
- SELECT posts.id, posts.post_title
1057
- FROM
1058
- {$wpdb->prefix}blc_links AS links,
1059
- {$wpdb->prefix}blc_instances AS instances,
1060
- {$wpdb->posts} AS posts
1061
- WHERE
1062
- links.link_id IN ($selected_links_sql)
1063
- AND links.link_id = instances.link_id
1064
- AND (instances.source_type = 'post' OR instances.source_type = 'custom_field')
1065
- AND instances.source_id = posts.id
1066
- AND posts.post_status <> \"trash\"
1067
- GROUP BY posts.id
1068
- ";
1069
-
1070
- $posts_to_delete = $wpdb->get_results($q);
1071
- $deleted_posts = array();
1072
-
1073
- //Delete the selected posts
1074
- foreach($posts_to_delete as $post){
1075
- if ( wp_delete_post($post->id) !== false) {
1076
- $deleted_posts[] = $post;
1077
- } else {
1078
- $messages[] = sprintf(
1079
- __('Failed to delete post "%s" (%d)', 'broken-link-checker'),
1080
- $post->pots_title,
1081
- $post->id
1082
- );
1083
- $msg_class = 'error';
1084
- };
1085
- }
1086
-
1087
- if ( count($deleted_posts) > 0 ) {
1088
- //Since the "Trash" feature has been introduced, calling wp_delete_post
1089
- //doesn't actually delete the post (unless you set force_delete to True),
1090
- //just moves it to the trash. So we pick the message accordingly.
1091
- if ( function_exists('wp_trash_post') ){
1092
- $delete_msg = _n("%d post moved to the trash", "%d posts moved to the trash", count($deleted_posts), 'broken-link-checker');
1093
- } else {
1094
- $delete_msg = _n("%d post deleted", "%d posts deleted", count($deleted_posts), 'broken-link-checker');
1095
- }
1096
-
1097
- $messages[] = sprintf(
1098
- $delete_msg,
1099
- count($deleted_posts)
1100
- );
1101
- }
1102
-
1103
- //Fetch blogroll links (AKA bookmarks) that match any of the selected links
1104
- $q = "
1105
- SELECT bookmarks.link_id AS bookmark_id, bookmarks.link_name
1106
- FROM
1107
- {$wpdb->prefix}blc_links AS links,
1108
- {$wpdb->prefix}blc_instances AS instances,
1109
- {$wpdb->links} AS bookmarks
1110
- WHERE
1111
- links.link_id IN ($selected_links_sql)
1112
- AND links.link_id = instances.link_id
1113
- AND instances.source_type = 'blogroll'
1114
- AND instances.source_id = bookmarks.link_id
1115
- GROUP BY bookmarks.link_id
1116
- ";
1117
- //echo "<pre>$q</pre>";
1118
-
1119
- $bookmarks_to_delete = $wpdb->get_results($q);
1120
- $deleted_bookmarks = array();
1121
-
1122
- if ( count($bookmarks_to_delete) > 0 ){
1123
- //Delete the matching blogroll links
1124
- foreach($bookmarks_to_delete as $bookmark){
1125
- if ( wp_delete_link($bookmark->bookmark_id) ){
1126
- $deleted_bookmarks[] = $bookmark;
1127
- } else {
1128
- $messages[] = sprintf(
1129
- __('Failed to delete blogroll link "%s" (%d)', 'broken-link-checker'),
1130
- $bookmark->link_name,
1131
- $bookmark->link_id
1132
- );
1133
- $msg_class = 'error';
1134
- }
1135
- }
1136
-
1137
- if ( count($deleted_bookmarks) > 0 ) {
1138
- $messages[] = sprintf(
1139
- _n("%d blogroll link deleted", "%d blogroll links deleted", count($deleted_bookmarks), 'broken-link-checker'),
1140
- count($deleted_bookmarks)
1141
- );
1142
- }
1143
- }
1144
-
1145
- if ( count($messages) > 0 ){
1146
- $message = implode('<br>', $messages);
1147
- } else {
1148
- $message = __("Didn't find anything to delete!", 'broken-link-checker');
1149
- $msg_class = 'error';
1150
- }
1151
-
1152
-
1153
- }
1154
-
1155
  } elseif ($action == 'bulk-unlink') {
1156
- //Unlink all selected links.
1157
-
1158
- check_admin_referer( 'bulk-action' );
1159
-
1160
- if ( count($selected_links) > 0 ) {
1161
- $selected_links_sql = implode(', ', $selected_links);
1162
-
1163
- //Fetch the selected links
1164
- $q = "SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id IN ($selected_links_sql)";
1165
- $links = $wpdb->get_results($q, ARRAY_A);
1166
-
1167
- if ( count($links) > 0 ) {
1168
- $processed_links = 0;
1169
- $failed_links = 0;
1170
-
1171
- //Unlink (delete) all selected links
1172
- foreach($links as $link){
1173
- $the_link = new blcLink($link);
1174
- $rez = $the_link->unlink();
1175
- if ( $rez !== false ){
1176
- $processed_links++;
1177
- } else {
1178
- $failed_links++;
1179
- }
1180
- }
1181
-
1182
- //This message is slightly misleading - it doesn't account for the fact that
1183
- //a link can be present in more than one post.
1184
- $message = sprintf(
1185
- _n(
1186
- '%d link removed',
1187
- '%d links removed',
1188
- $processed_links,
1189
- 'broken-link-checker'
1190
- ),
1191
- $processed_links
1192
- );
1193
-
1194
- if ( $failed_links > 0 ) {
1195
- $message .= '<br>' . sprintf(
1196
- _n(
1197
- 'Failed to remove %d link',
1198
- 'Failed to remove %d links',
1199
- $failed_links,
1200
- 'broken-link-checker'
1201
- ),
1202
- $failed_links
1203
- );
1204
- $msg_class = 'error';
1205
- }
1206
- }
1207
- }
1208
-
1209
  } elseif ($action == 'bulk-deredirect') {
1210
- //For all selected links, replace the URL with the final URL that it redirects to.
1211
-
1212
- check_admin_referer( 'bulk-action' );
1213
-
1214
- if ( count($selected_links) > 0 ) {
1215
- $selected_links_sql = implode(', ', $selected_links);
1216
-
1217
- //Fetch the selected links
1218
- $q = "SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id IN ($selected_links_sql) AND redirect_count > 0";
1219
- $links = $wpdb->get_results($q, ARRAY_A);
1220
-
1221
- if ( count($links) > 0 ) {
1222
- $processed_links = 0;
1223
- $failed_links = 0;
1224
-
1225
- //Deredirect all selected links
1226
- foreach($links as $link){
1227
- $the_link = new blcLink($link);
1228
- $rez = $the_link->deredirect();
1229
- if ( $rez !== false ){
1230
- $processed_links++;
1231
- } else {
1232
- $failed_links++;
1233
- }
1234
- }
1235
-
1236
- $message = sprintf(
1237
- _n(
1238
- 'Replaced %d redirect with a direct link',
1239
- 'Replaced %d redirects with direct links',
1240
- $processed_links,
1241
- 'broken-link-checker'
1242
- ),
1243
- $processed_links
1244
- );
1245
-
1246
- if ( $failed_links > 0 ) {
1247
- $message .= '<br>' . sprintf(
1248
- _n(
1249
- 'Failed to fix %d redirect',
1250
- 'Failed to fix %d redirects',
1251
- $failed_links,
1252
- 'broken-link-checker'
1253
- ),
1254
- $failed_links
1255
- );
1256
- $msg_class = 'error';
1257
- }
1258
- } else {
1259
- $message = __('None of the selected links are redirects!', 'broken-link-checker');
1260
- }
1261
- }
1262
-
1263
  }
1264
 
1265
  if ( !empty($message) ){
1266
- echo '<div id="message" class="'.$msg_class.' fade"><p><strong>'.$message.'</strong></p></div>';
1267
  }
1268
 
1269
- //Build the filter list
1270
- $filters = array_merge($this->native_filters, $this->get_custom_filters());
1271
 
1272
- //Add the special "search" filter
1273
- $filters['search'] = array(
1274
- 'name' => __('Search', 'broken-link-checker'),
1275
- 'heading' => __('Search Results', 'broken-link-checker'),
1276
- 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
1277
- 'is_search' => true,
1278
- 'where_expr' => 1,
1279
- 'hidden' => true,
1280
- );
1281
 
1282
- //Calculate the number of links for each filter
1283
- foreach ($filters as $filter => $data){
1284
- $filters[$filter]['count'] = $this->get_links($data, 0, 0, true);
1285
- }
1286
 
1287
  //Get the selected filter (defaults to displaying broken links)
1288
  $filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1289
- if ( !isset($filters[$filter_id]) ){
 
1290
  $filter_id = 'broken';
 
 
1291
  }
1292
 
1293
  //Get the desired page number (must be > 0)
@@ -1302,25 +1071,29 @@ class wsBrokenLinkChecker {
1302
  $per_page = 200;
1303
  }
1304
 
1305
- $current_filter = $filters[$filter_id];
1306
  $max_pages = ceil($current_filter['count'] / $per_page);
1307
 
1308
- //Select the required links + 1 instance per link.
1309
- $links = $this->get_links( $current_filter, ( ($page-1) * $per_page ), $per_page );
1310
- if ( is_null($links) && !empty($wpdb->last_error) ){
 
 
 
 
 
 
 
1311
  printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1312
  }
1313
 
1314
- //Save the search params (if any) in a handy array for later
1315
- if ( !empty($current_filter['is_search']) ){
1316
- $search_params = $this->get_search_params($current_filter);
1317
- } else {
1318
- $search_params = array();
1319
  }
1320
 
1321
- //Display the "Discard" button when listing broken links
1322
- $show_discard_button = ('broken' == $filter_id) || (!empty($search_params['s_filter']) && ($search_params['s_filter'] == 'broken'));
1323
-
1324
  //Figure out what the "safe" URL to acccess the current page would be.
1325
  //This is used by the bulk action form.
1326
  $special_args = array('_wpnonce', '_wp_http_referer', 'action', 'selected_links');
@@ -1330,6 +1103,13 @@ class wsBrokenLinkChecker {
1330
 
1331
  <script type='text/javascript'>
1332
  var blc_current_filter = '<?php echo $filter_id; ?>';
 
 
 
 
 
 
 
1333
  </script>
1334
 
1335
  <div class="wrap">
@@ -1364,98 +1144,12 @@ class wsBrokenLinkChecker {
1364
  ?>
1365
  </ul>
1366
 
1367
- <div class="search-box">
1368
-
1369
- <?php
1370
- //If we're currently displaying search results offer the user the option to s
1371
- //save the search query as a custom filter.
1372
- if ( $filter_id == 'search' ){
1373
- ?>
1374
- <form name="save-search-query" id="custom-filter-form" action="<?php echo admin_url("tools.php?page=view-broken-links"); ?>" method="post" class="blc-inline-form">
1375
- <?php wp_nonce_field('create-custom-filter'); ?>
1376
- <input type="hidden" name="name" id="blc-custom-filter-name" value="" />
1377
- <input type="hidden" name="params" id="blc-custom-filter-params" value="<?php echo http_build_query($search_params, null, '&'); ?>" />
1378
- <input type="hidden" name="action" value="create-custom-filter" />
1379
- <input type="button" value="<?php esc_attr_e( 'Save This Search As a Filter', 'broken-link-checker' ); ?>" id="blc-create-filter" class="button" />
1380
- </form>
1381
- <?php
1382
- } elseif ( !empty($current_filter['is_search']) ){
1383
- //If we're displaying a custom filter give an option to delete it.
1384
- ?>
1385
- <form name="save-search-query" id="custom-filter-form" action="<?php echo admin_url("tools.php?page=view-broken-links"); ?>" method="post" class="blc-inline-form">
1386
- <?php wp_nonce_field('delete-custom-filter'); ?>
1387
- <input type="hidden" name="filter_id" id="blc-custom-filter-id" value="<?php echo $filter_id; ?>" />
1388
- <input type="hidden" name="action" value="delete-custom-filter" />
1389
- <input type="submit" value="<?php esc_attr_e( 'Delete This Filter', 'broken-link-checker' ); ?>" id="blc-delete-filter" class="button" />
1390
- </form>
1391
- <?php
1392
- }
1393
- ?>
1394
-
1395
- <input type="button" value="<?php esc_attr_e( 'Search', 'broken-link-checker' ); ?> &raquo;" id="blc-open-search-box" class="button" />
1396
- </div>
1397
 
1398
- <!-- The search dialog -->
1399
- <div id='search-links-dialog' title='Search'>
1400
- <form class="search-form" action="<?php echo admin_url('tools.php?page=view-broken-links'); ?>" method="get">
1401
- <input type="hidden" name="page" value="view-broken-links" />
1402
- <input type="hidden" name="filter_id" value="search" />
1403
- <fieldset>
1404
-
1405
- <label for="s_link_text"><?php _e('Link text', 'broken-link-checker'); ?></label>
1406
- <input type="text" name="s_link_text" value="<?php if(!empty($search_params['s_link_text'])) echo esc_attr($search_params['s_link_text']); ?>" id="s_link_text" class="text ui-widget-content" />
1407
-
1408
- <label for="s_link_url"><?php _e('URL', 'broken-link-checker'); ?></label>
1409
- <input type="text" name="s_link_url" id="s_link_url" value="<?php if(!empty($search_params['s_link_url'])) echo esc_attr($search_params['s_link_url']); ?>" class="text ui-widget-content" />
1410
-
1411
- <label for="s_http_code"><?php _e('HTTP code', 'broken-link-checker'); ?></label>
1412
- <input type="text" name="s_http_code" id="s_http_code" value="<?php if(!empty($search_params['s_http_code'])) echo esc_attr($search_params['s_http_code']); ?>" class="text ui-widget-content" />
1413
-
1414
- <label for="s_filter"><?php _e('Link status', 'broken-link-checker'); ?></label>
1415
- <select name="s_filter" id="s_filter">
1416
- <?php
1417
- if ( !empty($search_params['s_filter']) ){
1418
- $search_subfilter = $search_params['s_filter'];
1419
- } else {
1420
- $search_subfilter = $filter_id;
1421
- }
1422
-
1423
- foreach ($this->native_filters as $filter => $data){
1424
- $selected = ($search_subfilter == $filter)?' selected="selected"':'';
1425
- printf('<option value="%s"%s>%s</option>', $filter, $selected, $data['name']);
1426
- }
1427
- ?>
1428
- </select>
1429
-
1430
- <label for="s_link_type"><?php _e('Link type', 'broken-link-checker'); ?></label>
1431
- <select name="s_link_type" id="s_link_type">
1432
- <?php
1433
- $link_types = array(
1434
- __('Any', 'broken-link-checker') => '',
1435
- __('Normal link', 'broken-link-checker') => 'link',
1436
- __('Image', 'broken-link-checker') => 'image',
1437
- __('Custom field', 'broken-link-checker') => 'custom_field',
1438
- __('Bookmark', 'broken-link-checker') => 'blogroll',
1439
- );
1440
-
1441
- foreach ($link_types as $name => $value){
1442
- $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $value )?' selected="selected"':'';
1443
- printf('<option value="%s"%s>%s</option>', $value, $selected, $name);
1444
- }
1445
- ?>
1446
- </select>
1447
-
1448
- </fieldset>
1449
-
1450
- <div id="blc-search-button-row">
1451
- <input type="submit" value="<?php esc_attr_e( 'Search Links', 'broken-link-checker' ); ?>" id="blc-search-button" name="search_button" class="button-primary" />
1452
- <input type="button" value="<?php esc_attr_e( 'Cancel', 'broken-link-checker' ); ?>" id="blc-cancel-search" class="button" />
1453
- </div>
1454
-
1455
- </form>
1456
- </div>
1457
 
1458
- <?php
1459
  //Do we have any links to display?
1460
  if( $links && ( count($links) > 0 ) ) {
1461
  ?>
@@ -1466,8 +1160,9 @@ class wsBrokenLinkChecker {
1466
 
1467
  $bulk_actions = array(
1468
  '-1' => __('Bulk Actions', 'broken-link-checker'),
1469
- "bulk-unlink" => __('Unlink', 'broken-link-checker'),
1470
  "bulk-deredirect" => __('Fix redirects', 'broken-link-checker'),
 
1471
  "bulk-delete-sources" => __('Delete sources', 'broken-link-checker'),
1472
  );
1473
 
@@ -1476,10 +1171,10 @@ class wsBrokenLinkChecker {
1476
  $bulk_actions_html .= sprintf('<option value="%s">%s</option>', $value, $name);
1477
  }
1478
  ?>
1479
-
1480
  <div class='tablenav'>
1481
  <div class="alignleft actions">
1482
- <select name="action">
1483
  <?php echo $bulk_actions_html; ?>
1484
  </select>
1485
  <input type="submit" name="doaction" id="doaction" value="<?php echo attribute_escape(__('Apply', 'broken-link-checker')); ?>" class="button-secondary action">
@@ -1516,14 +1211,9 @@ class wsBrokenLinkChecker {
1516
  <th scope="col" id="cb" class="check-column">
1517
  <input type="checkbox">
1518
  </th>
1519
- <th scope="col"><?php _e('Source', 'broken-link-checker'); ?></th>
1520
- <th scope="col"><?php _e('Link Text', 'broken-link-checker'); ?></th>
1521
- <th scope="col"><?php _e('URL', 'broken-link-checker'); ?></th>
1522
-
1523
- <?php if ( $show_discard_button ) { ?>
1524
- <th scope="col"> </th>
1525
- <?php } ?>
1526
-
1527
  </tr>
1528
  </thead>
1529
  <tbody id="the-list">
@@ -1532,97 +1222,81 @@ class wsBrokenLinkChecker {
1532
  foreach ($links as $link) {
1533
  $rownum++;
1534
 
1535
- $rowclass = 'alternate' == $rowclass ? '' : 'alternate';
1536
- $excluded = $this->is_excluded( $link['url'] );
1537
  if ( $excluded ) $rowclass .= ' blc-excluded-link';
1538
 
 
 
 
 
 
 
 
 
1539
  ?>
1540
- <tr id='<?php echo "blc-row-$rownum"; ?>' class='blc-row <?php echo $rowclass; ?>'>
1541
 
1542
  <th class="check-column" scope="row">
1543
- <input type="checkbox" name="selected_links[]" value="<?php echo $link['link_id']; ?>">
1544
  </th>
1545
 
1546
  <td class='post-title column-title'>
1547
- <span class='blc-link-id' style='display:none;'><?php echo $link['link_id']; ?></span>
1548
- <?php
1549
- if ( ('post' == $link['source_type']) || ('custom_field' == $link['source_type']) ){
1550
-
1551
- echo "<a class='row-title' href='post.php?action=edit&amp;post=$link[source_id]' title='",
1552
- attribute_escape(__('Edit this post')),
1553
- "'>{$link[post_title]}</a>";
1554
-
1555
- //Output inline action links (copied from edit-post-rows.php)
1556
- $actions = array();
1557
- if ( current_user_can('edit_post', $link['source_id']) ) {
1558
- $actions['edit'] = '<span class="edit"><a href="' . get_edit_post_link($link['source_id'], true) . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
1559
- $actions['delete'] = "<span class='delete'><a class='submitdelete' title='" . attribute_escape(__('Delete this post')) . "' href='" . wp_nonce_url("post.php?action=delete&amp;post=".$link['source_id'], 'delete-post_' . $link['source_id']) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete this post '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['post_title'] )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
1560
- }
1561
- $actions['view'] = '<span class="view"><a href="' . get_permalink($link['source_id']) . '" title="' . attribute_escape(sprintf(__('View "%s"', 'broken-link-checker'), $link['post_title'])) . '" rel="permalink">' . __('View') . '</a>';
1562
- echo '<div class="row-actions">';
1563
- echo implode(' | </span>', $actions);
1564
- echo '</div>';
1565
 
1566
- } elseif ( 'blogroll' == $link['source_type'] ) {
1567
-
1568
- echo "<a class='row-title' href='link.php?action=edit&amp;link_id=$link[source_id]' title='" . __('Edit this bookmark', 'broken-link-checker') . "'>{$link[link_text]}</a>";
1569
-
1570
- //Output inline action links
1571
- $actions = array();
1572
- if ( current_user_can('manage_links') ) {
1573
- $actions['edit'] = '<span class="edit"><a href="link.php?action=edit&amp;link_id=' . $link['source_id'] . '" title="' . attribute_escape(__('Edit this bookmark', 'broken-link-checker')) . '">' . __('Edit') . '</a>';
1574
- $actions['delete'] = "<span class='delete'><a class='submitdelete' href='" . wp_nonce_url("link.php?action=delete&amp;link_id={$link[source_id]}", 'delete-bookmark_' . $link['source_id']) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete this link '%s'\n 'Cancel' to stop, 'OK' to delete."), $link['link_text'])) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
 
 
 
 
1575
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1576
 
1577
  echo '<div class="row-actions">';
1578
  echo implode(' | </span>', $actions);
1579
  echo '</div>';
1580
-
1581
- } elseif ( empty($link['source_type']) ){
1582
-
1583
- _e("[An orphaned link! This is a bug.]", 'broken-link-checker');
1584
 
1585
- }
 
 
 
1586
  ?>
1587
  </td>
1588
  <td class='blc-link-text'><?php
1589
- if ( 'post' == $link['source_type'] ){
1590
-
1591
- if ( 'link' == $link['instance_type'] ) {
1592
- print strip_tags($link['link_text']);
1593
- } elseif ( 'image' == $link['instance_type'] ){
1594
- printf(
1595
- '<img src="%s/broken-link-checker/images/image.png" class="blc-small-image" alt="%2$s" title="%2$s"> %2$s',
1596
- WP_PLUGIN_URL,
1597
- __('Image', 'broken-link-checker')
1598
- );
1599
- } else {
1600
- echo '[ ??? ]';
1601
- }
1602
-
1603
- } elseif ( 'custom_field' == $link['source_type'] ){
1604
-
1605
- printf(
1606
- '<img src="%s/broken-link-checker/images/script_code.png" class="blc-small-image" title="%2$s" alt="%2$s"> ',
1607
- WP_PLUGIN_URL,
1608
- __('Custom field', 'broken-link-checker')
1609
- );
1610
- echo "<code>".$link['link_text']."</code>";
1611
-
1612
- } elseif ( 'blogroll' == $link['source_type'] ){
1613
- printf(
1614
- '<img src="%s/broken-link-checker/images/link.png" class="blc-small-image" title="%2$s" alt="%2$s"> %2$s',
1615
- WP_PLUGIN_URL,
1616
- __('Bookmark', 'broken-link-checker')
1617
- );
1618
  }
1619
  ?>
1620
  </td>
1621
  <td class='column-url'>
1622
- <a href='<?php print $link['url']; ?>' target='_blank' class='blc-link-url'>
1623
- <?php print $this->mytruncate($link['url']); ?></a>
1624
  <input type='text' id='link-editor-<?php print $rownum; ?>'
1625
- value='<?php print attribute_escape($link['url']); ?>'
1626
  class='blc-link-editor' style='display:none' />
1627
  <?php
1628
  //Output inline action links for the link/URL
@@ -1633,49 +1307,37 @@ class wsBrokenLinkChecker {
1633
  $actions['delete'] = "<span class='delete'><a class='submitdelete blc-unlink-button' title='" . attribute_escape( __('Remove this link from all posts', 'broken-link-checker') ). "' ".
1634
  "id='unlink-button-$rownum' href='javascript:void(0);'>" . __('Unlink', 'broken-link-checker') . "</a>";
1635
 
1636
- if ( $excluded ){
1637
- $actions['exclude'] = "<span class='delete'>" . __('Excluded', 'broken-link-checker');
1638
- } else {
1639
- $actions['exclude'] = "<span class='delete'><a class='submitdelete blc-exclude-button' title='" . attribute_escape( __('Add this URL to the exclusion list' , 'broken-link-checker') ) . "' ".
1640
- "id='exclude-button-$rownum' href='javascript:void(0);'>" . __('Exclude' , 'broken-link-checker'). "</a>";
 
1641
  }
1642
 
1643
  $actions['edit'] = "<span class='edit'><a href='javascript:void(0)' class='blc-edit-button' title='" . attribute_escape( __('Edit link URL' , 'broken-link-checker') ) . "'>". __('Edit URL' , 'broken-link-checker') ."</a>";
1644
-
1645
  echo '<div class="row-actions">';
1646
  echo implode(' | </span>', $actions);
1647
 
1648
- echo "<span style='display:none' class='blc-cancel-button-container'> ",
1649
  "| <a href='javascript:void(0)' class='blc-cancel-button' title='". attribute_escape(__('Cancel URL editing' , 'broken-link-checker')) ."'>". __('Cancel' , 'broken-link-checker') ."</a></span>";
1650
-
1651
  echo '</div>';
1652
  ?>
1653
  </td>
1654
- <?php
1655
- //Display the "Discard" button when listing broken links
1656
- if ( $show_discard_button ) {
1657
- ?>
1658
- <td><a href='javascript:void(0);'
1659
- id='discard_button-<?php print $rownum; ?>'
1660
- class='blc-discard-button'
1661
- title='<?php
1662
- echo attribute_escape(
1663
- __('Remove this link from the list of broken links and mark it as valid', 'broken-link-checker')
1664
- );
1665
- ?>'><?php _e('Discard', 'broken-link-checker'); ?></a>
1666
- </td>
1667
- <?php } ?>
1668
  </tr>
1669
  <!-- Link details -->
1670
  <tr id='<?php print "link-details-$rownum"; ?>' style='display:none;' class='blc-link-details'>
1671
- <td colspan='<?php echo $show_discard_button?5:4; ?>'><?php $this->link_details_row($link); ?></td>
1672
  </tr><?php
1673
  }
1674
  ?></tbody></table>
1675
 
1676
  <div class="tablenav">
1677
  <div class="alignleft actions">
1678
- <select name="action2">
1679
  <?php echo $bulk_actions_html; ?>
1680
  </select>
1681
  <input type="submit" name="doaction2" id="doaction2" value="<?php echo attribute_escape(__('Apply', 'broken-link-checker')); ?>" class="button-secondary action">
@@ -1703,634 +1365,406 @@ class wsBrokenLinkChecker {
1703
 
1704
  ?>
1705
 
1706
- <?php $this->links_page_js(); ?>
 
 
 
1707
  </div>
1708
  <?php
1709
  } //Function ends
1710
 
1711
- function links_page_js(){
1712
- ?>
1713
- <script type='text/javascript'>
1714
-
1715
- function alterLinkCounter(factor){
1716
- cnt = parseInt(jQuery('.current-link-count').eq(0).html());
1717
- cnt = cnt + factor;
1718
- jQuery('.current-link-count').html(cnt);
1719
- }
1720
-
1721
- jQuery(function($){
1722
-
1723
- //The discard button - manually mark the link as valid. The link will be checked again later.
1724
- $(".blc-discard-button").click(function () {
1725
- var me = this;
1726
- $(me).html('<?php echo js_escape(__('Wait...', 'broken-link-checker')); ?>');
1727
-
1728
- var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1729
-
1730
- $.post(
1731
- "<?php echo admin_url('admin-ajax.php'); ?>",
1732
- {
1733
- 'action' : 'blc_discard',
1734
- 'link_id' : link_id
1735
- },
1736
- function (data, textStatus){
1737
- if (data == 'OK'){
1738
- var master = $(me).parents('.blc-row');
1739
- var details = master.next('.blc-link-details');
1740
-
1741
- details.hide();
1742
- //Flash the main row green to indicate success, then hide it.
1743
- var oldColor = master.css('background-color');
1744
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1745
- master.hide();
1746
- });
1747
-
1748
- alterLinkCounter(-1);
1749
- } else {
1750
- $(me).html('<?php echo js_escape(__('Discard' , 'broken-link-checker')); ?>');
1751
- alert(data);
1752
- }
1753
- }
1754
- );
1755
- });
1756
-
1757
- //The details button - display/hide detailed info about a link
1758
- $(".blc-details-button, .blc-link-text").click(function () {
1759
- $(this).parents('.blc-row').next('.blc-link-details').toggle();
1760
- });
1761
-
1762
- //The edit button - edit/save the link's URL
1763
- $(".blc-edit-button").click(function () {
1764
- var edit_button = $(this);
1765
- var master = $(edit_button).parents('.blc-row');
1766
- var editor = $(master).find('.blc-link-editor');
1767
- var url_el = $(master).find('.blc-link-url');
1768
- var cancel_button_container = $(master).find('.blc-cancel-button-container');
1769
-
1770
- //Find the current/original URL
1771
- var orig_url = url_el.attr('href');
1772
- //Find the link ID
1773
- var link_id = $(master).find('.blc-link-id').html();
1774
 
1775
- if ( !$(editor).is(':visible') ){
1776
- //Begin editing
1777
- url_el.hide();
1778
- //Reset the edit box to the actual URL value in case the user has already tried and failed to edit this link.
1779
- editor.val( url_el.attr('href') );
1780
- editor.show();
1781
- cancel_button_container.show();
1782
- editor.focus();
1783
- editor.select();
1784
- edit_button.html('<?php echo js_escape(__('Save URL' , 'broken-link-checker')); ?>');
1785
- } else {
1786
- editor.hide();
1787
- cancel_button_container.hide();
1788
- url_el.show();
1789
 
1790
- new_url = editor.val();
1791
-
1792
- if (new_url != orig_url){
1793
- //Save the changed link
1794
- url_el.html('<?php echo js_escape(__('Saving changes...' , 'broken-link-checker')); ?>');
1795
-
1796
- $.getJSON(
1797
- "<?php echo admin_url('admin-ajax.php'); ?>",
1798
- {
1799
- 'action' : 'blc_edit',
1800
- 'link_id' : link_id,
1801
- 'new_url' : new_url
1802
- },
1803
- function (data, textStatus){
1804
- var display_url = '';
1805
-
1806
- if ( data && (typeof(data['error']) != 'undefined') ){
1807
- //data.error is an error message
1808
- alert(data.error);
1809
- display_url = orig_url;
1810
- } else {
1811
- //data contains info about the performed edit
1812
- if ( data.cnt_okay > 0 ){
1813
- display_url = new_url;
1814
-
1815
- url_el.attr('href', new_url);
1816
-
1817
- if ( data.cnt_error > 0 ){
1818
- //TODO: Internationalize this error message
1819
- var msg = "The link was successfully modifed.";
1820
- msg = msg + "\nHowever, "+data.cnt_error+" instances couldn't be edited and still point to the old URL."
1821
- alert(msg);
1822
- } else {
1823
- //Flash the row green to indicate success
1824
- var oldColor = master.css('background-color');
1825
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300);
1826
-
1827
- //Save the new ID
1828
- master.find('.blc-link-id').html(data.new_link_id);
1829
- //Load up the new link info (so sue me)
1830
- master.next('.blc-link-details').find('td').html('<center><?php echo js_escape(__('Loading...' , 'broken-link-checker')); ?></center>').load(
1831
- "<?php echo admin_url('admin-ajax.php'); ?>",
1832
- {
1833
- 'action' : 'blc_link_details',
1834
- 'link_id' : data.new_link_id
1835
- }
1836
- );
1837
- }
1838
- } else {
1839
- //TODO: Internationalize this error message
1840
- alert("Something went wrong. The plugin failed to edit "+
1841
- data.cnt_error + ' instance(s) of this link.');
1842
-
1843
- display_url = orig_url;
1844
- }
1845
- };
1846
-
1847
- //Shorten the displayed URL if it's > 50 characters
1848
- if ( display_url.length > 50 ){
1849
- display_url = display_url.substr(0, 47) + '...';
1850
- }
1851
- url_el.html(display_url);
1852
- }
1853
- );
1854
-
1855
- } else {
1856
- //It's the same URL, so do nothing.
1857
  }
1858
- edit_button.html('<?php echo js_escape(__('Edit URL', 'broken-link-checker')); ?>');
1859
- }
1860
- });
1861
-
1862
- //Let the user use Enter and Esc as shortcuts for "Save URL" and "Cancel"
1863
- $('input.blc-link-editor').keypress(function (e) {
1864
- if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
1865
- $(this).parents('.blc-row').find('.blc-edit-button').click();
1866
- return false;
1867
- } else if ((e.which && e.which == 27) || (e.keyCode && e.keyCode == 27)) {
1868
- $(this).parents('.blc-row').find('.blc-cancel-button').click();
1869
- return false;
1870
- } else {
1871
- return true;
1872
  }
1873
- });
1874
-
1875
- $(".blc-cancel-button").click(function () {
1876
- var master = $(this).parents('.blc-row');
1877
- var url_el = $(master).find('.blc-link-url');
1878
-
1879
- //Hide the cancel button
1880
- $(this).parent().hide();
1881
- //Show the un-editable URL again
1882
- url_el.show();
1883
- //reset and hide the editor
1884
- master.find('.blc-link-editor').hide().val(url_el.attr('href'));
1885
- //Set the edit button to say "Edit URL"
1886
- master.find('.blc-edit-button').html('<?php echo js_escape(__('Edit URL' , 'broken-link-checker')); ?>');
1887
- });
1888
-
1889
- //The unlink button - remove the link/image from all posts, custom fields, etc.
1890
- $(".blc-unlink-button").click(function () {
1891
- var me = this;
1892
- var master = $(me).parents('.blc-row');
1893
- $(me).html('<?php echo js_escape(__('Wait...' , 'broken-link-checker')); ?>');
1894
 
1895
- var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1896
-
1897
- $.post(
1898
- "<?php echo admin_url('admin-ajax.php'); ?>",
1899
- {
1900
- 'action' : 'blc_unlink',
1901
- 'link_id' : link_id
1902
- },
1903
- function (data, textStatus){
1904
- eval('data = ' + data);
1905
-
1906
- if ( data && ( typeof(data['ok']) != 'undefined') ){
1907
- //Hide the details
1908
- master.next('.blc-link-details').hide();
1909
- //Flash the main row green to indicate success, then hide it.
1910
- var oldColor = master.css('background-color');
1911
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
1912
- master.hide();
1913
- });
1914
-
1915
- alterLinkCounter(-1);
1916
- } else {
1917
- $(me).html('<?php echo js_escape(__('Unlink' , 'broken-link-checker')); ?>');
1918
- //Show the error message
1919
- alert(data.error);
1920
- }
1921
- }
1922
- );
1923
- });
1924
-
1925
- //The exclude button - Add this link to the exclusion list
1926
- $(".blc-exclude-button").click(function () {
1927
- var me = this;
1928
- var master = $(me).parents('.blc-row');
1929
- var details = master.next('.blc-link-details');
1930
- $(me).html('<?php echo js_escape(__('Wait...' , 'broken-link-checker')); ?>');
1931
-
1932
- var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
1933
-
1934
- $.post(
1935
- "<?php echo admin_url('admin-ajax.php'); ?>",
1936
- {
1937
- 'action' : 'blc_exclude_link',
1938
- 'link_id' : link_id
1939
- },
1940
- function (data, textStatus){
1941
- eval('data = ' + data);
1942
-
1943
- if ( data && ( typeof(data['ok']) != 'undefined' ) ){
1944
-
1945
- if ( 'broken' == blc_current_filter ){
1946
- //Flash the row green to indicate success, then hide it.
1947
- $(me).replaceWith('<?php echo js_escape(__('Excluded' , 'broken-link-checker')); ?>');
1948
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 200, function(){
1949
- details.hide();
1950
- master.hide();
1951
- alterLinkCounter(-1);
1952
- });
1953
- master.addClass('blc-excluded-link');
1954
- } else {
1955
- //Flash the row green to indicate success and fade to the "excluded link" color
1956
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: '#E2E2E2' }, 300);
1957
- master.addClass('blc-excluded-link');
1958
- $(me).replaceWith('<?php echo js_escape(__('Excluded' , 'broken-link-checker')); ?>');
1959
- }
1960
- } else {
1961
- $(me).html('<?php echo js_escape(__('Exclude' , 'broken-link-checker')); ?>');
1962
- alert(data.error);
1963
- }
1964
- }
1965
- );
1966
- });
1967
-
1968
- //--------------------------------------------
1969
- //The search box(es)
1970
- //--------------------------------------------
1971
-
1972
- var searchForm = $('#search-links-dialog');
1973
-
1974
- searchForm.dialog({
1975
- autoOpen : false,
1976
- dialogClass : 'blc-search-container',
1977
- resizable: false,
1978
- });
1979
-
1980
- $('#blc-open-search-box').click(function(){
1981
- if ( searchForm.dialog('isOpen') ){
1982
- searchForm.dialog('close');
1983
- } else {
1984
- var button_position = $('#blc-open-search-box').offset();
1985
- var button_height = $('#blc-open-search-box').outerHeight(true);
1986
- var button_width = $('#blc-open-search-box').outerWidth(true);
1987
-
1988
- var dialog_width = searchForm.dialog('option', 'width');
1989
-
1990
- searchForm.dialog('option', 'position',
1991
- [
1992
- button_position.left - dialog_width + button_width/2,
1993
- button_position.top + button_height + 1 - $(document).scrollTop()
1994
- ]
1995
- );
1996
- searchForm.dialog('open');
1997
- }
1998
- });
1999
-
2000
- $('#blc-cancel-search').click(function(){
2001
- searchForm.dialog('close');
2002
- });
2003
-
2004
- //The "Save This Search Query" button creates a new custom filter based on the current search
2005
- $('#blc-create-filter').click(function(){
2006
- var filter_name = prompt("<?php echo js_escape(__("Enter a name for the new custom filter", 'broken-link-checker')); ?>", "");
2007
- if ( filter_name ){
2008
- $('#blc-custom-filter-name').val(filter_name);
2009
- $('#custom-filter-form').submit();
2010
- }
2011
- });
2012
-
2013
- //Display a confirmation dialog when the user clicks the "Delete This Filter" button
2014
- $('#blc-delete-filter').click(function(){
2015
- if ( confirm('<?php
2016
- echo js_escape(
2017
- __("You are about to delete the current filter.\n'Cancel' to stop, 'OK' to delete", 'broken-link-checker')
2018
- );
2019
- ?>') ){
2020
- return true;
2021
- } else {
2022
- return false;
2023
- }
2024
- });
2025
-
2026
- //--------------------------------------------
2027
- // Bulk actions
2028
- //--------------------------------------------
2029
-
2030
- //Not implemented yet
2031
- });
2032
-
2033
- </script>
2034
- <?php
2035
- }
2036
-
2037
- function links_page_css(){
2038
- ?>
2039
- <style type='text/css'>
2040
- .blc-link-editor {
2041
- font-size: 1em;
2042
- width: 95%;
2043
- }
2044
-
2045
- .blc-excluded-link {
2046
- background-color: #E2E2E2;
2047
- }
2048
-
2049
- .blc-small-image {
2050
- display : block;
2051
- float: left;
2052
- padding-top: 2px;
2053
- margin-right: 3px;
2054
- }
2055
-
2056
- .blc-search-container {
2057
- background : white !important;
2058
- border: 3px solid #EEEEEE;
2059
- padding: 12px;
2060
- }
2061
-
2062
- .blc-search-container .ui-dialog-titlebar {
2063
- display: none;
2064
- margin: 0px;
2065
- }
2066
-
2067
- #search-links-dialog {
2068
- display: none;
2069
- }
2070
-
2071
- #search-links-dialog label, #search-links-dialog input.text, #search-links-dialog select { display:block; }
2072
- #search-links-dialog input.text { margin-bottom:12px; width:95%; padding: .4em; }
2073
- #search-links-dialog select { margin-bottom:12px; padding: .4em; }
2074
- #search-links-dialog fieldset { padding:0; border:0; margin-top:25px; }
2075
-
2076
- #blc-search-button-row {
2077
- text-align: center;
2078
- }
2079
-
2080
- #blc-search-button-row input {
2081
- padding: 0.4em;
2082
- margin-left: 8px;
2083
- margin-right: 8px;
2084
- margin-top: 8px;
2085
- }
2086
-
2087
- .blc-inline-form {
2088
- display: inline;
2089
- }
2090
-
2091
- div.search-box{
2092
- float: right;
2093
- margin-top: -5px;
2094
- margin-right: 0pt;
2095
- margin-bottom: 0pt;
2096
- margin-left: 0pt;
2097
- }
2098
- </style>
2099
- <?php
2100
  }
2101
 
2102
- function link_details_row($link){
2103
- ?>
2104
- <span id='post_date_full' style='display:none;'><?php
2105
-
2106
- print $link['post_date'];
2107
-
2108
- ?></span>
2109
- <span id='check_date_full' style='display:none;'><?php
2110
- print $link['last_check'];
2111
- ?></span>
2112
- <ol style='list-style-type: none; width: 50%; float: right;'>
2113
- <li><strong><?php _e('Log', 'broken-link-checker'); ?> :</strong>
2114
- <span class='blc_log'><?php
2115
- print nl2br($link['log']);
2116
- ?></span></li>
2117
- </ol>
2118
-
2119
- <ol style='list-style-type: none; padding-left: 2px;'>
2120
- <?php if ( !empty($link['post_date']) ) { ?>
2121
- <li><strong><?php _e('Post published on', 'broken-link-checker'); ?> :</strong>
2122
- <span class='post_date'><?php
2123
- echo date_i18n(get_option('date_format'),strtotime($link['post_date']));
2124
- ?></span></li>
2125
- <?php } ?>
2126
- <li><strong><?php _e('Link last checked', 'broken-link-checker'); ?> :</strong>
2127
- <span class='check_date'><?php
2128
- $last_check = strtotime($link['last_check']);
2129
- if ( $last_check < strtotime('-10 years') ){
2130
- _e('Never', 'broken-link-checker');
2131
- } else {
2132
- echo date_i18n(get_option('date_format'), $last_check);
2133
- }
2134
- ?></span></li>
2135
-
2136
- <li><strong><?php _e('HTTP code', 'broken-link-checker'); ?> :</strong>
2137
- <span class='http_code'><?php
2138
- print $link['http_code'];
2139
- ?></span></li>
2140
-
2141
- <li><strong><?php _e('Response time', 'broken-link-checker'); ?> :</strong>
2142
- <span class='request_duration'><?php
2143
- printf( __('%2.3f seconds', 'broken-link-checker'), $link['request_duration']);
2144
- ?></span></li>
2145
-
2146
- <li><strong><?php _e('Final URL', 'broken-link-checker'); ?> :</strong>
2147
- <span class='final_url'><?php
2148
- print $link['final_url'];
2149
- ?></span></li>
2150
-
2151
- <li><strong><?php _e('Redirect count', 'broken-link-checker'); ?> :</strong>
2152
- <span class='redirect_count'><?php
2153
- print $link['redirect_count'];
2154
- ?></span></li>
2155
-
2156
- <li><strong><?php _e('Instance count', 'broken-link-checker'); ?> :</strong>
2157
- <span class='instance_count'><?php
2158
- print $link['instance_count'];
2159
- ?></span></li>
2160
-
2161
- <?php if ( intval( $link['check_count'] ) > 0 ){ ?>
2162
- <li><br/>
2163
- <?php
2164
- printf(
2165
- _n('This link has failed %d time.', 'This link has failed %d times.', $link['check_count'], 'broken-link-checker'),
2166
- $link['check_count']
2167
- );
2168
- ?>
2169
- </li>
2170
- <?php } ?>
2171
- </ol>
2172
- <?php
2173
- }
2174
-
2175
  /**
2176
- * ws_broken_link_checker::cleanup_links()
2177
- * Remove orphaned links that have no corresponding instances
2178
  *
2179
- * @param int|array $link_id (optional) Only check these links
2180
- * @return bool
 
2181
  */
2182
- function cleanup_links( $link_id = null ){
2183
- global $wpdb;
2184
-
2185
- $q = "DELETE FROM {$wpdb->prefix}blc_links
2186
- USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
2187
- ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
2188
- WHERE
2189
- {$wpdb->prefix}blc_instances.link_id IS NULL";
2190
-
2191
- if ( $link_id !== null ) {
2192
- if ( !is_array($link_id) ){
2193
- $link_id = array( intval($link_id) );
 
 
 
 
 
 
 
2194
  }
2195
- $q .= " AND {$wpdb->prefix}blc_links.link_id IN (" . implode(', ', $link_id) . ')';
2196
  }
2197
 
2198
- return $wpdb->query( $q );
2199
  }
2200
 
2201
  /**
2202
- * ws_broken_link_checker::cleanup_instances()
2203
- * Remove instances that reference invalid posts or bookmarks
2204
  *
2205
- * @return bool
 
2206
  */
2207
- function cleanup_instances(){
2208
- global $wpdb;
2209
 
2210
- //Delete all instances that reference non-existent posts
2211
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
2212
- USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->posts} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->posts}.ID
2213
- WHERE
2214
- {$wpdb->posts}.ID IS NULL
2215
- AND ( ( {$wpdb->prefix}blc_instances.source_type = 'post' ) OR ( {$wpdb->prefix}blc_instances.source_type = 'custom_field' ) )";
2216
- $rez = $wpdb->query($q);
2217
-
2218
- //Delete all instances that reference non-existent bookmarks
2219
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
2220
- USING {$wpdb->prefix}blc_instances LEFT JOIN {$wpdb->links} ON {$wpdb->prefix}blc_instances.source_id = {$wpdb->links}.link_id
2221
- WHERE
2222
- {$wpdb->links}.link_id IS NULL
2223
- AND {$wpdb->prefix}blc_instances.source_type = 'blogroll' ";
2224
- $rez2 = $wpdb->query($q);
2225
-
2226
- return $rez and $rez2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2227
  }
2228
 
2229
  /**
2230
- * ws_broken_link_checker::parse_post()
2231
- * Parse a post for links and save them to the DB.
2232
  *
2233
- * @param string $content Post content
2234
- * @param int $post_id Post ID
2235
- * @return void
2236
  */
2237
- function parse_post($content, $post_id){
2238
- //remove all <code></code> blocks first
2239
- $content = preg_replace('/<code[^>]*>.+?<\/code>/si', ' ', $content);
2240
- //Get the post permalink - it's used to resolve relative URLs
2241
- $permalink = get_permalink( $post_id );
2242
-
2243
- //Find links
2244
- if(preg_match_all(blcUtility::link_pattern(), $content, $matches, PREG_SET_ORDER)){
2245
- foreach($matches as $link){
2246
- $url = $link[3];
2247
- $text = strip_tags( $link[5] );
2248
- //FB::log($url, "Found link");
2249
-
2250
- $url = blcUtility::normalize_url($url, $permalink);
2251
- //Skip invalid links
2252
- if ( !$url || (strlen($url)<6) ) continue;
2253
-
2254
- //Create or load the link
2255
- $link_obj = new blcLink($url);
2256
- //Add & save a new instance
2257
- $link_obj->add_instance($post_id, 'post', $text, 'link');
2258
- }
2259
- };
2260
 
2261
- //Find images (<img src=...>)
2262
- if(preg_match_all(blcUtility::img_pattern(), $content, $matches, PREG_SET_ORDER)){
2263
- foreach($matches as $img){
2264
- $url = $img[3];
2265
- //FB::log($url, "Found image");
 
 
 
 
 
 
2266
 
2267
- $url = blcUtility::normalize_url($url, $permalink);
2268
- if ( !$url || (strlen($url)<6) ) continue; //skip invalid URLs
 
 
 
 
 
 
 
2269
 
2270
- //Create or load the link
2271
- $link = new blcLink($url);
2272
- //Add & save a new image instance
2273
- $link->add_instance($post_id, 'post', '', 'image');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2274
  }
2275
- };
 
 
2276
  }
2277
 
2278
  /**
2279
- * ws_broken_link_checker::parse_post_meta()
2280
- * Parse a post's custom fields for links and save them in the DB.
2281
  *
2282
- * @param id $post_id
2283
- * @return void
2284
  */
2285
- function parse_post_meta($post_id){
2286
- //Get all custom fields of this post
2287
- $custom_fields = get_post_custom( $post_id );
2288
- //FB::log($custom_fields, "Custom fields loaded");
2289
-
2290
- //Parse the enabled fields
2291
- foreach( $this->conf->options['custom_fields'] as $field ){
2292
- if ( !isset($custom_fields[$field]) ) continue;
 
 
 
 
 
 
 
 
 
2293
 
2294
- //FB::log($field, "Parsing field");
 
 
 
 
2295
 
2296
- $values = $custom_fields[$field];
2297
- if ( !is_array( $values ) ) $values = array($values);
 
 
 
 
 
 
 
 
2298
 
2299
- foreach( $values as $value ){
2300
-
2301
- //If this is a multiline field take the first line (workaround for the enclosure field).
2302
- $value = trim( array_shift( explode("\n", $value) ) );
2303
 
2304
- //Attempt to parse the $value as URL
2305
- $url = blcUtility::normalize_url($value);
2306
- if ( empty($url) ){
2307
- //FB::warn($value, "Invalid URL in custom field ".$field);
2308
- continue;
2309
- }
2310
 
2311
- //FB::log($url, "Found URL");
2312
- $link = new blcLink( $url );
2313
- //FB::log($link, 'Created/loaded link');
2314
- $inst = $link->add_instance( $post_id, 'custom_field', $field, 'link' );
2315
- //FB::log($inst, 'Created instance');
2316
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2317
  }
2318
 
 
2319
  }
2320
 
2321
- function parse_blogroll_link( $the_link ){
2322
- //FB::log($the_link, "Parsing blogroll link");
 
 
 
 
 
 
2323
 
2324
- //Attempt to parse the URL
2325
- $url = blcUtility::normalize_url( $the_link['link_url'] );
2326
- if ( empty($url) ){
2327
- //FB::warn( $the_link['link_url'], "Invalid URL in for a blogroll link".$the_link['link_name'] );
2328
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
2329
  }
2330
 
2331
- //FB::log($url, "Found URL");
2332
- $link = new blcLink( $url );
2333
- return $link->add_instance( $the_link['link_id'], 'blogroll', $the_link['link_name'], 'link' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2334
  }
2335
 
2336
  function start_timer(){
@@ -2342,7 +1776,6 @@ div.search-box{
2342
  }
2343
 
2344
  /**
2345
- * ws_broken_link_checker::work()
2346
  * The main worker function that does all kinds of things.
2347
  *
2348
  * @return void
@@ -2350,9 +1783,20 @@ div.search-box{
2350
  function work(){
2351
  global $wpdb;
2352
 
 
 
 
 
 
 
2353
  if ( !$this->acquire_lock() ){
2354
  //FB::warn("Another instance of BLC is already working. Stop.");
2355
- return false;
 
 
 
 
 
2356
  }
2357
 
2358
  $this->start_timer();
@@ -2379,8 +1823,8 @@ div.search-box{
2379
  //Close the connection as per http://www.php.net/manual/en/features.connection-handling.php#71172
2380
  //This reduces resource usage and may solve the mysterious slowdowns certain users have
2381
  //encountered when activating the plugin.
2382
- //(Comment out when debugging or you won't get the FirePHP output)
2383
- if ( !defined('BLC_DEBUG') ){
2384
  ob_end_clean();
2385
  header("Connection: close");
2386
  ob_start();
@@ -2391,111 +1835,46 @@ div.search-box{
2391
  flush(); // Unless both are called !
2392
  }
2393
 
2394
- $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2395
- $recheck_threshold = date('Y-m-d H:i:s', strtotime('-20 minutes'));
2396
-
2397
  $orphans_possible = false;
2398
-
2399
  $still_need_resynch = $this->conf->options['need_resynch'];
2400
 
2401
  /*****************************************
2402
  Parse posts and bookmarks
2403
  ******************************************/
2404
 
2405
- if ( $this->conf->options['need_resynch'] ) {
2406
-
2407
- //FB::log("Looking for posts and bookmarks that need parsing...");
2408
 
2409
- $tsynch = $wpdb->prefix.'blc_synch';
2410
- $tposts = $wpdb->posts;
2411
- $tlinks = $wpdb->links;
2412
 
2413
- $synch_q = "SELECT $tsynch.source_id, $tsynch.source_type, $tposts.post_content, $tlinks.link_url, $tlinks.link_id, $tlinks.link_name
2414
-
2415
- FROM
2416
- $tsynch LEFT JOIN $tposts
2417
- ON ($tposts.id = $tsynch.source_id AND $tsynch.source_type='post')
2418
- LEFT JOIN $tlinks
2419
- ON ($tlinks.link_id = $tsynch.source_id AND $tsynch.source_type='blogroll')
2420
-
2421
- WHERE
2422
- $tsynch.synched = 0
2423
-
2424
- LIMIT 50";
2425
-
2426
- while ( $rows = $wpdb->get_results($synch_q, ARRAY_A) ) {
2427
-
2428
- //FB::log("Found ".count($rows)." items to analyze.");
2429
-
2430
- foreach ($rows as $row) {
2431
-
2432
- if ( $row['source_type'] == 'post' ){
2433
-
2434
- //FB::log("Parsing post ".$row['source_id']);
2435
-
2436
- //Remove instances associated with this post
2437
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
2438
- WHERE source_id = %d AND (source_type = 'post' OR source_type='custom_field')";
2439
- $q = $wpdb->prepare($q, intval($row['source_id']));
2440
-
2441
- //FB::log($q, "Executing query");
2442
-
2443
- if ( $wpdb->query( $q ) === false ){
2444
- //FB::error($wpdb->last_error, "Database error");
2445
- }
2446
-
2447
- //Gather links and images from the post
2448
- $this->parse_post( $row['post_content'], $row['source_id'] );
2449
- //Gather links from custom fields
2450
- $this->parse_post_meta( $row['source_id'] );
2451
-
2452
- //Some link records might be orhpaned now
2453
- $orphans_possible = true;
2454
-
2455
- } else {
2456
-
2457
- //FB::log("Parsing bookmark ".$row['source_id']);
2458
-
2459
- //Remove instances associated with this bookmark
2460
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
2461
- WHERE source_id = %d AND source_type = 'blogroll'";
2462
- $q = $wpdb->prepare($q, intval($row['source_id']));
2463
- //FB::log($q, "Executing query");
2464
-
2465
- if ( $wpdb->query( $q ) === false ){
2466
- //FB::error($wpdb->last_error, "Database error");
2467
- }
2468
-
2469
- //(Re)add the instance and link
2470
- $this->parse_blogroll_link( $row );
2471
-
2472
- //Some link records might be orhpaned now
2473
- $orphans_possible = true;
2474
-
2475
- }
2476
 
2477
- //Update the table to indicate the item has been parsed
2478
- $this->mark_synched( $row['source_id'], $row['source_type'] );
2479
-
2480
- //Check if we still have some execution time left
2481
  if( $this->execution_time() > $max_execution_time ){
2482
  //FB::log('The alloted execution time has run out');
2483
- $this->cleanup_links();
2484
  $this->release_lock();
2485
  return;
2486
  }
2487
 
 
 
 
 
 
 
 
2488
  }
2489
-
2490
  }
2491
 
2492
  //FB::log('No unparsed items found.');
2493
  $still_need_resynch = false;
2494
 
2495
- if ( $wpdb->last_error ){
2496
- //FB::error($wpdb->last_error, "Database error");
2497
- }
2498
-
2499
  } else {
2500
  //FB::log('Resynch not required.');
2501
  }
@@ -2514,7 +1893,7 @@ div.search-box{
2514
 
2515
  if ( $orphans_possible ) {
2516
  //FB::log('Cleaning up the link table.');
2517
- $this->cleanup_links();
2518
  }
2519
 
2520
  //Check if we still have some execution time left
@@ -2524,56 +1903,31 @@ div.search-box{
2524
  return;
2525
  }
2526
 
 
 
 
 
 
 
2527
  /*****************************************
2528
  Check links
2529
  ******************************************/
2530
- //FB::log('Looking for links to check (threshold : '.$check_threshold.')...');
2531
-
2532
- //Select some links that haven't been checked for a long time or
2533
- //that are broken and need to be re-checked again.
2534
-
2535
- //Note : This is a slow query, but AFAIK there is no way to speed it up.
2536
- //I could put an index on last_check, but that value is almost certainly unique
2537
- //for each row so it wouldn't be much better than a full table scan.
2538
- $q = "SELECT *, ( last_check < %s ) AS meets_check_threshold
2539
- FROM {$wpdb->prefix}blc_links
2540
- WHERE
2541
- ( last_check < %s )
2542
- OR
2543
- (
2544
- ( http_code >= 400 OR http_code < 200 OR timeout = 1)
2545
- AND check_count < %d
2546
- AND check_count > 0
2547
- AND last_check < %s
2548
- )
2549
- ORDER BY last_check ASC
2550
- LIMIT 50";
2551
- $link_q = $wpdb->prepare($q, $check_threshold, $check_threshold, $this->conf->options['recheck_count'], $recheck_threshold);
2552
- //FB::log($link_q);
2553
 
2554
- while ( $links = $wpdb->get_results($link_q, ARRAY_A) ){
2555
-
2556
- //some unchecked links found
2557
  //FB::log("Checking ".count($links)." link(s)");
2558
 
2559
  foreach ($links as $link) {
2560
- $link_obj = new blcLink($link);
2561
-
2562
- //Does this link need to be checked?
2563
- if ( !$this->is_excluded( $link['url'] ) ) {
2564
- //Yes, do it
2565
- //FB::log("Checking link {$link[link_id]}");
2566
- $link_obj->check( $this->conf->options['timeout'] );
2567
- $link_obj->save();
2568
  } else {
2569
- //Nope, mark it as already checked.
2570
- //FB::info("The URL {$link_obj->url} is excluded, marking link {$link_obj->link_id} as already checked.");
2571
- $link_obj->last_check = date('Y-m-d H:i:s');
2572
- $link_obj->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
2573
- $link_obj->timeout = false;
2574
- $link_obj->request_duration = 0;
2575
- $link_obj->log = __("This link wasn't checked because a matching keyword was found on your exclusion list.", 'broken-link-checker');
2576
- $link_obj->save();
2577
  }
2578
 
2579
  //Check if we still have some execution time left
@@ -2582,7 +1936,15 @@ div.search-box{
2582
  $this->release_lock();
2583
  return;
2584
  }
 
 
 
 
 
 
 
2585
  }
 
2586
  }
2587
  //FB::log('No links need to be checked right now.');
2588
 
@@ -2590,6 +1952,98 @@ div.search-box{
2590
  //FB::log('All done.');
2591
  }
2592
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2593
  function ajax_full_status( ){
2594
  $status = $this->get_status();
2595
  $text = $this->status_text( $status );
@@ -2603,7 +2057,6 @@ div.search-box{
2603
  }
2604
 
2605
  /**
2606
- * ws_broken_link_checker::status_text()
2607
  * Generates a status message based on the status info in $status
2608
  *
2609
  * @param array $status
@@ -2658,9 +2111,31 @@ div.search-box{
2658
  return $text;
2659
  }
2660
 
 
 
 
 
 
2661
  function ajax_dashboard_status(){
2662
  //Just display the full status.
2663
- $this->ajax_full_status( );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2664
  }
2665
 
2666
  /**
@@ -2676,10 +2151,10 @@ div.search-box{
2676
  * @return array
2677
  */
2678
  function get_status(){
2679
- global $wpdb;
2680
 
2681
  $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2682
- $recheck_threshold=date('Y-m-d H:i:s', strtotime('-20 minutes'));
2683
 
2684
  $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links WHERE 1";
2685
  $known_links = $wpdb->get_var($q);
@@ -2687,22 +2162,9 @@ div.search-box{
2687
  $q = "SELECT count(*) FROM {$wpdb->prefix}blc_instances WHERE 1";
2688
  $known_instances = $wpdb->get_var($q);
2689
 
2690
- /*
2691
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
2692
- WHERE check_count > 0 AND ( http_code < 200 OR http_code >= 400 OR timeout = 1 ) AND ( http_code <> ".BLC_CHECKING." )";
2693
- $broken_links = $wpdb->get_var($q);
2694
- */
2695
- $broken_links = $this->get_links( $this->native_filters['broken'], 0, 0, true );
2696
 
2697
- $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links
2698
- WHERE
2699
- ( ( last_check < '$check_threshold' ) OR
2700
- (
2701
- ( http_code >= 400 OR http_code < 200 )
2702
- AND check_count < 3
2703
- AND last_check < '$recheck_threshold' )
2704
- )";
2705
- $unchecked_links = $wpdb->get_var($q);
2706
 
2707
  return array(
2708
  'check_threshold' => $check_threshold,
@@ -2720,9 +2182,13 @@ div.search-box{
2720
  die();
2721
  }
2722
 
 
 
 
 
 
2723
  function ajax_discard(){
2724
- //TODO:Rewrite to use JSON instead of plaintext
2725
- if (!current_user_can('edit_others_posts')){
2726
  die( __("You're not allowed to do that!", 'broken-link-checker') );
2727
  }
2728
 
@@ -2734,11 +2200,10 @@ div.search-box{
2734
  printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2735
  die();
2736
  }
2737
- //Make it appear "not broken"
2738
- $link->last_check = date('Y-m-d H:i:s');
2739
- $link->http_code = 200;
2740
- $link->timeout = 0;
2741
- $link->check_count = 0;
2742
  $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2743
 
2744
  //Save the changes
@@ -2752,8 +2217,13 @@ div.search-box{
2752
  }
2753
  }
2754
 
 
 
 
 
 
2755
  function ajax_edit(){
2756
- if (!current_user_can('edit_others_posts')){
2757
  die( json_encode( array(
2758
  'error' => __("You're not allowed to do that!", 'broken-link-checker')
2759
  )));
@@ -2769,23 +2239,37 @@ div.search-box{
2769
  )));
2770
  }
2771
 
2772
- $new_url = blcUtility::normalize_url($_GET['new_url']);
2773
- if ( !$new_url ){
 
 
 
2774
  die( json_encode( array(
2775
  'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
2776
  )));
2777
  }
2778
 
2779
  //Try and edit the link
 
 
2780
  $rez = $link->edit($new_url);
2781
 
2782
- if ( $rez == false ){
2783
  die( json_encode( array(
2784
  'error' => __("An unexpected error occured!", 'broken-link-checker')
2785
  )));
2786
  } else {
2787
- $rez['ok'] = __('OK', 'broken-link-checker');
2788
- die( json_encode($rez) );
 
 
 
 
 
 
 
 
 
2789
  }
2790
 
2791
  } else {
@@ -2795,8 +2279,14 @@ div.search-box{
2795
  }
2796
  }
2797
 
 
 
 
 
 
 
2798
  function ajax_unlink(){
2799
- if (!current_user_can('edit_others_posts')){
2800
  die( json_encode( array(
2801
  'error' => __("You're not allowed to do that!", 'broken-link-checker')
2802
  )));
@@ -2813,14 +2303,23 @@ div.search-box{
2813
  }
2814
 
2815
  //Try and unlink it
2816
- if ( $link->unlink() ){
 
 
2817
  die( json_encode( array(
2818
- 'ok' => sprintf( __("URL %s was removed.", 'broken-link-checker'), $link->url )
2819
  )));
2820
  } else {
2821
- die( json_encode( array(
2822
- 'error' => __("The plugin failed to remove the link.", 'broken-link-checker')
2823
- )));
 
 
 
 
 
 
 
2824
  }
2825
 
2826
  } else {
@@ -2850,76 +2349,20 @@ div.search-box{
2850
  die( __('Error : link ID not specified', 'broken-link-checker') );
2851
  }
2852
 
2853
- //Load the link. link_details_row needs it as an array, so
2854
- //we'll have to do this the long way.
2855
- $q = "SELECT
2856
- links.*,
2857
- COUNT(*) as instance_count
2858
-
2859
- FROM
2860
- {$wpdb->prefix}blc_links AS links,
2861
- {$wpdb->prefix}blc_instances as instances
2862
-
2863
- WHERE
2864
- links.link_id = %d
2865
-
2866
- GROUP BY links.link_id";
2867
 
2868
- $link = $wpdb->get_row( $wpdb->prepare($q, $link_id), ARRAY_A );
2869
- if ( is_array($link) ){
2870
  //FB::info($link, 'Link loaded');
2871
  $this->link_details_row($link);
2872
  die();
2873
  } else {
2874
  printf( __('Failed to load link details (%s)', 'broken-link-checker'), $wpdb->last_error );
2875
- die ();
2876
- }
2877
- }
2878
-
2879
- function ajax_exclude_link(){
2880
- if ( !current_user_can('manage_options') ){
2881
- die( json_encode( array(
2882
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2883
- )));
2884
- }
2885
-
2886
- if ( isset($_POST['link_id']) ){
2887
- //Load the link
2888
- $link = new blcLink( intval($_POST['link_id']) );
2889
-
2890
- if ( !$link->valid() ){
2891
- die( json_encode( array(
2892
- 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
2893
- )));
2894
- }
2895
-
2896
- //Add the URL to the exclusion list
2897
- if ( !in_array( $link->url, $this->conf->options['exclusion_list'] ) ){
2898
- $this->conf->options['exclusion_list'][] = $link->url;
2899
- //Also mark it as already checked so that it doesn't show up with other broken links.
2900
- //FB::info("The URL {$link->url} is excluded, marking link {$link->link_id} as already checked.");
2901
- $link->last_check = date('Y-m-d H:i:s');
2902
- $link->http_code = 200; //Use a fake code so that the link doesn't show up in queries looking for broken links.
2903
- $link->timeout = false;
2904
- $link->request_duration = 0;
2905
- $link->log = __("This link wasn't checked because a matching keyword was found on your exclusion list.", 'broken-link-checker');
2906
- $link->save();
2907
- }
2908
-
2909
- $this->conf->save_options();
2910
-
2911
- die( json_encode( array(
2912
- 'ok' => sprintf( __('URL %s added to the exclusion list', 'broken-link-checker'), $link->url )
2913
- )));
2914
- } else {
2915
- die( json_encode( array(
2916
- 'error' => __("Link ID not specified", 'broken-link-checker')
2917
- )));
2918
  }
2919
  }
2920
 
2921
  /**
2922
- * ws_broken_link_checker::acquire_lock()
2923
  * Create and lock a temporary file.
2924
  *
2925
  * @return bool
@@ -2957,7 +2400,6 @@ div.search-box{
2957
  }
2958
 
2959
  /**
2960
- * ws_broken_link_checker::release_lock()
2961
  * Unlock and delete the temporary file
2962
  *
2963
  * @return bool
@@ -2980,7 +2422,6 @@ div.search-box{
2980
  }
2981
 
2982
  /**
2983
- * ws_broken_link_checker::lockfile_name()
2984
  * Generate system-specific lockfile filename
2985
  *
2986
  * @return string A filename or FALSE on error
@@ -3017,32 +2458,53 @@ div.search-box{
3017
  }
3018
  }
3019
 
3020
- function hook_add_link( $link_id ){
3021
- $this->mark_unsynched( $link_id, 'blogroll' );
3022
- }
3023
-
3024
- function hook_edit_link( $link_id ){
3025
- $this->mark_unsynched( $link_id, 'blogroll' );
 
 
 
 
 
 
 
 
3026
  }
3027
 
3028
- function hook_delete_link( $link_id ){
3029
- global $wpdb;
3030
- //Delete the synch record
3031
- $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}blc_synch WHERE source_id = %d AND source_type='blogroll'", $link_id ) );
 
 
 
 
 
 
 
3032
 
3033
- //Get the matching instance record.
3034
- $inst = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE source_id = %d AND source_type = 'blogroll'", $link_id), ARRAY_A );
 
3035
 
3036
- if ( !$inst ) {
3037
- //No instance record? No problem.
3038
- return;
 
 
 
 
 
 
 
3039
  }
3040
-
3041
- //Remove it
3042
- $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id = %d", $inst['instance_id']) );
3043
-
3044
- //Remove the link that was associated with this instance if it has no more related instances.
3045
- $this->cleanup_links( $inst['link_id'] );
3046
  }
3047
 
3048
  function hook_wp_dashboard_setup(){
@@ -3088,7 +2550,6 @@ div.search-box{
3088
  }
3089
 
3090
  /**
3091
- * wsBrokenLinkChecker::get_debug_info()
3092
  * Collect various debugging information and return it in an associative array
3093
  *
3094
  * @return array
@@ -3137,7 +2598,7 @@ div.search-box{
3137
  $debug[ __('CURL version', 'broken-link-checker') ] = $data;
3138
 
3139
  //Snoopy presence
3140
- if ( class_exists('Snoopy') ){
3141
  $data = array(
3142
  'state' => 'ok',
3143
  'value' => __('Installed', 'broken-link-checker'),
@@ -3205,174 +2666,148 @@ div.search-box{
3205
  return $debug;
3206
  }
3207
 
3208
- /**
3209
- * wsBrokenLinkChecker::load_language()
3210
- * Load the plugin's textdomain
3211
- *
3212
- * @return void
3213
- */
3214
- function load_language(){
3215
- load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
3216
- }
3217
-
3218
- /**
3219
- * wsBrokenLinkChecker::get_links()
3220
- * Get the list of links that match a given filter.
3221
- *
3222
- * @param array|null $filter The filter to apply. Set this to null to return all links (default).
3223
- * @param integer $offset Skip this many links from the beginning. If this parameter is nonzero you must also set the next one.
3224
- * @param integer $max_results The maximum number of links to return.
3225
- * @param bool $count_only Only return the total number of matching links, not the links themselves
3226
- * @return array|int Either an array of links, or the number of matching links. Null on error.
3227
- */
3228
- function get_links( $filter = null, $offset = 0, $max_results = 0, $count_only = false){
3229
- global $wpdb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3230
 
3231
- //Figure out the WHERE expression for this filter
3232
- $where_expr = '1'; //default = select all links
3233
 
3234
- if ( !empty($filter) ){
 
 
3235
 
3236
- //Is this a custom search filter?
3237
- if ( empty($filter['is_search']) ){
3238
- //It's a native filter, so it should have the WHERE epression already set
3239
- $where_expr = $filter['where_expr'];
3240
- } else {
3241
- //It's a search filter, so we must build the WHERE expr for the specific query
3242
- //from the query parameters.
3243
-
3244
- $params = $this->get_search_params($filter);
3245
-
3246
- //Generate the individual clauses of the WHERE expression
3247
- $pieces = array();
3248
-
3249
- //Anchor text - use fulltext search
3250
- if ( !empty($params['s_link_text']) ){
3251
- $pieces[] = 'MATCH(instances.link_text) AGAINST("' . $wpdb->escape($params['s_link_text']) . '")';
3252
- }
3253
-
3254
- //URL - try to match both the initial URL and the final URL.
3255
- //There is limited wildcard support, e.g. "google.*/search" will match both
3256
- //"google.com/search" and "google.lv/search"
3257
- if ( !empty($params['s_link_url']) ){
3258
- $s_link_url = like_escape($wpdb->escape($params['s_link_url']));
3259
- $s_link_url = str_replace('*', '%', $s_link_url);
3260
-
3261
- $pieces[] = '(links.url LIKE "%'. $s_link_url .'%") OR '.
3262
- '(links.final_url LIKE "%'. $s_link_url .'%")';
3263
- }
3264
-
3265
- //Link type should match either the instance_type or the source_type
3266
- if ( !empty($params['s_link_type']) ){
3267
- $s_link_type = $wpdb->escape($params['s_link_type']);
3268
- $pieces[] = "instances.instance_type = '$s_link_type' OR instances.source_type='$s_link_type'";
3269
- }
3270
-
3271
- //HTTP code - the user can provide a list of HTTP response codes and code ranges.
3272
- //Example : 201,400-410,500
3273
- if ( !empty($params['s_http_code']) ){
3274
- //Strip spaces.
3275
- $params['s_http_code'] = str_replace(' ', '', $params['s_http_code']);
3276
- //Split by comma
3277
- $codes = explode(',', $params['s_http_code']);
3278
-
3279
- $individual_codes = array();
3280
- $ranges = array();
3281
-
3282
- //Try to parse each response code or range. Invalid ones are simply ignored.
3283
- foreach($codes as $code){
3284
- if ( is_numeric($code) ){
3285
- //It's a single number
3286
- $individual_codes[] = abs(intval($code));
3287
- } elseif ( strpos($code, '-') !== false ) {
3288
- //Try to parse it as a range
3289
- $range = explode( '-', $code, 2 );
3290
- if ( (count($range) == 2) && is_numeric($range[0]) && is_numeric($range[0]) ){
3291
- //Make sure the smaller code comes first
3292
- $range = array( intval($range[0]), intval($range[1]) );
3293
- $ranges[] = array( min($range), max($range) );
3294
- }
3295
- }
3296
- }
3297
-
3298
- $piece = array();
3299
-
3300
- //All individual response codes get one "http_code IN (...)" clause
3301
- if ( !empty($individual_codes) ){
3302
- $piece[] = '(links.http_code IN ('. implode(', ', $individual_codes) .'))';
3303
- }
3304
-
3305
- //Ranges get a "http_code BETWEEN min AND max" clause each
3306
- if ( !empty($ranges) ){
3307
- $range_strings = array();
3308
- foreach($ranges as $range){
3309
- $range_strings[] = "(links.http_code BETWEEN $range[0] AND $range[1])";
3310
- }
3311
- $piece[] = '( ' . implode(' OR ', $range_strings) . ' )';
3312
- }
3313
-
3314
- //Finally, generate a composite WHERE clause for both types of response code queries
3315
- if ( !empty($piece) ){
3316
- $pieces[] = implode(' OR ', $piece);
3317
- }
3318
-
3319
- }
3320
 
3321
- //Custom filters can optionally call one of the native filters
3322
- //to narrow down the result set.
3323
- if ( !empty($params['s_filter']) && isset($this->native_filters[$params['s_filter']]) ){
3324
- $pieces[] = $this->native_filters[$params['s_filter']]['where_expr'];
3325
- }
3326
 
3327
- if ( !empty($pieces) ){
3328
- $where_expr = "\t( " . implode(" ) AND\n\t( ", $pieces) . ' ) ';
 
3329
  }
3330
  }
3331
-
3332
  }
3333
 
3334
- if ( $count_only ){
3335
- //Only get the number of matching links. This lets us use a simplified query with less joins.
3336
- $q = "
3337
- SELECT COUNT(*)
3338
- FROM (
3339
- SELECT 0
3340
-
3341
- FROM
3342
- {$wpdb->prefix}blc_links AS links,
3343
- {$wpdb->prefix}blc_instances as instances
3344
-
3345
- WHERE
3346
- links.link_id = instances.link_id
3347
- AND ". $where_expr ."
3348
-
3349
- GROUP BY links.link_id) AS foo";
3350
- return $wpdb->get_var($q);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3351
  } else {
3352
- //Select the required links + 1 instance per link + 1 post for instances contained in posts.
3353
- $q = "SELECT
3354
- links.*,
3355
- instances.instance_id, instances.source_id, instances.source_type,
3356
- instances.link_text, instances.instance_type,
3357
- COUNT(*) as instance_count,
3358
- posts.post_title,
3359
- posts.post_date
3360
-
3361
- FROM
3362
- {$wpdb->prefix}blc_links AS links,
3363
- {$wpdb->prefix}blc_instances as instances LEFT JOIN {$wpdb->posts} as posts ON instances.source_id = posts.ID
3364
-
3365
- WHERE
3366
- links.link_id = instances.link_id
3367
- AND ". $where_expr ."
3368
-
3369
- GROUP BY links.link_id";
3370
- if ( $max_results || $offset ){
3371
- $q .= "\nLIMIT $offset, $max_results";
3372
  }
3373
-
3374
- return $wpdb->get_results($q, ARRAY_A);
3375
  }
 
 
 
 
 
 
 
 
 
3376
  }
3377
 
3378
  }//class ends here
1
  <?php
2
 
 
 
 
3
  /**
4
  * Simple function to replicate PHP 5 behaviour
5
  */
19
  var $loader;
20
  var $my_basename = '';
21
 
22
+ var $db_version = 4; //The required version of the plugin's DB schema.
23
 
24
  var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
25
  var $lockfile_handle = null;
26
 
 
 
27
  /**
28
  * wsBrokenLinkChecker::wsBrokenLinkChecker()
29
  * Class constructor
35
  function wsBrokenLinkChecker ( $loader, $conf ) {
36
  global $wpdb;
37
 
 
38
  $this->conf = $conf;
39
+ $this->loader = $loader;
 
40
  $this->my_basename = plugin_basename( $this->loader );
41
+
42
+ register_activation_hook($this->my_basename, array(&$this,'activation'));
43
+ register_deactivation_hook($this->my_basename, array(&$this, 'deactivation'));
44
 
45
  add_action('init', array(&$this,'load_language'));
46
 
47
  add_action('admin_menu', array(&$this,'admin_menu'));
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  //Load jQuery on Dashboard pages (probably redundant as WP already does that)
50
  add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
51
 
53
  add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
54
 
55
  //AJAXy hooks
 
56
  add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
57
  add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
58
  add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
59
  add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
60
  add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
61
  add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
 
62
  add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
63
+ add_action( 'wp_ajax_blc_current_load', array(&$this,'ajax_current_load') );
64
 
65
  //Check if it's possible to create a lockfile and nag the user about it if not.
66
  if ( $this->lockfile_name() ){
72
  add_action( 'admin_notices', array( &$this, 'lockfile_warning' ) );
73
  }
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, 'send_email_notifications' ));
80
+ add_action('blc_cron_check_links', array(&$this, 'cron_check_links'));
81
  }
82
 
83
+ /**
84
+ * Output the script that runs the link monitor while the Dashboard is open.
85
+ *
86
+ * @return void
87
+ */
88
  function admin_footer(){
89
+ if ( !$this->conf->options['run_in_dashboard'] ){
90
+ return;
91
+ }
92
  ?>
93
  <!-- wsblc admin footer -->
 
94
  <script type='text/javascript'>
95
  (function($){
96
 
115
  <?php
116
  }
117
 
118
+ /**
119
+ * Check if an URL matches the exclusion list.
120
+ *
121
+ * @param string $url
122
+ * @return bool
123
+ */
124
  function is_excluded($url){
125
  if (!is_array($this->conf->options['exclusion_list'])) return false;
126
  foreach($this->conf->options['exclusion_list'] as $excluded_word){
191
  wp_enqueue_script('jquery');
192
  }
193
 
194
+ function enqueue_settings_scripts(){
195
+ //jQuery UI is used on the settings page
196
+ wp_enqueue_script('jquery-ui-core');
197
+ wp_enqueue_script('jquery-ui-dialog');
198
+ }
199
+
200
+ function enqueue_link_page_scripts(){
201
  wp_enqueue_script('jquery-ui-core');
202
  wp_enqueue_script('jquery-ui-dialog');
203
+ wp_enqueue_script('sprintf', WP_PLUGIN_URL . '/' . dirname($this->my_basename) . '/js/sprintf.js');
204
  }
205
 
206
  /**
207
+ * Initiate a full recheck - reparse everything and check all links anew.
 
208
  *
 
209
  * @return void
210
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  function initiate_recheck(){
212
  global $wpdb;
213
 
218
  $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
219
 
220
  //Mark all posts, custom fields and bookmarks for processing.
221
+ blc_resynch(true);
222
  }
223
 
224
+ /**
225
+ * This is a hook that's executed when the plugin is activated.
226
+ * It set's up and populates the plugin's DB tables & performs
227
+ * other installation tasks.
228
+ *
229
+ * @return void
230
+ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  function activation(){
232
+ blc_init_all_components();
233
+
234
  //Prepare the database.
235
  $this->upgrade_database();
236
 
237
+ //Mark all new posts and other parse-able objects as unsynchronized.
238
+ blc_resynch();
239
 
240
  //Save the default options.
241
  $this->conf->save_options();
244
  $this->optimize_database();
245
  }
246
 
247
+
248
  /**
249
+ * A hook executed when the plugin is deactivated.
 
250
  *
 
251
  * @return void
252
  */
253
+ function deactivation(){
254
+ //Remove our Cron events
255
+ wp_clear_scheduled_hook('blc_cron_check_links');
256
+ wp_clear_scheduled_hook('blc_cron_email_notifications');
257
+ }
258
+
259
+ /**
260
+ * Create and/or upgrade the plugin's database tables.
261
+ *
262
+ * @return void
263
+ */
264
+ function upgrade_database(){
265
  global $wpdb;
266
 
267
  //Do we need to upgrade?
268
+ if ( $this->db_version == $this->conf->options['current_db_version'] ) {
269
+ //The DB is up to date, but lets make sure all the required tables are present
270
+ //in case the user has decided to delete some of them.
271
+ return $this->maybe_create_tables();
 
 
 
 
272
  }
273
 
274
+ //Upgrade to DB version 4
275
+ if ( $this->db_version == 4 ){
276
+ //The 4th version makes a lot of backwards-incompatible changes to the main
277
+ //BLC tables (in particular, it adds several required fields to blc_instances).
278
+ //While it would be possible to import data from an older version of the DB,
279
+ //some things - like link editing - wouldn't work with the old data.
280
+
281
+ //So we just drop, recreate and repopulate most tables.
282
+ $tables = array(
283
+ $wpdb->prefix . 'blc_linkdata',
284
+ $wpdb->prefix . 'blc_postdata',
285
+ $wpdb->prefix . 'blc_instances',
286
+ $wpdb->prefix . 'blc_synch',
287
+ $wpdb->prefix . 'blc_links',
288
+ );
289
+
290
+ $q = "DROP TABLE IF EXISTS " . implode(', ', $tables);
291
+ $rez = $wpdb->query( $q );
292
+ if ( $rez === false ){
293
+ trigger_error(
294
+ sprintf(
295
+ __("Failed to delete old DB tables. Database error : %s", 'broken-link-checker'),
296
+ $wpdb->last_error
297
+ ),
298
+ E_USER_ERROR
299
+ );
300
+ }
301
+
302
+ //Create new DB tables.
303
+ if ( !$this->maybe_create_tables() ){
304
+ return false;
305
+ };
306
+
307
+ } else {
308
+ //This should never happen.
309
+ trigger_error(
310
+ sprintf(
311
+ __(
312
+ "Unexpected error: The plugin doesn't know how to upgrade its database to version '%d'.",
313
+ 'broken-link-checker'
314
+ ),
315
+ $this->db_version
316
+ ),
317
+ E_USER_ERROR
318
+ );
319
+ }
320
 
321
+ //Upgrade was successful.
322
+ $this->conf->options['current_db_version'] = $this->db_version;
323
+ $this->conf->save_options();
 
 
 
 
 
324
 
325
+ return true;
326
+ }
327
+
328
+ /**
329
+ * Create the plugin's DB tables, unless they already exist.
330
+ *
331
+ * @return bool
332
+ */
333
+ function maybe_create_tables(){
334
+ global $wpdb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
+ //Search filters
337
+ $q = <<<EOD
338
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_filters` (
339
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
340
  `name` varchar(100) NOT NULL,
341
+ `params` text NOT NULL,
342
+ PRIMARY KEY (`id`)
343
+ )
344
+ EOD;
345
+ if ( $wpdb->query($q) === false ){
346
+ trigger_error(
347
+ sprintf(
348
+ __("Failed to create table '%s'. Database error: %s", 'broken-link-checker'),
349
+ $wpdb->prefix . 'blc_filters',
350
+ $wpdb->last_error
351
+ ),
352
+ E_USER_ERROR
353
+ );
354
+ }
355
 
356
+ //Link instances (i.e. link occurences inside posts and other items)
357
+ $q = <<<EOT
358
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_instances` (
359
+ `instance_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
360
+ `link_id` int(10) unsigned NOT NULL,
361
+ `container_id` int(10) unsigned NOT NULL,
362
+ `container_type` varchar(40) NOT NULL DEFAULT 'post',
363
+ `link_text` varchar(250) NOT NULL,
364
+ `parser_type` varchar(40) NOT NULL DEFAULT 'link',
365
+ `container_field` varchar(250) NOT NULL,
366
+ `link_context` varchar(250) NOT NULL,
367
+ `raw_url` text NOT NULL,
368
+
369
+ PRIMARY KEY (`instance_id`),
370
+ KEY `link_id` (`link_id`),
371
+ KEY `source_id` (`container_id`,`container_type`)
372
+ );
373
+ EOT;
374
+ if ( $wpdb->query($q) === false ){
375
+ trigger_error(
376
+ sprintf(
377
+ __("Failed to create table '%s'. Database error: %s", 'broken-link-checker'),
378
+ $wpdb->prefix . 'blc_instances',
379
+ $wpdb->last_error
380
+ ),
381
+ E_USER_ERROR
382
+ );
383
+ }
384
+
385
+ //Links themselves. Note : The 'url' and 'final_url' columns must be collated
386
+ //in a case-sensitive manner. This is because "http://a.b/cde" may be a page
387
+ //very different from "http://a.b/CDe".
388
+ $q = <<<EOS
389
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_links` (
390
+ `link_id` int(20) unsigned NOT NULL AUTO_INCREMENT,
391
+ `url` text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
392
+ `first_failure` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
393
+ `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
394
+ `last_success` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
395
+ `last_check_attempt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
396
+ `check_count` int(4) unsigned NOT NULL DEFAULT '0',
397
+ `final_url` text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
398
+ `redirect_count` smallint(5) unsigned NOT NULL DEFAULT '0',
399
+ `log` text NOT NULL,
400
+ `http_code` smallint(6) NOT NULL,
401
+ `request_duration` float NOT NULL DEFAULT '0',
402
+ `timeout` tinyint(1) unsigned NOT NULL DEFAULT '0',
403
+ `broken` tinyint(1) NOT NULL DEFAULT '0',
404
+ `may_recheck` tinyint(1) NOT NULL DEFAULT '1',
405
+ `being_checked` tinyint(1) NOT NULL DEFAULT '0',
406
+ `result_hash` varchar(200) NOT NULL DEFAULT '',
407
+ `false_positive` tinyint(1) NOT NULL DEFAULT '0',
408
+
409
+ PRIMARY KEY (`link_id`),
410
+ KEY `url` (`url`(150)),
411
+ KEY `final_url` (`final_url`(150)),
412
+ KEY `http_code` (`http_code`),
413
+ KEY `broken` (`broken`)
414
+ );
415
+ EOS;
416
+ if ( $wpdb->query($q) === false ){
417
+ trigger_error(
418
+ sprintf(
419
+ __("Failed to create table '%s'. Database error: %s", 'broken-link-checker'),
420
+ $wpdb->prefix . 'blc_links',
421
+ $wpdb->last_error
422
+ ),
423
+ E_USER_ERROR
424
+ );
425
+ }
426
+
427
+ //Synchronization records. This table is used to keep track of if and when various items
428
+ //(e.g. posts, comments, etc) were parsed.
429
+ $q = <<<EOZ
430
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_synch` (
431
+ `container_id` int(20) unsigned NOT NULL,
432
+ `container_type` varchar(40) NOT NULL,
433
+ `synched` tinyint(3) unsigned NOT NULL,
434
+ `last_synch` datetime NOT NULL,
435
+
436
+ PRIMARY KEY (`container_type`,`container_id`),
437
+ KEY `synched` (`synched`)
438
+ );
439
+ EOZ;
440
+
441
+ if ( $wpdb->query($q) === false ){
442
+ trigger_error(
443
+ sprintf(
444
+ __("Failed to create table '%s'. Database error: %s", 'broken-link-checker'),
445
+ $wpdb->prefix . 'blc_synch',
446
+ $wpdb->last_error
447
+ ),
448
+ E_USER_ERROR
449
+ );
450
+ }
451
 
452
+ //All good.
453
  return true;
454
  }
455
 
456
  /**
 
457
  * Optimize the plugin's tables
458
  *
459
  * @return void
483
  );
484
 
485
  //Add plugin-specific scripts and CSS only to the it's own pages
 
486
  add_action( 'admin_print_styles-' . $options_page_hook, array(&$this, 'options_page_css') );
487
  add_action( 'admin_print_styles-' . $links_page_hook, array(&$this, 'links_page_css') );
488
+ add_action( 'admin_print_scripts-' . $options_page_hook, array(&$this, 'enqueue_settings_scripts') );
489
+ add_action( 'admin_print_scripts-' . $links_page_hook, array(&$this, 'enqueue_link_page_scripts') );
490
  }
491
 
492
+ /**
493
  * plugin_action_links()
494
  * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
495
  * on the plugin list.
504
  return $links;
505
  }
506
 
 
 
 
 
 
507
  function options_page(){
508
+ global $blc_container_registry;
509
+
510
+ //Sanity check : make sure the DB is all set up
511
+ if ( $this->db_version != $this->conf->options['current_db_version'] ) {
512
+ printf(
513
+ __("Error: The plugin's database tables are not up to date! (Current version : %d, expected : %d)", 'broken-link-checker'),
514
+ $this->conf->options['current_db_version'],
515
+ $this->db_version
516
+ );
517
+ return;
518
+ }
519
+
520
  if (isset($_GET['recheck']) && ($_GET['recheck'] == 'true')) {
521
  $this->initiate_recheck();
522
  }
523
+
524
  if(isset($_POST['submit'])) {
525
  check_admin_referer('link-checker-options');
526
 
544
  $new_removed_link_css = trim($_POST['removed_link_css']);
545
  $this->conf->options['removed_link_css'] = $new_removed_link_css;
546
 
 
547
  $this->conf->options['exclusion_list'] = array_filter(
548
  preg_split(
549
  '/[\s\r\n]+/', //split on newlines and whitespace
571
  if( $new_timeout > 0 ){
572
  $this->conf->options['timeout'] = $new_timeout ;
573
  }
574
+
575
+ //Server load limit
576
+ if ( isset($_POST['server_load_limit']) ){
577
+ $this->conf->options['server_load_limit'] = floatval($_POST['server_load_limit']);
578
+ if ( $this->conf->options['server_load_limit'] < 0 ){
579
+ $this->conf->options['server_load_limit'] = 0;
580
+ }
581
+ }
582
+ $this->conf->options['enable_load_limit'] = $this->conf->options['server_load_limit'] > 0;
583
+
584
+ //When to run the checker
585
+ $this->conf->options['run_in_dashboard'] = !empty($_POST['run_in_dashboard']);
586
+ $this->conf->options['run_via_cron'] = !empty($_POST['run_via_cron']);
587
+
588
+ //Email notifications on/off
589
+ $email_notifications = !empty($_POST['send_email_notifications']);
590
+ if ( $email_notifications && ! $this->conf->options['send_email_notifications']){
591
+ /*
592
+ The plugin should only send notifications about links that have become broken
593
+ since the time when email notifications were turned on. If we don't do this,
594
+ the first email notification will be sent nigh-immediately and list *all* broken
595
+ links that the plugin currently knows about.
596
+ */
597
+ $this->options['last_notification_sent'] = time();
598
+ }
599
+ $this->conf->options['send_email_notifications'] = $email_notifications;
600
+
601
+ //Make settings that affect our Cron events take effect immediately
602
+ $this->setup_cron_events();
603
+
604
  $this->conf->save_options();
605
 
606
  /*
609
  inefficient.
610
  */
611
  if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
612
+ $manager = $blc_container_registry->get_manager('custom_field');
613
+ if ( !is_null($manager) ){
614
+ $manager->resynch();
615
+ blc_got_unsynched_items();
616
+ }
617
  }
618
 
619
+ //Redirect back to the settings page
620
  $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
621
+ wp_redirect( add_query_arg( array( 'settings-updated' => true), $base_url ) );
622
+ }
623
+
624
+ //Show a confirmation message when settings are saved.
625
+ if ( !empty($_GET['settings-updated']) ){
626
+ echo '<div id="message" class="updated fade"><p><strong>',__('Settings saved.', 'broken-link-checker'), '</strong></p></div>';
627
+
628
  }
629
 
630
  $debug = $this->get_debug_info();
631
+
632
  ?>
633
 
634
  <div class="wrap"><h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
650
  </th>
651
  <td>
652
 
 
653
  <div id='wsblc_full_status'>
654
  <br/><br/><br/>
655
  </div>
779
  </td>
780
  </tr>
781
 
782
+ <tr valign="top">
783
+ <th scope="row"><?php _e('E-mail notifications', 'broken-link-checker'); ?></th>
784
+ <td>
785
+ <p>
786
+ <label for='send_email_notifications'>
787
+ <input type="checkbox" name="send_email_notifications" id="send_email_notifications"
788
+ <?php if ($this->conf->options['send_email_notifications']) echo ' checked="checked"'; ?>/>
789
+ <?php _e('Send me e-mail notifications about newly detected broken links', 'broken-link-checker'); ?>
790
+ </label><br>
791
+ </p>
792
+ </td>
793
+ </tr>
794
+
795
  </table>
796
 
797
  <h3><?php _e('Advanced','broken-link-checker'); ?></h3>
821
  </td>
822
  </tr>
823
 
824
+ <tr valign="top">
825
+ <th scope="row"><?php _e('Link monitor', 'broken-link-checker'); ?></th>
826
+ <td>
827
+ <label for='run_in_dashboard'>
828
+ <p>
829
+ <input type="checkbox" name="run_in_dashboard" id="run_in_dashboard"
830
+ <?php if ($this->conf->options['run_in_dashboard']) echo ' checked="checked"'; ?>/>
831
+ <?php _e('Run continuously while the Dashboard is open', 'broken-link-checker'); ?>
832
+ </p>
833
+ </label>
834
+
835
+ <label for='run_via_cron'>
836
+ <p>
837
+ <input type="checkbox" name="run_via_cron" id="run_via_cron"
838
+ <?php if ($this->conf->options['run_via_cron']) echo ' checked="checked"'; ?>/>
839
+ <?php _e('Run hourly in the background', 'broken-link-checker'); ?>
840
+ </p>
841
+ </label>
842
+
843
+ </td>
844
+ </tr>
845
+
846
+ <tr valign="top">
847
+ <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
848
+ <td>
849
+
850
+ <?php
851
+
852
+ printf(
853
+ __('%s seconds', 'broken-link-checker'),
854
+ sprintf(
855
+ '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
856
+ $this->conf->options['max_execution_time']
857
+ )
858
+ );
859
+
860
+ ?>
861
+ <br/><span class="description">
862
+ <?php
863
+
864
+ _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');
865
+
866
+ ?>
867
+ </span>
868
+
869
+ </td>
870
+ </tr>
871
 
872
  <tr valign="top">
873
  <th scope="row">
901
 
902
  </td>
903
  </tr>
904
+
905
+ <tr valign="top">
906
+ <th scope="row"><?php _e('Server load limit', 'broken-link-checker'); ?></th>
907
  <td>
 
908
  <?php
909
 
910
+ $load = $this->get_server_load();
911
+ $available = !empty($load);
 
 
 
 
 
912
 
913
+ if ( $available ){
914
+ $value = !empty($this->conf->options['server_load_limit'])?sprintf('%.2f', $this->conf->options['server_load_limit']):'';
915
+ printf(
916
+ '<input type="text" name="server_load_limit" id="server_load_limit" value="%s" size="5" maxlength="5"/> ',
917
+ $value
918
+ );
919
+
920
+ ?>
921
+ Current load : <span id='wsblc_current_load'>...</span>
922
+ <script type='text/javascript'>
923
+ (function($){
924
+
925
+ function blcUpdateLoad(){
926
+ $.get(
927
+ "<?php echo admin_url('admin-ajax.php'); ?>",
928
+ {
929
+ 'action' : 'blc_current_load'
930
+ },
931
+ function (data, textStatus){
932
+ $('#wsblc_current_load').html(data);
933
+
934
+ setTimeout(blcUpdateLoad, 10000); //...update every 10 seconds
935
+ }
936
+ );
937
+ }
938
+ blcUpdateLoad();//Call it the first time
939
+
940
+ })(jQuery);
941
+ </script>
942
+ <?
943
+
944
+ echo '<br/><span class="description">';
945
+ printf(
946
+ __(
947
+ '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.',
948
+ 'broken-link-checker'
949
+ ),
950
+ 'http://en.wikipedia.org/wiki/Load_(computing)'
951
+ );
952
+ echo '</span>';
953
 
954
+ } else {
955
+ echo '<input type="text" disabled="disabled" value="Not available" size="13"/><br>';
956
+ echo '<span class="description">';
957
+ _e('Load limiting only works on Linux-like systems where <code>/proc/loadavg</code> is present and accessible.', 'broken-link-checker');
958
+ echo '</span>';
959
+ }
960
  ?>
 
 
961
  </td>
962
  </tr>
963
+
964
  </table>
965
 
966
  <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
988
  }
989
 
990
  function options_page_css(){
991
+ wp_enqueue_style('blc-links-page', plugin_dir_url($this->loader) . 'css/options-page.css' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
992
  }
993
 
994
+
995
+ function links_page(){
996
+ global $wpdb, $blc_link_query;
997
+
998
+ //Sanity check : Make sure the plugin's tables are all set up.
999
+ if ( $this->db_version != $this->conf->options['current_db_version'] ) {
1000
+ printf(
1001
+ __("Error: The plugin's database tables are not up to date! (Current version : %d, expected : %d)", 'broken-link-checker'),
1002
+ $this->conf->options['current_db_version'],
1003
+ $this->db_version
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  );
1005
+ return;
 
1006
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1007
 
1008
  $action = !empty($_POST['action'])?$_POST['action']:'';
1009
  if ( intval($action) == -1 ){
1023
  $message = '';
1024
  $msg_class = 'updated';
1025
 
1026
+ //Run the selected bulk action, if any
1027
  if ( $action == 'create-custom-filter' ){
1028
+ list($message, $msg_class) = $this->do_create_custom_filter();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1029
  } elseif ( $action == 'delete-custom-filter' ){
1030
+ list($message, $msg_class) = $this->do_delete_custom_filter();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  } elseif ($action == 'bulk-delete-sources') {
1032
+ list($message, $msg_class) = $this->do_bulk_delete_sources($selected_links);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1033
  } elseif ($action == 'bulk-unlink') {
1034
+ list($message, $msg_class) = $this->do_bulk_unlink($selected_links);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
  } elseif ($action == 'bulk-deredirect') {
1036
+ list($message, $msg_class) = $this->do_bulk_deredirect($selected_links);
1037
+ } elseif ($action == 'bulk-recheck') {
1038
+ list($message, $msg_class) = $this->do_bulk_recheck($selected_links);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  }
1040
 
1041
  if ( !empty($message) ){
1042
+ echo '<div id="message" class="'.$msg_class.' fade"><p>'.$message.'</p></div>';
1043
  }
1044
 
1045
+ //Load custom filters, if any
1046
+ $blc_link_query->load_custom_filters();
1047
 
1048
+ //Calculate the number of links matching each filter
1049
+ $blc_link_query->count_filter_results();
 
 
 
 
 
 
 
1050
 
1051
+ $filters = $blc_link_query->get_filters();
 
 
 
1052
 
1053
  //Get the selected filter (defaults to displaying broken links)
1054
  $filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1055
+ $current_filter = $blc_link_query->get_filter($filter_id);
1056
+ if ( empty($current_filter) ){
1057
  $filter_id = 'broken';
1058
+ $current_filter = $blc_link_query->get_filter('broken');
1059
+
1060
  }
1061
 
1062
  //Get the desired page number (must be > 0)
1071
  $per_page = 200;
1072
  }
1073
 
1074
+ //Calculate the maximum number of pages.
1075
  $max_pages = ceil($current_filter['count'] / $per_page);
1076
 
1077
+ //Select the required links
1078
+ $extra_params = array(
1079
+ 'offset' => ( ($page-1) * $per_page ),
1080
+ 'max_results' => $per_page,
1081
+ 'purpose' => BLC_FOR_DISPLAY,
1082
+ );
1083
+ $links = $blc_link_query->get_filter_links($current_filter, $extra_params);
1084
+
1085
+ //Error?
1086
+ if ( empty($links) && !empty($wpdb->last_error) ){
1087
  printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1088
  }
1089
 
1090
+ //If the current request is a user-initiated search query (either directly or
1091
+ //via a custom filter), save the search params. They can later be used to pre-fill
1092
+ //the search form or build a new/modified custom filter.
1093
+ if ( !empty($current_filter['custom']) || ($filter_id == 'search') ){
1094
+ $search_params = $blc_link_query->get_search_params($current_filter);
1095
  }
1096
 
 
 
 
1097
  //Figure out what the "safe" URL to acccess the current page would be.
1098
  //This is used by the bulk action form.
1099
  $special_args = array('_wpnonce', '_wp_http_referer', 'action', 'selected_links');
1103
 
1104
  <script type='text/javascript'>
1105
  var blc_current_filter = '<?php echo $filter_id; ?>';
1106
+ var blc_is_broken_filter = <?php
1107
+ if ( ($filter_id == 'broken') || ( isset($current_filter['params']['s_filter']) && ($current_filter['params']['s_filter'] = 'broken') ) ){
1108
+ echo 'true';
1109
+ } else {
1110
+ echo 'false';
1111
+ }
1112
+ ?>;
1113
  </script>
1114
 
1115
  <div class="wrap">
1144
  ?>
1145
  </ul>
1146
 
1147
+ <?php
1148
+ //Display the "Search" form and associated buttons.
1149
+ //The form requires the $filter_id and $search_params variables to be set.
1150
+ include dirname($this->loader) . '/includes/admin/search-form.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1152
 
 
1153
  //Do we have any links to display?
1154
  if( $links && ( count($links) > 0 ) ) {
1155
  ?>
1160
 
1161
  $bulk_actions = array(
1162
  '-1' => __('Bulk Actions', 'broken-link-checker'),
1163
+ "bulk-recheck" => __('Recheck', 'broken-link-checker'),
1164
  "bulk-deredirect" => __('Fix redirects', 'broken-link-checker'),
1165
+ "bulk-unlink" => __('Unlink', 'broken-link-checker'),
1166
  "bulk-delete-sources" => __('Delete sources', 'broken-link-checker'),
1167
  );
1168
 
1171
  $bulk_actions_html .= sprintf('<option value="%s">%s</option>', $value, $name);
1172
  }
1173
  ?>
1174
+
1175
  <div class='tablenav'>
1176
  <div class="alignleft actions">
1177
+ <select name="action" id="blc-bulk-action">
1178
  <?php echo $bulk_actions_html; ?>
1179
  </select>
1180
  <input type="submit" name="doaction" id="doaction" value="<?php echo attribute_escape(__('Apply', 'broken-link-checker')); ?>" class="button-secondary action">
1211
  <th scope="col" id="cb" class="check-column">
1212
  <input type="checkbox">
1213
  </th>
1214
+ <th scope="col" class="column-title blc-column-source"><?php _e('Source', 'broken-link-checker'); ?></th>
1215
+ <th scope="col" class="blc-column-link-text"><?php _e('Link Text', 'broken-link-checker'); ?></th>
1216
+ <th scope="col" class="blc-column-url"><?php _e('URL', 'broken-link-checker'); ?></th>
 
 
 
 
 
1217
  </tr>
1218
  </thead>
1219
  <tbody id="the-list">
1222
  foreach ($links as $link) {
1223
  $rownum++;
1224
 
1225
+ $rowclass = ($rownum % 2)? 'alternate' : '';
1226
+ $excluded = $this->is_excluded( $link->url );
1227
  if ( $excluded ) $rowclass .= ' blc-excluded-link';
1228
 
1229
+ if ( $link->redirect_count > 0){
1230
+ $rowclass .= ' blc-redirect';
1231
+ }
1232
+
1233
+ if ( $link->broken ){
1234
+ $rowclass .= ' blc-broken-link';
1235
+ }
1236
+
1237
  ?>
1238
+ <tr id='<?php echo "blc-row-" . $link->link_id; ?>' class='blc-row <?php echo $rowclass; ?>'>
1239
 
1240
  <th class="check-column" scope="row">
1241
+ <input type="checkbox" name="selected_links[]" value="<?php echo $link->link_id; ?>">
1242
  </th>
1243
 
1244
  <td class='post-title column-title'>
1245
+ <span class='blc-link-id' style='display:none;'><?php echo $link->link_id; ?></span>
1246
+ <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1247
 
1248
+ //Pick one link instance to display in the table
1249
+ $instance = null;
1250
+ $instances = $link->get_instances();
1251
+
1252
+ if ( !empty($instances) ){
1253
+ //Try to find one that matches the selected link type, if any
1254
+ if( !empty($search_params['s_link_type']) ){
1255
+ foreach($instances as $candidate){
1256
+ if ( ($candidate->container_type == $search_params['s_link_type']) || ($candidate->parser_type == $search_params['s_link_type']) ){
1257
+ $instance = $candidate;
1258
+ break;
1259
+ }
1260
+ }
1261
  }
1262
+ //If there's no specific link type set, or no suitable instances were found,
1263
+ //just use the first one.
1264
+ if ( is_null($instance) ){
1265
+ $instance = $instances[0];
1266
+ }
1267
+
1268
+ }
1269
+
1270
+ //Print the contents of the "Source" column
1271
+ if ( !is_null($instance) ){
1272
+ echo $instance->ui_get_source();
1273
+
1274
+ $actions = $instance->ui_get_action_links();
1275
 
1276
  echo '<div class="row-actions">';
1277
  echo implode(' | </span>', $actions);
1278
  echo '</div>';
 
 
 
 
1279
 
1280
+ } else {
1281
+ _e("[An orphaned link! This is a bug.]", 'broken-link-checker');
1282
+ }
1283
+
1284
  ?>
1285
  </td>
1286
  <td class='blc-link-text'><?php
1287
+ //The "Link text" column
1288
+ if ( !is_null($instance) ){
1289
+ echo $instance->ui_get_link_text();
1290
+ } else {
1291
+ echo '<em>N/A</em>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
  }
1293
  ?>
1294
  </td>
1295
  <td class='column-url'>
1296
+ <a href="<?php print esc_attr($link->url); ?>" target='_blank' class='blc-link-url' title="<?php echo esc_attr($link->url); ?>">
1297
+ <?php print blcUtility::truncate($link->url, 50, ''); ?></a>
1298
  <input type='text' id='link-editor-<?php print $rownum; ?>'
1299
+ value="<?php print esc_attr($link->url); ?>"
1300
  class='blc-link-editor' style='display:none' />
1301
  <?php
1302
  //Output inline action links for the link/URL
1307
  $actions['delete'] = "<span class='delete'><a class='submitdelete blc-unlink-button' title='" . attribute_escape( __('Remove this link from all posts', 'broken-link-checker') ). "' ".
1308
  "id='unlink-button-$rownum' href='javascript:void(0);'>" . __('Unlink', 'broken-link-checker') . "</a>";
1309
 
1310
+ if ( $link->broken ){
1311
+ $actions['discard'] = sprintf(
1312
+ '<span><a href="#" title="%s" class="blc-discard-button">%s</a>',
1313
+ esc_attr(__('Remove this link from the list of broken links and mark it as valid', 'broken-link-checker')),
1314
+ __('Not broken', 'broken-link-checker')
1315
+ );
1316
  }
1317
 
1318
  $actions['edit'] = "<span class='edit'><a href='javascript:void(0)' class='blc-edit-button' title='" . attribute_escape( __('Edit link URL' , 'broken-link-checker') ) . "'>". __('Edit URL' , 'broken-link-checker') ."</a>";
1319
+
1320
  echo '<div class="row-actions">';
1321
  echo implode(' | </span>', $actions);
1322
 
1323
+ echo "<span style='display:none' class='blc-cancel-button-container'> " .
1324
  "| <a href='javascript:void(0)' class='blc-cancel-button' title='". attribute_escape(__('Cancel URL editing' , 'broken-link-checker')) ."'>". __('Cancel' , 'broken-link-checker') ."</a></span>";
1325
+
1326
  echo '</div>';
1327
  ?>
1328
  </td>
1329
+
 
 
 
 
 
 
 
 
 
 
 
 
 
1330
  </tr>
1331
  <!-- Link details -->
1332
  <tr id='<?php print "link-details-$rownum"; ?>' style='display:none;' class='blc-link-details'>
1333
+ <td colspan='4'><?php $this->link_details_row($link); ?></td>
1334
  </tr><?php
1335
  }
1336
  ?></tbody></table>
1337
 
1338
  <div class="tablenav">
1339
  <div class="alignleft actions">
1340
+ <select name="action2" id="blc-bulk-action2">
1341
  <?php echo $bulk_actions_html; ?>
1342
  </select>
1343
  <input type="submit" name="doaction2" id="doaction2" value="<?php echo attribute_escape(__('Apply', 'broken-link-checker')); ?>" class="button-secondary action">
1365
 
1366
  ?>
1367
 
1368
+ <?php
1369
+ //Load assorted JS event handlers and other shinies
1370
+ include dirname($this->loader) . '/includes/admin/links-page-js.php';
1371
+ ?>
1372
  </div>
1373
  <?php
1374
  } //Function ends
1375
 
1376
+ /**
1377
+ * Create a custom link filter using params passed in $_POST.
1378
+ *
1379
+ * @uses $_POST
1380
+ * @uses $_GET to replace the current filter ID (if any) with that of the newly created filter.
1381
+ *
1382
+ * @return array Message and the CSS class to apply to the message.
1383
+ */
1384
+ function do_create_custom_filter(){
1385
+ //Create a custom filter!
1386
+ global $blc_link_query;
1387
+ check_admin_referer( 'create-custom-filter' );
1388
+ $msg_class = 'updated';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1389
 
1390
+ //Filter name must be set
1391
+ if ( empty($_POST['name']) ){
1392
+ $message = __("You must enter a filter name!", 'broken-link-checker');
1393
+ $msg_class = 'error';
1394
+ //Filter parameters (a search query) must also be set
1395
+ } elseif ( empty($_POST['params']) ){
1396
+ $message = __("Invalid search query.", 'broken-link-checker');
1397
+ $msg_class = 'error';
1398
+ } else {
1399
+ //Save the new filter
1400
+ $filter_id = $blc_link_query->create_custom_filter($_POST['name'], $_POST['params']);
 
 
 
1401
 
1402
+ if ( $filter_id ){
1403
+ //Saved
1404
+ $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $_POST['name']);
1405
+ //A little hack to make the filter active immediately
1406
+ $_GET['filter_id'] = $filter_id;
1407
+ } else {
1408
+ //Error
1409
+ $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
1410
+ $msg_class = 'error';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1411
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1412
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1413
 
1414
+ return array($message, $msg_class);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1415
  }
1416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1417
  /**
1418
+ * Delete a custom link filter.
 
1419
  *
1420
+ * @uses $_POST
1421
+ *
1422
+ * @return array Message and a CSS class to apply to the message.
1423
  */
1424
+ function do_delete_custom_filter(){
1425
+ //Delete an existing custom filter!
1426
+ global $blc_link_query;
1427
+ check_admin_referer( 'delete-custom-filter' );
1428
+ $msg_class = 'updated';
1429
+
1430
+ //Filter ID must be set
1431
+ if ( empty($_POST['filter_id']) ){
1432
+ $message = __("Filter ID not specified.", 'broken-link-checker');
1433
+ $msg_class = 'error';
1434
+ } else {
1435
+ //Try to delete the filter
1436
+ if ( $blc_link_query->delete_custom_filter($_POST['filter_id']) ){
1437
+ //Success
1438
+ $message = __('Filter deleted', 'broken-link-checker');
1439
+ } else {
1440
+ //Either the ID is wrong or there was some other error
1441
+ $message = __('Database error : %s', 'broken-link-checker');
1442
+ $msg_class = 'error';
1443
  }
 
1444
  }
1445
 
1446
+ return array($message, $msg_class);
1447
  }
1448
 
1449
  /**
1450
+ * Modify multiple links to point to their target URLs.
 
1451
  *
1452
+ * @param array $selected_links
1453
+ * @return array The message to display and its CSS class.
1454
  */
1455
+ function do_bulk_deredirect($selected_links){
1456
+ //For all selected links, replace the URL with the final URL that it redirects to.
1457
 
1458
+ $message = '';
1459
+ $msg_class = 'updated';
1460
+
1461
+ check_admin_referer( 'bulk-action' );
1462
+
1463
+ if ( count($selected_links) > 0 ) {
1464
+ //Fetch all the selected links
1465
+ $links = blc_get_links(array(
1466
+ 'link_ids' => $selected_links,
1467
+ 'purpose' => BLC_FOR_EDITING,
1468
+ ));
1469
+
1470
+ if ( count($links) > 0 ) {
1471
+ $processed_links = 0;
1472
+ $failed_links = 0;
1473
+
1474
+ //Deredirect all selected links
1475
+ foreach($links as $link){
1476
+ $rez = $link->deredirect();
1477
+ if ( !is_wp_error($rez) ){
1478
+ $processed_links++;
1479
+ } else {
1480
+ $failed_links++;
1481
+ }
1482
+ }
1483
+
1484
+ $message = sprintf(
1485
+ _n(
1486
+ 'Replaced %d redirect with a direct link',
1487
+ 'Replaced %d redirects with direct links',
1488
+ $processed_links,
1489
+ 'broken-link-checker'
1490
+ ),
1491
+ $processed_links
1492
+ );
1493
+
1494
+ if ( $failed_links > 0 ) {
1495
+ $message .= '<br>' . sprintf(
1496
+ _n(
1497
+ 'Failed to fix %d redirect',
1498
+ 'Failed to fix %d redirects',
1499
+ $failed_links,
1500
+ 'broken-link-checker'
1501
+ ),
1502
+ $failed_links
1503
+ );
1504
+ $msg_class = 'error';
1505
+ }
1506
+ } else {
1507
+ $message = __('None of the selected links are redirects!', 'broken-link-checker');
1508
+ }
1509
+ }
1510
+
1511
+ return array($message, $msg_class);
1512
  }
1513
 
1514
  /**
1515
+ * Unlink multiple links.
 
1516
  *
1517
+ * @param array $selected_links
1518
+ * @return array Message and a CSS classname.
 
1519
  */
1520
+ function do_bulk_unlink($selected_links){
1521
+ //Unlink all selected links.
1522
+ $message = '';
1523
+ $msg_class = 'updated';
1524
+
1525
+ check_admin_referer( 'bulk-action' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1526
 
1527
+ if ( count($selected_links) > 0 ) {
1528
+
1529
+ //Fetch all the selected links
1530
+ $links = blc_get_links(array(
1531
+ 'link_ids' => $selected_links,
1532
+ 'purpose' => BLC_FOR_EDITING,
1533
+ ));
1534
+
1535
+ if ( count($links) > 0 ) {
1536
+ $processed_links = 0;
1537
+ $failed_links = 0;
1538
 
1539
+ //Unlink (delete) each one
1540
+ foreach($links as $link){
1541
+ $rez = $link->unlink();
1542
+ if ( ($rez == false) || is_wp_error($rez) ){
1543
+ $failed_links++;
1544
+ } else {
1545
+ $processed_links++;
1546
+ }
1547
+ }
1548
 
1549
+ //This message is slightly misleading - it doesn't account for the fact that
1550
+ //a link can be present in more than one post.
1551
+ $message = sprintf(
1552
+ _n(
1553
+ '%d link removed',
1554
+ '%d links removed',
1555
+ $processed_links,
1556
+ 'broken-link-checker'
1557
+ ),
1558
+ $processed_links
1559
+ );
1560
+
1561
+ if ( $failed_links > 0 ) {
1562
+ $message .= '<br>' . sprintf(
1563
+ _n(
1564
+ 'Failed to remove %d link',
1565
+ 'Failed to remove %d links',
1566
+ $failed_links,
1567
+ 'broken-link-checker'
1568
+ ),
1569
+ $failed_links
1570
+ );
1571
+ $msg_class = 'error';
1572
+ }
1573
  }
1574
+ }
1575
+
1576
+ return array($message, $msg_class);
1577
  }
1578
 
1579
  /**
1580
+ * Delete posts, bookmarks and other items that contain any of the specified links.
 
1581
  *
1582
+ * @param array $selected_links An array of link IDs
1583
+ * @return array Confirmation message and its CSS class.
1584
  */
1585
+ function do_bulk_delete_sources($selected_links){
1586
+ global $blc_container_registry;
1587
+
1588
+ $message = '';
1589
+ $msg_class = 'updated';
1590
+
1591
+ //Delete posts, blogroll entries and any other link containers that contain any of the selected links.
1592
+ //
1593
+ //Note that once all cotnainers containing a particular link have been deleted,
1594
+ //there is no need to explicitly delete the link record itself. The hooks attached to
1595
+ //the actions that execute when something is deleted (e.g. "post_deleted") will
1596
+ //take care of that.
1597
+
1598
+ check_admin_referer( 'bulk-action' );
1599
+
1600
+ if ( count($selected_links) > 0 ) {
1601
+ $messages = array();
1602
 
1603
+ //Fetch all the selected links
1604
+ $links = blc_get_links(array(
1605
+ 'link_ids' => $selected_links,
1606
+ 'load_instances' => true,
1607
+ ));
1608
 
1609
+ //Make a list of all containers associated with these links, with each container
1610
+ //listed only once.
1611
+ $containers = array();
1612
+ foreach($links as $link){
1613
+ $instances = $link->get_instances();
1614
+ foreach($instances as $instance){
1615
+ $key = $instance->container_type . '|' . $instance->container_id;
1616
+ $containers[$key] = array($instance->container_type, $instance->container_id);
1617
+ }
1618
+ }
1619
 
1620
+ //Instantiate the containers
1621
+ $containers = blc_get_containers($containers);
 
 
1622
 
1623
+ //Delete their associated entities
1624
+ $deleted = array();
1625
+ foreach($containers as $container){
1626
+ $container_type = $container->container_type;
 
 
1627
 
1628
+ $rez = $container->delete_wrapped_object();
1629
+
1630
+ if ( is_wp_error($rez) ){
1631
+ //Record error messages for later display
1632
+ $messages[] = $rez->get_error_message();
1633
+ $msg_class = 'error';
1634
+ } else {
1635
+ //Keep track of how many of each type were deleted.
1636
+ if ( isset($deleted[$container_type]) ){
1637
+ $deleted[$container_type]++;
1638
+ } else {
1639
+ $deleted[$container_type] = 1;
1640
+ }
1641
+ }
1642
+ }
1643
+
1644
+ //Generate delete confirmation messages
1645
+ foreach($deleted as $container_type => $number){
1646
+ $messages[] = $blc_container_registry->ui_bulk_delete_message($container_type, $number);
1647
+ }
1648
+
1649
+ if ( count($messages) > 0 ){
1650
+ $message = implode('<br>', $messages);
1651
+ } else {
1652
+ $message = __("Didn't find anything to delete!", 'broken-link-checker');
1653
+ $msg_class = 'error';
1654
+ }
1655
  }
1656
 
1657
+ return array($message, $msg_class);
1658
  }
1659
 
1660
+ /**
1661
+ * Mark multiple links as unchecked.
1662
+ *
1663
+ * @param array $selected_links An array of link IDs
1664
+ * @return array Confirmation nessage and the CSS class to use with that message.
1665
+ */
1666
+ function do_bulk_recheck($selected_links){
1667
+ global $wpdb;
1668
 
1669
+ $message = '';
1670
+ $msg_class = 'updated';
1671
+
1672
+ if ( count($selected_links) > 0 ){
1673
+ $q = "UPDATE {$wpdb->prefix}blc_links
1674
+ SET last_check_attempt = '0000-00-00 00:00:00'
1675
+ WHERE link_id IN (".implode(', ', $selected_links).")";
1676
+ $changes = $wpdb->query($q);
1677
+
1678
+ $message = sprintf(
1679
+ _n(
1680
+ "%d link scheduled for rechecking",
1681
+ "%d links scheduled for rechecking",
1682
+ $changes,
1683
+ 'broken-link-chekcer'
1684
+ ),
1685
+ $changes
1686
+ );
1687
  }
1688
 
1689
+ return array($message, $msg_class);
1690
+ }
1691
+
1692
+
1693
+ function links_page_css(){
1694
+ wp_enqueue_style('blc-links-page', plugin_dir_url($this->loader) . 'css/links-page.css' );
1695
+ }
1696
+
1697
+ function link_details_row($link){
1698
+ ?>
1699
+ <div class="blc-detail-container">
1700
+ <div class="blc-detail-block" style="float: left; width: 49%;">
1701
+ <ol style='list-style-type: none;'>
1702
+ <?php if ( !empty($link->post_date) ) { ?>
1703
+ <li><strong><?php _e('Post published on', 'broken-link-checker'); ?> :</strong>
1704
+ <span class='post_date'><?php
1705
+ echo date_i18n(get_option('date_format'),strtotime($link->post_date));
1706
+ ?></span></li>
1707
+ <?php } ?>
1708
+ <li><strong><?php _e('Link last checked', 'broken-link-checker'); ?> :</strong>
1709
+ <span class='check_date'><?php
1710
+ $last_check = $link->last_check;
1711
+ if ( $last_check < strtotime('-10 years') ){
1712
+ _e('Never', 'broken-link-checker');
1713
+ } else {
1714
+ echo date_i18n(get_option('date_format'), $last_check);
1715
+ }
1716
+ ?></span></li>
1717
+
1718
+ <li><strong><?php _e('HTTP code', 'broken-link-checker'); ?> :</strong>
1719
+ <span class='http_code'><?php
1720
+ print $link->http_code;
1721
+ ?></span></li>
1722
+
1723
+ <li><strong><?php _e('Response time', 'broken-link-checker'); ?> :</strong>
1724
+ <span class='request_duration'><?php
1725
+ printf( __('%2.3f seconds', 'broken-link-checker'), $link->request_duration);
1726
+ ?></span></li>
1727
+
1728
+ <li><strong><?php _e('Final URL', 'broken-link-checker'); ?> :</strong>
1729
+ <span class='final_url'><?php
1730
+ print $link->final_url;
1731
+ ?></span></li>
1732
+
1733
+ <li><strong><?php _e('Redirect count', 'broken-link-checker'); ?> :</strong>
1734
+ <span class='redirect_count'><?php
1735
+ print $link->redirect_count;
1736
+ ?></span></li>
1737
+
1738
+ <li><strong><?php _e('Instance count', 'broken-link-checker'); ?> :</strong>
1739
+ <span class='instance_count'><?php
1740
+ print count($link->get_instances());
1741
+ ?></span></li>
1742
+
1743
+ <?php if ( $link->broken && (intval( $link->check_count ) > 0) ){ ?>
1744
+ <li><br/>
1745
+ <?php
1746
+ printf(
1747
+ _n('This link has failed %d time.', 'This link has failed %d times.', $link->check_count, 'broken-link-checker'),
1748
+ $link->check_count
1749
+ );
1750
+ ?>
1751
+ </li>
1752
+ <?php } ?>
1753
+ </ol>
1754
+ </div>
1755
+
1756
+ <div class="blc-detail-block" style="float: right; width: 50%;">
1757
+ <ol style='list-style-type: none;'>
1758
+ <li><strong><?php _e('Log', 'broken-link-checker'); ?> :</strong>
1759
+ <span class='blc_log'><?php
1760
+ print nl2br($link->log);
1761
+ ?></span></li>
1762
+ </ol>
1763
+ </div>
1764
+
1765
+ <div style="clear:both;"> </div>
1766
+ </div>
1767
+ <?php
1768
  }
1769
 
1770
  function start_timer(){
1776
  }
1777
 
1778
  /**
 
1779
  * The main worker function that does all kinds of things.
1780
  *
1781
  * @return void
1783
  function work(){
1784
  global $wpdb;
1785
 
1786
+ //Sanity check : make sure the DB is all set up
1787
+ if ( $this->db_version != $this->conf->options['current_db_version'] ) {
1788
+ //FB::error("The plugin's database tables are not up to date! Stop.");
1789
+ return;
1790
+ }
1791
+
1792
  if ( !$this->acquire_lock() ){
1793
  //FB::warn("Another instance of BLC is already working. Stop.");
1794
+ return;
1795
+ }
1796
+
1797
+ if ( $this->server_too_busy() ){
1798
+ //FB::warn("Server is too busy. Stop.");
1799
+ return;
1800
  }
1801
 
1802
  $this->start_timer();
1823
  //Close the connection as per http://www.php.net/manual/en/features.connection-handling.php#71172
1824
  //This reduces resource usage and may solve the mysterious slowdowns certain users have
1825
  //encountered when activating the plugin.
1826
+ //(Disable when debugging or you won't get the FirePHP output)
1827
+ if ( !constant('BLC_DEBUG') ){
1828
  ob_end_clean();
1829
  header("Connection: close");
1830
  ob_start();
1835
  flush(); // Unless both are called !
1836
  }
1837
 
 
 
 
1838
  $orphans_possible = false;
 
1839
  $still_need_resynch = $this->conf->options['need_resynch'];
1840
 
1841
  /*****************************************
1842
  Parse posts and bookmarks
1843
  ******************************************/
1844
 
1845
+ if ( $still_need_resynch ) {
 
 
1846
 
1847
+ //FB::log("Looking for containers that need parsing...");
 
 
1848
 
1849
+ while( $containers = blc_get_unsynched_containers(50) ){
1850
+ //FB::log($containers, 'Found containers');
1851
+
1852
+ foreach($containers as $container){
1853
+ //FB::log($container, "Parsing container");
1854
+ $container->synch();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1855
 
1856
+ //Check if we still have some execution time left
 
 
 
1857
  if( $this->execution_time() > $max_execution_time ){
1858
  //FB::log('The alloted execution time has run out');
1859
+ blc_cleanup_links();
1860
  $this->release_lock();
1861
  return;
1862
  }
1863
 
1864
+ //Check if the server isn't overloaded
1865
+ if ( $this->server_too_busy() ){
1866
+ //FB::log('Server overloaded, bailing out.');
1867
+ blc_cleanup_links();
1868
+ $this->release_lock();
1869
+ return;
1870
+ }
1871
  }
1872
+ $orphans_possible = true;
1873
  }
1874
 
1875
  //FB::log('No unparsed items found.');
1876
  $still_need_resynch = false;
1877
 
 
 
 
 
1878
  } else {
1879
  //FB::log('Resynch not required.');
1880
  }
1893
 
1894
  if ( $orphans_possible ) {
1895
  //FB::log('Cleaning up the link table.');
1896
+ blc_cleanup_links();
1897
  }
1898
 
1899
  //Check if we still have some execution time left
1903
  return;
1904
  }
1905
 
1906
+ if ( $this->server_too_busy() ){
1907
+ //FB::log('Server overloaded, bailing out.');
1908
+ $this->release_lock();
1909
+ return;
1910
+ }
1911
+
1912
  /*****************************************
1913
  Check links
1914
  ******************************************/
1915
+ while ( $links = $this->get_links_to_check(50) ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1916
 
1917
+ //Some unchecked links found
 
 
1918
  //FB::log("Checking ".count($links)." link(s)");
1919
 
1920
  foreach ($links as $link) {
1921
+ //Does this link need to be checked? Excluded links aren't checked, but their URLs are still
1922
+ //tested periodically to see if they're still on the exlusion list.
1923
+ if ( !$this->is_excluded( $link->url ) ) {
1924
+ //Check the link.
1925
+ //FB::log($link->url, "Checking link {$link->link_id}");
1926
+ $link->check( true );
 
 
1927
  } else {
1928
+ //FB::info("The URL {$link->url} is excluded, skipping link {$link->link_id}.");
1929
+ $link->last_check_attempt = time();
1930
+ $link->save();
 
 
 
 
 
1931
  }
1932
 
1933
  //Check if we still have some execution time left
1936
  $this->release_lock();
1937
  return;
1938
  }
1939
+
1940
+ //Check if the server isn't overloaded
1941
+ if ( $this->server_too_busy() ){
1942
+ //FB::log('Server overloaded, bailing out.');
1943
+ $this->release_lock();
1944
+ return;
1945
+ }
1946
  }
1947
+
1948
  }
1949
  //FB::log('No links need to be checked right now.');
1950
 
1952
  //FB::log('All done.');
1953
  }
1954
 
1955
+ /**
1956
+ * This function is called when the plugin's cron hook executes.
1957
+ * Its only purpose is to invoke the worker function.
1958
+ *
1959
+ * @uses wsBrokenLinkChecker::work()
1960
+ *
1961
+ * @return void
1962
+ */
1963
+ function cron_check_links(){
1964
+ $this->work();
1965
+ }
1966
+
1967
+ /**
1968
+ * Retrieve links that need to be checked or re-checked.
1969
+ *
1970
+ * @param integer $max_results The maximum number of links to return. Defaults to 0 = no limit.
1971
+ * @param bool $count_only If true, only the number of found links will be returned, not the links themselves.
1972
+ * @return int|array
1973
+ */
1974
+ function get_links_to_check($max_results = 0, $count_only = false){
1975
+ global $wpdb;
1976
+
1977
+ $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
1978
+ $recheck_threshold = date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
1979
+
1980
+ //FB::log('Looking for links to check (threshold : '.$check_threshold.', recheck_threshold : '.$recheck_threshold.')...');
1981
+
1982
+ //Select some links that haven't been checked for a long time or
1983
+ //that are broken and need to be re-checked again. Links that are
1984
+ //marked as "being checked" and have been that way for several minutes
1985
+ //can also be considered broken/buggy, so those will be selected
1986
+ //as well.
1987
+
1988
+ //Note : This is a slow query, but AFAIK there is no way to speed it up.
1989
+ //I could put an index on last_check_attempt, but that value is almost
1990
+ //certainly unique for each row so it wouldn't be much better than a full table scan.
1991
+ if ( $count_only ){
1992
+ $q = "SELECT COUNT(*)\n";
1993
+ } else {
1994
+ $q = "SELECT *\n";
1995
+ }
1996
+ $q .= "FROM {$wpdb->prefix}blc_links
1997
+ WHERE
1998
+ ( last_check_attempt < %s )
1999
+ OR
2000
+ (
2001
+ (broken = 1 OR being_checked = 1)
2002
+ AND may_recheck = 1
2003
+ AND check_count < %d
2004
+ AND last_check_attempt < %s
2005
+ )";
2006
+ if ( !$count_only ){
2007
+ $q .= "\nORDER BY last_check_attempt ASC\n";
2008
+ if ( !empty($max_results) ){
2009
+ $q .= "LIMIT " . intval($max_results);
2010
+ }
2011
+ }
2012
+
2013
+ $link_q = $wpdb->prepare(
2014
+ $q,
2015
+ $check_threshold,
2016
+ $this->conf->options['recheck_count'],
2017
+ $recheck_threshold
2018
+ );
2019
+ //FB::log($link_q, "Find links to check");
2020
+
2021
+ //If we just need the number of links, retrieve it and return
2022
+ if ( $count_only ){
2023
+ return $wpdb->get_var($link_q);
2024
+ }
2025
+
2026
+ //Fetch the link data
2027
+ $link_data = $wpdb->get_results($link_q, ARRAY_A);
2028
+ if ( empty($link_data) ){
2029
+ return array();
2030
+ }
2031
+
2032
+ //Instantiate blcLink objects for all fetched links
2033
+ $links = array();
2034
+ foreach($link_data as $data){
2035
+ $links[] = new blcLink($data);
2036
+ }
2037
+
2038
+ return $links;
2039
+ }
2040
+
2041
+ /**
2042
+ * Output the current link checker status in JSON format.
2043
+ * Ajax hook for the 'blc_full_status' action.
2044
+ *
2045
+ * @return void
2046
+ */
2047
  function ajax_full_status( ){
2048
  $status = $this->get_status();
2049
  $text = $this->status_text( $status );
2057
  }
2058
 
2059
  /**
 
2060
  * Generates a status message based on the status info in $status
2061
  *
2062
  * @param array $status
2111
  return $text;
2112
  }
2113
 
2114
+ /**
2115
+ * @uses wsBrokenLinkChecker::ajax_full_status()
2116
+ *
2117
+ * @return void
2118
+ */
2119
  function ajax_dashboard_status(){
2120
  //Just display the full status.
2121
+ $this->ajax_full_status();
2122
+ }
2123
+
2124
+ /**
2125
+ * Output the current average server load (over the last one-minute period).
2126
+ * Called via AJAX.
2127
+ *
2128
+ * @return void
2129
+ */
2130
+ function ajax_current_load(){
2131
+ $load = $this->get_server_load();
2132
+ if ( empty($load) ){
2133
+ die('Unknown');
2134
+ }
2135
+
2136
+ $one_minute = reset($load);
2137
+ printf('%.2f', $one_minute);
2138
+ die();
2139
  }
2140
 
2141
  /**
2151
  * @return array
2152
  */
2153
  function get_status(){
2154
+ global $wpdb, $blc_link_query;
2155
 
2156
  $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2157
+ $recheck_threshold=date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2158
 
2159
  $q = "SELECT count(*) FROM {$wpdb->prefix}blc_links WHERE 1";
2160
  $known_links = $wpdb->get_var($q);
2162
  $q = "SELECT count(*) FROM {$wpdb->prefix}blc_instances WHERE 1";
2163
  $known_instances = $wpdb->get_var($q);
2164
 
2165
+ $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
 
 
 
 
 
2166
 
2167
+ $unchecked_links = $this->get_links_to_check(0, true);
 
 
 
 
 
 
 
 
2168
 
2169
  return array(
2170
  'check_threshold' => $check_threshold,
2182
  die();
2183
  }
2184
 
2185
+ /**
2186
+ * AJAX hook for the "Not broken" button. Marks a link as broken and as a likely false positive.
2187
+ *
2188
+ * @return void
2189
+ */
2190
  function ajax_discard(){
2191
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_discard', false, false)){
 
2192
  die( __("You're not allowed to do that!", 'broken-link-checker') );
2193
  }
2194
 
2200
  printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2201
  die();
2202
  }
2203
+ //Make it appear "not broken"
2204
+ $link->broken = false;
2205
+ $link->false_positive = true;
2206
+ $link->last_check_attempt = time();
 
2207
  $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2208
 
2209
  //Save the changes
2217
  }
2218
  }
2219
 
2220
+ /**
2221
+ * AJAX hook for the inline link editor on Tools -> Broken Links.
2222
+ *
2223
+ * @return void
2224
+ */
2225
  function ajax_edit(){
2226
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_edit', false, false)){
2227
  die( json_encode( array(
2228
  'error' => __("You're not allowed to do that!", 'broken-link-checker')
2229
  )));
2239
  )));
2240
  }
2241
 
2242
+ $new_url = $_GET['new_url'];
2243
+ $new_url = stripslashes($new_url);
2244
+
2245
+ $parsed = @parse_url($parsed);
2246
+ if ( !$parsed ){
2247
  die( json_encode( array(
2248
  'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
2249
  )));
2250
  }
2251
 
2252
  //Try and edit the link
2253
+ //FB::log($new_url, "Ajax edit");
2254
+ //FB::log($_GET, "Ajax edit");
2255
  $rez = $link->edit($new_url);
2256
 
2257
+ if ( $rez === false ){
2258
  die( json_encode( array(
2259
  'error' => __("An unexpected error occured!", 'broken-link-checker')
2260
  )));
2261
  } else {
2262
+ $response = array(
2263
+ 'new_link_id' => $rez['new_link_id'],
2264
+ 'cnt_okay' => $rez['cnt_okay'],
2265
+ 'cnt_error' => $rez['cnt_error'],
2266
+ 'errors' => array(),
2267
+ );
2268
+ foreach($rez['errors'] as $error){
2269
+ array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
2270
+ }
2271
+
2272
+ die( json_encode($response) );
2273
  }
2274
 
2275
  } else {
2279
  }
2280
  }
2281
 
2282
+ /**
2283
+ * AJAX hook for the "Unlink" action links in Tools -> Broken Links.
2284
+ * Removes the specified link from all posts and other supported items.
2285
+ *
2286
+ * @return void
2287
+ */
2288
  function ajax_unlink(){
2289
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_unlink', false, false)){
2290
  die( json_encode( array(
2291
  'error' => __("You're not allowed to do that!", 'broken-link-checker')
2292
  )));
2303
  }
2304
 
2305
  //Try and unlink it
2306
+ $rez = $link->unlink();
2307
+
2308
+ if ( $rez === false ){
2309
  die( json_encode( array(
2310
+ 'error' => __("An unexpected error occured!", 'broken-link-checker')
2311
  )));
2312
  } else {
2313
+ $response = array(
2314
+ 'cnt_okay' => $rez['cnt_okay'],
2315
+ 'cnt_error' => $rez['cnt_error'],
2316
+ 'errors' => array(),
2317
+ );
2318
+ foreach($rez['errors'] as $error){
2319
+ array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
2320
+ }
2321
+
2322
+ die( json_encode($response) );
2323
  }
2324
 
2325
  } else {
2349
  die( __('Error : link ID not specified', 'broken-link-checker') );
2350
  }
2351
 
2352
+ //Load the link.
2353
+ $link = new blcLink($link_id);
 
 
 
 
 
 
 
 
 
 
 
 
2354
 
2355
+ if ( !$link->is_new ){
 
2356
  //FB::info($link, 'Link loaded');
2357
  $this->link_details_row($link);
2358
  die();
2359
  } else {
2360
  printf( __('Failed to load link details (%s)', 'broken-link-checker'), $wpdb->last_error );
2361
+ die();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2362
  }
2363
  }
2364
 
2365
  /**
 
2366
  * Create and lock a temporary file.
2367
  *
2368
  * @return bool
2400
  }
2401
 
2402
  /**
 
2403
  * Unlock and delete the temporary file
2404
  *
2405
  * @return bool
2422
  }
2423
 
2424
  /**
 
2425
  * Generate system-specific lockfile filename
2426
  *
2427
  * @return string A filename or FALSE on error
2458
  }
2459
  }
2460
 
2461
+ /**
2462
+ * Check if server is currently too overloaded to run the link checker.
2463
+ *
2464
+ * @return bool
2465
+ */
2466
+ function server_too_busy(){
2467
+ if ( !$this->conf->options['enable_load_limit'] ){
2468
+ return false;
2469
+ }
2470
+
2471
+ $loads = $this->get_server_load();
2472
+ $one_minute = floatval(reset($loads));
2473
+
2474
+ return $one_minute > $this->conf->options['server_load_limit'];
2475
  }
2476
 
2477
+ /**
2478
+ * Get the server's load averages.
2479
+ *
2480
+ * Returns an array with three samples - the 1 minute avg, the 5 minute avg, and the 15 minute avg.
2481
+ *
2482
+ * @param integer $cache How long the load averages may be cached, in seconds. Set to 0 to get maximally up-to-date data.
2483
+ * @return array|null Array, or NULL if retrieving load data is impossible (e.g. when running on a Windows box).
2484
+ */
2485
+ function get_server_load($cache = 5){
2486
+ static $cached_load = null;
2487
+ static $cached_when = 0;
2488
 
2489
+ if ( !empty($cache) && ((time() - $cached_when) <= $cache) ){
2490
+ return $cached_load;
2491
+ }
2492
 
2493
+ $load = null;
2494
+
2495
+ if ( function_exists('sys_getloadavg') ){
2496
+ $load = sys_getloadavg();
2497
+ } else {
2498
+ $loadavg_file = '/proc/loadavg';
2499
+ if (@is_readable($loadavg_file)) {
2500
+ $load = explode(' ',file_get_contents($loadavg_file));
2501
+ $load = array_map('floatval', $load);
2502
+ }
2503
  }
2504
+
2505
+ $cached_load = $load;
2506
+ $cached_when = time();
2507
+ return $load;
 
 
2508
  }
2509
 
2510
  function hook_wp_dashboard_setup(){
2550
  }
2551
 
2552
  /**
 
2553
  * Collect various debugging information and return it in an associative array
2554
  *
2555
  * @return array
2598
  $debug[ __('CURL version', 'broken-link-checker') ] = $data;
2599
 
2600
  //Snoopy presence
2601
+ if ( class_exists('Snoopy') || file_exists(ABSPATH. WPINC . '/class-snoopy.php') ){
2602
  $data = array(
2603
  'state' => 'ok',
2604
  'value' => __('Installed', 'broken-link-checker'),
2666
  return $debug;
2667
  }
2668
 
2669
+ function send_email_notifications(){
2670
+ global $wpdb;
2671
+
2672
+ //Find links that have been detected as broken since the last sent notification.
2673
+ $last_notification = date('Y-m-d H:i:s', $this->conf->options['last_notification_sent']);
2674
+ $where = $wpdb->prepare('( first_failure >= %s )', $last_notification);
2675
+
2676
+ $links = blc_get_links(array(
2677
+ 's_filter' => 'broken',
2678
+ 'where_expr' => $where,
2679
+ 'load_instances' => true,
2680
+ 'max_results' => 0,
2681
+ ));
2682
+
2683
+ if ( empty($links) ){
2684
+ return;
2685
+ }
2686
+
2687
+ $cnt = count($links);
2688
+
2689
+ //Prepare email message
2690
+ $subject = sprintf(
2691
+ __("[%s] Broken links detected", 'broken-link-checker'),
2692
+ get_option('blogname')
2693
+ );
2694
+
2695
+ $body = sprintf(
2696
+ _n(
2697
+ "Broken Link Checker has detected %d new broken link on your site.",
2698
+ "Broken Link Checker has detected %d new broken links on your site.",
2699
+ $cnt,
2700
+ 'broken-link-checker'
2701
+ ),
2702
+ $cnt
2703
+ );
2704
+
2705
+ $body .= "<br>";
2706
+
2707
+ $max_displayed_links = 5;
2708
+
2709
+ if ( $cnt > $max_displayed_links ){
2710
+ $line = sprintf(
2711
+ _n(
2712
+ "Here's a list of the first %d broken links:",
2713
+ "Here's a list of the first %d broken links:",
2714
+ $max_displayed_links,
2715
+ 'broken-link-checker'
2716
+ ),
2717
+ $max_displayed_links
2718
+ );
2719
+ } else {
2720
+ $line = __("Here's a list of the new broken links: ", 'broken-link-checker');
2721
+ }
2722
 
2723
+ $body .= "<p>$line</p>";
 
2724
 
2725
+ //Show up to $max_displayed_links broken link instances right in the email.
2726
+ $displayed = 0;
2727
+ foreach($links as $link){
2728
 
2729
+ $instances = $link->get_instances();
2730
+ foreach($instances as $instance){
2731
+ $pieces = array(
2732
+ sprintf( __('Link text : %s', 'broken-link-checker'), $instance->ui_get_link_text('email') ),
2733
+ sprintf( __('Link URL : <a href="%s">%s</a>', 'broken-link-checker'), htmlentities($link->url), blcUtility::truncate($link->url, 70, '') ),
2734
+ sprintf( __('Source : %s', 'broken-link-checker'), $instance->ui_get_source('email') ),
2735
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2736
 
2737
+ $link_entry = implode("<br>", $pieces);
2738
+ $body .= "$link_entry<br><br>";
 
 
 
2739
 
2740
+ $displayed++;
2741
+ if ( $displayed >= $max_displayed_links ){
2742
+ break 2; //Exit both foreach loops
2743
  }
2744
  }
 
2745
  }
2746
 
2747
+ //Add a link to the "Broken Links" tab.
2748
+ $body .= __("You can see all broken links here:", 'brokenk-link-checker') . "<br>";
2749
+ $link_page = admin_url('tools.php?page=view-broken-links');
2750
+ $body .= sprintf('<a href="%1$s">%1$s</a>', $link_page);
2751
+
2752
+ //Need to override the default 'text/plain' content type to send a HTML email.
2753
+ add_filter('wp_mail_content_type', array(&$this, 'override_mail_content_type'));
2754
+
2755
+ //Send the notification
2756
+ $rez = wp_mail(
2757
+ get_option('admin_email'),
2758
+ $subject,
2759
+ $body
2760
+ );
2761
+ if ( $rez ){
2762
+ $this->conf->options['last_notification_sent'] = time();
2763
+ $this->conf->save_options();
2764
+ }
2765
+
2766
+ //Remove the override so that it doesn't interfere with other plugins that might
2767
+ //want to send normal plaintext emails.
2768
+ remove_filter('wp_mail_content_type', array(&$this, 'override_mail_content_type'));
2769
+
2770
+ }
2771
+
2772
+ function override_mail_content_type($content_type){
2773
+ return 'text/html';
2774
+ }
2775
+
2776
+ /**
2777
+ * Install or uninstall the plugin's Cron events based on current settings.
2778
+ *
2779
+ * @uses wsBrokenLinkChecker::$conf Uses $conf->options to determine if events need to be (un)installed.
2780
+ *
2781
+ * @return void
2782
+ */
2783
+ function setup_cron_events(){
2784
+ //Link monitor
2785
+ if ( $this->conf->options['run_via_cron'] ){
2786
+ if (!wp_next_scheduled('blc_cron_check_links')) {
2787
+ wp_schedule_event( time(), 'hourly', 'blc_cron_check_links' );
2788
+ }
2789
  } else {
2790
+ wp_clear_scheduled_hook('blc_cron_check_links');
2791
+ }
2792
+
2793
+ //Email notifications about broken links
2794
+ $notification_email = get_option('admin_email');
2795
+ if ( $this->conf->options['send_email_notifications'] && !empty($notification_email) ){
2796
+ if ( !wp_next_scheduled('blc_cron_email_notifications') ){
2797
+ wp_schedule_event(time(), $this->conf->options['notification_schedule'], 'blc_cron_email_notifications');
 
 
 
 
 
 
 
 
 
 
 
 
2798
  }
2799
+ } else {
2800
+ wp_clear_scheduled_hook('blc_cron_email_notifications');
2801
  }
2802
+ }
2803
+
2804
+ /**
2805
+ * Load the plugin's textdomain
2806
+ *
2807
+ * @return void
2808
+ */
2809
+ function load_language(){
2810
+ load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
2811
  }
2812
 
2813
  }//class ends here
css/links-page.css ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Link table layout */
2
+
3
+ table#blc-links {
4
+ width: 100%;
5
+ table-layout: fixed;
6
+ }
7
+
8
+ td.blc-link-details {
9
+ width: 100%;
10
+ }
11
+
12
+ .blc-detail-container {
13
+ display: block;
14
+ width: 100%;
15
+ }
16
+
17
+ .blc-detail-block {
18
+ width: 50%;
19
+ }
20
+
21
+ .blc-column-source {
22
+ width: 33%;
23
+ }
24
+
25
+ .blc-column-link-text {
26
+ width: 24%;
27
+ }
28
+
29
+ .blc-column-url {
30
+ /*width: 35%;*/
31
+ }
32
+
33
+ /* Styles for broken links, redirects and excluded links */
34
+
35
+ .blc-redirect { }
36
+
37
+ .blc-redirect .blc-link-url {
38
+ background-image: url("../images/bullet_blue.png");
39
+ background-position: left center;
40
+ background-repeat: no-repeat;
41
+ }
42
+
43
+ .blc-broken-link { }
44
+
45
+ .blc-broken-link .blc-link-url {
46
+ background-image: url("../images/bullet_error.png");
47
+ background-position: left center;
48
+ background-repeat: no-repeat;
49
+ }
50
+
51
+ .blc-excluded-link {
52
+ background-color: #E2E2E2;
53
+ }
54
+
55
+
56
+ /* Misc table styles */
57
+
58
+ .blc-link-url {
59
+ padding-left: 16px;
60
+ }
61
+
62
+ .blc-link-editor {
63
+ font-size: 1em;
64
+ width: 95%;
65
+ margin-left: 12px;
66
+ }
67
+
68
+ .column-url .row-actions {
69
+ margin-left: 16px;
70
+ }
71
+
72
+ .blc-link-text {
73
+ cursor: pointer;
74
+ }
75
+
76
+
77
+ .blc-small-image {
78
+ vertical-align: middle;
79
+ }
80
+
81
+ /* Search form */
82
+
83
+ .blc-search-container {
84
+ background : white !important;
85
+ border: 3px solid #EEEEEE;
86
+ padding: 12px;
87
+ }
88
+
89
+ .blc-search-container .ui-dialog-titlebar {
90
+ display: none;
91
+ margin: 0px;
92
+ }
93
+
94
+ #search-links-dialog {
95
+ display: none;
96
+ }
97
+
98
+ #search-links-dialog label, #search-links-dialog input.text, #search-links-dialog select { display:block; }
99
+ #search-links-dialog input.text { margin-bottom:12px; width:95%; padding: .4em; }
100
+ #search-links-dialog select { margin-bottom:12px; padding: .4em; }
101
+ #search-links-dialog fieldset { padding:0; border:0; margin-top:25px; }
102
+
103
+ #blc-search-button-row {
104
+ text-align: center;
105
+ }
106
+
107
+ #blc-search-button-row input {
108
+ padding: 0.4em;
109
+ margin-left: 8px;
110
+ margin-right: 8px;
111
+ margin-top: 8px;
112
+ }
113
+
114
+ .blc-inline-form {
115
+ display: inline;
116
+ }
117
+
118
+ div.search-box{
119
+ float: right;
120
+ margin-top: -5px;
121
+ margin-right: 0pt;
122
+ margin-bottom: 0pt;
123
+ margin-left: 0pt;
124
+ }
css/options-page.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #blc-debug-info-toggle {
2
+ font-size: smaller;
3
+ }
4
+
5
+ .blc-debug-item-ok {
6
+ background-color: #d7ffa2;
7
+ }
8
+ .blc-debug-item-warning {
9
+ background-color: #fcffa2;
10
+ }
11
+ .blc-debug-item-error {
12
+ background-color: #ffc4c4;
13
+ }
14
+
15
+ #blc-debug-info {
16
+ display: none;
17
+
18
+ text-align: left;
19
+
20
+ border-width: 1px;
21
+ border-color: gray;
22
+ border-style: solid;
23
+
24
+ border-spacing: 0px;
25
+ border-collapse: collapse;
26
+ }
27
+
28
+ #blc-debug-info th, #blc-debug-info td {
29
+ padding: 6px;
30
+ font-weight: normal;
31
+ text-shadow: none;
32
+
33
+ border-width: 1px ;
34
+ border-color: silver;
35
+ border-style: solid;
36
+
37
+ border-collapse: collapse;
38
+ }
highlighter-class.php DELETED
@@ -1,93 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * @author W-Shadow
5
- * @copyright 2009
6
- *
7
- * @requires blcUtility
8
- */
9
-
10
- if ( !class_exists('blcLinkHighlighter') ){
11
-
12
- class blcLinkHighlighter {
13
-
14
- var $links_to_remove;
15
- var $broken_link_css;
16
- var $current_permalink;
17
-
18
- function blcLinkHighlighter( $broken_link_css = '' ) {
19
- if ( !empty( $broken_link_css ) ){
20
- $this->broken_link_css = $broken_link_css;
21
- add_action( 'wp_head', array(&$this,'hook_wp_head') );
22
- }
23
-
24
- add_filter( 'the_content', array(&$this,'hook_the_content') );
25
- $this->current_permalink = '';
26
- }
27
-
28
- function hook_the_content($content){
29
- global $post, $wpdb;
30
- if ( empty($post) ) return $content;
31
-
32
- //Get the post permalink - it's used to resolve relative URLs
33
- $this->current_permalink = get_permalink( $post->ID );
34
-
35
- $q = "
36
- SELECT instances.link_text, links.*
37
-
38
- FROM {$wpdb->prefix}blc_instances AS instances, {$wpdb->prefix}blc_links AS links
39
-
40
- WHERE
41
- instances.source_id = %d
42
- AND instances.source_type = 'post'
43
- AND instances.instance_type = 'link'
44
-
45
- AND instances.link_id = links.link_id
46
- AND links.check_count > 0
47
- AND ( links.http_code < 200 OR links.http_code >= 400 OR links.timeout = 1 )
48
- AND links.http_code <> " . BLC_CHECKING;
49
-
50
- $rows = $wpdb->get_results( $wpdb->prepare( $q, $post->ID ), ARRAY_A );
51
- if( $rows ){
52
- $this->links_to_remove = array();
53
- foreach($rows as $row){
54
- $this->links_to_remove[$row['url']] = $row;
55
- }
56
- $content = preg_replace_callback( blcUtility::link_pattern(), array(&$this,'mark_broken_links'), $content );
57
-
58
- /*
59
- if ( BLC_DEBUG ){
60
- $content .= '<p><strong>Broken links in this post : </strong></p><ul>';
61
- $content .= '<li>' . implode('</li><li>', array_keys($this->links_to_remove)) . '</li></ul>';
62
- }
63
- //*/
64
- } else {
65
- /*
66
- if ( BLC_DEBUG ){
67
- $content .= '<p><strong>This post contains no broken links.</strong></p>';
68
- }
69
- //*/
70
- };
71
-
72
- return $content;
73
- }
74
-
75
- function mark_broken_links($matches){
76
- //TODO: Tooltip-style popups with more info
77
- $url = blcUtility::normalize_url( html_entity_decode( $matches[3] ), $this->current_permalink );
78
-
79
- if( isset( $this->links_to_remove[$url] ) ){
80
- return $matches[1].$matches[2].$matches[3].$matches[2].' class="broken_link" '.$matches[4].
81
- $matches[5].$matches[6];
82
- } else {
83
- return $matches[0];
84
- }
85
- }
86
-
87
- function hook_wp_head(){
88
- echo '<style type="text/css">',$this->broken_link_css,'</style>';
89
- }
90
- }
91
-
92
- }
93
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
images/bullet_blue.png ADDED
Binary file
images/bullet_error.png ADDED
Binary file
images/comment.png ADDED
Binary file
images/user_comment.png ADDED
Binary file
includes/admin/links-page-js.php ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script type='text/javascript'>
2
+
3
+ function alterLinkCounter(factor){
4
+ cnt = parseInt(jQuery('.current-link-count').eq(0).html());
5
+ cnt = cnt + factor;
6
+ jQuery('.current-link-count').html(cnt);
7
+ }
8
+
9
+ function replaceLinkId(old_id, new_id){
10
+ var master = jQuery('#blc-row-'+old_id);
11
+
12
+ //Save the new ID
13
+ master.attr('id', 'blc-row-'+new_id);
14
+ master.find('.blc-link-id').html(new_id);
15
+ }
16
+
17
+ function reloadDetailsRow(link_id){
18
+ var master = jQuery('#blc-row-'+link_id);
19
+
20
+ //Load up the new link info (so sue me)
21
+ master.next('.blc-link-details').find('td').html('<center><?php echo esc_js(__('Loading...' , 'broken-link-checker')); ?></center>').load(
22
+ "<?php echo admin_url('admin-ajax.php'); ?>",
23
+ {
24
+ 'action' : 'blc_link_details',
25
+ 'link_id' : link_id
26
+ }
27
+ );
28
+ }
29
+
30
+ jQuery(function($){
31
+
32
+ //The details button - display/hide detailed info about a link
33
+ $(".blc-details-button, .blc-link-text").click(function () {
34
+ $(this).parents('.blc-row').next('.blc-link-details').toggle();
35
+ });
36
+
37
+ //The discard button - manually mark the link as valid. The link will be checked again later.
38
+ $(".blc-discard-button").click(function () {
39
+ var me = $(this);
40
+ me.html('<?php echo esc_js(__('Wait...', 'broken-link-checker')); ?>');
41
+
42
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
43
+
44
+ $.post(
45
+ "<?php echo admin_url('admin-ajax.php'); ?>",
46
+ {
47
+ 'action' : 'blc_discard',
48
+ 'link_id' : link_id,
49
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_discard')); ?>'
50
+ },
51
+ function (data, textStatus){
52
+ if (data == 'OK'){
53
+ var master = $(me).parents('.blc-row');
54
+ var details = master.next('.blc-link-details');
55
+
56
+ //Remove the "Not broken" link
57
+ me.parent().remove();
58
+
59
+ master.removeClass('blc-broken-link');
60
+
61
+ //Flash the main row green to indicate success, then remove it if the current view
62
+ //is supposed to show only broken links.
63
+ var oldColor = master.css('background-color');
64
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
65
+ if ( blc_is_broken_filter ){
66
+ details.remove();
67
+ master.remove();
68
+ } else {
69
+ reloadDetailsRow(link_id);
70
+ }
71
+ });
72
+
73
+ //Update the elements displaying the number of results for the current filter.
74
+ if( blc_is_broken_filter ){
75
+ alterLinkCounter(-1);
76
+ }
77
+ } else {
78
+ $(me).html('<?php echo esc_js(__('Not broken' , 'broken-link-checker')); ?>');
79
+ alert(data);
80
+ }
81
+ }
82
+ );
83
+
84
+ return false;
85
+ });
86
+
87
+ //The edit button - edit/save the link's URL
88
+ $(".blc-edit-button").click(function () {
89
+ var edit_button = $(this);
90
+ var master = $(edit_button).parents('.blc-row');
91
+ var editor = $(master).find('.blc-link-editor');
92
+ var url_el = $(master).find('.blc-link-url');
93
+ var cancel_button_container = $(master).find('.blc-cancel-button-container');
94
+
95
+ //Find the current/original URL
96
+ var orig_url = url_el.attr('href');
97
+ //Find the link ID
98
+ var link_id = $(master).find('.blc-link-id').html();
99
+
100
+ if ( !$(editor).is(':visible') ){
101
+ //Dislay the editing UI
102
+ url_el.hide();
103
+ //Reset the edit box to the actual URL value in case the user has already tried and failed to edit this link.
104
+ editor.val( url_el.attr('href') );
105
+ editor.show();
106
+ cancel_button_container.show();
107
+ editor.focus();
108
+ editor.select();
109
+ edit_button.html('<?php echo esc_js(__('Save URL' , 'broken-link-checker')); ?>');
110
+ } else {
111
+ //"Save" clicked.
112
+ editor.hide();
113
+ cancel_button_container.hide();
114
+ url_el.show();
115
+
116
+ new_url = editor.val();
117
+
118
+ if (new_url != orig_url){
119
+ //Save the changed link
120
+ url_el.html('<?php echo esc_js(__('Saving changes...' , 'broken-link-checker')); ?>');
121
+
122
+ $.getJSON(
123
+ "<?php echo admin_url('admin-ajax.php'); ?>",
124
+ {
125
+ 'action' : 'blc_edit',
126
+ 'link_id' : link_id,
127
+ 'new_url' : new_url,
128
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_edit')); ?>'
129
+ },
130
+ function (data, textStatus){
131
+ var display_url = '';
132
+
133
+ if ( data && (typeof(data['error']) != 'undefined') ){
134
+ //An internal error occured before the link could be edited.
135
+ //data.error is an error message.
136
+ alert(data.error);
137
+ display_url = orig_url;
138
+ } else {
139
+ //data contains info about the performed edit
140
+ if ( data.errors.length == 0 ){
141
+ //Everything went well.
142
+
143
+ //Replace the displayed link URL with the new one.
144
+ display_url = new_url;
145
+ url_el.attr('href', new_url);
146
+
147
+ //Save the new ID
148
+ replaceLinkId(link_id, data.new_link_id);
149
+ //Load up the new link info
150
+ reloadDetailsRow(data.new_link_id);
151
+ //Remove classes indicating link state - they're probably wrong by now
152
+ master.removeClass('blc-broken-link').removeClass('blc-redirect');
153
+
154
+ //Flash the row green to indicate success
155
+ var oldColor = master.css('background-color');
156
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300);
157
+
158
+ } else {
159
+ display_url = orig_url;
160
+
161
+ //Build and display an error message.
162
+ var msg = '';
163
+
164
+ if ( data.cnt_okay > 0 ){
165
+ var msgpiece = sprintf(
166
+ '<?php echo esc_js(__('%d instances of the link were successfully modified.', 'broken-link-checker')); ?>',
167
+ data.cnt_okay
168
+ );
169
+ msg = msg + msgpiece + '\n';
170
+ if ( data.cnt_error > 0 ){
171
+ msgpiece = sprintf(
172
+ '<?php echo esc_js(__("However, %d instances couldn't be edited and still point to the old URL.", 'broken-link-checker')); ?>',
173
+ data.cnt_error
174
+ );
175
+ msg = msg + msgpiece + "\n";
176
+ }
177
+ } else {
178
+ msg = msg + '<?php echo esc_js(__('The link could not be modified.', 'broken-link-checker')); ?>\n';
179
+ }
180
+
181
+ msg = msg + '\n<?php echo esc_js(__("The following error(s) occured :", 'broken-link-checker')); ?>\n* ';
182
+ msg = msg + data.errors.join('\n* ');
183
+
184
+ alert(msg);
185
+ }
186
+ };
187
+
188
+ //Shorten the displayed URL if it's > 50 characters
189
+ if ( display_url.length > 50 ){
190
+ display_url = display_url.substr(0, 47) + '...';
191
+ }
192
+ url_el.html(display_url);
193
+ }
194
+ );
195
+
196
+ } else {
197
+ //It's the same URL, so do nothing.
198
+ }
199
+ edit_button.html('<?php echo esc_js(__('Edit URL', 'broken-link-checker')); ?>');
200
+ }
201
+ });
202
+
203
+ //Let the user use Enter and Esc as shortcuts for "Save URL" and "Cancel"
204
+ $('input.blc-link-editor').keypress(function (e) {
205
+ if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
206
+ $(this).parents('.blc-row').find('.blc-edit-button').click();
207
+ return false;
208
+ } else if ((e.which && e.which == 27) || (e.keyCode && e.keyCode == 27)) {
209
+ $(this).parents('.blc-row').find('.blc-cancel-button').click();
210
+ return false;
211
+ } else {
212
+ return true;
213
+ }
214
+ });
215
+
216
+ $(".blc-cancel-button").click(function () {
217
+ var master = $(this).parents('.blc-row');
218
+ var url_el = $(master).find('.blc-link-url');
219
+
220
+ //Hide the cancel button
221
+ $(this).parent().hide();
222
+ //Show the un-editable URL again
223
+ url_el.show();
224
+ //reset and hide the editor
225
+ master.find('.blc-link-editor').hide().val(url_el.attr('href'));
226
+ //Set the edit button to say "Edit URL"
227
+ master.find('.blc-edit-button').html('<?php echo esc_js(__('Edit URL' , 'broken-link-checker')); ?>');
228
+ });
229
+
230
+ //The unlink button - remove the link/image from all posts, custom fields, etc.
231
+ $(".blc-unlink-button").click(function () {
232
+ var me = this;
233
+ var master = $(me).parents('.blc-row');
234
+ $(me).html('<?php echo esc_js(__('Wait...' , 'broken-link-checker')); ?>');
235
+
236
+ var link_id = $(me).parents('.blc-row').find('.blc-link-id').html();
237
+
238
+ $.post(
239
+ "<?php echo admin_url('admin-ajax.php'); ?>",
240
+ {
241
+ 'action' : 'blc_unlink',
242
+ 'link_id' : link_id,
243
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_unlink')); ?>'
244
+ },
245
+ function (data, textStatus){
246
+ eval('data = ' + data);
247
+
248
+ if ( data && (typeof(data['error']) != 'undefined') ){
249
+ //An internal error occured before the link could be edited.
250
+ //data.error is an error message.
251
+ alert(data.error);
252
+ } else {
253
+ if ( data.errors.length == 0 ){
254
+ //The link was successfully removed. Hide its details.
255
+ master.next('.blc-link-details').hide();
256
+ //Flash the main row green to indicate success, then hide it.
257
+ var oldColor = master.css('background-color');
258
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
259
+ master.hide();
260
+ });
261
+
262
+ alterLinkCounter(-1);
263
+
264
+ return;
265
+ } else {
266
+ //Build and display an error message.
267
+ var msg = '';
268
+
269
+ if ( data.cnt_okay > 0 ){
270
+ msg = msg + sprintf(
271
+ '<?php echo esc_js(__("%d instances of the link were successfully unlinked.", 'broken-link-checker')); ?>\n',
272
+ data.cnt_okay
273
+ );
274
+
275
+ if ( data.cnt_error > 0 ){
276
+ msg = msg + sprintf(
277
+ '<?php echo esc_js(__("However, %d instances couldn't be removed.", 'broken-link-checker')); ?>\n',
278
+ data.cnt_error
279
+ );
280
+ }
281
+ } else {
282
+ msg = msg + '<?php echo esc_js(__("The plugin failed to remove the link.", 'broken-link-checker')); ?>\n';
283
+ }
284
+
285
+ msg = msg + '\n<?php echo esc_js(__("The following error(s) occured :", 'broken-link-checker')); ?>\n* ';
286
+ msg = msg + data.errors.join('\n* ');
287
+
288
+ //Show the error message
289
+ alert(msg);
290
+ }
291
+ }
292
+
293
+ $(me).html('<?php echo esc_js(__('Unlink' , 'broken-link-checker')); ?>');
294
+ }
295
+ );
296
+ });
297
+
298
+ //--------------------------------------------
299
+ //The search box(es)
300
+ //--------------------------------------------
301
+
302
+ var searchForm = $('#search-links-dialog');
303
+
304
+ searchForm.dialog({
305
+ autoOpen : false,
306
+ dialogClass : 'blc-search-container',
307
+ resizable: false,
308
+ });
309
+
310
+ $('#blc-open-search-box').click(function(){
311
+ if ( searchForm.dialog('isOpen') ){
312
+ searchForm.dialog('close');
313
+ } else {
314
+ //Display the search form under the "Search" button
315
+ var button_position = $('#blc-open-search-box').offset();
316
+ var button_height = $('#blc-open-search-box').outerHeight(true);
317
+ var button_width = $('#blc-open-search-box').outerWidth(true);
318
+
319
+ var dialog_width = searchForm.dialog('option', 'width');
320
+
321
+ searchForm.dialog('option', 'position',
322
+ [
323
+ button_position.left - dialog_width + button_width/2,
324
+ button_position.top + button_height + 1 - $(document).scrollTop()
325
+ ]
326
+ );
327
+ searchForm.dialog('open');
328
+ }
329
+ });
330
+
331
+ $('#blc-cancel-search').click(function(){
332
+ searchForm.dialog('close');
333
+ });
334
+
335
+ //The "Save This Search Query" button creates a new custom filter based on the current search
336
+ $('#blc-create-filter').click(function(){
337
+ var filter_name = prompt("<?php echo esc_js(__("Enter a name for the new custom filter", 'broken-link-checker')); ?>", "");
338
+ if ( filter_name ){
339
+ $('#blc-custom-filter-name').val(filter_name);
340
+ $('#custom-filter-form').submit();
341
+ }
342
+ });
343
+
344
+ //Display a confirmation dialog when the user clicks the "Delete This Filter" button
345
+ $('#blc-delete-filter').click(function(){
346
+ if ( confirm('<?php
347
+ echo esc_js(
348
+ __("You are about to delete the current filter.\n'Cancel' to stop, 'OK' to delete", 'broken-link-checker')
349
+ );
350
+ ?>') ){
351
+ return true;
352
+ } else {
353
+ return false;
354
+ }
355
+ });
356
+
357
+ //--------------------------------------------
358
+ // Bulk actions
359
+ //--------------------------------------------
360
+
361
+ $('#blc-bulk-action-form').submit(function(){
362
+ var action = $('#blc-bulk-action').val();
363
+ if ( action == '-1' ){
364
+ var action = $('#blc-bulk-action2').val();
365
+ }
366
+
367
+ //Convey the gravitas of deleting link sources.
368
+ if ( action == 'bulk-delete-sources' ){
369
+ var message = '<?php
370
+ echo esc_js(
371
+ __("Are you sure you want to delete all posts, bookmarks or other items that contain any of the selected links? This action can't be undone.\n'Cancel' to stop, 'OK' to delete", 'broken-link-checker')
372
+ );
373
+ ?>';
374
+ if ( !confirm(message) ){
375
+ return false;
376
+ }
377
+ }
378
+ });
379
+
380
+ //Not implemented yet
381
+ });
382
+
383
+ </script>
includes/admin/search-form.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="search-box">
2
+
3
+ <?php
4
+ //If we're currently displaying search results offer the user the option to s
5
+ //save the search query as a custom filter.
6
+ if ( $filter_id == 'search' ){
7
+ ?>
8
+ <form name="save-search-query" id="custom-filter-form" action="<?php echo admin_url("tools.php?page=view-broken-links"); ?>" method="post" class="blc-inline-form">
9
+ <?php wp_nonce_field('create-custom-filter'); ?>
10
+ <input type="hidden" name="name" id="blc-custom-filter-name" value="" />
11
+ <input type="hidden" name="params" id="blc-custom-filter-params" value="<?php echo http_build_query($search_params, null, '&'); ?>" />
12
+ <input type="hidden" name="action" value="create-custom-filter" />
13
+ <input type="button" value="<?php esc_attr_e( 'Save This Search As a Filter', 'broken-link-checker' ); ?>" id="blc-create-filter" class="button" />
14
+ </form>
15
+ <?php
16
+ } elseif ( !empty($current_filter['custom']) ){
17
+ //If we're displaying a custom filter give an option to delete it.
18
+ ?>
19
+ <form name="save-search-query" id="custom-filter-form" action="<?php echo admin_url("tools.php?page=view-broken-links"); ?>" method="post" class="blc-inline-form">
20
+ <?php wp_nonce_field('delete-custom-filter'); ?>
21
+ <input type="hidden" name="filter_id" id="blc-custom-filter-id" value="<?php echo $filter_id; ?>" />
22
+ <input type="hidden" name="action" value="delete-custom-filter" />
23
+ <input type="submit" value="<?php esc_attr_e( 'Delete This Filter', 'broken-link-checker' ); ?>" id="blc-delete-filter" class="button" />
24
+ </form>
25
+ <?php
26
+ }
27
+ ?>
28
+
29
+ <input type="button" value="<?php esc_attr_e( 'Search', 'broken-link-checker' ); ?> &raquo;" id="blc-open-search-box" class="button" />
30
+ </div>
31
+
32
+ <!-- The search dialog -->
33
+ <div id='search-links-dialog' title='Search'>
34
+ <form class="search-form" action="<?php echo admin_url('tools.php?page=view-broken-links'); ?>" method="get">
35
+ <input type="hidden" name="page" value="view-broken-links" />
36
+ <input type="hidden" name="filter_id" value="search" />
37
+ <fieldset>
38
+
39
+ <label for="s_link_text"><?php _e('Link text', 'broken-link-checker'); ?></label>
40
+ <input type="text" name="s_link_text" value="<?php if(!empty($search_params['s_link_text'])) echo esc_attr($search_params['s_link_text']); ?>" id="s_link_text" class="text ui-widget-content" />
41
+
42
+ <label for="s_link_url"><?php _e('URL', 'broken-link-checker'); ?></label>
43
+ <input type="text" name="s_link_url" id="s_link_url" value="<?php if(!empty($search_params['s_link_url'])) echo esc_attr($search_params['s_link_url']); ?>" class="text ui-widget-content" />
44
+
45
+ <label for="s_http_code"><?php _e('HTTP code', 'broken-link-checker'); ?></label>
46
+ <input type="text" name="s_http_code" id="s_http_code" value="<?php if(!empty($search_params['s_http_code'])) echo esc_attr($search_params['s_http_code']); ?>" class="text ui-widget-content" />
47
+
48
+ <label for="s_filter"><?php _e('Link status', 'broken-link-checker'); ?></label>
49
+ <select name="s_filter" id="s_filter">
50
+ <?php
51
+ if ( !empty($search_params['s_filter']) ){
52
+ $search_subfilter = $search_params['s_filter'];
53
+ } else {
54
+ $search_subfilter = $filter_id;
55
+ }
56
+
57
+ foreach ($blc_link_query->native_filters as $filter => $data){
58
+ $selected = ($search_subfilter == $filter)?' selected="selected"':'';
59
+ printf('<option value="%s"%s>%s</option>', $filter, $selected, $data['name']);
60
+ }
61
+ ?>
62
+ </select>
63
+
64
+ <label for="s_link_type"><?php _e('Link type', 'broken-link-checker'); ?></label>
65
+ <select name="s_link_type" id="s_link_type">
66
+ <?php
67
+ $link_types = array(
68
+ __('Any', 'broken-link-checker') => '',
69
+ __('Normal link', 'broken-link-checker') => 'link',
70
+ __('Image', 'broken-link-checker') => 'image',
71
+ __('Custom field', 'broken-link-checker') => 'custom_field',
72
+ __('Bookmark', 'broken-link-checker') => 'blogroll',
73
+ __('Comment', 'broken-link-checker') => 'comment',
74
+ );
75
+
76
+ foreach ($link_types as $name => $value){
77
+ $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $value )?' selected="selected"':'';
78
+ printf('<option value="%s"%s>%s</option>', $value, $selected, $name);
79
+ }
80
+ ?>
81
+ </select>
82
+
83
+ </fieldset>
84
+
85
+ <div id="blc-search-button-row">
86
+ <input type="submit" value="<?php esc_attr_e( 'Search Links', 'broken-link-checker' ); ?>" id="blc-search-button" name="search_button" class="button-primary" />
87
+ <input type="button" value="<?php esc_attr_e( 'Cancel', 'broken-link-checker' ); ?>" id="blc-cancel-search" class="button" />
88
+ </div>
89
+
90
+ </form>
91
+ </div>
includes/checkers.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Base class for link checking algorithms.
5
+ *
6
+ * All link checkering algorithms should extend this class.
7
+ *
8
+ * @package Broken Link Checker
9
+ * @access public
10
+ */
11
+ class blcChecker {
12
+
13
+ /**
14
+ * Priority determines the order in which the plugin will try all registered checkers
15
+ * when looking for one that can check a particular URL. Registered checkers will be
16
+ * tried in order, from highest to lowest priority, and the first one that returns
17
+ * true when its can_check() method is called will be used.
18
+ *
19
+ * Checker implementations should set their priority depending on how specific they are
20
+ * in choosing the URLs that they check.
21
+ *
22
+ * -10 .. 10 : checks all URLs that have a certain protocol, e.g. all HTTP URLs.
23
+ * 11 .. 100 : checks only URLs from a restricted number of domains, e.g. video site URLs.
24
+ * 100+ : checks only certain URLs from a certain domain, e.g. YouTube video links.
25
+ *
26
+ */
27
+ var $priority = -100;
28
+
29
+ /**
30
+ * Check if this checker knows how to check a particular URL.
31
+ *
32
+ * @param string $url
33
+ * @param array|false $parsed_url The result of parsing $url with parse_url(). See PHP docs for details.
34
+ * @return bool
35
+ */
36
+ function can_check($url, $parsed_url){
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Check an URL.
42
+ *
43
+ * This method returns an associative array containing results of
44
+ * the check. The following array keys are recognized by the plugin and
45
+ * their values will be stored in the link's DB record :
46
+ * 'broken' (bool) - True if the URL points to a missing/broken page. Required.
47
+ * 'http_code' (int) - HTTP code returned when requesting the URL. Defaults to 0.
48
+ * 'redirect_count' (int) - The number of redirects. Defaults to 0.
49
+ * 'final_url' (string) - The redirected-to URL. Assumed to be equal to the checked URL by default.
50
+ * 'request_duration' (float) - How long it took for the server to respond. Defaults to 0 seconds.
51
+ * 'timeout' (bool) - True if checking the URL resulted in a timeout. Defaults to false.
52
+ * 'may_recheck' (bool) - Allow the plugin to re-check the URL after 'recheck_threshold' seconds (see broken-link-checker.php).
53
+ * 'log' (string) - Free-form log of the performed check. It will be displayed in the "Details" section of the checked link.
54
+ * 'result_hash' (string) - A free-form hash or code uniquely identifying the detected link status. See sub-classes for examples. Max 200 characters.
55
+ *
56
+ * @see blcLink:check()
57
+ *
58
+ * @param string $url
59
+ * @return array
60
+ */
61
+ function check($url){
62
+ trigger_error('Function blcChecker::check() must be over-ridden in a subclass', E_USER_ERROR);
63
+ }
64
+ }
65
+
66
+ class blcCheckerRegistry {
67
+ var $registered_checkers = array();
68
+
69
+ /**
70
+ * Register a link checker.
71
+ *
72
+ * @param string $class_name Class name of the checker.
73
+ * @return void
74
+ */
75
+ function register_checker($class_name){
76
+ $checker = new $class_name;
77
+ $this->registered_checkers[] = $checker;
78
+
79
+ usort($this->registered_checkers, array(&$this, 'compare_checkers'));
80
+ }
81
+
82
+ /**
83
+ * Callback for sorting checkers by priority.
84
+ *
85
+ * @access private
86
+ *
87
+ * @param blcChecker $a
88
+ * @param blcChecker $b
89
+ * @return int
90
+ */
91
+ function compare_checkers($a, $b){
92
+ return $b->priority - $a->priority;
93
+ }
94
+
95
+ /**
96
+ * Get a checker object that can check the specified URL.
97
+ *
98
+ * @param string $url
99
+ * @return blcChecker|null
100
+ */
101
+ function get_checker_for($url){
102
+ $parsed = @parse_url($url);
103
+
104
+ foreach($this->registered_checkers as $checker){
105
+ if ( $checker->can_check($url, $parsed) ){
106
+ return $checker;
107
+ }
108
+ }
109
+
110
+ return null;
111
+ }
112
+ }
113
+
114
+ $GLOBALS['blc_checker_registry'] = new blcCheckerRegistry();
115
+
116
+ /**
117
+ * Register a new link checker.
118
+ *
119
+ * @param string $class_name
120
+ * @return void
121
+ */
122
+ function blc_register_checker($class_name){
123
+ return $GLOBALS['blc_checker_registry']->register_checker($class_name);
124
+ }
125
+
126
+ /**
127
+ * Get the checker algo. implementation that knows how to check a specific URL.
128
+ *
129
+ * @param string $url The URL that needs to be checked.
130
+ * @return blcChecker|null
131
+ */
132
+ function blc_get_checker_for($url){
133
+ return $GLOBALS['blc_checker_registry']->get_checker_for($url);
134
+ }
135
+
136
+ ?>
includes/checkers/http.php ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Base class for checkers that deal with HTTP(S) URLs.
5
+ *
6
+ * @package Broken Link Checker
7
+ * @access public
8
+ */
9
+ class blcHttpChecker extends blcChecker{
10
+
11
+ var $priority = -1;
12
+
13
+ function clean_url($url){
14
+ $url = html_entity_decode($url);
15
+ $url = preg_replace(
16
+ array(
17
+ '/([\?&]PHPSESSID=\w+)$/i', //remove session ID
18
+ '/(#[^\/]*)$/', //and anchors/fragments
19
+ '/&amp;/', //convert improper HTML entities
20
+ '/([\?&]sid=\w+)$/i' //remove another flavour of session ID
21
+ ),
22
+ array('','','&','',''),
23
+ $url
24
+ );
25
+ $url = trim($url);
26
+
27
+ return $url;
28
+ }
29
+
30
+ function is_error_code($http_code){
31
+ /*"Good" response codes are anything in the 2XX range (e.g "200 OK") and redirects - the 3XX range.
32
+ HTTP 401 Unauthorized is a special case that is considered OK as well. Other errors - the 4XX range -
33
+ are treated as "page doesn't exist'". */
34
+ $good_code = ( ($http_code >= 200) && ($http_code < 400) ) || ( $http_code == 401 );
35
+ return !$good_code;
36
+ }
37
+
38
+ /**
39
+ * This checker only accepts HTTP(s) links.
40
+ *
41
+ * @param string $url
42
+ * @param array|false $parsed
43
+ * @return bool
44
+ */
45
+ function can_check($url, $parsed){
46
+ if ( !isset($parsed['scheme']) ) return false;
47
+
48
+ return in_array( strtolower($parsed['scheme']), array('http', 'https') );
49
+ }
50
+
51
+ /**
52
+ * Takes an URL and replaces spaces and some other non-alphanumeric characters with their urlencoded equivalents.
53
+ *
54
+ * @param string $str
55
+ * @return string
56
+ */
57
+ function urlencodefix($url){
58
+ return preg_replace_callback(
59
+ '|[^a-z0-9\+\-\/\\#:.=?&%@]|i',
60
+ create_function('$str','return rawurlencode($str[0]);'),
61
+ $url
62
+ );
63
+ }
64
+
65
+ }
66
+
67
+ //TODO: Rewrite sub-classes as transports, not stand-alone checkers
68
+ class blcCurlHttp extends blcHttpChecker {
69
+
70
+ var $last_headers = '';
71
+
72
+ function check($url, $use_get = false){
73
+ $this->last_headers = '';
74
+
75
+ $url = $this->clean_url($url);
76
+
77
+ $result = array(
78
+ 'broken' => false,
79
+ );
80
+ $log = '';
81
+
82
+ //Get the BLC configuration. It's used below to set the right timeout values and such.
83
+ $conf = blc_get_configuration();
84
+
85
+ //Init curl.
86
+ $ch = curl_init();
87
+ curl_setopt($ch, CURLOPT_URL, $this->urlencodefix($url));
88
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
89
+
90
+ //Masquerade as Internet explorer
91
+ //$ua = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)';
92
+ $ua = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)';
93
+ curl_setopt($ch, CURLOPT_USERAGENT, $ua);
94
+
95
+ //Add a semi-plausible referer header to avoid tripping up some bot traps
96
+ curl_setopt($ch, CURLOPT_REFERER, get_option('home'));
97
+
98
+ //Redirects don't work when safe mode or open_basedir is enabled.
99
+ if ( !blcUtility::is_safe_mode() && !blcUtility::is_open_basedir() ) {
100
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
101
+ }
102
+ //Set maximum redirects
103
+ curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
104
+
105
+ //Set the timeout
106
+ curl_setopt($ch, CURLOPT_TIMEOUT, $conf->options['timeout']);
107
+
108
+ //Set the proxy configuration. The user can provide this in wp-config.php
109
+ if (defined('WP_PROXY_HOST')) {
110
+ curl_setopt($ch, CURLOPT_PROXY, WP_PROXY_HOST);
111
+ }
112
+ if (defined('WP_PROXY_PORT')) {
113
+ curl_setopt($ch, CURLOPT_PROXYPORT, WP_PROXY_PORT);
114
+ }
115
+ if (defined('WP_PROXY_USERNAME')){
116
+ $auth = WP_PROXY_USERNAME;
117
+ if (defined('WP_PROXY_PASSWORD')){
118
+ $auth .= ':' . WP_PROXY_PASSWORD;
119
+ }
120
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth);
121
+ }
122
+
123
+ //Make CURL return a valid result even if it gets a 404 or other error.
124
+ curl_setopt($ch, CURLOPT_FAILONERROR, false);
125
+
126
+
127
+ $nobody = !$use_get; //Whether to send a HEAD request (the default) or a GET request
128
+
129
+ $parts = @parse_url($url);
130
+ if( $parts['scheme'] == 'https' ){
131
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); //Required to make HTTPS URLs work.
132
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); //Likewise.
133
+ $nobody = false; //Can't use HEAD with HTTPS.
134
+ }
135
+
136
+ if ( $nobody ){
137
+ //If possible, use HEAD requests for speed.
138
+ curl_setopt($ch, CURLOPT_NOBODY, true);
139
+ } else {
140
+ //If we must use GET at least limit the amount of downloaded data.
141
+ curl_setopt($ch, HTTP_HEADER, array('Range: bytes=0-2048')); //2 KB
142
+ }
143
+
144
+ //Register a callback function which will process the HTTP header(s).
145
+ //It can be called multiple times if the remote server performs a redirect.
146
+ curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this,'read_header'));
147
+
148
+ //Execute the request
149
+ curl_exec($ch);
150
+
151
+ $info = curl_getinfo($ch);
152
+ curl_close($ch);
153
+
154
+ //Store the results
155
+ $result['http_code'] = intval( $info['http_code'] );
156
+ $result['timeout'] = ($result['http_code'] == 0); //If the code is 0 then it's probably a timeout
157
+ $result['final_url'] = $info['url'];
158
+ $result['request_duration'] = $info['total_time'];
159
+ $result['redirect_count'] = $info['redirect_count'];
160
+
161
+ if ( $nobody && ($result['http_code'] == 405) ){
162
+ //405 = Method not allowed. The site in question is probably expecting
163
+ //GET instead of HEAD. So lets retry the request using the GET verb.
164
+ return $this->check($url, true);
165
+ }
166
+
167
+ //When safe_mode or open_basedir is enabled CURL will be forbidden from following redirects,
168
+ //so redirect_count will be 0 for all URLs. As a workaround, set it to 1 when the HTTP
169
+ //response codes indicates a redirect but redirect_count is zero.
170
+ //Note to self : Extracting the Location header might also be helpful.
171
+ if ( ($result['redirect_count'] == 0) && ( in_array( $result['http_code'], array(301, 302, 303, 307) ) ) ){
172
+ $result['redirect_count'] = 1;
173
+ }
174
+
175
+ //Determine if the link counts as "broken"
176
+ $result['broken'] = $this->is_error_code($result['http_code']) || $result['timeout'];
177
+
178
+ //Build the log from HTTP code and headers.
179
+ //TODO: Put some kind of a color-coded error explanation at the top of the log, not a cryptic HTTP code.
180
+ $log .= '=== ';
181
+ if ( $result['http_code'] ){
182
+ $log .= sprintf( __('HTTP code : %d', 'broken-link-checker'), $result['http_code']);
183
+ } else {
184
+ $log .= __('(No response)', 'broken-link-checker');
185
+ }
186
+ $log .= " ===\n\n";
187
+ $log .= $this->last_headers;
188
+
189
+ if ( $result['broken'] && $result['timeout'] ) {
190
+ $log .= "\n(" . __("Most likely the connection timed out or the domain doesn't exist.", 'broken-link-checker') . ')';
191
+ }
192
+
193
+ $result['log'] = $log;
194
+
195
+ //The hash should contain info about all pieces of data that pertain to determining if the
196
+ //link is working.
197
+ $result['result_hash'] = implode('|', array(
198
+ $result['http_code'],
199
+ $result['broken']?'broken':'0',
200
+ $result['timeout']?'timeout':'0',
201
+ md5($result['final_url']),
202
+ ));
203
+
204
+ return $result;
205
+ }
206
+
207
+ function read_header($ch, $header){
208
+ $this->last_headers .= $header;
209
+ return strlen($header);
210
+ }
211
+
212
+ }
213
+
214
+ class blcSnoopyHttp extends blcHttpChecker {
215
+
216
+ function check($url){
217
+ $url = $this->clean_url($url); //Note : Snoopy doesn't work too well with HTTPS URLs.
218
+
219
+ $result = array(
220
+ 'broken' => false,
221
+ );
222
+ $log = '';
223
+
224
+ //Get the timeout setting from the BLC configuration.
225
+ $conf = blc_get_configuration();
226
+ $timeout = $conf->options['timeout'];
227
+
228
+ $start_time = microtime_float(true);
229
+
230
+ //Fetch the URL with Snoopy
231
+ $snoopy = new Snoopy;
232
+ $snoopy->read_timeout = $timeout; //read timeout in seconds
233
+ $snoopy->agent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)"; //masquerade as IE 7
234
+ $snoopy->maxlength = 1024*5; //load up to 5 kilobytes
235
+ $snoopy->fetch($url);
236
+
237
+ $result['request_duration'] = microtime_float(true) - $start_time;
238
+
239
+ $result['http_code'] = $snoopy->status; //HTTP status code
240
+ //Snoopy returns -100 on timeout
241
+ if ( $result['http_code'] == -100 ){
242
+ $result['http_code'] = 0;
243
+ $result['timeout'] = true;
244
+ }
245
+
246
+ //Build the log
247
+ $log .= '=== ';
248
+ if ( $result['http_code'] ){
249
+ $log .= sprintf( __('HTTP code : %d', 'broken-link-checker'), $result['http_code']);
250
+ } else {
251
+ $log .= __('(No response)', 'broken-link-checker');
252
+ }
253
+ $log .= " ===\n\n";
254
+
255
+ if ($snoopy->error)
256
+ $log .= $snoopy->error."\n";
257
+ if ($snoopy->timed_out) {
258
+ $log .= __("Request timed out.", 'broken-link-checker') . "\n";
259
+ $result['timeout'] = true;
260
+ }
261
+
262
+ if ( is_array($snoopy->headers) )
263
+ $log .= implode("", $snoopy->headers)."\n"; //those headers already contain newlines
264
+
265
+ //Redirected?
266
+ if ( $snoopy->lastredirectaddr ) {
267
+ $result['final_url'] = $snoopy->lastredirectaddr;
268
+ $result['redirect_count'] = $snoopy->_redirectdepth;
269
+ } else {
270
+ $result['final_url'] = $url;
271
+ }
272
+
273
+ //Determine if the link counts as "broken"
274
+ $result['broken'] = $this->is_error_code($result['http_code']) || $result['timeout'];
275
+
276
+ $log .= "<em>(" . __('Using Snoopy', 'broken-link-checker') . ")</em>";
277
+ $result['log'] = $log;
278
+
279
+ //The hash should contain info about all pieces of data that pertain to determining if the
280
+ //link is working.
281
+ $result['result_hash'] = implode('|', array(
282
+ $result['http_code'],
283
+ $result['broken']?'broken':'0',
284
+ $result['timeout']?'timeout':'0',
285
+ md5($result['final_url']),
286
+ ));
287
+
288
+ return $result;
289
+ }
290
+
291
+ }
292
+
293
+ if ( function_exists('curl_init') ) {
294
+ blc_register_checker('blcCurlHttp');
295
+ } else {
296
+ //Try to load Snoopy.
297
+ if ( !class_exists('Snoopy') ){
298
+ $snoopy_file = ABSPATH. WPINC . '/class-snoopy.php';
299
+ if (file_exists($snoopy_file) ){
300
+ include $snoopy_file;
301
+ }
302
+ }
303
+
304
+ //If Snoopy is available, it will be used in place of CURL.
305
+ if ( class_exists('Snoopy') ){
306
+ blc_register_checker('blcSnoopyHttp');
307
+ }
308
+ }
309
+
310
+
311
+
312
+ ?>
includes/containers.php ADDED
@@ -0,0 +1,886 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A class for handling link container types.
5
+ *
6
+ * This class keeps track of all registered container types and is used to retrieve and/or
7
+ * instantiate container objects. Mostly acts as a proxy between various container managers
8
+ * and the rest of plugin code. It does't know anything about how individual containers are
9
+ * implemented.
10
+ *
11
+ * Used as a singleton.
12
+ *
13
+ * @package Broken Link Checker
14
+ */
15
+ class blcContainerRegistry {
16
+
17
+ var $registered_managers;
18
+
19
+ /**
20
+ * blcContainerRegistry::__construct()
21
+ *
22
+ * @return void
23
+ */
24
+ function __construct(){
25
+ $this->registered_managers = array();
26
+ }
27
+
28
+ /**
29
+ * blcContainerRegistry::blcContainerRegistry()
30
+ * Old-style class constructor
31
+ *
32
+ * @return void
33
+ */
34
+ function blcContainerRegistry(){
35
+ $this->__construct();
36
+ }
37
+
38
+ /**
39
+ * Register a new container type.
40
+ *
41
+ * A "container type" comprises three things :
42
+ * - An alphanumeric container type name, e.g. "post"
43
+ * - A class representing an individual container. Must inherit from blcContainer.
44
+ * - A "manager" class that builds container objects, knows how to retrieve multiple
45
+ * containers and their associated data from the database, and handles all other
46
+ * ancillary tasks associated with the particular container type (e.g. WP hooks).
47
+ *
48
+ * When registering a new container type you only need to provide the container type
49
+ * name and the container manager class name.
50
+ *
51
+ * @see blcContainer
52
+ * @see blcContainerManager
53
+ *
54
+ * @param string $container_type
55
+ * @param string $manager_class
56
+ * @return bool True on success, false if the container type is already registered.
57
+ */
58
+ function register_container( $container_type, $manager_class ){
59
+ if ( isset($this->registered_managers[$container_type]) ){
60
+ return false;
61
+ }
62
+
63
+ $this->registered_managers[$container_type] = new $manager_class($container_type);
64
+
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Get the manager associated with a container type.
70
+ *
71
+ * @param string $container_type
72
+ * @param string $fallback If there is no manager associated with $container_type, return the manager of this container type instead.
73
+ * @return blcContainerManager|null
74
+ */
75
+ function get_manager( $container_type, $fallback = '' ){
76
+ if ( isset($this->registered_managers[$container_type]) ){
77
+ return $this->registered_managers[$container_type];
78
+ } elseif ( !empty($fallback) && isset($this->registered_managers[$fallback]) ) {
79
+ return $this->registered_managers[$fallback];
80
+ } else {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Retrieve or instantiate a container object.
87
+ *
88
+ * Pass an array containing the container type (string) and ID (int) to retrieve the container
89
+ * from the database. Alternatively, pass an associative array to create a new container object
90
+ * from the data in the array.
91
+ *
92
+ * @param array $container Either [container_type, container_id], or an assoc. array of container data.
93
+ * @return blcContainer|null
94
+ */
95
+ function get_container( $container ){
96
+ global $wpdb;
97
+
98
+ if ( !is_array($container) || ( count($container) < 2 ) ){
99
+ return null;
100
+ }
101
+
102
+ if ( is_string($container[0]) && is_numeric($container[1]) ){
103
+ //The argument is in the [container_type, id] format
104
+ //Fetch the container's synch record.
105
+ $q = "SELECT * FROM {$wpdb->prefix}blc_synch WHERE container_type = %s AND container_id = %d";
106
+ $q = $wpdb->prepare($q, $container[0], $container[1]);
107
+ $rez = $wpdb->get_row($q, ARRAY_A);
108
+
109
+ if ( empty($rez) ){
110
+ //The container wasn't found, so we'll create a new one.
111
+ $container = array(
112
+ 'container_type' => $container[0],
113
+ 'container_id' => $container[1],
114
+ );
115
+ } else {
116
+ $container = $rez;
117
+ }
118
+ }
119
+
120
+ if ( !isset($this->registered_managers[$container['container_type']]) ){
121
+ return null;
122
+ }
123
+
124
+ $manager = $this->registered_managers[$container['container_type']];
125
+
126
+ return $manager->get_container($container);
127
+ }
128
+
129
+ /**
130
+ * Retrieve or instantiate multiple link containers.
131
+ *
132
+ * Takes an array of container specifications and returns an array of container objects.
133
+ * Each input array entry should be an array and consist either of a container type (string)
134
+ * and container ID (int), or name => value pairs describing a container object.
135
+ *
136
+ * @see blcContainerRegistry::get_container()
137
+ *
138
+ * @param array $containers
139
+ * @param string $purpose Optional code indicating how the retrieved containers will be used.
140
+ * @param string $fallback The fallback container type to use for unrecognized containers.
141
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
142
+ * @return array of blcContainer indexed by "container_type|container_id"
143
+ */
144
+ function get_containers( $containers, $purpose = '', $fallback = '', $load_wrapped_objects = false ){
145
+ global $wpdb;
146
+
147
+ //If the input is invalid or empty, return an empty array.
148
+ if ( !is_array($containers) || (count($containers) < 1) ) {
149
+ return array();
150
+ }
151
+
152
+ $first = reset($containers);
153
+ if ( !is_array($first) ){
154
+ return array();
155
+ }
156
+
157
+ if ( is_string($first[0]) && is_numeric($first[1]) ){
158
+ //The argument is an array of [container_type, id].
159
+ //Divide the container IDs by container type.
160
+ $by_type = array();
161
+
162
+ foreach($containers as $container){
163
+ if ( isset($by_type[$container[0]]) ){
164
+ array_push($by_type[$container[0]], intval($container[1]));
165
+ } else {
166
+ $by_type[$container[0]] = array( intval($container[1]) );
167
+ }
168
+ }
169
+
170
+ //Build the SQL to fetch all the specified containers
171
+ $q = "SELECT *
172
+ FROM {$wpdb->prefix}blc_synch
173
+ WHERE";
174
+
175
+ $pieces = array();
176
+ foreach($by_type as $container_type => $container_ids){
177
+ $pieces[] = '( container_type = "'. $wpdb->escape($container_type) .'" AND container_id IN ('. implode(', ', $container_ids) .') )';
178
+ }
179
+
180
+ $q .= implode("\n\t OR ", $pieces);
181
+
182
+ //Fetch the container synch. records from the DB.
183
+ $containers = $wpdb->get_results($q, ARRAY_A);
184
+ }
185
+
186
+ /*
187
+ Divide the inputs into separate arrays by container type (again), then invoke
188
+ the appropriate manager for each type to instantiate the container objects.
189
+ */
190
+
191
+ //At this point, $containers is an array of assoc. arrays comprising container data.
192
+ $by_type = array();
193
+ foreach($containers as $container){
194
+ if ( isset($by_type[$container['container_type']]) ){
195
+ array_push($by_type[$container['container_type']], $container);
196
+ } else {
197
+ $by_type[$container['container_type']] = array($container);
198
+ }
199
+ }
200
+
201
+ $results = array();
202
+ foreach($by_type as $container_type => $entries){
203
+ $manager = $this->get_manager($container_type, $fallback);
204
+ if ( !is_null($manager) ){
205
+ $partial_results = $manager->get_containers($entries, $purpose, $load_wrapped_objects);
206
+ $results = array_merge($results, $partial_results);
207
+ }
208
+ }
209
+
210
+ return $results;
211
+ }
212
+
213
+ /**
214
+ * Retrieve link containers that need to be synchronized (parsed).
215
+ *
216
+ * @param integer $max_results The maximum number of containers to return. Defaults to returning all unsynched containers.
217
+ * @return array of blcContainer
218
+ */
219
+ function get_unsynched_containers($max_results = 0){
220
+ global $wpdb;
221
+
222
+ $q = "SELECT * FROM {$wpdb->prefix}blc_synch WHERE synched = 0";
223
+ if ( $max_results > 0 ){
224
+ $q .= " LIMIT $max_results";
225
+ }
226
+
227
+ $container_data = $wpdb->get_results($q, ARRAY_A);
228
+ //FB::log($container_data, "Unsynched containers");
229
+ if( empty($container_data) ){
230
+ return array();
231
+ }
232
+
233
+ $containers = $this->get_containers($container_data, BLC_FOR_PARSING, 'dummy');
234
+ return $containers;
235
+ }
236
+
237
+ /**
238
+ * (Re)create and update synchronization records for all supported containers.
239
+ * Calls the resynch() method of all registered managers.
240
+ *
241
+ * @param bool $forced If true, assume that no synch. records exist and build all of them from scratch.
242
+ * @return void
243
+ */
244
+ function resynch($forced = false){
245
+ global $wpdb;
246
+
247
+ foreach($this->registered_managers as $manager){
248
+ $manager->resynch($forced);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Get the message to display after $n containers of a specific type have been deleted.
254
+ *
255
+ * @param string $container_type
256
+ * @param int $n Number of deleted containers.
257
+ * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
258
+ */
259
+ function ui_bulk_delete_message($container_type, $n){
260
+ $manager = $this->get_manager($container_type);
261
+ if ( is_null($manager) ){
262
+ return sprintf(__("Container type '%s' not recognized", 'broken-link-checker'), $container_type);
263
+ } else {
264
+ return $manager->ui_bulk_delete_message($n);
265
+ }
266
+ }
267
+ }
268
+
269
+ //Init the container registry & make it global
270
+ $GLOBALS['blc_container_registry'] = new blcContainerRegistry();
271
+
272
+
273
+
274
+ /**
275
+ * The base class for link containers. All containers should extend this class.
276
+ *
277
+ * @package Broken Link Checker
278
+ * @access public
279
+ */
280
+ class blcContainer {
281
+
282
+ var $fields = array();
283
+ var $default_field = '';
284
+
285
+ var $container_type;
286
+ var $container_id = 0;
287
+
288
+ var $synched = false;
289
+ var $last_synch = '0000-00-00 00:00:00';
290
+
291
+ var $wrapped_object = null;
292
+
293
+ /**
294
+ * Constructor
295
+ *
296
+ * @param array $data
297
+ * @param object $wrapped_object
298
+ * @return void
299
+ */
300
+ function __construct( $data = null, $wrapped_object = null ){
301
+ $this->wrapped_object = $wrapped_object;
302
+ if ( !empty($data) && is_array($data) ){
303
+ foreach($data as $name => $value){
304
+ $this->$name = $value;
305
+ }
306
+ }
307
+ }
308
+
309
+ /**
310
+ * blcContainer::blcContainer()
311
+ * Old style class constructor
312
+ *
313
+ * @return void
314
+ */
315
+ function blcContainer( $data = null, $wrapped_object = null ){
316
+ $this->__construct($data, $wrapped_object);
317
+ }
318
+
319
+ /**
320
+ * Get the value of the specified field of the object wrapped by this container.
321
+ *
322
+ * @access protected
323
+ *
324
+ * @param string $field Field name. If omitted, the value of the default field will be returned.
325
+ * @return string
326
+ */
327
+ function get_field($field = ''){
328
+ if ( empty($field) ){
329
+ $field = $this->default_field;
330
+ }
331
+
332
+ $w = $this->get_wrapped_object();
333
+ return $w->$field;
334
+ }
335
+
336
+ /**
337
+ * Update the value of the specified field in the wrapped object.
338
+ * This method will also immediately save the changed value by calling update_wrapped_object().
339
+ *
340
+ * @access protected
341
+ *
342
+ * @param string $field Field name.
343
+ * @param string $new_value Set the field to this value.
344
+ * @param string $old_value The previous value of the field. Optional, but can be useful for container that need the old value to distinguish between several instances of the same field (e.g. post metadata).
345
+ * @return bool|WP_Error True on success, an error object if something went wrong.
346
+ */
347
+ function update_field($field, $new_value, $old_value = ''){
348
+ $w = $this->get_wrapped_object();
349
+ $w->$field = $new_value;
350
+ return $this->update_wrapped_object();
351
+ }
352
+
353
+ /**
354
+ * Retrieve the entity wrapped by this container.
355
+ * The fetched object will also be cached in the $wrapped_object variable.
356
+ *
357
+ * @access protected
358
+ *
359
+ * @param bool $ensure_consistency Set this to true to ignore the cached $wrapped_object value and retrieve an up-to-date copy of the wrapped object from the DB (or WP's internal cache).
360
+ * @return object The wrapped object.
361
+ */
362
+ function get_wrapped_object($ensure_consistency = false){
363
+ trigger_error('Function blcContainer::get_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
364
+ }
365
+
366
+ /**
367
+ * Update the entity wrapped by the container with values currently in the $wrapped_object.
368
+ *
369
+ * @access protected
370
+ *
371
+ * @return bool|WP_Error True on success, an error if something went wrong.
372
+ */
373
+ function update_wrapped_object(){
374
+ trigger_error('Function blcContainer::update_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
375
+ }
376
+
377
+ /**
378
+ * Parse the container for links and save the results to the DB.
379
+ *
380
+ * @return void
381
+ */
382
+ function synch(){
383
+ //FB::log("Parsing {$this->container_type}[{$this->container_id}]");
384
+
385
+ //Remove any existing link instance records associated with the container
386
+ $this->delete_instances();
387
+
388
+ //Load the wrapped object, if not done already
389
+ $this->get_wrapped_object();
390
+
391
+ //FB::log($this->fields, "Parseable fields :");
392
+
393
+ //Iterate over all parse-able fields
394
+ foreach($this->fields as $name => $format){
395
+ //Get the field value
396
+ $value = $this->get_field($name);
397
+ if ( empty($value) ){
398
+ //FB::log($name, "Skipping empty field");
399
+ continue;
400
+ }
401
+ //FB::log($name, "Parsing field");
402
+
403
+ //Get all parsers applicable to this field
404
+ $parsers = blc_get_parsers( $format, $this->container_type );
405
+ //FB::log($parsers, "Applicable parsers");
406
+
407
+ if ( empty($parsers) ) continue;
408
+
409
+ $base_url = $this->base_url();
410
+ $default_link_text = $this->default_link_text($name);
411
+
412
+ //Parse the field with each parser
413
+ foreach($parsers as $parser){
414
+ //FB::log("Parsing $name with '{$parser->parser_type}' parser");
415
+ $found_instances = $parser->parse( $value, $base_url, $default_link_text );
416
+ //FB::log($found_instances, "Found instances");
417
+
418
+ //Complete the link instances by adding container info, then save them to the DB.
419
+ foreach($found_instances as $instance){
420
+ $instance->set_container($this, $name);
421
+ $instance->save();
422
+ }
423
+ }
424
+ }
425
+
426
+ $this->mark_as_synched();
427
+ }
428
+
429
+ /**
430
+ * Mark the container as successfully synchronized (parsed for links).
431
+ *
432
+ * @return bool
433
+ */
434
+ function mark_as_synched(){
435
+ global $wpdb;
436
+
437
+ $this->last_synch = time();
438
+
439
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch( container_id, container_type, synched, last_synch)
440
+ VALUES( %d, %s, %d, NOW() )
441
+ ON DUPLICATE KEY UPDATE synched = VALUES(synched), last_synch = VALUES(last_synch)";
442
+ $rez = $wpdb->query( $wpdb->prepare( $q, $this->container_id, $this->container_type, 1 ) );
443
+
444
+ return ($rez !== false);
445
+ }
446
+
447
+ /**
448
+ * blcContainer::mark_as_unsynched()
449
+ * Mark the container as not synchronized (not parsed, or modified since the last parse).
450
+ * The plugin will attempt to (re)parse the container at the earliest opportunity.
451
+ *
452
+ * @return bool
453
+ */
454
+ function mark_as_unsynched(){
455
+ global $wpdb;
456
+
457
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch( container_id, container_type, synched, last_synch)
458
+ VALUES( %d, %s, %d, '0000-00-00 00:00:00' )
459
+ ON DUPLICATE KEY UPDATE synched = VALUES(synched)";
460
+ $rez = $wpdb->query( $wpdb->prepare( $q, $this->container_id, $this->container_type, 0 ) );
461
+
462
+ blc_got_unsynched_items();
463
+
464
+ return ($rez !== false);
465
+ }
466
+
467
+ /**
468
+ * Get the base URL of the container. Used to normalize relative URLs found
469
+ * in the container. For example, for posts this would be the post permalink.
470
+ *
471
+ * @return string
472
+ */
473
+ function base_url(){
474
+ return get_option('siteurl');
475
+ }
476
+
477
+ /**
478
+ * Get the default link text to use for links found in a specific container field.
479
+ *
480
+ * This is generally only meaningful for non-HTML container fields.
481
+ * For example, if the container is post metadata, the default
482
+ * link text might be equal to the name of the custom field.
483
+ *
484
+ * @param string $field
485
+ * @return string
486
+ */
487
+ function default_link_text($field = ''){
488
+ return '';
489
+ }
490
+
491
+
492
+
493
+ /**
494
+ * Delete the DB record of this container.
495
+ * Also deletes the DB records of all link instances associated with it.
496
+ * Calling this method will not affect the WP entity (e.g. a post) corresponding to this container.
497
+ *
498
+ * @return bool
499
+ */
500
+ function delete(){
501
+ global $wpdb;
502
+
503
+ //Delete instances first.
504
+ $rez = $this->delete_instances();
505
+ if ( !$rez ){
506
+ return false;
507
+ }
508
+
509
+ //Now delete the container record.
510
+ $q = "DELETE FROM {$wpdb->prefix}blc_synch
511
+ WHERE container_id = %d AND container_type = %s";
512
+ $q = $wpdb->prepare($q, $this->container_id, $this->container_type);
513
+
514
+ if ( $wpdb->query( $q ) === false ){
515
+ return false;
516
+ } else {
517
+ return true;
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Delete all link instance records associated with this container.
523
+ * NB: Calling this mehtod will not affect the WP entity (e.g. a post) corresponding to this container.
524
+ *
525
+ * @return bool
526
+ */
527
+ function delete_instances(){
528
+ global $wpdb;
529
+
530
+ //Remove instances associated with this container
531
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
532
+ WHERE container_id = %d AND container_type = %s";
533
+ $q = $wpdb->prepare($q, $this->container_id, $this->container_type);
534
+
535
+ if ( $wpdb->query( $q ) === false ){
536
+ return false;
537
+ } else {
538
+ return true;
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Delete the WP entity corresponding to this container.
544
+ * Also remove the synch. record of the container and all associated instances.
545
+ *
546
+ * Must be over-ridden in a sub-class.
547
+ *
548
+ * @return bool|WP_Error
549
+ */
550
+ function delete_wrapped_object(){
551
+ trigger_error('Function blcContainer::delete_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
552
+ }
553
+
554
+
555
+ /**
556
+ * Change all links with the specified URL to a new URL.
557
+ *
558
+ * @param string $field_name
559
+ * @param blcParser $parser
560
+ * @param string $new_url
561
+ * @param string $old_url
562
+ * @param string $old_raw_url
563
+ * @return string|WP_Error The new value of raw_url on success, or an error object if something went wrong.
564
+ */
565
+ function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = ''){
566
+ //Ensure we're operating on a consistent copy of the wrapped object.
567
+ /*
568
+ Explanation
569
+
570
+ Consider this scenario where the container object wraps a blog post :
571
+ 1) The container object gets created and loads the post data.
572
+ 2) Someone modifies the DB data corresponding to the post.
573
+ 3) The container tries to edit a link present in the post. However, the pots
574
+ has changed since the time it was first cached, so when the container updates
575
+ the post with it's changes, it will overwrite whatever modifications were made
576
+ in step 2.
577
+
578
+ This would not be a problem if WP entities like posts and comments were
579
+ actually real objects, not just bags of key=>value pairs, but oh well.
580
+
581
+ Therefore, it is necessary to re-load the wrapped object before editing it.
582
+ */
583
+ $this->get_wrapped_object(true);
584
+
585
+ //Get the current value of the field that needs to be edited.
586
+ $old_value = $this->get_field($field_name);
587
+
588
+ //Have the parser modify the specified link. If successful, the parser will
589
+ //return an associative array with two keys - 'content' and 'raw_url'.
590
+ //Otherwise we'll get an instance of WP_Error.
591
+ $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
592
+ if ( is_wp_error($edit_result) ){
593
+ return $edit_result;
594
+ }
595
+
596
+ //Update the field with the new value returned by the parser.
597
+ $update_result = $this->update_field( $field_name, $edit_result['content'], $old_value );
598
+ if ( is_wp_error($update_result) ){
599
+ return $update_result;
600
+ }
601
+
602
+ //Return the new "raw" URL.
603
+ return $edit_result['raw_url'];
604
+ }
605
+
606
+ /**
607
+ * Remove all links with the specified URL, leaving their anchor text intact.
608
+ *
609
+ * @param string $field_name
610
+ * @param blcParser $parser_type
611
+ * @param string $url
612
+ * @param string $raw_url
613
+ * @return bool|WP_Error True on success, or an error object if something went wrong.
614
+ */
615
+ function unlink($field_name, $parser, $url, $raw_url =''){
616
+ //Ensure we're operating on a consistent copy of the wrapped object.
617
+ $this->get_wrapped_object(true);
618
+
619
+ $old_value = $this->get_field($field_name);
620
+
621
+ $new_value = $parser->unlink($old_value, $url, $raw_url);
622
+ if ( is_wp_error($new_value) ){
623
+ return $new_value;
624
+ }
625
+
626
+ return $this->update_field( $field_name, $new_value, $old_value );
627
+ }
628
+
629
+ /**
630
+ * Retrieve a list of links found in this container.
631
+ *
632
+ * @access public
633
+ *
634
+ * @return array of blcLink
635
+ */
636
+ function get_links(){
637
+ $params = array(
638
+ 's_container_type' => $this->container_type,
639
+ 's_container_id' => $this->container_id,
640
+ );
641
+ return blc_get_links($params);
642
+ }
643
+
644
+
645
+ /**
646
+ * Get action links to display in the "Source" column of the Tools -> Broken Links link table.
647
+ *
648
+ * @param string $container_field
649
+ * @return array
650
+ */
651
+ function ui_get_action_links($container_field){
652
+ return array();
653
+ }
654
+
655
+ /**
656
+ * Get the container name to display in the "Source" column of the Tools -> Broken Links link table.
657
+ *
658
+ * @param string $container_field
659
+ * @param string $context
660
+ * @return string
661
+ */
662
+ function ui_get_source($container_field, $context = 'display'){
663
+ return sprintf('%s[%d] : %s', $this->container_type, $this->container_id, $container_field);
664
+ }
665
+
666
+ /**
667
+ * Get edit URL. Returns the URL of the Dashboard page where the item associated with this
668
+ * container can be edited.
669
+ *
670
+ * HTML entities like '&' will be properly escaped for display.
671
+ *
672
+ * @access protected
673
+ *
674
+ * @return string
675
+ */
676
+ function get_edit_url(){
677
+ //Should be over-ridden in a sub-class.
678
+ return '';
679
+ }
680
+ }
681
+
682
+ /**
683
+ * The base class for link container manager.
684
+ *
685
+ * Sub-classes should override at least the get_containers() and resynch() methods.
686
+ *
687
+ * @package Broken Link Checker
688
+ * @access public
689
+ */
690
+ class blcContainerManager {
691
+
692
+ var $container_type = '';
693
+ var $container_class_name = 'blcContainer';
694
+
695
+ function __construct($container_type){
696
+ $this->container_type = $container_type;
697
+ $this->init();
698
+ }
699
+
700
+ /**
701
+ * blcContainerManager::blcContainerManager()
702
+ * Old-style class constructor
703
+ *
704
+ * @return void
705
+ */
706
+ function blcContainerManager($container_type){
707
+ $this->__construct($container_type);
708
+ }
709
+
710
+ /**
711
+ * Do whatever setup necessary that wasn't already done in the constructor.
712
+ *
713
+ * This method was added so that sub-classes would have something "safe" to
714
+ * over-ride without having to deal with PHP4/5 constructors and remember to
715
+ * call the parent constructor.
716
+ *
717
+ * @return void
718
+ */
719
+ function init(){
720
+ //Do nothing. Sub-classes might use it to set up hooks, etc.
721
+ }
722
+
723
+ /**
724
+ * Instantiate a link container.
725
+ *
726
+ * @param array $container An associative array of container data.
727
+ * @return blcContainer
728
+ */
729
+ function get_container($container){
730
+ return new $this->container_class_name($container);
731
+ }
732
+
733
+ /**
734
+ * Instantiate multiple containers of the container type managed by this class and optionally
735
+ * pre-load container data used for display/parsing.
736
+ *
737
+ * Sub-classes should, if possible, use the $purpose argument to pre-load any extra data required for
738
+ * the specified task right away, instead of making multiple DB roundtrips later. For example, if
739
+ * $purpose is set to the BLC_FOR_DISPLAY constant, you might want to preload any DB data that the
740
+ * container will need in blcContainer::ui_get_source().
741
+ *
742
+ * @see blcContainer::make_containers()
743
+ * @see blcContainer::ui_get_source()
744
+ * @see blcContainer::ui_get_action_links()
745
+ *
746
+ * @param array $containers Array of assoc. arrays containing container data.
747
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
748
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
749
+ *
750
+ * @return array of blcContainer indexed by "container_type|container_id"
751
+ */
752
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
753
+ return $this->make_containers($containers);
754
+ }
755
+
756
+ /**
757
+ * Instantiate multiple containers of the container type managed by this class
758
+ *
759
+ * @param array $containers Array of assoc. arrays containing container data.
760
+ * @return array of blcContainer indexed by "container_type|container_id"
761
+ */
762
+ function make_containers($containers){
763
+ $results = array();
764
+ foreach($containers as $container){
765
+ $key = $container['container_type'] . '|' . $container['container_id'];
766
+ $results[ $key ] = $this->get_container($container);
767
+ }
768
+ return $results;
769
+ }
770
+
771
+ /**
772
+ * Create or update synchronization records for all containers managed by this class.
773
+ *
774
+ * Must be over-ridden in subclasses.
775
+ *
776
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
777
+ * @return void
778
+ */
779
+ function resynch($forced = false){
780
+ trigger_error('Function blcContainerManager::resynch() must be over-ridden in a sub-class', E_USER_ERROR);
781
+ }
782
+
783
+ /**
784
+ * Get the message to display after $n containers have been deleted.
785
+ *
786
+ * @param int $n Number of deleted containers.
787
+ * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
788
+ */
789
+ function ui_bulk_delete_message($n){
790
+ return sprintf(
791
+ _n(
792
+ "%d '%s' has been deleted",
793
+ "%d '%s' have been deleted",
794
+ $n,
795
+ 'broken-link-checker'
796
+ ),
797
+ $n,
798
+ $this->container_type
799
+ );
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Register a new link container.
805
+ *
806
+ * Each container type is implemented by two related classes - the container manager class, and the
807
+ * container class itself. The container manager handles tasks like creating new instances of the container
808
+ * class, loading & filtering them to the database, handling WordPress hooks relevant to the container
809
+ * type, and so on. The container class handles the synchronization, parsing and modification of
810
+ * individual link containers (e.g. posts or comments).
811
+ *
812
+ * @see blcContainer
813
+ * @see blcContainerManager
814
+ *
815
+ * @param string $container_type A unique string identifying the container, e.g. "post" or "comment".
816
+ * @param string $manager_class Class name of the container manager corresponding to the container type you want to register.
817
+ * @return bool True if the container was successfully registered, false otherwise.
818
+ */
819
+ function blc_register_container( $container_type, $manager_class ){
820
+ global $blc_container_registry;
821
+ return $blc_container_registry->register_container($container_type, $manager_class);
822
+ }
823
+
824
+ /**
825
+ * Retrieve or create a link container.
826
+ *
827
+ * @param array $container Either (container type, id), or an assoc. array of container data.
828
+ * @return blcContainer|null Returns null if the container type is unrecognized.
829
+ */
830
+ function blc_get_container($container){
831
+ global $blc_container_registry;
832
+ return $blc_container_registry->get_container($container);
833
+ }
834
+
835
+ /**
836
+ * Retrieve multiple link containers.
837
+ *
838
+ * If you specify the optional $purpose argument, the relevant container managers might be
839
+ * able to use it to (pre)load container data more efficiently - e.g. loading all posts associated
840
+ * with the selected batch of containers in one go, instead of running a separate DB query for
841
+ * each individual container later.
842
+ *
843
+ * There are several predefined constants that can be used for the $purpose argument :
844
+ * BLC_FOR_EDITING, BLC_FOR_PARSING, BLC_FOR_DISPLAY
845
+ *
846
+ * For containers not found in the DB, the behaviour of this function depends on the format
847
+ * of the $containers argument. If $containers is an array of [container_type, container_id] pairs,
848
+ * only existing containers will be returned. If it's an array of assoc. arrays specifying container
849
+ * data, the function will create and return container objects for all specified containers.
850
+ *
851
+ * @see blc_get_container()
852
+ *
853
+ * @param array $containers Array of (container_type, id) pairs, or assoc. arrays with container data.
854
+ * @param string $purpose Optional code indicating how the retrieved containers are going to be used.
855
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
856
+ * @return array of blcContainer indexed by 'container_type|container_id'
857
+ */
858
+ function blc_get_containers( $containers, $purpose = '', $load_wrapped_objects = false ){
859
+ global $blc_container_registry;
860
+ return $blc_container_registry->get_containers($containers, $purpose, '', $load_wrapped_objects);
861
+ }
862
+
863
+ /**
864
+ * Retrieve link containers that need to be synchronized (parsed).
865
+ *
866
+ * @param integer $max_results The maximum number of containers to return. Defaults to returning all unsynched containers.
867
+ * @return array of blcContainer
868
+ */
869
+ function blc_get_unsynched_containers($max_results = 0){
870
+ global $blc_container_registry;
871
+ return $blc_container_registry->get_unsynched_containers($max_results);
872
+ }
873
+
874
+ /**
875
+ * (Re)create and update synchronization records for all supported containers.
876
+ * Calls the resynch() method of all registered managers.
877
+ *
878
+ * @param bool $forced If true, assume that no synch. records exist and build all of them from scratch.
879
+ * @return void
880
+ */
881
+ function blc_resynch_containers($forced = false){
882
+ global $blc_container_registry;
883
+ $blc_container_registry->resynch($forced);
884
+ }
885
+
886
+ ?>
includes/containers/blogroll.php ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class blcBookmark extends blcContainer{
4
+
5
+ var $fields = array('link_url' => 'url_field');
6
+
7
+ function ui_get_source($container_field, $context = 'display'){
8
+ $bookmark = $this->get_wrapped_object();
9
+
10
+ $image = sprintf(
11
+ '<img src="%s/broken-link-checker/images/link.png" class="blc-small-image" title="%2$s" alt="%2$s">',
12
+ WP_PLUGIN_URL,
13
+ __('Bookmark', 'broken-link-checker')
14
+ );
15
+
16
+ $link_name = sprintf(
17
+ '<a class="row-title" href="%s" title="%s">%s</a>',
18
+ $this->get_edit_url(),
19
+ __('Edit this bookmark', 'broken-link-checker'),
20
+ sanitize_bookmark_field('link_name', $bookmark->link_name, $this->container_id, 'display')
21
+ );
22
+
23
+ if ( $context != 'email' ){
24
+ return "$image $link_name";
25
+ } else {
26
+ return $link_name;
27
+ }
28
+ }
29
+
30
+ function ui_get_action_links($container_field){
31
+ //Inline action links for bookmarks
32
+ $bookmark = $this->get_wrapped_object();
33
+
34
+ $delete_url = admin_url( wp_nonce_url("link.php?action=delete&link_id={$this->container_id}", 'delete-bookmark_' . $this->container_id) );
35
+
36
+ $actions = array();
37
+ if ( current_user_can('manage_links') ) {
38
+ $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . attribute_escape(__('Edit this bookmark', 'broken-link-checker')) . '">' . __('Edit') . '</a>';
39
+ $actions['delete'] = "<span class='delete'><a class='submitdelete' href='" . esc_url($delete_url) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete this link '%s'\n 'Cancel' to stop, 'OK' to delete."), $bookmark->link_name)) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
40
+ }
41
+
42
+ return $actions;
43
+ }
44
+
45
+ function get_edit_url(){
46
+ return esc_url(admin_url("link.php?action=edit&link_id={$this->container_id}"));
47
+ }
48
+
49
+ /**
50
+ * Retrieve the bookmark associated with this container.
51
+ *
52
+ * @access protected
53
+ *
54
+ * @param bool $ensure_consistency Set this to true to ignore the cached $wrapped_object value and retrieve an up-to-date copy of the wrapped object from the DB (or WP's internal cache).
55
+ * @return object Bookmark data.
56
+ */
57
+ function get_wrapped_object($ensure_consistency = false){
58
+ if( $ensure_consistency || is_null($this->wrapped_object) ){
59
+ $this->wrapped_object = get_bookmark($this->container_id);
60
+ }
61
+ return $this->wrapped_object;
62
+ }
63
+
64
+ /**
65
+ * Update the bookmark associated with this container.
66
+ *
67
+ * @access protected
68
+ *
69
+ * @return bool|WP_Error True on success, an error if something went wrong.
70
+ */
71
+ function update_wrapped_object(){
72
+ if ( is_null($this->wrapped_object) ){
73
+ return new WP_Error(
74
+ 'no_wrapped_object',
75
+ __('Nothing to update', 'broken-link-checker')
76
+ );
77
+ }
78
+
79
+ //wp_update_link() expects it's argument to be an array.
80
+ $data = (array)$this->wrapped_object;
81
+ //Update the bookmark
82
+ $rez = wp_update_link($data);
83
+
84
+ if ( !empty($rez) ){
85
+ return true;
86
+ } else {
87
+ return new WP_Error(
88
+ 'update_failed',
89
+ sprintf(__('Updating bookmark %d failed', 'broken-link-checker'), $this->container_id)
90
+ );
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Delete the bookmark corresponding to this container.
96
+ * Also removes the synch. record of the container and removes all associated instances.
97
+ *
98
+ * @return bool|WP_error
99
+ */
100
+ function delete_wrapped_object(){
101
+ if ( wp_delete_link($this->container_id) ){
102
+ //Note that there is no need to explicitly delete the synch. record and instances
103
+ //associated with this link - wp_delete_link() will execute the 'delete_link' action,
104
+ //which will be noticed by blcBookmarkManager, which will then delete anything that needs
105
+ //to be deleted.
106
+
107
+ //But in case the (undocumented) behaviour of wp_delete_link() changes in a later WP version,
108
+ //add a call to $this->delete() here.
109
+ return true;
110
+ } else {
111
+ $bookmark = $this->get_wrapped_object();
112
+
113
+ if ( is_null($bookmark) ){
114
+ $link_name = "[nonexistent]";
115
+ } else {
116
+ $link_name = sanitize_bookmark_field('link_name', $bookmark->link_name, $this->container_id, 'display');
117
+ }
118
+
119
+ $msg = sprintf(
120
+ __('Failed to delete blogroll link "%s" (%d)', 'broken-link-checker'),
121
+ $link_name,
122
+ $this->container_id
123
+ );
124
+
125
+ return new WP_Error( 'delete_failed', $msg );
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Get the default link text. For bookmarks, this is the bookmark name.
131
+ *
132
+ * @param string $field
133
+ * @return string
134
+ */
135
+ function default_link_text($field = ''){
136
+ $bookmark = $this->get_wrapped_object();
137
+ return sanitize_bookmark_field('link_name', $bookmark->link_name, $this->container_id, 'display');
138
+ }
139
+
140
+ /**
141
+ * For bookmarks, calling unlink() simply removes the bookmark.
142
+ *
143
+ * @return bool|WP_Error True on success, or an error object if something went wrong.
144
+ */
145
+ function unlink($field_name, $parser, $url, $raw_url =''){
146
+ return $this->delete_wrapped_object();
147
+ }
148
+
149
+ }
150
+
151
+ class blcBookmarkManager extends blcContainerManager{
152
+ var $container_class_name = 'blcBookmark';
153
+
154
+ /**
155
+ * Set up hooks that monitor added/modified/deleted bookmarks.
156
+ *
157
+ * @return void
158
+ */
159
+ function init(){
160
+ add_action('add_link', array(&$this,'hook_add_link'));
161
+ add_action('edit_link', array(&$this,'hook_edit_link'));
162
+ add_action('delete_link', array(&$this,'hook_delete_link'));
163
+ }
164
+
165
+ /**
166
+ * Instantiate multiple containers of the container type managed by this class.
167
+ *
168
+ * @param array $containers Array of assoc. arrays containing container data.
169
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
170
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
171
+ *
172
+ * @return array of blcBookmark indexed by "container_type|container_id"
173
+ */
174
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
175
+ $containers = $this->make_containers($containers);
176
+
177
+ //Preload bookmark data if it is likely to be useful later
178
+ $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_PARSING));
179
+ if ( $preload ){
180
+ $bookmark_ids = array();
181
+ foreach($containers as $container){
182
+ $bookmark_ids[] = $container->container_id;
183
+ }
184
+
185
+ $args = array('include' => implode(',', $bookmark_ids));
186
+ $bookmarks = get_bookmarks($args);
187
+
188
+ foreach($bookmarks as $bookmark){
189
+ $key = $this->container_type . '|' . $bookmark->link_id;
190
+ if ( isset($containers[$key]) ){
191
+ $containers[$key]->wrapped_object = $bookmark;
192
+ }
193
+ }
194
+ }
195
+
196
+ return $containers;
197
+ }
198
+
199
+ /**
200
+ * Create or update synchronization records for all posts.
201
+ *
202
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
203
+ * @return void
204
+ */
205
+ function resynch($forced = false){
206
+ global $wpdb;
207
+
208
+ if ( !$forced ){
209
+ //Usually the number of bookmarks is rather small, so it's cheap enough to always
210
+ //drop the entire list of bookmark-related synch records and recreate it from scratch.
211
+ $q = $wpdb->prepare(
212
+ "DELETE FROM {$wpdb->prefix}blc_synch WHERE container_type = %s",
213
+ $this->container_type
214
+ );
215
+ $wpdb->query( $q );
216
+ }
217
+
218
+ //Create new synchronization records for all bookmarks (AKA the blogroll).
219
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
220
+ SELECT link_id, %s, 0
221
+ FROM {$wpdb->links}
222
+ WHERE 1";
223
+ $q = $wpdb->prepare($q, $this->container_type);
224
+ $wpdb->query( $q );
225
+ }
226
+
227
+ /**
228
+ * When a bookmark is added mark it as unsynched.
229
+ *
230
+ * @param int $link_id
231
+ * @return void
232
+ */
233
+ function hook_add_link( $link_id ){
234
+ $container = blc_get_container( array($this->container_type, $link_id) );
235
+ $container->mark_as_unsynched();
236
+ }
237
+
238
+ /**
239
+ * Ditto for modified bookmarks.
240
+ *
241
+ * @param int $link_id
242
+ * @return void
243
+ */
244
+ function hook_edit_link( $link_id ){
245
+ $this->hook_add_link( $link_id );
246
+ }
247
+
248
+ /**
249
+ * When a bookmark is deleted, remove the related DB records.
250
+ *
251
+ * @param int $link_id
252
+ * @return void
253
+ */
254
+ function hook_delete_link( $link_id ){
255
+ //Get the container object.
256
+ $container = blc_get_container( array($this->container_type, $link_id) );
257
+ //Get the link(s) associated with it.
258
+ $links = $container->get_links();
259
+
260
+ //Remove synch. record & instance records.
261
+ $container->delete();
262
+
263
+ //Clean up links associated with this bookmark (there's probably only one)
264
+ $link_ids = array();
265
+ foreach($links as $link){
266
+ $link_ids[] = $link->link_id;
267
+ }
268
+ blc_cleanup_links($link_ids);
269
+ }
270
+
271
+ /**
272
+ * Get the message to display after $n bookmarks have been deleted.
273
+ *
274
+ * @param int $n Number of deleted bookmarks.
275
+ * @return string The delete confirmation message.
276
+ */
277
+ function ui_bulk_delete_message($n){
278
+ return sprintf(
279
+ _n(
280
+ "%d blogroll link deleted",
281
+ "%d blogroll links deleted",
282
+ $n,
283
+ 'broken-link-checker'
284
+ ),
285
+ $n
286
+ );
287
+ }
288
+ }
289
+
290
+ blc_register_container('blogroll', 'blcBookmarkManager');
291
+
292
+ ?>
includes/containers/comment.php ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class blcComment extends blcContainer{
4
+ var $fields = array(
5
+ 'comment_author_url' => 'url_field',
6
+ 'comment_content' => 'html',
7
+ );
8
+
9
+ /**
10
+ * Retrieve the comment wrapped by this container.
11
+ * The fetched object will also be cached in the $wrapped_object variable.
12
+ *
13
+ * @access protected
14
+ *
15
+ * @param bool $ensure_consistency
16
+ * @return object The comment.
17
+ */
18
+ function get_wrapped_object($ensure_consistency = false){
19
+ if( $ensure_consistency || is_null($this->wrapped_object) ){
20
+ $this->wrapped_object = get_comment($this->container_id);
21
+ }
22
+ return $this->wrapped_object;
23
+ }
24
+
25
+ /**
26
+ * Update the comment wrapped by the container with values currently in the $wrapped_object.
27
+ *
28
+ * @access protected
29
+ *
30
+ * @return bool|WP_Error True on success, an error if something went wrong.
31
+ */
32
+ function update_wrapped_object(){
33
+ if ( is_null($this->wrapped_object) ){
34
+ return new WP_Error(
35
+ 'no_wrapped_object',
36
+ __('Nothing to update', 'broken-link-checker')
37
+ );
38
+ }
39
+
40
+ $data = (array)$this->wrapped_object;
41
+ if ( wp_update_comment($data) ){
42
+ return true;
43
+ } else {
44
+ return new WP_Error(
45
+ 'update_failed',
46
+ sprintf(__('Updating comment %d failed', 'broken-link-checker'), $this->container_id)
47
+ );
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Delete the comment corresponding to this container.
53
+ * This will actually move the comment to the trash in newer versions of WP.
54
+ *
55
+ * @return bool|WP_error
56
+ */
57
+ function delete_wrapped_object(){
58
+ if ( wp_delete_comment($this->container_id) ){
59
+ return true;
60
+ } else {
61
+ return new WP_Error(
62
+ 'delete_failed',
63
+ sprintf(
64
+ __('Failed to delete comment %d', 'broken-link-checker'),
65
+ $this->container_id
66
+ )
67
+ );
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Get the default link text to use for links found in a specific container field.
73
+ * For links in the comment body there is no default link text. For author links,
74
+ * the link text will be equal to the author name + comment type (if any).
75
+ *
76
+ * @param string $field
77
+ * @return string
78
+ */
79
+ function default_link_text($field = ''){
80
+
81
+ if ( $field == 'comment_author_url' ){
82
+ $w = $this->get_wrapped_object();
83
+ if ( !is_null($w) ){
84
+ $text = $w->comment_author;
85
+
86
+ //This lets us identify pingbacks & trackbacks.
87
+ if ( !empty($w->comment_type) ){
88
+ $text .= sprintf(' [%s]', $w->comment_type);
89
+ }
90
+
91
+ return $text;
92
+ }
93
+ }
94
+
95
+ return '';
96
+ }
97
+
98
+ function ui_get_action_links($container_field){
99
+ $actions = array();
100
+
101
+ $comment = $this->get_wrapped_object();
102
+
103
+ //Display Edit & Delete/Trash links only if the user has the right caps.
104
+ $user_can = current_user_can('edit_post', $comment->comment_post_ID);
105
+ if ( $user_can ){
106
+ $actions['edit'] = "<a href='". $this->get_edit_url() ."' title='" . esc_attr__('Edit comment') . "'>". __('Edit') . '</a>';
107
+
108
+ $del_nonce = esc_html( '_wpnonce=' . wp_create_nonce( "delete-comment_$comment->comment_ID" ) );
109
+ $trash_url = esc_url( admin_url("comment.php?action=trashcomment&p=$post->ID&c=$comment->comment_ID&$del_nonce") );
110
+ $delete_url = esc_url( admin_url("comment.php?action=deletecomment&p=$post->ID&c=$comment->comment_ID&$del_nonce") );
111
+
112
+ if ( !EMPTY_TRASH_DAYS ) {
113
+ $actions['delete'] = "<a href='$delete_url' class='delete:the-comment-list:comment-$comment->comment_ID::delete=1 delete vim-d vim-destructive'>" . __('Delete Permanently') . '</a>';
114
+ } else {
115
+ $actions['trash'] = "<a href='$trash_url' class='delete:the-comment-list:comment-$comment->comment_ID::trash=1 delete vim-d vim-destructive' title='" . esc_attr__( 'Move this comment to the trash' ) . "'>" . _x('Trash', 'verb') . '</a>';
116
+ }
117
+ }
118
+
119
+ $actions['view'] = '<span class="view"><a href="' . get_comment_link($this->container_id) . '" title="' . attribute_escape(__('View comment', 'broken-link-checker')) . '" rel="permalink">' . __('View') . '</a>';
120
+
121
+ return $actions;
122
+ }
123
+
124
+ function ui_get_source($container_field, $context = 'display'){
125
+ //Display a comment icon.
126
+ if ( $container_field == 'comment_author_url' ){
127
+ $image = 'user_comment.png';
128
+ } else {
129
+ $image = 'comment.png';
130
+ }
131
+
132
+ $image = sprintf(
133
+ '<img src="%s/broken-link-checker/images/%s" class="blc-small-image" title="%3$s" alt="%3$s"> ',
134
+ WP_PLUGIN_URL,
135
+ $image,
136
+ __('Comment', 'broken-link-checker')
137
+ );
138
+
139
+ $comment = $this->get_wrapped_object();
140
+
141
+ //Display a small text sample from the comment
142
+ $text_sample = strip_tags($comment->comment_content);
143
+ $text_sample = blcUtility::truncate($text_sample, 65);
144
+
145
+ $html = sprintf(
146
+ '<a href="%s" title="%s"><b>%s</b> &mdash; %s</a>',
147
+ $this->get_edit_url(),
148
+ esc_attr__('Edit comment'),
149
+ esc_attr($comment->comment_author),
150
+ $text_sample
151
+ );
152
+
153
+ //Don't show the image in email notifications.
154
+ if ( $context != 'email' ){
155
+ $html = $image . $html;
156
+ }
157
+
158
+ return $html;
159
+ }
160
+
161
+ function get_edit_url(){
162
+ return esc_url(admin_url("comment.php?action=editcomment&c={$this->container_id}"));
163
+ }
164
+ }
165
+
166
+ class blcCommentManager extends blcContainerManager {
167
+ var $container_class_name = 'blcComment';
168
+
169
+ function init(){
170
+ add_action('edit_comment', array(&$this, 'hook_modified_comment'));
171
+ add_action('unspammed_comment', array(&$this, 'hook_modified_comment'));
172
+ add_action('untrashed_comment', array(&$this, 'hook_modified_comment'));
173
+
174
+ add_action('wp_insert_comment', array(&$this, 'hook_wp_insert_comment'), 10, 2);
175
+
176
+ add_action('deleted_comment', array(&$this, 'hook_deleted_comment'));
177
+ add_action('spammed_comment', array(&$this, 'hook_deleted_comment'));
178
+ add_action('trashed_comment', array(&$this, 'hook_deleted_comment'));
179
+
180
+ add_action('transition_comment_status', array(&$this, 'hook_comment_status'), 10, 3);
181
+ }
182
+
183
+ function hook_modified_comment($comment_id){
184
+ $comment = get_comment($comment_id);
185
+
186
+ if ( $comment->comment_approved == '1'){
187
+ $container = blc_get_container(array($this->container_type, $comment_id));
188
+ $container->mark_as_unsynched();
189
+ }
190
+ }
191
+
192
+ function hook_wp_insert_comment($comment_id, $comment){
193
+ if ( $comment->comment_approved == '1'){
194
+ $container = blc_get_container(array($this->container_type, $comment_id));
195
+ $container->mark_as_unsynched();
196
+ }
197
+ }
198
+
199
+ function hook_deleted_comment($comment_id){
200
+ $container = blc_get_container(array($this->container_type, $comment_id));
201
+ $container->delete();
202
+ //Clean up any dangling links
203
+ blc_cleanup_links();
204
+ }
205
+
206
+ function hook_comment_status($new_status, $old_status, $comment){
207
+ $container = blc_get_container(array($this->container_type, $comment->comment_ID));
208
+ if ( $new_status == 'approved' ){
209
+ $container->mark_as_unsynched();
210
+ } else {
211
+ $container->delete();
212
+ blc_cleanup_links();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Create or update synchronization records for all comments.
218
+ *
219
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
220
+ * @return void
221
+ */
222
+ function resynch($forced = false){
223
+ global $wpdb;
224
+
225
+ if ( $forced ){
226
+ //Create new synchronization records for all comments.
227
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
228
+ SELECT comment_ID, '{$this->container_type}', 0
229
+ FROM {$wpdb->comments}
230
+ WHERE
231
+ {$wpdb->comments}.comment_approved = '1'";
232
+ $wpdb->query( $q );
233
+ } else {
234
+ //Delete synch records corresponding to comments that no longer exist.
235
+ $q = "DELETE synch.*
236
+ FROM
237
+ {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->comments} AS comments
238
+ ON comments.ID = synch.container_id
239
+ WHERE
240
+ synch.container_type = '{$this->container_type}' AND comments.comment_ID IS NULL";
241
+ $wpdb->query( $q );
242
+
243
+ //Create synch. records for comments that don't have them.
244
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
245
+ SELECT comment_ID, '{$this->container_type}', 0
246
+ FROM
247
+ {$wpdb->comments} AS comments LEFT JOIN {$wpdb->prefix}blc_synch AS synch
248
+ ON (synch.container_id = comments.comment_ID and synch.container_type='{$this->container_type}')
249
+ WHERE
250
+ comments.comment_approved = '1'
251
+ AND synch.container_id IS NULL";
252
+ $wpdb->query($q);
253
+
254
+ /*
255
+ Note that there is no way to detect comments that were *edited* (not added - those
256
+ will be caught by the above query) while the plugin was inactive. Unlike with posts,
257
+ WP doesn't track comment modification times.
258
+ */
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Get the message to display after $n comments have been deleted.
264
+ *
265
+ * @param int $n Number of deleted comments.
266
+ * @return string A delete confirmation message, e.g. "5 comments were moved to trash"
267
+ */
268
+ function ui_bulk_delete_message($n){
269
+ if ( EMPTY_TRASH_DAYS ){
270
+ return sprintf(
271
+ _n(
272
+ "%d comment moved to the trash",
273
+ "%d comments moved to the trash",
274
+ $n,
275
+ 'broken-link-checker'
276
+ ),
277
+ $n
278
+ );
279
+ } else {
280
+ return sprintf(
281
+ _n(
282
+ "%d comment has been deleted",
283
+ "%d comments have been deleted",
284
+ $n,
285
+ 'broken-link-checker'
286
+ ),
287
+ $n
288
+ );
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Instantiate multiple containers of the container type managed by this class.
294
+ *
295
+ * @param array $containers Array of assoc. arrays containing container data.
296
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
297
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
298
+ *
299
+ * @return array of blcPostContainer indexed by "container_type|container_id"
300
+ */
301
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
302
+ global $wpdb;
303
+
304
+ $containers = $this->make_containers($containers);
305
+
306
+ //Preload comment data if it is likely to be useful later
307
+ $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_PARSING));
308
+ if ( $preload ){
309
+ $comment_ids = array();
310
+ foreach($containers as $container){
311
+ $comment_ids[] = $container->container_id;
312
+ }
313
+
314
+ //There's no WP function for retrieving multiple comments by their IDs,
315
+ //so we query the DB directly.
316
+ $q = "SELECT * FROM {$wpdb->comments} WHERE comment_ID IN (" . implode(', ', $comment_ids) . ")";
317
+ $comments = $wpdb->get_results($q);
318
+
319
+ foreach($comments as $comment){
320
+ //Cache the comment in the internal WP object cache
321
+ $comment = get_comment($comment);
322
+
323
+ //Attach it to the container
324
+ $key = $this->container_type . '|' . $post->ID;
325
+ if ( isset($containers[$key]) ){
326
+ $containers[$key]->wrapped_object = $comment;
327
+ }
328
+ }
329
+ }
330
+
331
+ return $containers;
332
+ }
333
+ }
334
+
335
+ blc_register_container('comment', 'blcCommentManager');
336
+ ?>
includes/containers/custom_field.php ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //Note : If it ever becomes necessary to check metadata on objects other than posts, it will
4
+ //be fairly easy to extract a more general metadata container class from blcPostMeta.
5
+
6
+ /**
7
+ * blcPostMeta - A link container class for post metadata (AKA custom fields).
8
+ *
9
+ * Due to the way metadata works, this container differs significantly from other containers :
10
+ * - container_field is equal to meta name, and container_id holds the ID of the post.
11
+ * - There is one synch. record per post that determines the synch. state of all metadata fields of that post.
12
+ * - Unlinking simply deletes the meta entry in question without involving the parser.
13
+ * - The list of parse-able $fields is not fixed. Instead, it's initialized based on the
14
+ * custom field list defined in Settings -> Link Checker.
15
+ * - The $wrapped_object is an array (and isn't really used for anything).
16
+ * - update_wrapped_object() does nothing.
17
+ *
18
+ * @package Broken Link Checker
19
+ * @access public
20
+ */
21
+ class blcPostMeta extends blcContainer {
22
+
23
+ var $meta_type = 'post';
24
+
25
+ /**
26
+ * Retrieve all metadata fields of the post associated with this container.
27
+ * The results are cached in the internal $wrapped_object variable.
28
+ *
29
+ * @param bool $ensure_consistency
30
+ * @return object The wrapped object.
31
+ */
32
+ function get_wrapped_object($ensure_consistency = false){
33
+ if ( is_null($this->wrapped_object) || $ensure_consistency ) {
34
+ $meta = get_metadata($this->meta_type, $this->container_id);
35
+ }
36
+ return $this->wrapped_object;
37
+ }
38
+
39
+ function update_wrapped_object(){
40
+ trigger_error('Function blcPostMeta::update_wrapped_object() does nothing and should not be used.', E_USER_WARNING);
41
+ }
42
+
43
+ /**
44
+ * Get the value of the specified metadata field of the object wrapped by this container.
45
+ *
46
+ * @access protected
47
+ *
48
+ * @param string $field Field name. If omitted, the value of the default field will be returned.
49
+ * @return array
50
+ */
51
+ function get_field($field = ''){
52
+ return get_metadata($this->meta_type, $this->container_id, $field);
53
+ }
54
+
55
+ /**
56
+ * Update the value of the specified metadata field of the object wrapped by this container.
57
+ *
58
+ * @access protected
59
+ *
60
+ * @param string $field Meta name.
61
+ * @param string $new_value New meta value.
62
+ * @param string $old_value old meta value.
63
+ * @return bool|WP_Error True on success, an error object if something went wrong.
64
+ */
65
+ function update_field($field, $new_value, $old_value = ''){
66
+ $rez = update_metadata($this->meta_type, $this->container_id, $field, $new_value, $old_value);
67
+ if ( $rez ){
68
+ return true;
69
+ } else {
70
+ return new WP_Error(
71
+ 'metadata_update_failed',
72
+ sprintf(
73
+ __("Failed to update the meta field '%s' on %s [%d]", 'broken-link-checker'),
74
+ $field,
75
+ $this->meta_type,
76
+ $this->container_id
77
+ )
78
+ );
79
+ }
80
+ }
81
+
82
+ /**
83
+ * "Unlink"-ing a custom fields removes all metadata fields that contain the specified URL.
84
+ *
85
+ * @param string $field_name
86
+ * @param blcParser $parser_type
87
+ * @param string $url
88
+ * @param string $raw_url
89
+ * @return bool|WP_Error True on success, or an error object if something went wrong.
90
+ */
91
+ function unlink($field_name, $parser, $url, $raw_url =''){
92
+ $rez = delete_metadata($this->meta_type, $this->container_id, $field_name, $raw_url);
93
+ if ( $rez ){
94
+ return true;
95
+ } else {
96
+ return new WP_Error(
97
+ 'metadata_delete_failed',
98
+ sprintf(
99
+ __("Failed to delete the meta field '%s' on %s [%d]", 'broken-link-checker'),
100
+ $field,
101
+ $this->meta_type,
102
+ $this->container_id
103
+ )
104
+ );
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Change a meta field containing the specified URL to a new URL.
110
+ *
111
+ * @param string $field_name Meta name
112
+ * @param blcParser $parser
113
+ * @param string $new_url New URL.
114
+ * @param string $old_url
115
+ * @param string $old_raw_url Old meta value.
116
+ * @return string|WP_Error The new value of raw_url on success, or an error object if something went wrong.
117
+ */
118
+ function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = ''){
119
+ /*
120
+ FB::log(sprintf(
121
+ 'Editing %s[%d]:%s - %s to %s',
122
+ $this->container_type,
123
+ $this->container_id,
124
+ $field_name,
125
+ $old_url,
126
+ $new_url
127
+ ));
128
+ */
129
+
130
+ if ( empty($old_raw_url) ){
131
+ $old_raw_url = $old_url;
132
+ }
133
+
134
+ //Get the current values of the field that needs to be edited.
135
+ //The default metadata parser ignores them, but we're still going
136
+ //to set this argument to a valid value in case someone writes a
137
+ //custom meta parser that needs it.
138
+ $old_value = $this->get_field($field_name);
139
+
140
+ //Get the new field value (a string).
141
+ $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
142
+ if ( is_wp_error($edit_result) ){
143
+ return $edit_result;
144
+ }
145
+
146
+ //Update the field with the new value returned by the parser.
147
+ //Notice how $old_raw_url is usead instead of $old_value. $old_raw_url contains the entire old
148
+ //value of the metadata field (see blcMetadataParser::parse()) and thus can be used to
149
+ //differentiate between multiple meta fields with identical names.
150
+ $update_result = $this->update_field( $field_name, $edit_result['content'], $old_raw_url );
151
+ if ( is_wp_error($update_result) ){
152
+ return $update_result;
153
+ }
154
+
155
+ //Return the new "raw" URL.
156
+ return $edit_result['raw_url'];
157
+ }
158
+
159
+ /**
160
+ * Get the default link text to use for links found in a specific container field.
161
+ *
162
+ * @param string $field
163
+ * @return string
164
+ */
165
+ function default_link_text($field = ''){
166
+ //Just use the field name. There's no way to know how the links inside custom fields are
167
+ //used, so no way to know the "real" link text. Displaying the field name at least gives
168
+ //the user a clue where to look if they want to find/modify the field.
169
+ return $field;
170
+ }
171
+
172
+ function ui_get_source($container_field, $context = 'display'){
173
+ $image_html = sprintf(
174
+ '<img src="%s/broken-link-checker/images/script_code.png" class="blc-small-image" title="%2$s" alt="%2$s"> ',
175
+ WP_PLUGIN_URL,
176
+ __('Custom field', 'broken-link-checker')
177
+ );
178
+
179
+ $field_html = sprintf(
180
+ '<code>%s</code>',
181
+ $container_field
182
+ );
183
+
184
+ if ( $context != 'email' ){
185
+ $field_html = $image_html . $field_html;
186
+ }
187
+
188
+ $post_html = sprintf(
189
+ '<a class="row-title" href="%s" title="%s">%s</a>',
190
+ esc_url($this->get_edit_url()),
191
+ attribute_escape(__('Edit this post')),
192
+ get_the_title($this->container_id)
193
+ );
194
+
195
+ return "$post_html &mdash; $field_html";
196
+ }
197
+
198
+ function ui_get_action_links($container_field){
199
+ $actions = array();
200
+ if ( current_user_can('edit_post', $this->container_id) ) {
201
+ $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
202
+
203
+ if ( EMPTY_TRASH_DAYS ) {
204
+ $actions['trash'] = "<a class='submitdelete' title='" . esc_attr(__('Move this post to the Trash')) . "' href='" . get_delete_post_link($this->container_id) . "'>" . __('Trash') . "</a>";
205
+ } else {
206
+ $actions['delete'] = "<a class='submitdelete' title='" . esc_attr(__('Delete this post permanently')) . "' href='" . wp_nonce_url( admin_url("post.php?action=delete&amp;post=".$this->container_id), 'delete-post_' . $this->container_id) . "' onclick=\"if ( confirm('" . js_escape(sprintf( __("You are about to delete this post '%s'\n 'Cancel' to stop, 'OK' to delete."), get_the_title($this->container_id) )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
207
+ }
208
+ }
209
+ $actions['view'] = '<span class="view"><a href="' . get_permalink($this->container_id) . '" title="' . attribute_escape(sprintf(__('View "%s"', 'broken-link-checker'), get_the_title($this->container_id))) . '" rel="permalink">' . __('View') . '</a>';
210
+
211
+ return $actions;
212
+ }
213
+
214
+ /**
215
+ * Get edit URL for this container. Returns the URL of the Dashboard page where the item
216
+ * associated with this container can be edited.
217
+ *
218
+ * @access protected
219
+ *
220
+ * @return
221
+ */
222
+ function get_edit_url(){
223
+ return get_edit_post_link($this->container_id);
224
+ }
225
+
226
+ /**
227
+ * Get the base URL of the container. For custom fields, the base URL is the permalink of
228
+ * the post that the field is attached to.
229
+ *
230
+ * @return string
231
+ */
232
+ function base_url(){
233
+ return get_permalink($this->container_id);
234
+ }
235
+
236
+ /**
237
+ * Delete the post corresponding to this container.
238
+ *
239
+ * @return bool|WP_error
240
+ */
241
+ function delete_wrapped_object(){
242
+ if ( wp_delete_post($this->container_id) ){
243
+ return true;
244
+ } else {
245
+ return new WP_Error(
246
+ 'delete_failed',
247
+ sprintf(
248
+ __('Failed to delete post "%s" (%d)', 'broken-link-checker'),
249
+ get_the_title($this->container_id),
250
+ $this->container_id
251
+ )
252
+ );
253
+ };
254
+ }
255
+
256
+ }
257
+
258
+ class blcPostMetaManager extends blcContainerManager {
259
+ var $container_class_name = 'blcPostMeta';
260
+ var $meta_type = 'post';
261
+
262
+ function init(){
263
+ //Intercept 2.9+ style metadata modification actions
264
+ add_action( "added_{$this->meta_type}_meta", array(&$this, 'meta_modified'), 10, 4 );
265
+ add_action( "updated_{$this->meta_type}_meta", array(&$this, 'meta_modified'), 10, 4 );
266
+ add_action( "deleted_{$this->meta_type}_meta", array(&$this, 'meta_modified'), 10, 4 );
267
+ //Also intercept the equivalent actions used in /wp-admin/includes/post.php.
268
+ //(WP is bloody inconsitent. The action names differ by a single character
269
+ //but have different argument counts)
270
+ add_action( "added_{$this->meta_type}meta", array(&$this, 'meta_modified'), 10, 4 );
271
+ add_action( "deleted_{$this->meta_type}meta", array(&$this, 'meta_modified'), 10, 1 );//NB : 1 argument!
272
+
273
+ //When a post is deleted, also delete the custom field container associated with it.
274
+ add_action('delete_post', array(&$this,'post_deleted'));
275
+ add_action('trash_post', array(&$this,'post_deleted'));
276
+
277
+ //Re-parse custom fields when a post is restored from trash
278
+ add_action('untrashed_post', array(&$this,'post_untrashed'));
279
+ }
280
+
281
+
282
+ /**
283
+ * Instantiate a link container.
284
+ *
285
+ * @param array $container An associative array of container data.
286
+ * @return blcPostMeta
287
+ */
288
+ function get_container($container){
289
+ $container = parent::get_container($container);
290
+
291
+ //Set up the parseable fields
292
+ $fields = array();
293
+
294
+ $conf = blc_get_configuration();
295
+ if ( is_array($conf->options['custom_fields']) ){
296
+ foreach($conf->options['custom_fields'] as $meta_name){
297
+ $fields[$meta_name] = 'metadata';
298
+ }
299
+ }
300
+
301
+ $container->fields = $fields;
302
+ return $container;
303
+ }
304
+
305
+ /**
306
+ * Instantiate multiple containers of the container type managed by this class.
307
+ *
308
+ * @param array $containers Array of assoc. arrays containing container data.
309
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
310
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
311
+ *
312
+ * @return array of blcPostMeta indexed by "container_type|container_id"
313
+ */
314
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
315
+ $containers = $this->make_containers($containers);
316
+
317
+ /*
318
+ When links from custom fields are displayed in Tools -> Broken Links,
319
+ each one also shows the title of the post that the custom field(s)
320
+ belong to. Thus it makes sense to pre-cache the posts beforehand - it's
321
+ faster to load them all at once than to make a separate query for each
322
+ one later.
323
+
324
+ So make a list of involved post IDs and load them.
325
+
326
+ Calling get_posts() will automatically populate the post cache, so we
327
+ don't need to actually store the results anywhere in the container object().
328
+ */
329
+ $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY));
330
+ if ( $preload ){
331
+ $post_ids = array();
332
+ foreach($containers as $container){
333
+ $post_ids[] = $container->container_id;
334
+ }
335
+
336
+ $args = array('include' => implode(',', $post_ids));
337
+ get_posts($args);
338
+ }
339
+
340
+ return $containers;
341
+ }
342
+
343
+ /**
344
+ * Create or update synchronization records for all containers managed by this class.
345
+ *
346
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
347
+ * @return void
348
+ */
349
+ function resynch($forced = false){
350
+ global $wpdb;
351
+
352
+ if ( $forced ){
353
+ //Create new synchronization records for all posts.
354
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
355
+ SELECT id, '{$this->container_type}', 0
356
+ FROM {$wpdb->posts}
357
+ WHERE
358
+ {$wpdb->posts}.post_status = 'publish'
359
+ AND {$wpdb->posts}.post_type IN ('post', 'page')";
360
+ $wpdb->query( $q );
361
+ } else {
362
+ //Delete synch records corresponding to posts that no longer exist.
363
+ $q = "DELETE synch.*
364
+ FROM
365
+ {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->posts} AS posts
366
+ ON posts.ID = synch.container_id
367
+ WHERE
368
+ synch.container_type = '{$this->container_type}' AND posts.ID IS NULL";
369
+ $wpdb->query( $q );
370
+
371
+ //Remove the 'synched' flag from all posts that have been updated
372
+ //since the last time they were parsed/synchronized.
373
+ $q = "UPDATE
374
+ {$wpdb->prefix}blc_synch AS synch
375
+ JOIN {$wpdb->posts} AS posts ON (synch.container_id = posts.ID and synch.container_type='{$this->container_type}')
376
+ SET
377
+ synched = 0
378
+ WHERE
379
+ synch.last_synch < posts.post_modified";
380
+ $wpdb->query( $q );
381
+
382
+ //Create synch. records for posts that don't have them.
383
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
384
+ SELECT id, '{$this->container_type}', 0
385
+ FROM
386
+ {$wpdb->posts} AS posts LEFT JOIN {$wpdb->prefix}blc_synch AS synch
387
+ ON (synch.container_id = posts.ID and synch.container_type='{$this->container_type}')
388
+ WHERE
389
+ posts.post_status = 'publish'
390
+ AND posts.post_type IN ('post', 'page')
391
+ AND synch.container_id IS NULL";
392
+ $wpdb->query($q);
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Mark custom fields as unsynched when they're modified or deleted.
398
+ *
399
+ * @param array|int $meta_id
400
+ * @param int $object_id
401
+ * @param string $meta_key
402
+ * @param string $meta_value
403
+ * @return void
404
+ */
405
+ function meta_modified($meta_id, $object_id = 0, $meta_key= '', $meta_value = ''){
406
+ global $wpdb;
407
+
408
+ //If object_id isn't specified then the hook was probably called from the
409
+ //stupidly inconsistent delete_meta() function in /wp-admin/includes/post.php.
410
+ if ( empty($object_id) ){
411
+ //We must manually retrieve object_id and meta_key from the DB.
412
+ if ( is_array($meta_id) ){
413
+ $meta_id = array_shift($meta_id);
414
+ }
415
+
416
+ $meta = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->postmeta WHERE meta_id = %d", $meta_id), ARRAY_A );
417
+ if ( empty($meta) ){
418
+ return;
419
+ }
420
+
421
+ $object_id = $meta['post_id'];
422
+ $meta_key = $meta['meta_key'];
423
+ }
424
+
425
+
426
+ //Metadata changes only matter to us if the modified key
427
+ //is one that the user wants checked.
428
+ $conf = blc_get_configuration();
429
+ if ( !is_array($conf->options['custom_fields']) ){
430
+ return;
431
+ }
432
+ if ( !in_array($meta_key, $conf->options['custom_fields']) ){
433
+ return;
434
+ }
435
+
436
+ $container = blc_get_container( array($this->container_type, intval($object_id)) );
437
+ $container->mark_as_unsynched();
438
+ }
439
+
440
+ /**
441
+ * Delete custom field synch. records when the post that they belong to is deleted.
442
+ *
443
+ * @param int $post_id
444
+ * @return void
445
+ */
446
+ function post_deleted($post_id){
447
+ //Get the associated container object
448
+ $container = blc_get_container( array($this->container_type, intval($post_id)) );
449
+ //Delete it
450
+ $container->delete();
451
+ //Clean up any dangling links
452
+ blc_cleanup_links();
453
+ }
454
+
455
+ /**
456
+ * When a post is restored, mark all of its custom fields as unparsed.
457
+ * Called via the 'untrashed_post' action.
458
+ *
459
+ * @param int $post_id
460
+ * @return void
461
+ */
462
+ function post_untrashed($post_id){
463
+ //Get the associated container object
464
+ $container = blc_get_container( array($this->container_type, intval($post_id)) );
465
+ $container->mark_as_unsynched();
466
+ }
467
+
468
+ /**
469
+ * Get the message to display after $n posts have been deleted.
470
+ *
471
+ * @see blcPostContainer::ui_bulk_delete_message()
472
+ *
473
+ * @param int $n Number of deleted posts.
474
+ * @return string A delete confirmation message, e.g. "5 posts were moved to the trash"
475
+ */
476
+ function ui_bulk_delete_message($n){
477
+ //This is identical
478
+ if ( function_exists('wp_trash_post') && EMPTY_TRASH_DAYS ){
479
+ $delete_msg = _n("%d post moved to the trash", "%d posts moved to the trash", $n, 'broken-link-checker');
480
+ } else {
481
+ $delete_msg = _n("%d post deleted", "%d posts deleted", $n, 'broken-link-checker');
482
+ }
483
+ return sprintf($delete_msg, $n);
484
+ }
485
+ }
486
+
487
+ blc_register_container('custom_field', 'blcPostMetaManager');
488
+
489
+ ?>
includes/containers/dummy.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A "dummy" container class that can be used as a fallback when the real container class can't be found.
5
+ *
6
+ *
7
+ * @package Broken Link Checker
8
+ * @access public
9
+ */
10
+ class blcDummyContainer extends blcContainer{
11
+
12
+ function synch(){
13
+ //Just mark it as synched so that it doesn't bother us anymore.
14
+ $this->mark_as_synched();
15
+ }
16
+
17
+ function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = ''){
18
+ return new WP_Error(
19
+ 'container_not_found',
20
+ sprintf(
21
+ __("I don't know how to edit a '%s' [%d].", 'broken-link-checker'),
22
+ $this->container_type,
23
+ $this->container_id
24
+ )
25
+ );
26
+ }
27
+
28
+ function unlink($field_name, $parser, $url, $raw_url =''){
29
+ return new WP_Error(
30
+ 'container_not_found',
31
+ sprintf(
32
+ __("I don't know how to edit a '%s' [%d].", 'broken-link-checker'),
33
+ $this->container_type,
34
+ $this->container_id
35
+ )
36
+ );
37
+ }
38
+
39
+ function ui_get_source($container_field, $context = 'display'){
40
+ return sprintf(
41
+ '<em>Unknown source %s[%d]:%s</em>',
42
+ $this->container_type,
43
+ $this->container_id,
44
+ $container_field
45
+ );
46
+ }
47
+ }
48
+
49
+ /**
50
+ * A dummy manager class.
51
+ *
52
+ * @package Broken Link Checker
53
+ * @access public
54
+ */
55
+ class blcDummyManager extends blcContainerManager {
56
+
57
+ var $container_class_name = 'blcDummyContainer';
58
+
59
+ function resynch($forced = false){
60
+ //Do nothing.
61
+ }
62
+ }
63
+
64
+ blc_register_container('dummy', 'blcDummyManager');
65
+
66
+ ?>
includes/containers/post.php ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class blcPostContainer extends blcContainer {
4
+ var $fields = array('post_content' => 'html');
5
+ var $default_field = 'post_content';
6
+
7
+ /**
8
+ * Get action links for this post.
9
+ *
10
+ * @param string $container_field Ignored.
11
+ * @return array of action link HTML.
12
+ */
13
+ function ui_get_action_links($container_field = ''){
14
+ $actions = array();
15
+ if ( current_user_can('edit_post', $this->container_id) ) {
16
+ $actions['edit'] = '<span class="edit"><a href="' . $this->get_edit_url() . '" title="' . attribute_escape(__('Edit this post')) . '">' . __('Edit') . '</a>';
17
+
18
+ if ( EMPTY_TRASH_DAYS ) {
19
+ $actions['trash'] = "<a class='submitdelete' title='" . esc_attr(__('Move this post to the Trash')) . "' href='" . get_delete_post_link($this->container_id) . "'>" . __('Trash') . "</a>";
20
+ } else {
21
+ $actions['delete'] = "<a class='submitdelete' title='" . esc_attr(__('Delete this post permanently')) . "' href='" . wp_nonce_url( admin_url("post.php?action=delete&amp;post=".$this->container_id), 'delete-post_' . $this->container_id) . "' onclick=\"if ( confirm('" . esc_js(sprintf( __("You are about to delete this post '%s'\n 'Cancel' to stop, 'OK' to delete."), get_the_title($this->container_id) )) . "') ) { return true;}return false;\">" . __('Delete') . "</a>";
22
+ }
23
+ }
24
+ $actions['view'] = '<span class="view"><a href="' . get_permalink($this->container_id) . '" title="' . attribute_escape(sprintf(__('View "%s"', 'broken-link-checker'), get_the_title($this->container_id))) . '" rel="permalink">' . __('View') . '</a>';
25
+
26
+ return $actions;
27
+ }
28
+
29
+ /**
30
+ * Get the HTML for displaying the post title in the "Source" column.
31
+ *
32
+ * @param string $container_field Ignored.
33
+ * @param string $context How to filter the output. Optional, defaults to 'display'.
34
+ * @return string HTML
35
+ */
36
+ function ui_get_source($container_field = '', $context = 'display'){
37
+ $source = '<a class="row-title" href="%s" title="%s">%s</a>';
38
+ $source = sprintf(
39
+ $source,
40
+ $this->get_edit_url(),
41
+ attribute_escape(__('Edit this post')),
42
+ get_the_title($this->container_id)
43
+ );
44
+
45
+ return $source;
46
+ }
47
+
48
+ /**
49
+ * Get edit URL for this container. Returns the URL of the Dashboard page where the item
50
+ * associated with this container can be edited.
51
+ *
52
+ * @access protected
53
+ *
54
+ * @return
55
+ */
56
+ function get_edit_url(){
57
+ return get_edit_post_link($this->container_id);
58
+ }
59
+
60
+ /**
61
+ * Retrieve the post associated with this container.
62
+ *
63
+ * @access protected
64
+ *
65
+ * @param bool $ensure_consistency Set this to true to ignore the cached $wrapped_object value and retrieve an up-to-date copy of the wrapped object from the DB (or WP's internal cache).
66
+ * @return object Post data.
67
+ */
68
+ function get_wrapped_object($ensure_consistency = false){
69
+ if( $ensure_consistency || is_null($this->wrapped_object) ){
70
+ $this->wrapped_object = get_post($this->container_id);
71
+ }
72
+ return $this->wrapped_object;
73
+ }
74
+
75
+ /**
76
+ * Update the post associated with this container.
77
+ *
78
+ * @access protected
79
+ *
80
+ * @return bool|WP_Error True on success, an error if something went wrong.
81
+ */
82
+ function update_wrapped_object(){
83
+ if ( is_null($this->wrapped_object) ){
84
+ return new WP_Error(
85
+ 'no_wrapped_object',
86
+ __('Nothing to update', 'broken-link-checker')
87
+ );
88
+ }
89
+
90
+ $id = wp_update_post($this->wrapped_object);
91
+ if ( $id != 0 ){
92
+ return true;
93
+ } else {
94
+ return new WP_Error(
95
+ 'update_failed',
96
+ sprintf(__('Updating post %d failed', 'broken-link-checker'), $this->container_id)
97
+ );
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get the base URL of the container. For posts, the post permalink is used
103
+ * as the base URL when normalizing relative links.
104
+ *
105
+ * @return string
106
+ */
107
+ function base_url(){
108
+ return get_permalink($this->container_id);
109
+ }
110
+
111
+ /**
112
+ * Delete the post corresponding to this container.
113
+ *
114
+ * @return bool|WP_error
115
+ */
116
+ function delete_wrapped_object(){
117
+ if ( wp_delete_post($this->container_id) ){
118
+ //Note that we don't need to delete the synch record and instances here -
119
+ //wp_delete_post() will run the post_delete hook, which will be caught
120
+ //by blcPostContainerManager, which will delete anything that needs to be
121
+ //deleted.
122
+ return true;
123
+ } else {
124
+ return new WP_Error(
125
+ 'delete_failed',
126
+ sprintf(
127
+ __('Failed to delete post "%s" (%d)', 'broken-link-checker'),
128
+ get_the_title($this->container_id),
129
+ $this->container_id
130
+ )
131
+ );
132
+ };
133
+ }
134
+ }
135
+
136
+ class blcPostContainerManager extends blcContainerManager {
137
+ var $container_class_name = 'blcPostContainer';
138
+
139
+ /**
140
+ * Set up hooks that monitor added/modified/deleted posts.
141
+ *
142
+ * @return void
143
+ */
144
+ function init(){
145
+ //These hooks update the synch & instance records when posts are added, deleted or modified.
146
+ add_action('delete_post', array(&$this,'post_deleted'));
147
+ add_action('save_post', array(&$this,'post_saved'));
148
+ //We also treat post trashing/untrashing as delete/save.
149
+ add_action('trash_post', array(&$this,'post_deleted'));
150
+ add_action('untrash_post', array(&$this,'post_saved'));
151
+
152
+ //Highlight broken links in posts & pages
153
+ $conf = blc_get_configuration();
154
+ if ( $conf->options['mark_broken_links'] ){
155
+ add_filter( 'the_content', array(&$this,'hook_the_content') );
156
+ if ( !empty( $conf->options['broken_link_css'] ) ){
157
+ add_action( 'wp_head', array(&$this,'hook_wp_head') );
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Remove the synch. record and link instances associated with a post when it's deleted
164
+ *
165
+ * @param int $post_id
166
+ * @return void
167
+ */
168
+ function post_deleted($post_id){
169
+ //Get the associated container object
170
+ $post_container = blc_get_container( array($this->container_type, intval($post_id)) );
171
+ //Delete it
172
+ $post_container->delete();
173
+ //Clean up any dangling links
174
+ blc_cleanup_links();
175
+ }
176
+
177
+ /**
178
+ * When a post is saved or modified, mark it as unparsed.
179
+ *
180
+ * @param int $post_id
181
+ * @return void
182
+ */
183
+ function post_saved($post_id){
184
+ //Get the container
185
+ $args = array($this->container_type, intval($post_id));
186
+ $post_container = blc_get_container( $args );
187
+
188
+ //Get the post
189
+ $post = $post_container->get_wrapped_object();
190
+
191
+ //Only check links in posts, not revisions and attachments
192
+ if ( ($post->post_type != 'post') && ($post->post_type != 'page') ) return;
193
+ //Only check published posts
194
+ if ( $post->post_status != 'publish' ) return;
195
+
196
+ $post_container->mark_as_unsynched();
197
+ }
198
+
199
+
200
+ /**
201
+ * Instantiate multiple containers of the container type managed by this class.
202
+ *
203
+ * @param array $containers Array of assoc. arrays containing container data.
204
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
205
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
206
+ *
207
+ * @return array of blcPostContainer indexed by "container_type|container_id"
208
+ */
209
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
210
+ $containers = $this->make_containers($containers);
211
+
212
+ //Preload post data if it is likely to be useful later
213
+ $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_PARSING));
214
+ if ( $preload ){
215
+ $post_ids = array();
216
+ foreach($containers as $container){
217
+ $post_ids[] = $container->container_id;
218
+ }
219
+
220
+ $args = array('include' => implode(',', $post_ids));
221
+ $posts = get_posts($args);
222
+
223
+ foreach($posts as $post){
224
+ $key = $this->container_type . '|' . $post->ID;
225
+ if ( isset($containers[$key]) ){
226
+ $containers[$key]->wrapped_object = $post;
227
+ }
228
+ }
229
+ }
230
+
231
+ return $containers;
232
+ }
233
+
234
+ /**
235
+ * Create or update synchronization records for all posts.
236
+ *
237
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
238
+ * @return void
239
+ */
240
+ function resynch($forced = false){
241
+ global $wpdb;
242
+
243
+ if ( $forced ){
244
+ //Create new synchronization records for all posts.
245
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
246
+ SELECT id, 'post', 0
247
+ FROM {$wpdb->posts}
248
+ WHERE
249
+ {$wpdb->posts}.post_status = 'publish'
250
+ AND {$wpdb->posts}.post_type IN ('post', 'page')";
251
+ $wpdb->query( $q );
252
+ } else {
253
+ //Delete synch records corresponding to posts that no longer exist.
254
+ $q = "DELETE synch.*
255
+ FROM
256
+ {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->posts} AS posts
257
+ ON posts.ID = synch.container_id
258
+ WHERE
259
+ synch.container_type = 'post' AND posts.ID IS NULL";
260
+ $wpdb->query( $q );
261
+
262
+ //Remove the 'synched' flag from all posts that have been updated
263
+ //since the last time they were parsed/synchronized.
264
+ $q = "UPDATE
265
+ {$wpdb->prefix}blc_synch AS synch
266
+ JOIN {$wpdb->posts} AS posts ON (synch.container_id = posts.ID and synch.container_type='post')
267
+ SET
268
+ synched = 0
269
+ WHERE
270
+ synch.last_synch < posts.post_modified";
271
+ $wpdb->query( $q );
272
+
273
+ //Create synch. records for posts that don't have them.
274
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
275
+ SELECT id, 'post', 0
276
+ FROM
277
+ {$wpdb->posts} AS posts LEFT JOIN {$wpdb->prefix}blc_synch AS synch
278
+ ON (synch.container_id = posts.ID and synch.container_type='post')
279
+ WHERE
280
+ posts.post_status = 'publish'
281
+ AND posts.post_type IN ('post', 'page')
282
+ AND synch.container_id IS NULL";
283
+ $wpdb->query($q);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Get the message to display after $n posts have been deleted.
289
+ *
290
+ * @param int $n Number of deleted posts.
291
+ * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
292
+ */
293
+ function ui_bulk_delete_message($n){
294
+ //Since the "Trash" feature has been introduced, calling wp_delete_post
295
+ //doesn't actually delete the post (unless you set force_delete to True),
296
+ //just moves it to the trash. So we pick the message accordingly.
297
+ if ( function_exists('wp_trash_post') && EMPTY_TRASH_DAYS ){
298
+ $delete_msg = _n("%d post moved to the trash", "%d posts moved to the trash", $n, 'broken-link-checker');
299
+ } else {
300
+ $delete_msg = _n("%d post deleted", "%d posts deleted", $n, 'broken-link-checker');
301
+ }
302
+ return sprintf($delete_msg, $n);
303
+ }
304
+
305
+ /**
306
+ * Hook for the 'the_content' filter. Scans the current post and adds the 'broken_link'
307
+ * CSS class to all links that are known to be broken. Currently works only on standard
308
+ * HTML links (i.e. the '<a href=...' kind).
309
+ *
310
+ * @param string $content Post content
311
+ * @return string Modified post content.
312
+ */
313
+ function hook_the_content($content){
314
+ global $post, $wpdb;
315
+ if ( empty($post) ) return $content;
316
+
317
+ //Retrieve info about all occurences of broken links in the current post
318
+ $q = "
319
+ SELECT instances.raw_url
320
+ FROM {$wpdb->prefix}blc_instances AS instances JOIN {$wpdb->prefix}blc_links AS links
321
+ ON instances.link_id = links.link_id
322
+ WHERE
323
+ instances.container_type = %s
324
+ AND instances.container_id = %d
325
+ AND links.broken = 1
326
+ AND parser_type = 'link'
327
+ ";
328
+ $q = $wpdb->prepare($q, $this->container_type, $post->ID);
329
+ $links = $wpdb->get_results($q, ARRAY_A);
330
+
331
+ //Return the content unmodified if there are no broken links in this post.
332
+ if ( empty($links) || !is_array($links) ){
333
+ return $content;
334
+ }
335
+
336
+ //Put the broken link URLs in an array
337
+ $broken_link_urls = array();
338
+ foreach($links as $link){
339
+ $broken_link_urls[] = $link['raw_url'];
340
+ }
341
+
342
+
343
+ //Iterate over all HTML links and modify the broken ones
344
+ $parser = blc_get_parser('link');
345
+ $content = $parser->multi_edit($content, array(&$this, 'highlight_broken_link'), $broken_link_urls);
346
+
347
+ return $content;
348
+ }
349
+
350
+ /**
351
+ * Analyse a link and add 'broken_link' CSS class if the link is broken.
352
+ *
353
+ * @see blcHtmlLink::multi_edit()
354
+ *
355
+ * @param array $link Associative array of link data.
356
+ * @param array $broken_link_urls List of broken link URLs present in the current post.
357
+ * @return array|string The modified link
358
+ */
359
+ function highlight_broken_link($link, $broken_link_urls){
360
+ if ( !in_array($link['href'], $broken_link_urls) ){
361
+ //Link not broken = return the original link tag
362
+ return $link['#raw'];
363
+ }
364
+
365
+ //Add 'broken_link' to the 'class' attribute (unless already present).
366
+ if ( isset($link['class']) ){
367
+ $classes = explode(' ', $link['class']);
368
+ if ( !in_array('broken_link', $classes) ){
369
+ $classes[] = 'broken_link';
370
+ $link['class'] = implode(' ', $classes);
371
+ }
372
+ } else {
373
+ $link['class'] = 'broken_link';
374
+ }
375
+
376
+ return $link;
377
+ }
378
+
379
+ /**
380
+ * A hook for the 'wp_head' action. Outputs the user-defined broken link CSS.
381
+ *
382
+ * @return void
383
+ */
384
+ function hook_wp_head(){
385
+ $conf = blc_get_configuration();
386
+ echo '<style type="text/css">',$conf->options['broken_link_css'],'</style>';
387
+ }
388
+ }
389
+
390
+ blc_register_container('post', 'blcPostContainerManager');
391
+
392
+ ?>
includes/instances.php ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ */
7
+
8
+ if (!class_exists('blcLinkInstance')) {
9
+ class blcLinkInstance {
10
+
11
+ //Object state
12
+ var $is_new = false;
13
+
14
+ //DB fields
15
+ var $instance_id = 0;
16
+ var $link_id = 0;
17
+
18
+ var $container_id = 0;
19
+ var $container_type = '';
20
+ var $container_field = '';
21
+
22
+ var $parser_type = '';
23
+
24
+ var $link_text = '';
25
+ var $link_context = '';
26
+ var $raw_url = '';
27
+
28
+ var $_container = null;
29
+ var $_parser = null;
30
+ var $_link = null;
31
+
32
+ /**
33
+ * blcLinkInstance::__construct()
34
+ * Class constructor
35
+ *
36
+ * @param int|array $arg Either the instance ID or an associate array repreenting the instance's DB record. Should be NULL for new instances.
37
+ * @return void
38
+ */
39
+ function __construct($arg = null){
40
+
41
+ if (is_int($arg)){
42
+ //Load an instance with ID = $arg from the DB.
43
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d LIMIT 1", $arg);
44
+ $arr = $wpdb->get_row( $q, ARRAY_A );
45
+
46
+ if ( is_array($arr) ){ //Loaded successfully
47
+ $this->set_values($arr);
48
+ } else {
49
+ //Link instance not found. The object is invalid.
50
+ }
51
+
52
+ } else if (is_array($arg)){
53
+ $this->set_values($arg);
54
+
55
+ //Is this a new instance?
56
+ $this->is_new = empty($this->instance_id);
57
+
58
+ } else {
59
+ $this->is_new = true;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * blcLinkInstance::blcLinkInstance()
65
+ * Old-style constructor for PHP 4. Do not use.
66
+ *
67
+ * @param mixed $arg
68
+ * @return void
69
+ */
70
+ function blcLinkInstance($arg = null){
71
+ $this->__construct($arg);
72
+ }
73
+
74
+ /**
75
+ * blcLinkInstance::set_values()
76
+ * Set property values to the ones provided in an array (doesn't sanitize).
77
+ *
78
+ * @param array $arr An associative array
79
+ * @return void
80
+ */
81
+ function set_values($arr){
82
+ foreach( $arr as $key => $value ){
83
+ $this->$key = $value;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Replace this instance's URL with a new one.
89
+ * Warning : this shouldn't be called directly. Use blcLink->edit() instead.
90
+ *
91
+ * @param string $new_url
92
+ * @param string $old_url
93
+ * @return bool|WP_Error True on success, or an instance of WP_Error if something went wrong.
94
+ */
95
+ function edit($new_url, $old_url = ''){
96
+
97
+ //Get the container that contains this link
98
+ $container = $this->get_container();
99
+ if ( is_null($container) ){
100
+ return new WP_Error(
101
+ 'container_not_found',
102
+ sprintf(__("Container %s[%d] not found", 'broken-link-checker'), $this->container_type, $this->container_id)
103
+ );
104
+ }
105
+
106
+ //Get the parser.
107
+ $parser = $this->get_parser();
108
+ if ( is_null($parser) ){
109
+ return new WP_Error(
110
+ 'parser_not_found',
111
+ sprintf(__("Parser '%s' not found.", 'broken-link-checker'), $this->parser_type)
112
+ );
113
+ }
114
+
115
+ //If the old URL isn't specified get it from the link record
116
+ if ( empty($old_url) ){
117
+ $old_url = $this->get_url();
118
+ }
119
+
120
+ //Attempt to modify the link(s)
121
+ $result = $container->edit_link($this->container_field, $parser, $new_url, $old_url, $this->raw_url);
122
+ if ( is_string($result) ){
123
+ //If the modification was successful, the container will return
124
+ //the new raw_url for the instance. Save the URL and return true,
125
+ //indicating success.
126
+ $this->raw_url = $result;
127
+ return true;
128
+ } else {
129
+ //Otherwise, it will return an error object. In this case we'll
130
+ //just pass it back to the caller and let them sort it out.
131
+ return $result;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * blcLinkInstance::unlink()
137
+ * Remove this instance from the post/blogroll/etc. Also deletes the appropriate DB record(s).
138
+ *
139
+ * @return bool|WP_Error
140
+ */
141
+ function unlink( $url = null ) {
142
+
143
+ //Get the container that contains this link
144
+ $container = $this->get_container();
145
+ if ( is_null($container) ){
146
+ return new WP_Error(
147
+ 'container_not_found',
148
+ sprintf(__("Container %s[%d] not found", 'broken-link-checker'), $this->container_type, $this->container_id)
149
+ );
150
+ }
151
+
152
+ //Get the parser.
153
+ $parser = $this->get_parser();
154
+ if ( is_null($parser) ){
155
+ return new WP_Error(
156
+ 'parser_not_found',
157
+ sprintf(__("Parser '%s' not found.", 'broken-link-checker'), $this->parser_type)
158
+ );
159
+ }
160
+
161
+ //If the old URL isn't specified get it from the link record
162
+ if ( empty($url) ){
163
+ $url = $this->get_url();
164
+ }
165
+
166
+ //Attempt to remove the link(s)
167
+ return $container->unlink($this->container_field, $parser, $url, $this->raw_url);
168
+ }
169
+
170
+ /**
171
+ * Remove the link instance record from database. Doesn't affect the thing that contains the link.
172
+ *
173
+ * @return mixed 1 on success, 0 if the instance wasn't found, false on error
174
+ */
175
+ function forget(){
176
+ global $wpdb;
177
+
178
+ if ( !empty($this->instance_id) ) {
179
+ $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d", $this->instance_id) );
180
+ return $rez;
181
+ } else {
182
+ return false;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Store the link instance in the database.
188
+ * Saving the instance will also implicitly save the link record associated with it, if it wasn't already saved.
189
+ *
190
+ * @return bool TRUE on success, FALSE on error
191
+ */
192
+ function save(){
193
+ global $wpdb;
194
+
195
+ //Refresh the locally cached link & container properties, in case
196
+ //the objects have changed since they were set.
197
+
198
+ if ( !is_null($this->_link) ){
199
+
200
+ //If we have a link object assigned, but it's new, it won't have a DB ID yet.
201
+ //We need to save the link to get the ID and be able to maintain the link <-> instance
202
+ //association.
203
+ if ( $this->_link->is_new ){
204
+ $rez = $this->_link->save();
205
+ if ( !$rez ){
206
+ return false;
207
+ }
208
+ }
209
+
210
+ $this->link_id = $this->_link->link_id;
211
+ }
212
+
213
+ if ( !is_null($this->_container) ){
214
+ $this->container_type = $this->_container->container_type;
215
+ $this->container_id = $this->_container->container_id;
216
+ }
217
+
218
+ //If the link is new, insert a new row into the DB. Otherwise updat the existing row.
219
+ if ( $this->is_new ){
220
+
221
+ $q = "
222
+ INSERT INTO {$wpdb->prefix}blc_instances
223
+ ( link_id, container_type, container_id, container_field, parser_type, link_text, link_context, raw_url )
224
+ VALUES( %d, %s, %d, %s, %s, %s, %s, %s )";
225
+
226
+ $q = $wpdb->prepare(
227
+ $q,
228
+
229
+ $this->link_id,
230
+ $this->container_type,
231
+ $this->container_id,
232
+ $this->container_field,
233
+ $this->parser_type,
234
+ $this->link_text,
235
+ $this->link_context,
236
+ $this->raw_url
237
+ );
238
+
239
+ $rez = $wpdb->query($q) !== false;
240
+
241
+ if ($rez){
242
+ $this->instance_id = $wpdb->insert_id;
243
+ //If the instance was successfully saved then it's no longer "new".
244
+ $this->is_new = !$rez;
245
+ }
246
+
247
+ return $rez;
248
+
249
+ } else {
250
+
251
+ $q = "UPDATE {$wpdb->prefix}blc_instances
252
+
253
+ SET
254
+ link_id = %d,
255
+ container_type = %s,
256
+ container_id = %d,
257
+ container_field = %s,
258
+ parser_type = %s,
259
+ link_text = %s,
260
+ link_context = %s,
261
+ raw_url = %s
262
+
263
+ WHERE instance_id = %d";
264
+
265
+ $q = $wpdb->prepare(
266
+ $q,
267
+
268
+ $this->link_id,
269
+ $this->container_type,
270
+ $this->container_id,
271
+ $this->container_field,
272
+ $this->parser_type,
273
+ $this->link_text,
274
+ $this->link_context,
275
+ $this->raw_url,
276
+
277
+ $this->instance_id
278
+ );
279
+
280
+ $rez = $wpdb->query($q) !== false;
281
+
282
+ if ($rez){
283
+ //FB::info($this, "Instance updated");
284
+ } else {
285
+ //FB::error("DB error while updating instance {$this->instance_id} : {$wpdb->last_error}");
286
+ }
287
+
288
+ return $rez;
289
+
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Get the URL associated with this instance.
295
+ *
296
+ * @return string The associated URL, or an empty string if the instance is currently not assigned to any link.
297
+ */
298
+ function get_url(){
299
+ $link = $this->get_link();
300
+
301
+ if ( !is_null($link) ){
302
+ return $link->url;
303
+ } else {
304
+ return '';
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get the container object associated with this link instance
310
+ *
311
+ * @return blcContainer|null
312
+ */
313
+ function get_container(){
314
+ if( is_null($this->_container) ){
315
+ $this->_container = blc_get_container( array($this->container_type, $this->container_id) );
316
+ }
317
+
318
+ return $this->_container;
319
+ }
320
+
321
+ /**
322
+ * Set a new container for the link instance.
323
+ *
324
+ * @param blcContainer $new_container
325
+ * @param string $field
326
+ * @return void
327
+ */
328
+ function set_container($new_container, $field = ''){
329
+ $this->_container = $new_container;
330
+
331
+ $this->container_field = $field;
332
+
333
+ if( !is_null($new_container) ){
334
+ $this->container_type = $new_container->container_type;
335
+ $this->container_id = $new_container->container_id;
336
+ } else {
337
+ $this->container_type = '';
338
+ $this->container_id = 0;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Get the parser associated with this link instance.
344
+ *
345
+ * @return blcParser|null
346
+ */
347
+ function get_parser(){
348
+ if ( is_null($this->_parser) ){
349
+ $this->_parser = blc_get_parser($this->parser_type);
350
+ }
351
+
352
+ return $this->_parser;
353
+ }
354
+
355
+ /**
356
+ * Set a new parser fo this link instance.
357
+ *
358
+ * @param blcParser|null $new_parser
359
+ * @return void
360
+ */
361
+ function set_parser($new_parser){
362
+ $this->_parser = $new_parser;
363
+
364
+ if ( is_null($new_parser) ){
365
+ $this->parser_type = '';
366
+ } else {
367
+ $this->parser_type = $new_parser->parser_type;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Get the link object associated with this link intance.
373
+ *
374
+ * @return blcLink|null
375
+ */
376
+ function get_link(){
377
+ if ( !is_null($this->_link) ){
378
+ return $this->_link;
379
+ }
380
+
381
+ if ( empty($this->link_id) ) {
382
+ return null;
383
+ }
384
+
385
+ $this->_link = new blcLink($this->link_id);
386
+ return $this->_link;
387
+ }
388
+
389
+ /**
390
+ * Set the link associated with this link instance.
391
+ *
392
+ * @param blcLink $new_link
393
+ * @return void
394
+ */
395
+ function set_link($new_link){
396
+ $this->_link = $new_link;
397
+
398
+ if ( is_null($new_link) ){
399
+ $this->link_id = 0;
400
+ } else {
401
+ $this->link_id = $new_link->link_id;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Get the link text for printing in the "Broken Links" table.
407
+ *
408
+ * @param string $context How to filter the link text. Optional, defaults to 'display'.
409
+ * @return string HTML
410
+ */
411
+ function ui_get_link_text($context = 'display'){
412
+ $parser = $this->get_parser();
413
+
414
+ if ( !is_null($parser) ){
415
+ $text = $parser->ui_get_link_text($this, $context);
416
+ } else {
417
+ $text = strip_tags($this->link_text);
418
+ }
419
+
420
+ if ( empty($text) ){
421
+ $text = "<em>(None)</em>";
422
+ }
423
+
424
+ return $text;
425
+ }
426
+
427
+ /**
428
+ * Get action links that should be displayed in the "Source" column of the "Broken Links" table.
429
+ *
430
+ * @return array An array of HTML links.
431
+ */
432
+ function ui_get_action_links(){
433
+ //The container is responsible for generating the links.
434
+ $container = $this->get_container();
435
+ if ( !is_null($container) ){
436
+ return $container->ui_get_action_links($this->container_field);
437
+ } else {
438
+ //No valid container = no links.
439
+ return array();
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Get the HTML describing the "source" of the instance. For example, for links found in posts,
445
+ * this could be the post title.
446
+ *
447
+ * @param string $context How to filter the output. Optional, defaults to 'display'.
448
+ * @return string HTML
449
+ */
450
+ function ui_get_source($context = 'display'){
451
+ //The container is also responsible for generating the "Source" column HTML.
452
+ $container = $this->get_container();
453
+ if ( !is_null($container) ){
454
+ return $container->ui_get_source($this->container_field, $context);
455
+ } else {
456
+ //No valid container = generate some bare-bones debug output.
457
+ return sprintf('%s[%d] : %s', $this->container_type, $this->container_id, $this->container_field);
458
+ }
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Get all link instances associated with one or more links.
464
+ *
465
+ * @param array $link_ids Array of link IDs.
466
+ * @param string $purpose An optional code indicating how the instances will be used. Available predefined constants : BLC_FOR_DISPLAY, BLC_FOR_EDITING
467
+ * @param bool $load_containers Preload containers regardless of purpose.
468
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
469
+ * @return array An array indexed by link ID. Each item of the array will be an array of blcLinkInstance objects.
470
+ */
471
+ function blc_get_instances( $link_ids, $purpose = '', $load_containers = false, $load_wrapped_objects = false ){
472
+ global $wpdb;
473
+
474
+ if ( empty($link_ids) ){
475
+ return array();
476
+ }
477
+
478
+ $link_ids_in = implode(', ', $link_ids);
479
+
480
+ $q = "SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id IN ($link_ids_in)";
481
+ $results = $wpdb->get_results($q, ARRAY_A);
482
+
483
+ if ( empty($results) ) {
484
+ return array();
485
+ }
486
+
487
+ //Also retrieve the containers, if it could be useful.
488
+ $load_containers = $load_containers || in_array( $purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING) );
489
+ if ( $load_containers ){
490
+ //Collect a list of (container_type, container_id) pairs
491
+ $container_ids = array();
492
+
493
+ foreach($results as $result){
494
+ array_push(
495
+ $container_ids,
496
+ array( $result['container_type'], intval($result['container_id']) )
497
+ );
498
+ }
499
+
500
+ $containers = blc_get_containers($container_ids, $purpose, $load_wrapped_objects);
501
+ }
502
+
503
+ //Create an object for each instance and group them by link ID
504
+ $instances = array();
505
+ foreach ($results as $result){
506
+ $instance = new blcLinkInstance($result);
507
+
508
+ //Assign a container to the link instance, if available
509
+ if( $load_containers && !empty($containers) ){
510
+ $key = $instance->container_type . '|' . $instance->container_id;
511
+ if( isset($containers[$key]) ){
512
+ $instance->_container = $containers[$key];
513
+ }
514
+ }
515
+
516
+ if ( isset($instances[$instance->link_id]) ){
517
+ array_push( $instances[$instance->link_id], $instance );
518
+ } else {
519
+ $instances[$instance->link_id] = array($instance);
520
+ }
521
+ }
522
+
523
+ return $instances;
524
+ }
525
+
526
+ /**
527
+ * Remove instances that reference invalid containers
528
+ *
529
+ * @return bool
530
+ */
531
+ function blc_cleanup_instances(){
532
+ global $wpdb;
533
+
534
+ //Delete all instances that reference non-existent containers
535
+ $q = "DELETE instances.*
536
+ FROM
537
+ {$wpdb->prefix}blc_instances AS instances LEFT JOIN {$wpdb->prefix}blc_synch AS synch
538
+ ON instances.container_type = synch.container_type AND instances.container_id = synch.container_id
539
+ WHERE
540
+ synch.container_id IS NULL";
541
+ $rez = $wpdb->query($q);
542
+
543
+ return $rez !== false;
544
+ }
545
+
546
+
547
+ }//class_exists
548
+
549
+ ?>
includes/links.php ADDED
@@ -0,0 +1,1346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2010
6
+ */
7
+
8
+ if (!class_exists('blcLink')){
9
+ class blcLink {
10
+
11
+ //Object state
12
+ var $is_new = false;
13
+
14
+ //DB fields
15
+ var $link_id = 0;
16
+ var $url = '';
17
+
18
+ var $being_checked = false;
19
+ var $last_check = 0;
20
+ var $last_check_attempt = 0;
21
+ var $check_count = 0;
22
+ var $http_code = 0;
23
+ var $request_duration = 0;
24
+ var $timeout = false;
25
+
26
+ var $redirect_count = 0;
27
+ var $final_url = '';
28
+
29
+ var $broken = false;
30
+ var $first_failure = 0;
31
+ var $last_success = 0;
32
+ var $may_recheck = 1;
33
+
34
+ var $false_positive = false;
35
+ var $result_hash = '';
36
+
37
+ var $log = '';
38
+
39
+ //A list of DB fields and their storage formats
40
+ var $field_format;
41
+
42
+ //A cached list of the link's instances
43
+ var $_instances = null;
44
+
45
+ function __construct($arg = null){
46
+ global $wpdb;
47
+
48
+ $this->field_format = array(
49
+ 'url' => '%s',
50
+ 'first_failure' => 'datetime',
51
+ 'last_check' => 'datetime',
52
+ 'last_success' => 'datetime',
53
+ 'last_check_attempt' => 'datetime',
54
+ 'check_count' => '%d',
55
+ 'final_url' => '%s',
56
+ 'redirect_count' => '%d',
57
+ 'log' => '%s',
58
+ 'http_code' => '%d',
59
+ 'request_duration' => '%f',
60
+ 'timeout' => 'bool',
61
+ 'result_hash' => '%s',
62
+ 'broken' => 'bool',
63
+ 'false_positive' => 'bool',
64
+ 'may_recheck' => 'bool',
65
+ 'being_checked' => 'bool',
66
+ );
67
+
68
+ if (is_int($arg)){
69
+ //Load a link with ID = $arg from the DB.
70
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id=%d LIMIT 1", $arg);
71
+ $arr = $wpdb->get_row( $q, ARRAY_A );
72
+
73
+ if ( is_array($arr) ){ //Loaded successfully
74
+ $this->set_values($arr);
75
+ } else {
76
+ //Link not found. The object is invalid.
77
+ //I'd throw an error, but that wouldn't be PHP 4 compatible...
78
+ }
79
+
80
+ } else if (is_string($arg)){
81
+ //Load a link with URL = $arg from the DB. Create a new one if the record isn't found.
82
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE url=%s LIMIT 1", $arg);
83
+ $arr = $wpdb->get_row( $q, ARRAY_A );
84
+
85
+ if ( is_array($arr) ){ //Loaded successfully
86
+ $this->set_values($arr);
87
+ } else { //Link not found, treat as new
88
+ $this->url = $arg;
89
+ $this->is_new = true;
90
+ }
91
+
92
+ } else if (is_array($arg)){
93
+ $this->set_values($arg);
94
+ //Is this a new link?
95
+ $this->is_new = empty($this->link_id);
96
+ } else {
97
+ $this->is_new = true;
98
+ }
99
+ }
100
+
101
+ function blcLink($arg = null){
102
+ $this->__construct($arg);
103
+ }
104
+
105
+ /**
106
+ * blcLink::set_values()
107
+ * Set the internal values to the ones provided in an array (doesn't sanitize).
108
+ *
109
+ * @param array $arr An associative array of values
110
+ * @return void
111
+ */
112
+ function set_values($arr){
113
+ $arr = $this->to_native_format($arr);
114
+
115
+ foreach( $arr as $key => $value ){
116
+ $this->$key = $value;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Check whether the object represents a valid link
122
+ *
123
+ * @return bool
124
+ */
125
+ function valid(){
126
+ return !empty( $this->url ) && ( !empty($this->link_id) || $this->is_new );
127
+ }
128
+
129
+ /**
130
+ * Check if the link is working.
131
+ *
132
+ * @param bool $save_results Automatically save the results of the check.
133
+ * @return bool
134
+ */
135
+ function check( $save_results = true ){
136
+ if ( !$this->valid() ) return false;
137
+
138
+ $this->last_check_attempt = time();
139
+
140
+ /*
141
+ If the link is stil marked as in the process of being checked, that probably means
142
+ that the last time the plugin tried to check it the script got terminated by PHP for
143
+ running over the execution time limit or causing a fatal error. Lets assume the link is broken.
144
+ */
145
+ if ( $this->being_checked ) {
146
+
147
+ $this->being_checked = false;
148
+
149
+ $this->broken = true;
150
+ $this->timeout = true;
151
+ $this->http_code = BLC_TIMEOUT;
152
+
153
+ $this->request_duration = 0;
154
+ $this->redirect_count = 0;
155
+ $this->final_url = $this->url;
156
+
157
+ $this->log .= "\r\n[" . __("The plugin script was terminated while trying to check the link.", 'broken-link-checker') . "]";
158
+
159
+ $this->status_changed($this->broken, 'link_checker_terminated');
160
+
161
+
162
+ if ( $save_results ){
163
+ $this->save();
164
+ }
165
+
166
+ return false;
167
+ }
168
+
169
+ $this->being_checked = true;
170
+ $this->check_count++;
171
+
172
+ if ( $save_results ) {
173
+
174
+ //Update the DB record before actually performing the check.
175
+ //Useful if something goes terribly wrong while checking this particular URL
176
+ //(e.g. the server might kill the script for running over the exec. time limit).
177
+ //Note : might be unnecessary.
178
+ $this->save();
179
+ }
180
+
181
+ $defaults = array(
182
+ 'broken' => false,
183
+ 'http_code' => 0,
184
+ 'redirect_count' => 0,
185
+ 'final_url' => $this->url,
186
+ 'request_duration' => 0,
187
+ 'timeout' => false,
188
+ 'may_recheck' => true,
189
+ 'log' => '',
190
+ 'result_hash' => '',
191
+ );
192
+
193
+
194
+ $checker = blc_get_checker_for($this->url);
195
+
196
+ if ( is_null($checker) ){
197
+ //Oops, there are no checker implementations that can handle this link.
198
+ //Assume the link is working, but leave a note in the log.
199
+ $this->broken = false;
200
+ $this->being_checked = false;
201
+ $this->log = __("The plugin doesn't know how to check this type of link.", 'broken-link-checker');
202
+
203
+ if ( $save_results ){
204
+ $this->save();
205
+ }
206
+
207
+ return true;
208
+ }
209
+
210
+ //Check the link
211
+ $rez = $checker->check($this->url);
212
+ //FB::info($rez, "Check results");
213
+
214
+ //Filter the returned array to leave only the restricted set of keys that we're interested in.
215
+ $results = array();
216
+ foreach($rez as $name => $value){
217
+ if ( array_key_exists($name, $defaults) ){
218
+ $results[$name] = $value;
219
+ }
220
+ }
221
+ $results = array_merge($defaults, $results);
222
+
223
+ //The result hash is special - see blcLink::status_changed()
224
+ $new_result_hash = $results['result_hash'];
225
+ unset($results['result_hash']);
226
+
227
+ //Update the object's fields with the new results
228
+ $this->set_values($results);
229
+
230
+ //Update timestamps & state-dependent fields
231
+ $this->status_changed($results['broken'], $new_result_hash);
232
+ $this->being_checked = false;
233
+
234
+ //Save results to the DB
235
+ if($save_results){
236
+ $this->save();
237
+ }
238
+
239
+ return $this->broken;
240
+ }
241
+
242
+ /**
243
+ * A helper method used to update timestamps & other state-dependent fields
244
+ * after the state of the link (broken vs working) has just been determined.
245
+ *
246
+ * @access private
247
+ *
248
+ * @param bool $broken
249
+ * @return void
250
+ */
251
+ function status_changed($broken, $new_result_hash = ''){
252
+
253
+ if ( $this->false_positive && !empty($new_result_hash) ){
254
+ //If the link has been marked as a (probable) false positive,
255
+ //mark it as broken *only* if the new result is different from
256
+ //the one that caused the user to mark it as a false positive.
257
+ if ( $broken ){
258
+ if ( $this->result_hash == $new_result_hash ){
259
+ //Got the same result as before, assume it's still incorrect and the link actually works.
260
+ $broken = false;
261
+ } else {
262
+ //Got a new result. Assume (quite optimistically) that it's not a false positive.
263
+ $this->false_positive = false;
264
+ }
265
+ } else {
266
+ //The plugin now thinks the link is working,
267
+ //so it's no longer a false positive.
268
+ $this->false_positive = false;
269
+ }
270
+ }
271
+
272
+ $this->broken = $broken;
273
+ $this->result_hash = $new_result_hash;
274
+
275
+ //Update timestamps
276
+ $this->last_check = $this->last_check_attempt;
277
+ if ( $this->broken ){
278
+ if ( empty($this->first_failure) ){
279
+ $this->first_failure = $this->last_check;
280
+ }
281
+ } else {
282
+ $this->first_failure = 0;
283
+ $this->last_success = $this->last_check;
284
+ $this->check_count = 0;
285
+ }
286
+
287
+ //Add a line indicating link status to the log
288
+ if ( !$broken ) {
289
+ $this->log .= "\n" . __("Link is valid.", 'broken-link-checker');
290
+ } else {
291
+ $this->log .= "\n" . __("Link is broken.", 'broken-link-checker');
292
+ }
293
+ }
294
+
295
+ /**
296
+ * blcLink::save()
297
+ * Save link data to DB.
298
+ *
299
+ * @return bool True if saved successfully, false otherwise.
300
+ */
301
+ function save(){
302
+ global $wpdb;
303
+
304
+ if ( !$this->valid() ) return false;
305
+
306
+ //Make a list of fields to be saved and their values in DB format
307
+ $values = array();
308
+ foreach($this->field_format as $field => $format){
309
+ $values[$field] = $this->$field;
310
+ }
311
+ $values = $this->to_db_format($values);
312
+
313
+ if ( $this->is_new ){
314
+
315
+ //Insert a new row
316
+ $q = sprintf(
317
+ "INSERT INTO {$wpdb->prefix}blc_links( %s ) VALUES( %s )",
318
+ implode(', ', array_keys($values)),
319
+ implode(', ', array_values($values))
320
+ );
321
+ //FB::log($q, 'Link add query');
322
+
323
+ $rez = $wpdb->query($q) !== false;
324
+
325
+ if ($rez){
326
+ $this->link_id = $wpdb->insert_id;
327
+ //FB::info($this->link_id, "Link added");
328
+ //If the link was successfully saved then it's no longer "new"
329
+ $this->is_new = false;
330
+ } else {
331
+ //FB::error($wpdb->last_error, "Error adding link {$this->url}");
332
+ }
333
+
334
+ return $rez;
335
+
336
+ } else {
337
+
338
+ //Generate the field = dbvalue expressions
339
+ $set_exprs = array();
340
+ foreach($values as $name => $value){
341
+ $set_exprs[] = "$name = $value";
342
+ }
343
+ $set_exprs = implode(', ', $set_exprs);
344
+
345
+ //Update an existing DB record
346
+ $q = sprintf(
347
+ "UPDATE {$wpdb->prefix}blc_links SET %s WHERE link_id=%d",
348
+ $set_exprs,
349
+ intval($this->link_id)
350
+ );
351
+ //FB::log($q, 'Link update query');
352
+
353
+ $rez = $wpdb->query($q) !== false;
354
+
355
+ if ( $rez ){
356
+ //FB::log($this->link_id, "Link updated");
357
+ } else {
358
+ //FB::error($wpdb->last_error, "Error updating link {$this->url}");
359
+ }
360
+
361
+ return $rez;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * A helper method for converting the link's field values to DB format and escaping them
367
+ * for use in SQL queries.
368
+ *
369
+ * @param array $values
370
+ * @return array
371
+ */
372
+ function to_db_format($values){
373
+ global $wpdb;
374
+
375
+ $dbvalues = array();
376
+
377
+ foreach($values as $name => $value){
378
+ //Skip fields that don't exist in the blc_links table.
379
+ if ( !isset($this->field_format[$name]) ){
380
+ continue;
381
+ }
382
+
383
+ $format = $this->field_format[$name];
384
+
385
+ //Convert native values to a format comprehensible to the DB
386
+ switch($format){
387
+
388
+ case 'datetime' :
389
+ if ( empty($value) ){
390
+ $value = '0000-00-00 00:00:00';
391
+ } else {
392
+ $value = date('Y-m-d H:i:s', $value);
393
+ }
394
+ $format = '%s';
395
+ break;
396
+
397
+ case 'bool':
398
+ if ( $value ){
399
+ $value = 1;
400
+ } else {
401
+ $value = 0;
402
+ }
403
+ $format = '%d';
404
+ break;
405
+ }
406
+
407
+ //Escapize
408
+ $value = $wpdb->prepare($format, $value);
409
+
410
+ $dbvalues[$name] = $value;
411
+ }
412
+
413
+ return $dbvalues;
414
+ }
415
+
416
+ /**
417
+ * A helper method for converting values fetched from the database to native datatypes.
418
+ *
419
+ * @param array $values
420
+ * @return array
421
+ */
422
+ function to_native_format($values){
423
+
424
+ foreach($values as $name => $value){
425
+ //Don't process ffields that don't exist in the blc_links table.
426
+ if ( !isset($this->field_format[$name]) ){
427
+ continue;
428
+ }
429
+
430
+ $format = $this->field_format[$name];
431
+
432
+ //Convert values in DB format to native datatypes.
433
+ switch($format){
434
+
435
+ case 'datetime' :
436
+ if ( $value == '0000-00-00 00:00:00' ){
437
+ $value = 0;
438
+ } elseif (is_string($value)) {
439
+ $value = strtotime($value);
440
+ }
441
+ break;
442
+
443
+ case 'bool':
444
+ $value = (bool)$value;
445
+ break;
446
+
447
+ case '%d':
448
+ $value = intval($value);
449
+ break;
450
+
451
+ case '%f':
452
+ $value = floatval($value);
453
+ break;
454
+
455
+ }
456
+
457
+ $values[$name] = $value;
458
+ }
459
+
460
+ return $values;
461
+ }
462
+
463
+ /**
464
+ * blcLink::edit()
465
+ * Edit all instances of the link by changing the URL.
466
+ *
467
+ * Here's how this really works : create a new link with the new URL. Then edit()
468
+ * all instances and point them to the new link record. If some instance can't be
469
+ * edited they will still point to the old record. The old record is deleted
470
+ * if all instances were edited successfully.
471
+ *
472
+ * @param string $new_url
473
+ * @return array An associative array with these keys :
474
+ * new_link_id - the database ID of the new link.
475
+ * new_link - the new link (an instance of blcLink).
476
+ * cnt_okay - the number of successfully edited link instances.
477
+ * cnt_error - the number of instances that caused problems.
478
+ * errors - an array of WP_Error objects corresponding to the failed edits.
479
+ */
480
+ function edit($new_url){
481
+ if ( !$this->valid() ){
482
+ return new WP_Error(
483
+ 'link_invalid',
484
+ __("Link is not valid", 'broken-link-checker')
485
+ );
486
+ }
487
+
488
+ //FB::info('Changing link '.$this->link_id .' to URL "'.$new_url.'"');
489
+
490
+ $instances = $this->get_instances();
491
+ //Fail if there are no instances
492
+ if (empty($instances)) {
493
+ return array(
494
+ 'new_link_id' => $this->link_id,
495
+ 'new_link' => $this,
496
+ 'cnt_okay' => 0,
497
+ 'cnt_error' => 0,
498
+ 'errors' => array(
499
+ new WP_Error(
500
+ 'no_instances_found',
501
+ __('This link can not be edited because it is not used anywhere on this site.', 'broken-link-checker')
502
+ )
503
+ )
504
+ );
505
+ };
506
+
507
+ //Load or create a link with the URL = $new_url
508
+ $new_link = new blcLink($new_url);
509
+ $was_new = $new_link->is_new;
510
+ if ($new_link->is_new) {
511
+ //FB::log($new_link, 'Saving a new link');
512
+ $new_link->save(); //so that we get a valid link_id
513
+ }
514
+
515
+ //FB::log("Changing link to $new_url");
516
+
517
+ if ( empty($new_link->link_id) ){
518
+ //FB::error("Failed to create a new link record");
519
+ return array(
520
+ 'new_link_id' => $this->link_id,
521
+ 'new_link' => $this,
522
+ 'cnt_okay' => 0,
523
+ 'cnt_error' => 0,
524
+ 'errors' => array(
525
+ new WP_Error(
526
+ 'link_creation_failed',
527
+ __('Failed to create a DB entry for the new URL.', 'broken-link-checker')
528
+ )
529
+ )
530
+ );;
531
+ }
532
+
533
+ $cnt_okay = $cnt_error = 0;
534
+ $errors = array();
535
+
536
+ //Edit each instance.
537
+ //FB::info('Editing ' . count($instances) . ' instances');
538
+ foreach ( $instances as $instance ){
539
+ $rez = $instance->edit( $new_url, $this->url );
540
+ if ( is_wp_error($rez) ){
541
+ $cnt_error++;
542
+ array_push($errors, $rez);
543
+ //FB::error($instance, 'Failed to edit instance ' . $instance->instance_id);
544
+ } else {
545
+ $cnt_okay++;
546
+ $instance->link_id = $new_link->link_id;
547
+ $instance->save();
548
+ //FB::info($instance, 'Successfully edited instance ' . $instance->instance_id);
549
+ }
550
+ }
551
+
552
+ //If all instances were edited successfully we can delete the old link record.
553
+ //UNLESS this link is equal to the new link (which should never happen, but whatever).
554
+ if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) && ( $this->link_id != $new_link->link_id ) ){
555
+ $this->forget( false );
556
+ }
557
+
558
+ //On the other hand, if no instances could be edited and the $new_link was really new,
559
+ //then delete it.
560
+ if ( ( $cnt_okay == 0 ) && $was_new ){
561
+ $new_link->forget( false );
562
+ $new_link = $this;
563
+ }
564
+
565
+ return array(
566
+ 'new_link_id' => $new_link->link_id,
567
+ 'new_link' => $new_link,
568
+ 'cnt_okay' => $cnt_okay,
569
+ 'cnt_error' => $cnt_error,
570
+ 'errors' => $errors,
571
+ );
572
+ }
573
+
574
+ /**
575
+ * Edit all of of this link's instances and replace the URL with the URL that it redirects to.
576
+ * This method does nothing if the link isn't a redirect.
577
+ *
578
+ * @see blcLink::edit()
579
+ *
580
+ * @return array|WP_Error
581
+ */
582
+ function deredirect(){
583
+ if ( !$this->valid() ){
584
+ return new WP_Error(
585
+ 'link_invalid',
586
+ __("Link is not valid", 'broken-link-checker')
587
+ );
588
+ }
589
+
590
+ if ( ($this->redirect_count <= 0) || empty($this->final_url) ){
591
+ return array(
592
+ 'new_link_id' => $this->link_id,
593
+ 'new_link' => $this,
594
+ 'cnt_okay' => 0,
595
+ 'cnt_error' => 0,
596
+ 'errors' => array(
597
+ new WP_Error(
598
+ 'not_redirect',
599
+ __("This link is not a redirect", 'broken-link-checker')
600
+ )
601
+ ),
602
+ );
603
+ }
604
+
605
+ return $this->edit($this->final_url);
606
+ }
607
+
608
+ /**
609
+ * Unlink all instances and delete the link record.
610
+ *
611
+ * @return array|WP_Error An associative array with these keys :
612
+ * cnt_okay - the number of successfully removed instances.
613
+ * cnt_error - the number of instances that couldn't be removed.
614
+ * link_deleted - true if the link record was deleted.
615
+ * errors - an array of WP_Error objects describing the errors that were encountered, if any.
616
+ */
617
+ function unlink(){
618
+ if ( !$this->valid() ){
619
+ return new WP_Error(
620
+ 'link_invalid',
621
+ __("Link is not valid", 'broken-link-checker')
622
+ );
623
+ }
624
+
625
+ //FB::info($this, 'Removing link');
626
+ $instances = $this->get_instances();
627
+
628
+ //No instances? Just remove the link then.
629
+ if (empty($instances)) {
630
+ //FB::warn("This link has no instances. Deleting the link.");
631
+ $rez = $this->forget( false ) !== false;
632
+
633
+ if ( $rez ){
634
+ return array(
635
+ 'cnt_okay' => 1,
636
+ 'cnt_error' => 0,
637
+ 'link_deleted' => true,
638
+ 'errors' => array(),
639
+ );
640
+ } else {
641
+ return array(
642
+ 'cnt_okay' => 0,
643
+ 'cnt_error' => 0,
644
+ 'link_deleted' => false,
645
+ 'errors' => array(
646
+ new WP_Error(
647
+ "deletion_failed",
648
+ __("Couldn't delete the link's database record", 'broken-link-checker')
649
+ )
650
+ ),
651
+ );
652
+ }
653
+ }
654
+
655
+
656
+ //FB::info('Unlinking ' . count($instances) . ' instances');
657
+
658
+ $cnt_okay = $cnt_error = 0;
659
+ $errors = array();
660
+
661
+ //Unlink each instance.
662
+ foreach ( $instances as $instance ){
663
+ $rez = $instance->unlink( $this->url );
664
+
665
+ if ( is_wp_error($rez) ){
666
+ $cnt_error++;
667
+ array_push($errors, $rez);
668
+ //FB::error( $instance, 'Failed to unlink instance' );
669
+ } else {
670
+ $cnt_okay++;
671
+ //FB::info( $instance, 'Successfully unlinked instance' );
672
+ }
673
+ }
674
+
675
+ //If all instances were unlinked successfully we can delete the link record.
676
+ if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) ){
677
+ //FB::log('Instances removed, deleting the link.');
678
+ $link_deleted = $this->forget() !== false;
679
+
680
+ if ( !$link_deleted ){
681
+ array_push(
682
+ $errors,
683
+ new WP_Error(
684
+ "deletion_failed",
685
+ __("Couldn't delete the link's database record", 'broken-link-checker')
686
+ )
687
+ );
688
+ }
689
+
690
+ } else {
691
+ //FB::error("Something went wrong. Unlinked instances : $cnt_okay, errors : $cnt_error");
692
+ $link_deleted = false;
693
+ }
694
+
695
+ return array(
696
+ 'cnt_okay' => $cnt_okay,
697
+ 'cnt_error' => $cnt_error,
698
+ 'link_deleted' => $link_deleted,
699
+ 'errors' => $errors,
700
+ );
701
+ }
702
+
703
+ /**
704
+ * Remove the link and (optionally) its instance records from the DB. Doesn't alter posts/etc.
705
+ *
706
+ * @return mixed 1 on success, 0 if link not found, false on error.
707
+ */
708
+ function forget($remove_instances = true){
709
+ global $wpdb;
710
+ if ( !$this->valid() ) return false;
711
+
712
+ if ( !empty($this->link_id) ){
713
+ //FB::info($this, 'Deleting link from DB');
714
+
715
+ if ( $remove_instances ){
716
+ //Remove instances, if any
717
+ $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE link_id=%d", $this->link_id) );
718
+ }
719
+
720
+ //Remove the link itself
721
+ $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_links WHERE link_id=%d", $this->link_id) );
722
+ $this->link_id = 0;
723
+
724
+ return $rez;
725
+ } else {
726
+ return false;
727
+ }
728
+
729
+ }
730
+
731
+ /**
732
+ * Get a list of the link's instances
733
+ *
734
+ * @param bool $ignore_cache Don't use the internally cached instance list.
735
+ * @param string $purpose
736
+ * @return array An array of instance objects or FALSE on failure.
737
+ */
738
+ function get_instances( $ignore_cache = false, $purpose = '' ){
739
+ global $wpdb;
740
+ if ( !$this->valid() || empty($this->link_id) ) return false;
741
+
742
+ if ( $ignore_cache || is_null($this->_instances) ){
743
+ $instances = blc_get_instances( array($this->link_id), $purpose );
744
+ if ( !empty($instances) ){
745
+ $this->_instances = $instances[$this->link_id];
746
+ }
747
+ }
748
+
749
+ return $this->_instances;
750
+ }
751
+ }
752
+
753
+ } //class_exists
754
+
755
+ class blcLinkQuery {
756
+
757
+ var $native_filters;
758
+ var $search_filter;
759
+ var $custom_filters = array();
760
+
761
+ var $valid_url_params = array();
762
+
763
+ function __construct(){
764
+ //Init. the available native filters.
765
+ $this->native_filters = array(
766
+ 'broken' => array(
767
+ 'params' => array(
768
+ 'where_expr' => '( broken = 1 )',
769
+ ),
770
+ 'name' => __('Broken', 'broken-link-checker'),
771
+ 'heading' => __('Broken Links', 'broken-link-checker'),
772
+ 'heading_zero' => __('No broken links found', 'broken-link-checker'),
773
+ 'native' => true,
774
+ ),
775
+ 'redirects' => array(
776
+ 'params' => array(
777
+ 'where_expr' => '( redirect_count > 0 )',
778
+ ),
779
+ 'name' => __('Redirects', 'broken-link-checker'),
780
+ 'heading' => __('Redirected Links', 'broken-link-checker'),
781
+ 'heading_zero' => __('No redirects found', 'broken-link-checker'),
782
+ 'native' => true,
783
+ ),
784
+
785
+ 'all' => array(
786
+ 'params' => array(
787
+ 'where_expr' => '1',
788
+ ),
789
+ 'name' => __('All', 'broken-link-checker'),
790
+ 'heading' => __('Detected Links', 'broken-link-checker'),
791
+ 'heading_zero' => __('No links found (yet)', 'broken-link-checker'),
792
+ 'native' => true,
793
+ ),
794
+ );
795
+
796
+ //Create the special "search" filter
797
+ $this->search_filter = array(
798
+ 'name' => __('Search', 'broken-link-checker'),
799
+ 'heading' => __('Search Results', 'broken-link-checker'),
800
+ 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
801
+ 'params' => array(),
802
+ 'use_url_params' => true,
803
+ 'hidden' => true,
804
+ );
805
+
806
+ //These search arguments may be passed via the URL if the filter's 'use_url_params' field is set to True.
807
+ //They map to the fields of the search form on the Tools -> Broken Links page. Only these arguments
808
+ //can be used in user-defined filters.
809
+ $this->valid_url_params = array(
810
+ 's_link_text',
811
+ 's_link_url',
812
+ 's_parser_type',
813
+ 's_container_type',
814
+ 's_link_type',
815
+ 's_http_code',
816
+ 's_filter',
817
+ );
818
+ }
819
+
820
+ function blcLinkQuery(){
821
+ $this->__construct();
822
+ }
823
+
824
+ /**
825
+ * Load and return the list of user-defined link filters.
826
+ *
827
+ * @return array An array of custom filter definitions. If there are no custom filters defined returns an empty array.
828
+ */
829
+ function load_custom_filters(){
830
+ global $wpdb;
831
+
832
+ $filter_data = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}blc_filters ORDER BY name ASC", ARRAY_A);
833
+ $filters = array();
834
+
835
+ if ( !empty($filter_data) ) {
836
+ foreach($filter_data as $data){
837
+ wp_parse_str($data['params'], $params);
838
+
839
+ $filters[ 'f'.$data['id'] ] = array(
840
+ 'name' => $data['name'],
841
+ 'params' => $params,
842
+ 'heading' => ucwords($data['name']),
843
+ 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
844
+ 'custom' => true,
845
+ );
846
+ }
847
+ }
848
+
849
+ $this->custom_filters = $filters;
850
+
851
+ return $filters;
852
+ }
853
+
854
+ /**
855
+ * Add a custom link filter.
856
+ *
857
+ * @param string $name Filter name.
858
+ * @param string|array $params Filter params. Either as a query string, or an array.
859
+ * @return string|bool The ID of the newly added filter, or False.
860
+ */
861
+ function create_custom_filter($name, $params){
862
+ global $wpdb;
863
+
864
+ if ( is_array($params) ){
865
+ $params = http_build_query($params, null, '&');
866
+ }
867
+
868
+ //Save the new filter
869
+ $q = $wpdb->prepare(
870
+ "INSERT INTO {$wpdb->prefix}blc_filters(name, params) VALUES (%s, %s)",
871
+ $name, $params
872
+ );
873
+
874
+ if ( $wpdb->query($q) !== false ){
875
+ $filter_id = 'f'.$wpdb->insert_id;
876
+ return $filter_id;
877
+ } else {
878
+ return false;
879
+ }
880
+ }
881
+
882
+ /**
883
+ * Delete a custom filter
884
+ *
885
+ * @param string $filter_id
886
+ * @return bool True on success, False if a database error occured.
887
+ */
888
+ function delete_custom_filter($filter_id){
889
+ global $wpdb;
890
+
891
+ //Remove the "f" character from the filter ID to get its database key
892
+ $filter_id = intval(ltrim($_POST['filter_id'], 'f'));
893
+
894
+ //Try to delete the filter
895
+ $q = $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_filters WHERE id = %d", $filter_id);
896
+ if ( $wpdb->query($q) !== false ){
897
+ return true;
898
+ } else {
899
+ return false;
900
+ }
901
+ }
902
+
903
+ function get_filters(){
904
+ $filters = array_merge($this->native_filters, $this->custom_filters);
905
+ $filters['search'] = $this->search_filter;
906
+ return $filters;
907
+ }
908
+
909
+ /**
910
+ * Get a link search filter by filter ID.
911
+ *
912
+ * @param string $filter_id
913
+ * @return array|null
914
+ */
915
+ function get_filter($filter_id){
916
+ $filters = $this->get_filters();
917
+ if ( isset($filters[$filter_id]) ){
918
+ return $filters[$filter_id];
919
+ } else {
920
+ return null;
921
+ }
922
+ }
923
+
924
+ /**
925
+ * Get link search parameters from the specified filter.
926
+ *
927
+ * @param array $filter
928
+ * @return array An array of parameters suitable for use with blcLinkQuery::get_links()
929
+ */
930
+ function get_search_params( $filter = null ){
931
+ //If present, the filter's parameters may be saved either as an array or a string.
932
+ $params = array();
933
+ if ( !empty($filter) && !empty($filter['params']) ){
934
+ $params = $filter['params'];
935
+ if ( is_string( $params ) ){
936
+ wp_parse_str($params, $params);
937
+ }
938
+ }
939
+
940
+ //Merge in the parameters from the current request, if required
941
+ if ( isset($filter['use_url_params']) && $filter['use_url_params'] ){
942
+ $params = array_merge($params, $this->get_url_search_params());
943
+ }
944
+
945
+ return $params;
946
+ }
947
+
948
+ /**
949
+ * Extract search query parameters from the current URL
950
+ *
951
+ * @return array
952
+ */
953
+ function get_url_search_params(){
954
+ $url_params = array();
955
+ foreach ($_GET as $param => $value){
956
+ if ( in_array($param, $this->valid_url_params) ){
957
+ $url_params[$param] = $value;
958
+ }
959
+ }
960
+ return $url_params;
961
+ }
962
+
963
+
964
+
965
+ /**
966
+ * A helper method for parsing a list of search criteria and generating the parts of the SQL query.
967
+ *
968
+ * @see blcLinkQuery::get_links()
969
+ *
970
+ * @param array $params An array of search criteria.
971
+ * @return array 'where_exprs' - an array of search expressions, 'join_instances' - whether joining the instance table is required.
972
+ */
973
+ function compile_search_params($params){
974
+ global $wpdb;
975
+
976
+ //Track whether we'll need to left-join the instance table to run the query.
977
+ $join_instances = false;
978
+
979
+ //Generate the individual clauses of the WHERE expression and store them in an array.
980
+ $pieces = array();
981
+
982
+ //A part of the WHERE expression can be specified explicitly
983
+ if ( !empty($params['where_expr']) ){
984
+ $pieces[] = $params['where_expr'];
985
+ $join_instances = $join_instances || ( stripos($params['where_expr'], 'instances') !== false );
986
+ }
987
+
988
+ //List of allowed link ids (either an array or comma-separated)
989
+ if ( !empty($params['link_ids']) ){
990
+ $link_ids = $params['link_ids'];
991
+
992
+ if ( is_string($link_ids) ){
993
+ $link_ids = preg_split('/[,\s]+/', $link_ids);
994
+ }
995
+
996
+ //Only accept non-zero integers
997
+ $sanitized_link_ids = array();
998
+ foreach($link_ids as $id){
999
+ $id = intval($id);
1000
+ if ( $id != 0 ){
1001
+ $sanitized_link_ids[] = $id;
1002
+ }
1003
+ }
1004
+
1005
+ $pieces[] = 'link_id IN (' . implode(', ', $sanitized_link_ids) . ')';
1006
+ }
1007
+
1008
+ //Anchor text - use LIKE search
1009
+ if ( !empty($params['s_link_text']) ){
1010
+ $s_link_text = like_escape($wpdb->escape($params['s_link_text']));
1011
+ $s_link_text = str_replace('*', '%', $s_link_text);
1012
+
1013
+ $pieces[] = '(instances.link_text LIKE "%' . $s_link_text . '%")';
1014
+ $join_instances = true;
1015
+ }
1016
+
1017
+ //URL - try to match both the initial URL and the final URL.
1018
+ //There is limited wildcard support, e.g. "google.*/search" will match both
1019
+ //"google.com/search" and "google.lv/search"
1020
+ if ( !empty($params['s_link_url']) ){
1021
+ $s_link_url = like_escape($wpdb->escape($params['s_link_url']));
1022
+ $s_link_url = str_replace('*', '%', $s_link_url);
1023
+
1024
+ $pieces[] = '(links.url LIKE "%'. $s_link_url .'%") OR '.
1025
+ '(links.final_url LIKE "%'. $s_link_url .'%")';
1026
+ }
1027
+
1028
+ //Parser type should match the parser_type column in the instance table.
1029
+ if ( !empty($params['s_parser_type']) ){
1030
+ $s_parser_type = $wpdb->escape($params['s_parser_type']);
1031
+ $pieces[] = "instances.parser_type = '$s_parser_type'";
1032
+ $join_instances = true;
1033
+ }
1034
+
1035
+ //Container type should match the container_type column in the instance table.
1036
+ if ( !empty($params['s_container_type']) ){
1037
+ $s_container_type = $wpdb->escape($params['s_container_type']);
1038
+ $pieces[] = "instances.container_type = '$s_container_type'";
1039
+ $join_instances = true;
1040
+ }
1041
+
1042
+ //Container ID should match... you guessed it - container_id
1043
+ if ( !empty($params['s_container_id']) ){
1044
+ $s_container_id = intval($params['s_container_id']);
1045
+ if ( $s_container_id != 0 ){
1046
+ $pieces[] = "instances.container_id = $s_container_id";
1047
+ $join_instances = true;
1048
+ }
1049
+ }
1050
+
1051
+ //Link type can match either the the parser_type or the container_type.
1052
+ if ( !empty($params['s_link_type']) ){
1053
+ $s_link_type = $wpdb->escape($params['s_link_type']);
1054
+ $pieces[] = "instances.parser_type = '$s_link_type' OR instances.container_type='$s_link_type'";
1055
+ $join_instances = true;
1056
+ }
1057
+
1058
+ //HTTP code - the user can provide a list of HTTP response codes and code ranges.
1059
+ //Example : 201,400-410,500
1060
+ if ( !empty($params['s_http_code']) ){
1061
+ //Strip spaces.
1062
+ $params['s_http_code'] = str_replace(' ', '', $params['s_http_code']);
1063
+ //Split by comma
1064
+ $codes = explode(',', $params['s_http_code']);
1065
+
1066
+ $individual_codes = array();
1067
+ $ranges = array();
1068
+
1069
+ //Try to parse each response code or range. Invalid ones are simply ignored.
1070
+ foreach($codes as $code){
1071
+ if ( is_numeric($code) ){
1072
+ //It's a single number
1073
+ $individual_codes[] = abs(intval($code));
1074
+ } elseif ( strpos($code, '-') !== false ) {
1075
+ //Try to parse it as a range
1076
+ $range = explode( '-', $code, 2 );
1077
+ if ( (count($range) == 2) && is_numeric($range[0]) && is_numeric($range[0]) ){
1078
+ //Make sure the smaller code comes first
1079
+ $range = array( intval($range[0]), intval($range[1]) );
1080
+ $ranges[] = array( min($range), max($range) );
1081
+ }
1082
+ }
1083
+ }
1084
+
1085
+ $piece = array();
1086
+
1087
+ //All individual response codes get one "http_code IN (...)" clause
1088
+ if ( !empty($individual_codes) ){
1089
+ $piece[] = '(links.http_code IN ('. implode(', ', $individual_codes) .'))';
1090
+ }
1091
+
1092
+ //Ranges get a "http_code BETWEEN min AND max" clause each
1093
+ if ( !empty($ranges) ){
1094
+ $range_strings = array();
1095
+ foreach($ranges as $range){
1096
+ $range_strings[] = "(links.http_code BETWEEN $range[0] AND $range[1])";
1097
+ }
1098
+ $piece[] = '( ' . implode(' OR ', $range_strings) . ' )';
1099
+ }
1100
+
1101
+ //Finally, generate a composite WHERE clause for both types of response code queries
1102
+ if ( !empty($piece) ){
1103
+ $pieces[] = implode(' OR ', $piece);
1104
+ }
1105
+
1106
+ }
1107
+
1108
+ //Custom filters can optionally call one of the native filters
1109
+ //to narrow down the result set.
1110
+ if ( !empty($params['s_filter']) && isset($this->native_filters[$params['s_filter']]) ){
1111
+ $the_filter = $this->native_filters[$params['s_filter']];
1112
+ $extra_criteria = $this->compile_search_params($the_filter['params']);
1113
+
1114
+ $pieces = array_merge($pieces, $extra_criteria['where_exprs']);
1115
+ $join_instances = $join_instances || $extra_criteria['join_instances'];
1116
+ }
1117
+
1118
+ return array(
1119
+ 'where_exprs' => $pieces,
1120
+ 'join_instances' => $join_instances,
1121
+ );
1122
+ }
1123
+
1124
+ /**
1125
+ * blcLinkQuery::get_links()
1126
+ *
1127
+ * @see blc_get_links()
1128
+ *
1129
+ * @param array $params
1130
+ * @param string $purpose
1131
+ * @return array|int
1132
+ */
1133
+ function get_links($params = null){
1134
+ global $wpdb;
1135
+
1136
+ if( !is_array($params) ){
1137
+ $params = array();
1138
+ }
1139
+
1140
+ $defaults = array(
1141
+ 'offset' => 0,
1142
+ 'max_results' => 0,
1143
+ 'load_instances' => false,
1144
+ 'load_containers' => false,
1145
+ 'load_wrapped_objects' => false,
1146
+ 'count_only' => false,
1147
+ 'purpose' => '',
1148
+ );
1149
+
1150
+ $params = array_merge($defaults, $params);
1151
+
1152
+ //Compile the search-related params into search expressions usable in a WHERE clause
1153
+ $criteria = $this->compile_search_params($params);
1154
+
1155
+ //Build the WHERE clause
1156
+ if ( !empty($criteria['where_exprs']) ){
1157
+ $where_expr = "\t( " . implode(" ) AND\n\t( ", $criteria['where_exprs']) . ' ) ';
1158
+ } else {
1159
+ $where_expr = '1';
1160
+ }
1161
+
1162
+ //Join the blc_instances table if it's required to perform the search.
1163
+ $joins = "";
1164
+ if ( $criteria['join_instances'] ){
1165
+ $joins = "JOIN {$wpdb->prefix}blc_instances AS instances ON links.link_id = instances.link_id";
1166
+ }
1167
+
1168
+ if ( $params['count_only'] ){
1169
+ //Only get the number of matching links.
1170
+ $q = "
1171
+ SELECT COUNT(*)
1172
+ FROM (
1173
+ SELECT 0
1174
+
1175
+ FROM
1176
+ {$wpdb->prefix}blc_links AS links
1177
+ $joins
1178
+
1179
+ WHERE
1180
+ $where_expr
1181
+
1182
+ GROUP BY links.link_id) AS foo";
1183
+
1184
+ return $wpdb->get_var($q);
1185
+ }
1186
+
1187
+ //Select the required links.
1188
+ $q = "SELECT
1189
+ links.*
1190
+
1191
+ FROM
1192
+ {$wpdb->prefix}blc_links AS links
1193
+ $joins
1194
+
1195
+ WHERE
1196
+ $where_expr
1197
+
1198
+ GROUP BY links.link_id";
1199
+
1200
+ //Add the LIMIT clause
1201
+ if ( $params['max_results'] || $params['offset'] ){
1202
+ $q .= sprintf("\nLIMIT %d, %d", $params['offset'], $params['max_results']);
1203
+ }
1204
+
1205
+ $results = $wpdb->get_results($q, ARRAY_A);
1206
+ if ( empty($results) ){
1207
+ return array();
1208
+ }
1209
+
1210
+ //Create the link objects
1211
+ $links = array();
1212
+
1213
+ foreach($results as $result){
1214
+ $link = new blcLink($result);
1215
+ $links[$link->link_id] = $link;
1216
+ }
1217
+
1218
+ $purpose = $params['purpose'];
1219
+ /*
1220
+ Preload instances if :
1221
+ * It has been requested via the 'load_instances' argument.
1222
+ * The links are going to be displayed or edited, which involves instances.
1223
+ */
1224
+ $load_instances = $params['load_instances'] || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING));
1225
+
1226
+ if ( $load_instances ){
1227
+ $link_ids = array_keys($links);
1228
+ $all_instances = blc_get_instances($link_ids, $purpose, $params['load_containers'], $params['load_wrapped_objects']);
1229
+ //Assign each batch of instances to the right link
1230
+ foreach($all_instances as $link_id => $instances){
1231
+ $links[$link_id]->_instances = $instances;
1232
+ }
1233
+ }
1234
+
1235
+ return $links;
1236
+ }
1237
+
1238
+ /**
1239
+ * Calculate the number of results for all known filters
1240
+ *
1241
+ * @return void
1242
+ */
1243
+ function count_filter_results(){
1244
+ foreach($this->native_filters as $filter_id => $filter){
1245
+ $this->native_filters[$filter_id]['count'] = $this->get_filter_links(
1246
+ $filter, array('count_only' => true)
1247
+ );
1248
+ }
1249
+
1250
+ foreach($this->custom_filters as $filter_id => $filter){
1251
+ $this->custom_filters[$filter_id]['count'] = $this->get_filter_links(
1252
+ $filter, array('count_only' => true)
1253
+ );
1254
+ }
1255
+
1256
+ $this->search_filter['count'] = $this->get_filter_links($this->search_filter, array('count_only' => true));
1257
+ }
1258
+
1259
+ /**
1260
+ * Retrieve a list of links matching a filter.
1261
+ *
1262
+ * @uses blcLinkQuery::get_links()
1263
+ *
1264
+ * @param string|array $filter Either a filter ID or an array containing filter data.
1265
+ * @param array $extra_params Optional extra criteria that will override those set by the filter. See blc_get_links() for details.
1266
+ * @return array|int Either an array of blcLink objects, or an integer indicating the number of links that match the filter.
1267
+ */
1268
+ function get_filter_links($filter, $extra_params = null){
1269
+ if ( is_string($filter) ){
1270
+ $filter = $this->get_filter($filter);
1271
+ }
1272
+
1273
+ $params = $this->get_search_params($filter);
1274
+
1275
+
1276
+ if ( !empty($extra_params) ){
1277
+ $params = array_merge($params, $extra_params);
1278
+ }
1279
+
1280
+ return $this->get_links($params);
1281
+ }
1282
+ }
1283
+
1284
+ $GLOBALS['blc_link_query'] = new blcLinkQuery();
1285
+
1286
+ /**
1287
+ * Retrieve a list of links matching some criteria.
1288
+ *
1289
+ * The function argument should be an associative array describing the criteria.
1290
+ * The supported keys are :
1291
+ * 'offset' - Skip the first X results. Default is 0.
1292
+ * 'max_results' - The maximum number of links to return. Defaults to returning all results.
1293
+ * 'link_ids' - Retrieve only links with these IDs. This should either be a comma-separated list or an array.
1294
+ * 's_link_text' - Link text must match this keyphrase (performs a fulltext search).
1295
+ * 's_link_url' - Link URL must contain this string. You can use "*" as a wildcard.
1296
+ * 's_parser_type' - Filter links by the type of link parser that was used to find them.
1297
+ * 's_container_type' - Filter links by where they were found, e.g. 'post'.
1298
+ * 's_container_id' - Find links that belong to a container with this ID (should be used together with s_container_type).
1299
+ * 's_link_type' - Either parser type or container type must match this.
1300
+ * 's_http_code' - Filter by HTTP code. Example : 201,400-410,500
1301
+ * 's_filter' - Use a built-in filter. Available filters : 'broken', 'redirects', 'all'
1302
+ * 'where_expr' - Advanced. Lets you directly specify a part of the WHERE clause.
1303
+ * 'load_instances' - Pre-load all link instance data for each link. Default is false.
1304
+ * 'load_containers' - Pre-load container data for each instance. Default is false.
1305
+ * 'load_wrapped_objects' - Pre-load wrapped object data (e.g. posts, comments, etc) for each container. Default is false.
1306
+ * 'count_only' - Only return the number of results (int), not the whole result set. 'offset' and 'max_results' will be ignored if this is set. Default is false.
1307
+ * 'purpose' - An optional code indicating how the links will be used.
1308
+ *
1309
+ * All keys are optional.
1310
+ *
1311
+ * @uses blcLinkQuery::get_links();
1312
+ *
1313
+ * @param array $params
1314
+ * @return int|array Either an array of blcLink objects, or the number of results for the query.
1315
+ */
1316
+ function blc_get_links($params = null){
1317
+ global $blc_link_query;
1318
+ return $blc_link_query->get_links($params, $purpose);
1319
+ }
1320
+
1321
+ /**
1322
+ * Remove orphaned links that have no corresponding instances.
1323
+ *
1324
+ * @param int|array $link_id (optional) Only check these links
1325
+ * @return bool
1326
+ */
1327
+ function blc_cleanup_links( $link_id = null ){
1328
+ global $wpdb;
1329
+
1330
+ $q = "DELETE FROM {$wpdb->prefix}blc_links
1331
+ USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1332
+ ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1333
+ WHERE
1334
+ {$wpdb->prefix}blc_instances.link_id IS NULL";
1335
+
1336
+ if ( $link_id !== null ) {
1337
+ if ( !is_array($link_id) ){
1338
+ $link_id = array( intval($link_id) );
1339
+ }
1340
+ $q .= " AND {$wpdb->prefix}blc_links.link_id IN (" . implode(', ', $link_id) . ')';
1341
+ }
1342
+
1343
+ return $wpdb->query( $q ) !== false;
1344
+ }
1345
+
1346
+ ?>
includes/parsers.php ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Parser Registry class for managing parsers.
5
+ *
6
+ * @see blcParser
7
+ *
8
+ * @package Broken Link Checker
9
+ * @access public
10
+ */
11
+ class blcParserRegistry {
12
+
13
+ /**
14
+ * @access protected
15
+ */
16
+ var $registered_parsers = array();
17
+
18
+ /**
19
+ * Register a new link parser.
20
+ *
21
+ * @param string $parser_type A unique string identifying the parser.
22
+ * @param string $class_name Name of the class implementing the parser.
23
+ * @return bool True on success, false if this parser type is already registered.
24
+ */
25
+ function register_parser( $parser_type, $class_name ){
26
+ if ( isset($this->registered_parsers[$parser_type]) ){
27
+ return false;
28
+ }
29
+
30
+ $parser = new $class_name($parser_type);
31
+ $this->registered_parsers[$parser_type] = $parser;
32
+
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Get the parser matching a parser type ID.
38
+ *
39
+ * @param string $parser_type
40
+ * @return blcParser|null
41
+ */
42
+ function get_parser( $parser_type ){
43
+ if ( isset($this->registered_parsers[$parser_type]) ){
44
+ return $this->registered_parsers[$parser_type];
45
+ } else {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Get all parsers that support either the specified format or the container type.
52
+ * If a parser supports both, it will still be included only once.
53
+ *
54
+ * @param string $format
55
+ * @param string $container_type
56
+ * @return array of blcParser
57
+ */
58
+ function get_parsers( $format, $container_type ){
59
+ $found = array();
60
+
61
+ foreach($this->registered_parsers as $parser){
62
+ if ( in_array($format, $parser->supported_formats) || in_array($container_type, $parser->supported_containers) ){
63
+ array_push($found, $parser);
64
+ }
65
+ }
66
+
67
+ return $found;
68
+ }
69
+
70
+ }
71
+
72
+ //Create the parser registry singleton.
73
+ $GLOBALS['blc_parser_registry'] = new blcParserRegistry();
74
+
75
+
76
+ /**
77
+ * A base class for parsers.
78
+ *
79
+ * In the context of this plugin, a "parser" is a class that knows how to extract or modfify
80
+ * a specific type of links from a given piece of text. For example, there could be a "HTML Link"
81
+ * parser that knows how to find and modify standard HTML links such as this one :
82
+ * <a href="http://example.com/">Example</a>
83
+ *
84
+ * Other parsers could extract plaintext URLs or handle metadata fields.
85
+ *
86
+ * Each parser has a list of supported formats (e.g. "html", "plaintext", etc) and container types
87
+ * (e.g. "post", "comment", "blogroll", etc). When something needs to be parsed, the involved
88
+ * container class will look up the parsers that support the relevant format or the container's type,
89
+ * and apply them to the to-be-parsed string.
90
+ *
91
+ * All sub-classes of blcParser should override at least the blcParser::parse() method.
92
+ *
93
+ * @see blcContainer::$fields
94
+ *
95
+ * @package Broken Link Checker
96
+ * @access public
97
+ */
98
+ class blcParser {
99
+
100
+ var $parser_type;
101
+ var $supported_formats = array();
102
+ var $supported_containers = array();
103
+
104
+ /**
105
+ * Class construtor.
106
+ *
107
+ * @param string $parser_type
108
+ * @return void
109
+ */
110
+ function __construct( $parser_type ){
111
+ $this->parser_type = $parser_type;
112
+ }
113
+
114
+ /**
115
+ * PHP4 constructor
116
+ *
117
+ * @param string $parser_type
118
+ * @return void
119
+ */
120
+ function blcParser( $parser_type ){
121
+ $this->__construct( $parser_type );
122
+ }
123
+
124
+ /**
125
+ * Parse a string for links.
126
+ *
127
+ * @param string $content The text to parse.
128
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
129
+ * @param string $default_link_text
130
+ * @return array An array of new blcLinkInstance objects. The objects will include info about the links found, but not about the corresponding container entity.
131
+ */
132
+ function parse($content, $base_url = '', $default_link_text = ''){
133
+ return array();
134
+ }
135
+
136
+ /**
137
+ * Change all links that have a certain URL to a new URL.
138
+ *
139
+ * @param string $content Look for links in this string.
140
+ * @param string $new_url Change the links to this URL.
141
+ * @param string $old_url The URL to look for.
142
+ * @param string $old_raw_url The raw, not-normalized URL of the links to look for. Optional.
143
+ *
144
+ * @return array|WP_Error If successful, the return value will be an associative array with two
145
+ * keys : 'content' - the modified content, and 'raw_url' - the new raw, non-normalized URL used
146
+ * for the modified links. In most cases, the returned raw_url will be equal to the new_url.
147
+ */
148
+ function edit($content, $new_url, $old_url, $old_raw_url){
149
+ return new WP_Error(
150
+ 'not_implemented',
151
+ sprintf(__("Editing is not implemented in the '%s' parser", 'broken-link-checker'), $this->parser_type)
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Remove all links that have a certain URL, leaving anchor text intact.
157
+ *
158
+ * @param string $content Look for links in this string.
159
+ * @param string $url The URL to look for.
160
+ * @param string $raw_url The raw, non-normalized version of the URL to look for. Optional.
161
+ * @return string Input string with all matching links removed.
162
+ */
163
+ function unlink($content, $url, $raw_url){
164
+ return new WP_Error(
165
+ 'not_implemented',
166
+ sprintf(__("Unlinking is not implemented in the '%s' parser", 'broken-link-checker'), $this->parser_type)
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Get the link text for printing in the "Broken Links" table.
172
+ * Sub-classes should override this method and display the link text in a way appropriate for the link type.
173
+ *
174
+ * @param blcLinkInstance $instance
175
+ * @return string HTML
176
+ */
177
+ function ui_get_link_text($instance, $context = 'display'){
178
+ return $instance->link_text;
179
+ }
180
+
181
+ /**
182
+ * Turn a relative URL into an absolute one.
183
+ *
184
+ * @param string $url Relative URL.
185
+ * @param string $base_url Base URL. If omitted, the blog's root URL will be used.
186
+ * @return string
187
+ */
188
+ function relative2absolute($url, $base_url = ''){
189
+ if ( empty($base_url) ){
190
+ $base_url = get_option('siteurl');
191
+ }
192
+
193
+ $p = @parse_url($url);
194
+ if(!$p) {
195
+ //URL is a malformed
196
+ return false;
197
+ }
198
+ if( isset($p["scheme"]) ) return $url;
199
+
200
+ //If the relative URL is just a query string, simply attach it to the absolute URL and return
201
+ if ( substr($relative, 0, 1) == '?' ){
202
+ return $absolute . $relative;
203
+ }
204
+
205
+ $parts=(parse_url($base_url));
206
+
207
+ if(substr($url,0,1)=='/') {
208
+ //Relative URL starts with a slash => ignore the base path and jump straight to the root.
209
+ $path_segments = explode("/", $url);
210
+ array_shift($path_segments);
211
+ } else {
212
+ if(isset($parts['path'])){
213
+ $aparts=explode('/',$parts['path']);
214
+ array_pop($aparts);
215
+ $aparts=array_filter($aparts);
216
+ } else {
217
+ $aparts=array();
218
+ }
219
+
220
+ //Merge together the base path & the relative path
221
+ $aparts = array_merge($aparts, explode("/", $url));
222
+
223
+ //Filter the merged path
224
+ $path_segments = array();
225
+ foreach($aparts as $part){
226
+ if ( $part == '.' ){
227
+ continue; //. = "this directory". It's basically a no-op, so we skip it.
228
+ } elseif ( $part == '..' ) {
229
+ array_pop($path_segments); //.. = one directory up. Remove the last seen path segment.
230
+ } else {
231
+ array_push($path_segments, $part); //Normal directory -> add it to the path.
232
+ }
233
+ }
234
+ }
235
+ $path = implode("/", $path_segments);
236
+
237
+ //Build the absolute URL.
238
+ $url = '';
239
+ if($parts['scheme']) {
240
+ $url = "$parts[scheme]://";
241
+ }
242
+ if(isset($parts['user'])) {
243
+ $url .= $parts['user'];
244
+ if(isset($parts['pass'])) {
245
+ $url .= ":".$parts['pass'];
246
+ }
247
+ $url .= "@";
248
+ }
249
+ if(isset($parts['host'])) {
250
+ $url .= $parts['host']."/";
251
+ }
252
+ $url .= $path;
253
+
254
+ return $url;
255
+ }
256
+
257
+ /**
258
+ * Apply a callback function to all links found in a string and return the results.
259
+ *
260
+ * The first argument passed to the callback function will be an associative array
261
+ * of link data. If the optional $extra parameter is set, it will be passed as the
262
+ * second argument to the callback function.
263
+ *
264
+ * The link data array will contain at least these keys :
265
+ * 'href' - the URL of the link, as-is (i.e. without any sanitization or relative-to-absolute translation).
266
+ * '#raw' - the raw link code, e.g. the entire '<a href="...">...</a>' tag of a HTML link.
267
+ *
268
+ * Sub-classes may also set additional keys.
269
+ *
270
+ * This method is currently used only internally, so sub-classes are not required
271
+ * to implement it.
272
+ *
273
+ * @param string $content A text string to parse for links.
274
+ * @param callback $callback Callback function to apply to all found links.
275
+ * @param mixed $extra If the optional $extra param. is supplied, it will be passed as the second parameter to the function $callback.
276
+ * @return array An array of all detected links after applying $callback to each of them.
277
+ */
278
+ function map($content, $callback, $extra = null){
279
+ return array();
280
+ }
281
+
282
+ /**
283
+ * Modify all links found in a string using a callback function.
284
+ *
285
+ * The first argument passed to the callback function will be an associative array
286
+ * of link data. If the optional $extra parameter is set, it will be passed as the
287
+ * second argument to the callback function. See the map() method of this class for
288
+ * details on the first argument.
289
+ *
290
+ * The callback function should return either an associative array or a string. If
291
+ * a string is returned, the parser will replace the current link with the contents
292
+ * of that string. If an array is returned, the current link will be modified/rebuilt
293
+ * by substituting the new values for the old ones (e.g. returning array with the key
294
+ * 'href' set to 'http://example.com/' will replace the current link's URL with
295
+ * http://example.com/).
296
+ *
297
+ * This method is currently only used internally, so sub-classes are not required
298
+ * to implement it.
299
+ *
300
+ * @see blcParser::map()
301
+ *
302
+ * @param string $content A text string containing the links to edit.
303
+ * @param callback $callback Callback function used to modify the links.
304
+ * @param mixed $extra If supplied, $extra will be passed as the second parameter to the function $callback.
305
+ * @return string The modified input string.
306
+ */
307
+ function multi_edit($content, $callback, $extra = null){
308
+ return $content; //No-op
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Register a new link parser.
314
+ *
315
+ * @see blcParser
316
+ *
317
+ * @uses blcParserRegistry::register_parser()
318
+ *
319
+ * @param string $parser_type A unique string identifying the parser, e.g. "html_link"
320
+ * @param string $class_name Name of the class that implements the parser.
321
+ * @return bool
322
+ */
323
+ function blc_register_parser( $parser_type, $class_name ) {
324
+ global $blc_parser_registry;
325
+ return $blc_parser_registry->register_parser($parser_type, $class_name);
326
+ }
327
+
328
+ /**
329
+ * Get the parser matching a parser type id.
330
+ *
331
+ * @uses blcParserRegistry::get_parser()
332
+ *
333
+ * @param string $parser_type
334
+ * @return blcParser|null
335
+ */
336
+ function blc_get_parser( $parser_type ){
337
+ global $blc_parser_registry;
338
+ return $blc_parser_registry->get_parser($parser_type);
339
+ }
340
+
341
+ /**
342
+ * Get all parsers that support either the specified format or container type.
343
+ *
344
+ * @uses blcParserRegistry::get_parsers()
345
+ *
346
+ * @param string $format
347
+ * @param string $container_type
348
+ * @return array of blcParser
349
+ */
350
+ function blc_get_parsers( $format, $container_type ){
351
+ global $blc_parser_registry;
352
+ return $blc_parser_registry->get_parsers($format, $container_type);
353
+ }
354
+
355
+
356
+ ?>
includes/parsers/html_link.php ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class blcHTMLLink extends blcParser {
4
+ var $supported_formats = array('html');
5
+
6
+ /**
7
+ * Parse a string for HTML links - <a href="URL">anchor text</a>
8
+ *
9
+ * @param string $content The text to parse.
10
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
11
+ * @param string $default_link_text
12
+ * @return array An array of new blcLinkInstance objects. The objects will include info about the links found, but not about the corresponding container entity.
13
+ */
14
+ function parse($content, $base_url = '', $default_link_text = ''){
15
+ $instances = array();
16
+
17
+ //remove all <code></code> blocks first
18
+ $content = preg_replace('/<code[^>]*>.+?<\/code>/si', ' ', $content);
19
+
20
+ //Find links
21
+ $params = array(
22
+ 'base_url' => $base_url,
23
+ 'default_link_text' => $default_link_text,
24
+ );
25
+ $instances = $this->map($content, array(&$this, 'parser_callback'), $params);
26
+
27
+ //The parser callback returns NULL when it finds an invalid link. Filter out those nulls
28
+ //from the list of instances.
29
+ $instances = array_filter($instances);
30
+
31
+ return $instances;
32
+ }
33
+
34
+ /**
35
+ * blcHTMLLink::parser_callback()
36
+ *
37
+ * @access private
38
+ *
39
+ * @param array $link
40
+ * @param array $params
41
+ * @return blcLinkInstance|null
42
+ */
43
+ function parser_callback($link, $params){
44
+ extract($params);
45
+
46
+ $url = $raw_url = $link['href'];
47
+ $url = trim($url);
48
+ //FB::log($url, "Found link");
49
+
50
+ //Sometimes links may contain shortcodes. Execute them.
51
+ $url = do_shortcode($url);
52
+
53
+ //Skip empty URLs
54
+ if ( empty($url) ){
55
+ return null;
56
+ };
57
+
58
+ //Attempt to parse the URL
59
+ $parts = @parse_url($url);
60
+ if(!$parts) {
61
+ return null; //Skip invalid URLs
62
+ };
63
+
64
+ if ( !isset($parts['scheme']) ){
65
+ //No sheme - likely a relative URL. Turn it into an absolute one.
66
+ $url = $this->relative2absolute($url, $base_url);
67
+ }
68
+
69
+ //Skip invalid links (again)
70
+ if ( !$url || (strlen($url)<6) ) {
71
+ continue;
72
+ }
73
+
74
+ $text = strip_tags( $link['#link_text'] );
75
+
76
+ //The URL is okay, create and populate a new link instance.
77
+ $instance = new blcLinkInstance();
78
+
79
+ $instance->set_parser($this);
80
+ $instance->raw_url = $raw_url;
81
+ $instance->link_text = $text;
82
+
83
+ $link_obj = new blcLink($url); //Creates or loads the link
84
+ $instance->set_link($link_obj);
85
+
86
+ return $instance;
87
+ }
88
+
89
+ /**
90
+ * Change all links that have a certain URL to a new URL.
91
+ *
92
+ * @param string $content Look for links in this string.
93
+ * @param string $new_url Change the links to this URL.
94
+ * @param string $old_url The URL to look for.
95
+ * @param string $old_raw_url The raw, not-normalized URL of the links to look for. Optional.
96
+ *
97
+ * @return array|WP_Error If successful, the return value will be an associative array with two
98
+ * keys : 'content' - the modified content, and 'raw_url' - the new raw, non-normalized URL used
99
+ * for the modified links. In most cases, the returned raw_url will be equal to the new_url.
100
+ */
101
+ function edit($content, $new_url, $old_url, $old_raw_url){
102
+ if ( empty($old_raw_url) ){
103
+ $old_raw_url = $old_url;
104
+ }
105
+
106
+ //Save the old & new URLs for use in the edit callback.
107
+ $args = array(
108
+ 'old_url' => $old_raw_url,
109
+ 'new_url' => $new_url,
110
+ );
111
+
112
+ //Find all links and replace those that match $old_url.
113
+ $content = $this->multi_edit($content, array(&$this, 'edit_callback'), $args);
114
+
115
+ return array(
116
+ 'content' => $content,
117
+ 'raw_url' => $new_url,
118
+ );
119
+ }
120
+
121
+ function edit_callback($link, $params){
122
+ if ($link['href'] == $params['old_url']){
123
+ return array(
124
+ 'href' => $params['new_url'],
125
+ );
126
+ } else {
127
+ return $link['#raw'];
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Remove all links that have a certain URL, leaving anchor text intact.
133
+ *
134
+ * @param string $content Look for links in this string.
135
+ * @param string $url The URL to look for.
136
+ * @param string $raw_url The raw, non-normalized version of the URL to look for. Optional.
137
+ * @return string Input string with all matching links removed.
138
+ */
139
+ function unlink($content, $url, $raw_url){
140
+ if ( empty($raw_url) ){
141
+ $raw_url = $url;
142
+ }
143
+
144
+ $args = array(
145
+ 'old_url' => $raw_url,
146
+ );
147
+
148
+ //Find all links and remove those that match $raw_url.
149
+ $content = $this->multi_edit($content, array(&$this, 'unlink_callback'), $args);
150
+
151
+ return $content;
152
+ }
153
+
154
+ /**
155
+ * blcHTMLLink::unlink_callback()
156
+ *
157
+ * @access private
158
+ *
159
+ * @param array $link
160
+ * @param array $params
161
+ * @return string
162
+ */
163
+ function unlink_callback($link, $params){
164
+ //Skip links that don't match the specified URL
165
+ if ($link['href'] != $params['old_url']){
166
+ return $link['#raw'];
167
+ }
168
+
169
+ $config = blc_get_configuration();
170
+ if ( $config->options['mark_removed_links'] ){
171
+ //Leave only the anchor text + the removed_link CSS class
172
+ return '<span class="removed_link">' . $link['#link_text'] . '</span>';
173
+ } else {
174
+ //Just the anchor text
175
+ return $link['#link_text'];
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Get the link text for printing in the "Broken Links" table.
181
+ * Sub-classes should override this method and display the link text in a way appropriate for the link type.
182
+ *
183
+ * @param blcLinkInstance $instance
184
+ * @return string HTML
185
+ */
186
+ function ui_get_link_text($instance){
187
+ return $instance->link_text;
188
+ }
189
+
190
+ /**
191
+ * Apply a callback function to all HTML links found in a string and return the results.
192
+ *
193
+ * The link data array will contain at least these keys :
194
+ * 'href' - the URL of the link (with htmlentitydecode() already applied).
195
+ * '#raw' - the raw link code, e.g. the entire '<a href="...">...</a>' tag of a HTML link.
196
+ * '#offset' - the offset within $content at which the first character of the link tag was found.
197
+ * '#link_text' - the link's anchor text, if any. May contain HTML tags.
198
+ *
199
+ * Any attributes of the link tag will also be included in the returned array as attr_name => attr_value
200
+ * pairs. This function will also automatically decode any HTML entities found in attribute values.
201
+ *
202
+ * @see blcParser::map()
203
+ *
204
+ * @param string $content A text string to parse for links.
205
+ * @param callback $callback Callback function to apply to all found links.
206
+ * @param mixed $extra If the optional $extra param. is supplied, it will be passed as the second parameter to the function $callback.
207
+ * @return array An array of all detected links after applying $callback to each of them.
208
+ */
209
+ function map($content, $callback, $extra = null){
210
+ $results = array();
211
+
212
+ //Find all links
213
+ $links = blcUtility::extract_tags($content, 'a', false, true);
214
+
215
+ //Iterate over the links and apply $callback to each
216
+ foreach($links as $link){
217
+
218
+ //Massage the found link into a form required for the callback function
219
+ $param = $link['attributes'];
220
+ $param = array_merge(
221
+ $param,
222
+ array(
223
+ '#raw' => $link['full_tag'],
224
+ '#offset' => $link['offset'],
225
+ '#link_text' => $link['contents'],
226
+ 'href' => isset($link['attributes']['href'])?$link['attributes']['href']:'',
227
+ )
228
+ );
229
+
230
+ //Prepare arguments for the callback
231
+ $params = array($param);
232
+ if ( isset($extra) ){
233
+ $params[] = $extra;
234
+ }
235
+
236
+ //Execute & store :)
237
+ $results[] = call_user_func_array($callback, $params);
238
+ }
239
+
240
+ return $results;
241
+ }
242
+
243
+ /**
244
+ * Modify all HTML links found in a string using a callback function.
245
+ *
246
+ * The callback function should return either an associative array or a string. If
247
+ * a string is returned, the parser will replace the current link with the contents
248
+ * of that string. If an array is returned, the current link will be modified/rebuilt
249
+ * by substituting the new values for the old ones.
250
+ *
251
+ * htmlentities() will be automatically applied to attribute values (but not to #link_text).
252
+ *
253
+ * @see blcParser::multi_edit()
254
+ *
255
+ * @param string $content A text string containing the links to edit.
256
+ * @param callback $callback Callback function used to modify the links.
257
+ * @param mixed $extra If supplied, $extra will be passed as the second parameter to the function $callback.
258
+ * @return string The modified input string.
259
+ */
260
+ function multi_edit($content, $callback, $extra = null){
261
+ //Just reuse map() + a little helper func. to apply the callback to all links and get modified links
262
+ $modified_links = $this->map($content, array(&$this, 'execute_edit_callback'), array($callback, $extra));
263
+
264
+ //Replace each old link with the modified one
265
+ $offset = 0;
266
+ foreach($modified_links as $link){
267
+ if ( isset($link['#new_raw']) ){
268
+ $new_html = $link['#new_raw'];
269
+ } else {
270
+ //Assemble the new link tag
271
+ $new_html = '<a';
272
+ foreach ( $link as $name => $value ){
273
+
274
+ //Skip special keys like '#raw' and '#offset'
275
+ if ( substr($name, 0, 1) == '#' ){
276
+ continue;
277
+ }
278
+
279
+ $new_html .= sprintf(' %s="%s"', $name, htmlentities( $value, ENT_COMPAT ));
280
+ }
281
+ $new_html .= '>' . $link['#link_text'] . '</a>';
282
+ }
283
+
284
+ $content = substr_replace($content, $new_html, $link['#offset'] + $offset, strlen($link['#raw']));
285
+ //Update the replacement offset
286
+ $offset += ( strlen($new_html) - strlen($link['#raw']) );
287
+ }
288
+
289
+ return $content;
290
+ }
291
+
292
+ /**
293
+ * Helper function for blcHtmlLink::multi_edit()
294
+ * Applies the specified callback function to each link and merges
295
+ * the result with the current link attributes. If the callback returns
296
+ * a replacement HTML tag instead, it will be stored in the '#new_raw'
297
+ * key of the return array.
298
+ *
299
+ * @access protected
300
+ *
301
+ * @param array $link
302
+ * @param array $info The callback function and the extra argument to pass to that function (if any).
303
+ * @return array
304
+ */
305
+ function execute_edit_callback($link, $info){
306
+ list($callback, $extra) = $info;
307
+
308
+ //Prepare arguments for the callback
309
+ $params = array($link);
310
+ if ( isset($extra) ){
311
+ $params[] = $extra;
312
+ }
313
+
314
+ $new_link = call_user_func_array($callback, $params);
315
+
316
+ if ( is_array($new_link) ){
317
+ $link = array_merge($link, $new_link);
318
+ } elseif (is_string($new_link)) {
319
+ $link['#new_raw'] = $new_link;
320
+ }
321
+
322
+ return $link;
323
+ }
324
+ }
325
+
326
+ blc_register_parser('link', 'blcHTMLLink');
327
+
328
+ ?>
includes/parsers/image.php ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //TODO: Update image parser to use the same HTML tag parsing routine as the HTML link parser.
4
+ class blcHTMLImage extends blcParser {
5
+ var $supported_formats = array('html');
6
+
7
+ // \1 \2 \3 URL \4
8
+ var $img_pattern = '/(<img[\s]+[^>]*src\s*=\s*)([\"\'])([^>]+?)\2([^<>]*>)/i';
9
+
10
+ /**
11
+ * Parse a string for HTML images - <img src="URL">
12
+ *
13
+ * @param string $content The text to parse.
14
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
15
+ * @param string $default_link_text
16
+ * @return array An array of new blcLinkInstance objects. The objects will include info about the links found, but not about the corresponding container entity.
17
+ */
18
+ function parse($content, $base_url = '', $default_link_text = ''){
19
+ $instances = array();
20
+
21
+ //remove all <code></code> blocks first
22
+ $content = preg_replace('/<code[^>]*>.+?<\/code>/si', ' ', $content);
23
+
24
+ //Find images
25
+ if(preg_match_all($this->img_pattern, $content, $matches, PREG_SET_ORDER)){
26
+ foreach($matches as $link){
27
+ $url = $raw_url = $link[3];
28
+ //FB::log($url, "Found image");
29
+
30
+ //Decode &amp; and other entities
31
+ $url = html_entity_decode($url);
32
+ $url = trim($url);
33
+
34
+ //Attempt to parse the URL
35
+ $parts = @parse_url($url);
36
+ if(!$parts) {
37
+ continue; //Skip invalid URLs
38
+ };
39
+
40
+ if ( !isset($parts['scheme']) ){
41
+ //No sheme - likely a relative URL. Turn it into an absolute one.
42
+ $url = $this->relative2absolute($url, $base_url);
43
+ }
44
+
45
+ //Skip invalid URLs (again)
46
+ if ( !$url || (strlen($url)<6) ) {
47
+ continue;
48
+ }
49
+
50
+ //The URL is okay, create and populate a new link instance.
51
+ $instance = new blcLinkInstance();
52
+
53
+ $instance->set_parser($this);
54
+ $instance->raw_url = $raw_url;
55
+ $instance->link_text = '';
56
+
57
+ $link_obj = new blcLink($url); //Creates or loads the link
58
+ $instance->set_link($link_obj);
59
+
60
+ $instances[] = $instance;
61
+ }
62
+ };
63
+
64
+ return $instances;
65
+ }
66
+
67
+ /**
68
+ * Change all images that have a certain source URL to a new URL.
69
+ *
70
+ * @param string $content Look for images in this string.
71
+ * @param string $new_url Change the images to this URL.
72
+ * @param string $old_url The URL to look for.
73
+ * @param string $old_raw_url The raw, not-normalized URL of the links to look for. Optional.
74
+ *
75
+ * @return array|WP_Error If successful, the return value will be an associative array with two
76
+ * keys : 'content' - the modified content, and 'raw_url' - the new raw, non-normalized URL used
77
+ * for the modified images. In most cases, the returned raw_url will be equal to the new_url.
78
+ */
79
+ function edit($content, $new_url, $old_url, $old_raw_url){
80
+ if ( empty($old_raw_url) ){
81
+ $old_raw_url = $old_url;
82
+ }
83
+ //Save the old & new URLs for use in the regex callback.
84
+ $this->old_url = $old_raw_url;
85
+ $this->new_url = htmlentities($new_url);
86
+
87
+ //Find all images and replace those that match $old_url.
88
+ $content = preg_replace_callback($this->img_pattern, array(&$this, 'edit_callback'), $content);
89
+
90
+ return array(
91
+ 'content' => $content,
92
+ 'raw_url' => $this->new_url,
93
+ );
94
+ }
95
+
96
+ function edit_callback($matches){
97
+ $url = $matches[3];
98
+ if ($url == $this->old_url){
99
+ return $matches[1].'"'.$this->new_url.'"'.$matches[4];
100
+ } else {
101
+ return $matches[0];
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Remove all images that have a certain URL.
107
+ *
108
+ * @param string $content Look for images in this string.
109
+ * @param string $url The URL to look for.
110
+ * @param string $raw_url The raw, non-normalized version of the URL to look for. Optional.
111
+ * @return string Input string with all matching images removed.
112
+ */
113
+ function unlink($content, $url, $raw_url){
114
+ if ( empty($raw_url) ){
115
+ $raw_url = $url;
116
+ }
117
+ $this->old_url = $raw_url; //used by the callback
118
+ $content = preg_replace_callback($this->img_pattern, array(&$this, 'unlink_callback'), $content);
119
+ return $content;
120
+ }
121
+
122
+ function unlink_callback($matches){
123
+ $url = $matches[3];
124
+
125
+ //Does the URL match?
126
+ if ($url == $this->old_url){
127
+ return ''; //Completely remove the IMG tag
128
+ } else {
129
+ return $matches[0]; //return the image unchanged
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Get the link text for printing in the "Broken Links" table.
135
+ * Sub-classes should override this method and display the link text in a way appropriate for the link type.
136
+ *
137
+ * @param blcLinkInstance $instance
138
+ * @param string $context
139
+ * @return string HTML
140
+ */
141
+ function ui_get_link_text($instance, $context = 'display'){
142
+ $text = __('Image', 'broken-link-checker');
143
+
144
+ $image = sprintf(
145
+ '<img src="%s/broken-link-checker/images/image.png" class="blc-small-image" alt="%2$s" title="%2$s"> ',
146
+ WP_PLUGIN_URL, //TODO: Use plugin_dir_url() instead
147
+ esc_attr($text)
148
+ );
149
+
150
+ if ( $context != 'email' ){
151
+ $text = $image . $text;
152
+ }
153
+
154
+ return $text;
155
+ }
156
+ }
157
+
158
+ blc_register_parser('image', 'blcHTMLImage');
159
+
160
+
161
+ ?>
includes/parsers/metadata.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class blcMetadataParser extends blcParser {
4
+ var $supported_formats = array('metadata');
5
+ var $supported_containers = array('custom_field');
6
+
7
+ /**
8
+ * Parse a metadata value.
9
+ *
10
+ * @param string|array $content Metadata value(s).
11
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
12
+ * @param string $default_link_text
13
+ * @return array An array of new blcLinkInstance objects.
14
+ */
15
+ function parse($content, $base_url = '', $default_link_text = ''){
16
+ $instances = array();
17
+
18
+ if ( !is_array($content) ){
19
+ $content = array($content);
20
+ }
21
+
22
+ foreach($content as $value){
23
+ //The complete contents of the meta field are stored in raw_url.
24
+ //This is useful for editing/unlinking, when one may need to
25
+ //distinguish between multiple fields with the same name.
26
+ $raw_url = $value;
27
+
28
+ //If this is a multiline metadata field take only the first line (workaround for the 'enclosure' field).
29
+ $url = trim( array_shift( explode("\n", $value) ) );
30
+
31
+ //Attempt to parse the URL
32
+ $parts = @parse_url($url);
33
+ if(!$parts) {
34
+ return $instances; //Ignore invalid URLs
35
+ };
36
+
37
+ if ( !isset($parts['scheme']) ){
38
+ //No sheme - likely a relative URL. Turn it into an absolute one.
39
+ $url = $this->relative2absolute($url, $base_url);
40
+
41
+ //Skip invalid URLs (again)
42
+ if ( !$url || (strlen($url)<6) ) {
43
+ return $instances;
44
+ }
45
+ }
46
+
47
+ //The URL is okay, create and populate a new link instance.
48
+ $instance = new blcLinkInstance();
49
+
50
+ $instance->set_parser($this);
51
+ $instance->raw_url = $raw_url;
52
+ $instance->link_text = $default_link_text;
53
+
54
+ $link_obj = new blcLink($url); //Creates or loads the link
55
+ $instance->set_link($link_obj);
56
+
57
+ $instances[] = $instance;
58
+ }
59
+
60
+ return $instances;
61
+ }
62
+
63
+ /**
64
+ * Change the URL in a metadata field to another one.
65
+ *
66
+ * This is tricky because there can be multiple metadata fields with the same name
67
+ * but different values. So we ignore $content (which might be an array of multiple
68
+ * metadata values) and use the old raw_url that we stored when parsing the field(s)
69
+ * instead.
70
+ *
71
+ * @see blcMetadataParser::parse()
72
+ *
73
+ * @param string $content Ignored.
74
+ * @param string $new_url The new URL.
75
+ * @param string $old_url Ignored.
76
+ * @param string $old_raw_url The current meta value.
77
+ *
78
+ * @return array|WP_Error
79
+ */
80
+ function edit($content, $new_url, $old_url, $old_raw_url){
81
+ //For multiline fields (like 'enclosure') we only want to change the first line.
82
+ $lines = explode("\n", $old_raw_url);
83
+ array_shift($lines); //Discard the old first line
84
+ array_unshift($lines, $new_url); //Insert the new URL in its place.
85
+ $content = implode("\n", $lines);
86
+
87
+ return array(
88
+ 'content' => $content,
89
+ 'raw_url' => $new_url,
90
+ );
91
+ }
92
+ }
93
+
94
+ blc_register_parser('metadata', 'blcMetadataParser');
95
+
96
+
97
+ ?>
includes/parsers/url_field.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A "parser" for data fields that contain a single, plaintext URL.
5
+ *
6
+ * Intended for parsing stuff like bookmarks and comment author links.
7
+ *
8
+ * @package Broken Link Checker
9
+ * @access public
10
+ */
11
+ class blcUrlField extends blcParser {
12
+ var $supported_formats = array('url_field');
13
+
14
+ /**
15
+ * "Parse" an URL into an instance.
16
+ *
17
+ * @param string $content The entire content is expected to be a single plaintext URL.
18
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
19
+ * @param string $default_link_text
20
+ * @return array An array of new blcLinkInstance objects.
21
+ */
22
+ function parse($content, $base_url = '', $default_link_text = ''){
23
+ $instances = array();
24
+
25
+ $url = $raw_url = trim($content);
26
+
27
+ //Attempt to parse the URL
28
+ $parts = @parse_url($url);
29
+ if(!$parts) {
30
+ return $instances; //Ignore invalid URLs
31
+ };
32
+
33
+ if ( !isset($parts['scheme']) ){
34
+ //No sheme - likely a relative URL. Turn it into an absolute one.
35
+ $url = $this->relative2absolute($url, $base_url);
36
+
37
+ //Skip invalid URLs (again)
38
+ if ( !$url || (strlen($url)<6) ) {
39
+ return $instances;
40
+ }
41
+ }
42
+
43
+ //The URL is okay, create and populate a new link instance.
44
+ $instance = new blcLinkInstance();
45
+
46
+ $instance->set_parser($this);
47
+ $instance->raw_url = $raw_url;
48
+ $instance->link_text = $default_link_text;
49
+
50
+ $link_obj = new blcLink($url); //Creates or loads the link
51
+ $instance->set_link($link_obj);
52
+
53
+ $instances[] = $instance;
54
+
55
+ return $instances;
56
+ }
57
+
58
+ /**
59
+ * Change one URL to another (just returns the new URL).
60
+ *
61
+ * @param string $content The old URL.
62
+ * @param string $new_url The new URL.
63
+ * @param string $old_url Ignored.
64
+ * @param string $old_raw_url Ignored.
65
+ *
66
+ * @return array|WP_Error
67
+ */
68
+ function edit($content, $new_url, $old_url, $old_raw_url){
69
+ return array(
70
+ 'content' => $new_url,
71
+ 'raw_url' => $new_url,
72
+ );
73
+ }
74
+
75
+ /**
76
+ * For URL fields, "unlinking" simply means blanking the field.
77
+ * (However, invididual link containers may implement a different logic for those fields.)
78
+ */
79
+ function unlink($content, $url, $raw_url){
80
+ return '';
81
+ }
82
+ }
83
+
84
+ blc_register_parser('url_field', 'blcUrlField');
85
+
86
+
87
+ ?>
instance-classes.php DELETED
@@ -1,585 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * @author W-Shadow
5
- * @copyright 2009
6
- */
7
-
8
- if (!class_exists('blcLinkInstance')) {
9
- class blcLinkInstance {
10
-
11
- //Object state
12
- var $is_new = false;
13
-
14
- //DB fields
15
- var $instance_id = 0;
16
- var $link_id = 0;
17
- var $source_id = 0;
18
- var $source_type = '';
19
- var $link_text = '';
20
- var $instance_type = '';
21
-
22
- //These are used to pass info to callbacks when editing an instance
23
- var $old_url = null;
24
- var $new_url = null;
25
-
26
- /**
27
- * blcLinkInstance::__construct()
28
- * Class constructor
29
- *
30
- * @param mixed $arg XXXXX look up how to do a multiline doc here (phpdoc)
31
- * @return void
32
- */
33
- function __construct($arg = null){
34
-
35
- if (is_int($arg)){
36
- //Load an instance with ID = $arg from the DB.
37
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d LIMIT 1", $arg);
38
- $arr = $wpdb->get_row( $q, ARRAY_A );
39
-
40
- if ( is_array($arr) ){ //Loaded successfully
41
- $this->set_values($arr);
42
- } else {
43
- //Link instance not found. The object is invalid.
44
- }
45
-
46
- } else if (is_array($arg)){
47
- $this->set_values($arg);
48
-
49
- //Is this a new instance?
50
- $this->is_new = empty($this->instance_id);
51
-
52
- } else {
53
- $this->is_new = true;
54
- }
55
- }
56
-
57
- /**
58
- * blcLinkInstance::blcLinkInstance()
59
- * Old-style constructor for PHP 4. Do not use.
60
- *
61
- * @param mixed $arg
62
- * @return void
63
- */
64
- function blcLinkInstance($arg = null){
65
- $this->__construct($arg);
66
- }
67
-
68
- /**
69
- * blcLinkInstance::valid()
70
- * Verifies whether the object represents a valid link instance
71
- *
72
- * @return bool
73
- */
74
- function valid(){
75
- //Some basic validation to ensure the required properties are set.
76
- return !empty($this->link_id) && !empty($this->instance_type) && !empty($this->source_id)
77
- && !empty($this->source_type) && (!empty($this->instance_id) || $this->is_new);
78
- }
79
-
80
- /**
81
- * blcLinkInstance::set_values()
82
- * Set property values to the ones provided in an array (doesn't sanitize).
83
- *
84
- * @param array $arr An associative array
85
- * @return void
86
- */
87
- function set_values($arr){
88
- foreach( $arr as $key => $value ){
89
- $this->$key = $value;
90
- }
91
- }
92
-
93
- /**
94
- * blcLinkInstance::edit()
95
- * Replace this instance's URL with a new one.
96
- * Warning : this shouldn't be called directly. Use blcLink->edit() instead.
97
- *
98
- * @param string $new_url
99
- * @return bool
100
- */
101
- function edit($old_url, $new_url){
102
- echo "Error : The stub function blcLinkInstance->edit() was executed!\r\n";
103
- return false;
104
- }
105
-
106
- /**
107
- * blcLinkInstance::unlink()
108
- * Remove this instance from the post/blogroll/etc. Also deletes the appropriate DB record(s).
109
- *
110
- * @return bool
111
- */
112
- function unlink( $url = null ) {
113
- //FB::warn("The stub function blcLinkInstance->unlink() was executed!");
114
- return false;
115
- }
116
-
117
- /**
118
- * blcLinkInstance::forget()
119
- * Remove the link instance record from database. Doesn't affect the post/link/whatever.
120
- *
121
- * @return mixed 1 on success, 0 if the instance wasn't found, false on error
122
- */
123
- function forget(){
124
- global $wpdb;
125
-
126
- if ( !$this->valid() ) return false;
127
-
128
- if ( !empty($this->link_id) ) {
129
- $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d", $this->instance_id) );
130
- $this->link_id = 0;
131
- return $rez;
132
- } else {
133
- return false;
134
- }
135
- }
136
-
137
- /**
138
- * blcLinkInstance::save()
139
- * Store the instance in the database.
140
- *
141
- * @return bool TRUE on success, FALSE on failure
142
- */
143
- function save(){
144
- global $wpdb;
145
-
146
- if ( !$this->valid() ) return false;
147
-
148
- if ( $this->is_new ){
149
-
150
- //Insert a new row
151
- $q = "
152
- INSERT INTO {$wpdb->prefix}blc_instances
153
- ( link_id, source_id, source_type, link_text, instance_type )
154
- VALUES( %d, %d, %s, %s, %s )";
155
-
156
- $q = $wpdb->prepare($q, $this->link_id, $this->source_id, $this->source_type,
157
- $this->link_text, $this->instance_type );
158
-
159
- $rez = $wpdb->query($q);
160
- $rez = $rez !== false;
161
-
162
- if ($rez){
163
- $this->instance_id = $wpdb->insert_id;
164
- //If the instance was successfully saved then it's no longer "new".
165
- $this->is_new = !$rez;
166
- }
167
-
168
- return $rez;
169
-
170
- } else {
171
-
172
- //Create a new DB record
173
- $q = "UPDATE {$wpdb->prefix}blc_instances
174
- SET link_id = %d, source_id = %d, source_type = %s, link_text = %s, instance_type = %s
175
- WHERE instance_id = %d";
176
-
177
- $q = $wpdb->prepare($q, $this->link_id, $this->source_id, $this->source_type,
178
- $this->link_text, $this->instance_type, $this->instance_id );
179
-
180
- $rez = $wpdb->query($q) !== false;
181
-
182
- if ($rez){
183
- //FB::info($this, "Instance updated");
184
- } else {
185
- //FB::error("DB error while updating instance {$this->instance_id} : {$wpdb->last_error}");
186
- }
187
-
188
- return $rez;
189
-
190
- }
191
- }
192
-
193
- /**
194
- * blcLinkInstance::get_url()
195
- * Get the URL associated with this instance
196
- *
197
- * @return string
198
- */
199
- function get_url(){
200
- if ( !$this->valid() ){
201
- return false;
202
- }
203
-
204
- //If the URL isn't specified get it from the link record
205
- $link = new blcLink( intval($this->link_id) );
206
- return $link->url;
207
- }
208
- }
209
-
210
- class blcLinkInstance_post_link extends blcLinkInstance {
211
-
212
- var $post_permalink = '';
213
- var $changed_links = 0;
214
-
215
- function edit($old_url, $new_url){
216
- global $wpdb;
217
-
218
- if ( !$this->valid() ){
219
- return false;
220
- }
221
-
222
- //If the old URL isn't specified get it from the link record
223
- if ( empty($old_url) ){
224
- $old_url = $this->get_url();
225
- }
226
-
227
- //Load the post
228
- $post = get_post($this->source_id, ARRAY_A);
229
- if (!$post){
230
- //FB::error('Can\'t load post ' . $this->source_id);
231
- return false;
232
- }
233
- //FB::info('Post ' . $this->source_id . ' loaded successfully');
234
- //Figure out the post's permalink - it'll be needed when normalizing relative URLs
235
- $this->post_permalink = get_permalink( $post['ID'] );
236
-
237
- $this->old_url = $old_url;
238
- $this->new_url = $new_url;
239
-
240
- //Track how many links in the post are successfully edited so that we can report an error if none are.
241
- $this->changed_links = 0;
242
-
243
- //Find all links and replace those that match $old_url.
244
- $content = preg_replace_callback(blcUtility::link_pattern(), array(&$this, 'edit_callback'), $post['post_content']);
245
-
246
- if ( $this->changed_links <= 0 ){
247
- //FB::error("Didn't find any links to edit in this post!");
248
- return false;
249
- }
250
-
251
- //Clear the post/page cache. This ensures that any further calls to this method
252
- //will not load the post content from the cache and thus discard the changes
253
- //we just made.
254
- if ( 'page' == $post['post_type'] )
255
- clean_page_cache($this->source_id);
256
- else
257
- clean_post_cache($this->source_id);
258
-
259
- //Update the post
260
- $rez = $wpdb->update(
261
- $wpdb->posts,
262
- array( 'post_content' => $content ),
263
- array( 'id' => $this->source_id )
264
- );
265
-
266
- return $rez !== false;
267
- }
268
-
269
- function edit_callback($matches){
270
- $url = blcUtility::normalize_url($matches[3], $this->post_permalink);
271
- //FB::log('Found a link with URL "' . $matches[3] . '", normalized URL = "' . $url . '"');
272
-
273
- if ($url == $this->old_url){
274
- //FB::log('Changing this link');
275
- $this->changed_links++;
276
- return $matches[1].$matches[2].$this->new_url.$matches[2].$matches[4].$matches[5].$matches[6];
277
- } else {
278
- return $matches[0];
279
- }
280
- }
281
-
282
- function unlink( $url = null ){
283
- global $wpdb;
284
-
285
- if ( !$this->valid() ){
286
- return false;
287
- }
288
-
289
- //If the URL isn't specified get it from the link record
290
- if ( empty($url) ){
291
- $url = $this->get_url();
292
- }
293
-
294
- //Load the post
295
- $post = get_post($this->source_id, ARRAY_A);
296
- if (!$post){
297
- //FB::error('Can\'t load post ' . $this->source_id);
298
- return false;
299
- }
300
- //FB::info('Post ' . $this->source_id . ' loaded successfully');
301
- //Figure out the post's permalink - it'll be needed when normalizing relative URLs
302
- $this->post_permalink = get_permalink( $post['ID'] );
303
-
304
- //Track how many links in the post are successfully removed so that we can report an error if none are.
305
- $this->changed_links = 0;
306
-
307
- //Find all links and remove those that match $url.
308
- $this->old_url = $url; //used by the callback
309
- $content = preg_replace_callback(blcUtility::link_pattern(), array(&$this, 'unlink_callback'), $post['post_content']);
310
-
311
- if ( $this->changed_links <= 0 ){
312
- return false;
313
- }
314
-
315
- //Clear the post/page cache. This ensures that any further calls to this method
316
- //will not load the post content from the cache and thus discard the changes
317
- //we just made.
318
- if ( 'page' == $post['post_type'] )
319
- clean_page_cache($this->source_id);
320
- else
321
- clean_post_cache($this->source_id);
322
-
323
- //Update the post
324
- $rez = $wpdb->update(
325
- $wpdb->posts,
326
- array( 'post_content' => $content ),
327
- array( 'id' => $this->source_id )
328
- );
329
-
330
- if ( $rez !== false ){
331
- //Delete the instance record
332
- //FB::info("Post updated, deleting instance from DB");
333
- return $this->forget() !== false;
334
- } else {
335
- //FB::error("Failed to update the post");
336
- return false;
337
- };
338
- }
339
-
340
- /**
341
- * blcLinkInstance_post_link::unlink_callback()
342
- * Remove the link while leaving the anchor text intact.
343
- *
344
- * @uses $blc_config_manager Global variable pointing to the plugin's configuration manager
345
- *
346
- * @param array $matches
347
- * @return string
348
- */
349
- function unlink_callback($matches){
350
- global $blc_config_manager;
351
-
352
- $url = blcUtility::normalize_url($matches[3], $this->post_permalink);
353
-
354
- //Does the URL match?
355
- if ($url == $this->old_url){
356
- $this->changed_links++;
357
- if ( $blc_config_manager->options['mark_removed_links'] ){
358
- //leave only the anchor text + the removed_link CSS class
359
- return '<span class="removed_link">' . $matches[5] . '</span>';
360
- } else {
361
- return $matches[5]; //just the anchor text
362
- }
363
-
364
- } else {
365
- return $matches[0]; //return the link unchanged
366
- }
367
- }
368
-
369
- }
370
-
371
- class blcLinkInstance_post_image extends blcLinkInstance {
372
-
373
- var $post_permalink = '';
374
- var $changed_images = 0;
375
-
376
- function edit($old_url, $new_url){
377
- global $wpdb;
378
-
379
- if ( !$this->valid() ){
380
- return false;
381
- }
382
-
383
- //If the URL isn't specified get it from the link record
384
- if ( empty($old_url) ){
385
- $old_url = $this->get_url();
386
- }
387
-
388
- //Load the post
389
- $post = get_post($this->source_id, ARRAY_A);
390
- if (!$post){
391
- return false;
392
- }
393
- //Figure out the post's permalink - it'll be needed when normalizing relative URLs
394
- $this->post_permalink = get_permalink( $post['ID'] );
395
-
396
- $this->old_url = $old_url;
397
- $this->new_url = $new_url;
398
-
399
- //Find all images and change the URL of those that match $old_url.
400
- //Note : this might be inefficient if there's more than one instance of the same link
401
- //in one post, as each instances would be called when editing the link.
402
- //Either way, I thing the overhead is small enough to ignore for now.
403
- $this->changed_images = 0;
404
- $content = preg_replace_callback(blcUtility::img_pattern(), array(&$this, 'edit_callback'), $post['post_content']);
405
-
406
- if ( $this->changed_images <= 0 ){
407
- return false;
408
- }
409
-
410
- //Clear the post/page cache. This ensures that any further calls to this method
411
- //will not load the post content from the cache and thus discard the changes
412
- //we just made.
413
- if ( 'page' == $post['post_type'] )
414
- clean_page_cache($this->source_id);
415
- else
416
- clean_post_cache($this->source_id);
417
-
418
- //Save the modified post
419
- $rez = $wpdb->update(
420
- $wpdb->posts,
421
- array( 'post_content' => $content ),
422
- array( 'id' => $this->source_id )
423
- );
424
-
425
- return $rez !== false;
426
- }
427
-
428
- function edit_callback($matches){
429
- $url = blcUtility::normalize_url($matches[3], $this->post_permalink);
430
-
431
- if ($url == $this->old_url){
432
- $this->changed_images++;
433
- return $matches[1].$matches[2].$this->new_url.$matches[2].$matches[4].$matches[5];
434
- } else {
435
- return $matches[0];
436
- }
437
- }
438
-
439
- function unlink( $url = null ){
440
- global $wpdb;
441
-
442
- if ( !$this->valid() ){
443
- return false;
444
- }
445
-
446
- //If the URL isn't specified get it from the link record
447
- if ( empty($url) ){
448
- $url = $this->get_url();
449
- }
450
-
451
- //Load the post
452
- $post = get_post($this->source_id, ARRAY_A);
453
- if (!$post){
454
- //FB::error('Can\'t load post ' . $this->source_id);
455
- return false;
456
- }
457
- //FB::info('Post ' . $this->source_id . ' loaded successfully');
458
- //Figure out the post's permalink - it'll be needed when normalizing relative URLs
459
- $this->post_permalink = get_permalink( $post['ID'] );
460
-
461
- //Find all links and remove those that match $url.
462
- $this->old_url = $url; //used by the callback
463
- $this->changed_images = 0;
464
- $content = preg_replace_callback(blcUtility::img_pattern(), array(&$this, 'unlink_callback'), $post['post_content']);
465
-
466
- if ( $this->changed_images <= 0 ){
467
- return false;
468
- }
469
-
470
- //Clear the post/page cache. This ensures that any further calls to this method
471
- //will not load the post content from the cache and thus discard the changes
472
- //we just made.
473
- if ( 'page' == $post['post_type'] )
474
- clean_page_cache($this->source_id);
475
- else
476
- clean_post_cache($this->source_id);
477
-
478
- //Save the modified post
479
- $rez = $wpdb->update(
480
- $wpdb->posts,
481
- array( 'post_content' => $content ),
482
- array( 'id' => $this->source_id )
483
- );
484
-
485
- if ( $rez !== false ){
486
- //Delete the instance record
487
- //FB::info("Post updated, deleting instance from DB");
488
- return $this->forget() !== false;
489
- } else {
490
- //FB::error("Failed to update the post");
491
- return false;
492
- };
493
- }
494
-
495
- function unlink_callback($matches){
496
- $url = blcUtility::normalize_url($matches[3], $this->post_permalink);
497
-
498
- if ($url == $this->old_url){
499
- $this->changed_images++;
500
- return ''; //remove the image completely
501
- } else {
502
- return $matches[0]; //return the image unchanged
503
- }
504
- }
505
-
506
- }
507
-
508
- class blcLinkInstance_custom_field_link extends blcLinkInstance {
509
-
510
- function edit($old_url, $new_url){
511
- if ( !$this->valid() ){
512
- return false;
513
- }
514
-
515
- //If the URL isn't specified get it from the link record
516
- if ( empty($old_url) ){
517
- $old_url = $this->old_url;
518
- }
519
-
520
- //FB::log("Changing [{$this->link_text}] to '$new_url' on post {$this->source_id}");
521
- //Change the meta value
522
- return update_post_meta( $this->source_id, $this->link_text, $new_url, $old_url );
523
- }
524
-
525
- function unlink( $url = null ){
526
- //Get the URL from the link record if it wasn't specified
527
- if ( empty($url) ){
528
- $url = $this->get_url();
529
- }
530
-
531
- //FB::log("Removing [{$this->link_text}] from post {$this->source_id} where value is '$url'");
532
- delete_post_meta( $this->source_id, $this->link_text, $url );
533
- //TODO: Make unlink work for custom fields where the URL is only the first line, not the entire value
534
-
535
- return $this->forget() !== false;
536
- }
537
-
538
- }
539
-
540
- class blcLinkInstance_blogroll_link extends blcLinkInstance {
541
-
542
- function edit($old_url, $new_url){
543
- if ( !$this->valid() ){
544
- return false;
545
- }
546
-
547
- //FB::log("Changing the bookmark [{$this->link_text}] to '$new_url'");
548
- //Update the bookmark. Note : wp_update_link calls the edit_link hook, which is also
549
- //hooked by the plugin for maintaining bookmark->instance integrity... Conclusion :
550
- //don't ever call $instance->edit() in that hook!
551
- return wp_update_link( array(
552
- 'link_id' => $this->source_id,
553
- 'link_url' => $new_url
554
- ) );
555
- }
556
-
557
- function unlink( $url = null ){
558
- if ( !$this->valid() ){
559
- return false;
560
- }
561
-
562
- //Get the URL from the link record if it wasn't specified
563
- if ( empty($url) ){
564
- $url = $this->get_url();
565
- }
566
-
567
- //FB::log("Removing bookmark [{$this->link_text}] ( ID : {$this->source_id} )");
568
- //Note : wp_delete_link calls the delete_link hook, which is also used by the plugin
569
- //for removing instances associated with links deleted through the WP link manager.
570
- //This means that when you delete a bookmark via the plugin's interface, the plugin will
571
- //attempt to delete it twice. Anybody have a better idea?
572
- if ( wp_delete_link( $this->source_id ) ){
573
- return $this->forget() !== false;
574
- } else {
575
- //FB::error("Failed to delete the bookmark.");
576
- return false;
577
- };
578
-
579
- }
580
-
581
- }
582
-
583
- }//class_exists
584
-
585
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/sprintf.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * sprintf() for JavaScript v.0.4
3
+ *
4
+ * Copyright (c) 2007 Alexandru Marasteanu <http://alexei.417.ro/>
5
+ * Thanks to David Baird (unit test and patch).
6
+ *
7
+ * This program is free software; you can redistribute it and/or modify it under
8
+ * the terms of the GNU General Public License as published by the Free Software
9
+ * Foundation; either version 2 of the License, or (at your option) any later
10
+ * version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful, but WITHOUT
13
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15
+ * details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License along with
18
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19
+ * Place, Suite 330, Boston, MA 02111-1307 USA
20
+ */
21
+
22
+ function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }
23
+
24
+ function sprintf () {
25
+ var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
26
+ while (f) {
27
+ if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
28
+ else if (m = /^\x25{2}/.exec(f)) o.push('%');
29
+ else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
30
+ if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
31
+ if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
32
+ throw("Expecting number but found " + typeof(a));
33
+ switch (m[7]) {
34
+ case 'b': a = a.toString(2); break;
35
+ case 'c': a = String.fromCharCode(a); break;
36
+ case 'd': a = parseInt(a); break;
37
+ case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
38
+ case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
39
+ case 'o': a = a.toString(8); break;
40
+ case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
41
+ case 'u': a = Math.abs(a); break;
42
+ case 'x': a = a.toString(16); break;
43
+ case 'X': a = a.toString(16).toUpperCase(); break;
44
+ }
45
+ a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
46
+ c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
47
+ x = m[5] - String(a).length;
48
+ p = m[5] ? str_repeat(c, x) : '';
49
+ o.push(m[4] ? a + p : p + a);
50
+ }
51
+ else throw ("Huh ?!");
52
+ f = f.substring(m[0].length);
53
+ }
54
+ return o.join('');
55
+ }
languages/broken-link-checker-cs_CZ.mo ADDED
Binary file
languages/broken-link-checker-cs_CZ.po ADDED
@@ -0,0 +1,872 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Czech translation for Broken Link Checker plugin.
2
+ # Copyright (C) 2010 Janis Elsts
3
+ # This file is distributed under the same license as the Broken Link Checker package.
4
+ # Janis Elsts <whiteshadow@w-shadow.com>, 2010.
5
+ # Lelkoun <vydrus@klikni.cz>, http://lelkoun.cz/, 2010.
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: Broken Link Checker 0.8\n"
9
+ "Report-Msgid-Bugs-To: whiteshadow@w-shadow.com\n"
10
+ "POT-Creation-Date: 2010-01-24 15:25+0000\n"
11
+ "PO-Revision-Date: 2010-03-11 00:54+0100\n"
12
+ "Last-Translator: Lelkoun <vydrus@klikni.cz>\n"
13
+ "MIME-Version: 1.0\n"
14
+ "Content-Type: text/plain; charset=utf-8\n"
15
+ "Content-Transfer-Encoding: 8bit\n"
16
+ "Plural-Forms: nplurals=2; plural=n != 1;\n"
17
+ "Language-Team: Czech <vydrus@klikni.cz>\n"
18
+
19
+ #: core.php:143
20
+ #: core.php:1835
21
+ msgid "Loading..."
22
+ msgstr "Načítání..."
23
+
24
+ #: core.php:166
25
+ #: core.php:604
26
+ msgid "[ Network error ]"
27
+ msgstr "[ Chyba sítě ]"
28
+
29
+ #: core.php:191
30
+ msgid "Automatically expand the widget if broken links have been detected"
31
+ msgstr "Automaticky rozvinout widget v případě detekování nefunkčních odkazů"
32
+
33
+ #: core.php:375
34
+ #: core.php:384
35
+ #: core.php:414
36
+ #: core.php:426
37
+ #: core.php:1014
38
+ #: core.php:1038
39
+ #: core.php:1316
40
+ #, php-format
41
+ msgid "Database error : %s"
42
+ msgstr "Chyba databáze: %s"
43
+
44
+ #: core.php:452
45
+ msgid "Link Checker Settings"
46
+ msgstr "Link Checker - Nastavení"
47
+
48
+ #: core.php:453
49
+ msgid "Link Checker"
50
+ msgstr "Link Checker"
51
+
52
+ #: core.php:459
53
+ msgid "View Broken Links"
54
+ msgstr "Zobrazit nefunkční odkazy"
55
+
56
+ #: core.php:460
57
+ #: core.php:892
58
+ msgid "Broken Links"
59
+ msgstr "Nefunkční odkazy"
60
+
61
+ #: core.php:484
62
+ msgid "Settings"
63
+ msgstr "Nastavení"
64
+
65
+ #: core.php:568
66
+ msgid "Broken Link Checker Options"
67
+ msgstr "Broken Link Checker - Nastavení"
68
+
69
+ #: core.php:581
70
+ msgid "Status"
71
+ msgstr "Status"
72
+
73
+ #: core.php:583
74
+ #: core.php:823
75
+ msgid "Show debug info"
76
+ msgstr "Zobrazit ladící informace"
77
+
78
+ #: core.php:617
79
+ msgid "Re-check all pages"
80
+ msgstr "Překontrolovat všechny stránky"
81
+
82
+ #: core.php:641
83
+ msgid "Check each link"
84
+ msgstr "Zkontrolovat každý odkaz"
85
+
86
+ #: core.php:646
87
+ #, php-format
88
+ msgid "Every %s hours"
89
+ msgstr "Každých %s hodin"
90
+
91
+ #: core.php:655
92
+ msgid "Existing links will be checked this often. New links will usually be checked ASAP."
93
+ msgstr "Existující odkazy budou kontrolovány v určeném intervalu. Nové odkazy budou zkontrolovány hned, jak to bude možné."
94
+
95
+ #: core.php:662
96
+ msgid "Broken link CSS"
97
+ msgstr "CSS nefunkčního odkazu"
98
+
99
+ #: core.php:667
100
+ msgid "Apply <em>class=\"broken_link\"</em> to broken links"
101
+ msgstr "Přidat <em>class=\"broken_link\"</em> k nefunkčním odkazům"
102
+
103
+ #: core.php:679
104
+ msgid "Removed link CSS"
105
+ msgstr "CSS odstraněného odkazu"
106
+
107
+ #: core.php:684
108
+ msgid "Apply <em>class=\"removed_link\"</em> to unlinked links"
109
+ msgstr "Přidat <em>class=\"removed_link\"</em> k odstraněným odkazům"
110
+
111
+ #: core.php:696
112
+ msgid "Exclusion list"
113
+ msgstr "Seznam výjimek"
114
+
115
+ #: core.php:697
116
+ msgid "Don't check links where the URL contains any of these words (one per line) :"
117
+ msgstr "Nekontrolovat odkazy, kde URL adresa obsahuje nějaký z těchto výrazů (jeden na řádek):"
118
+
119
+ #: core.php:707
120
+ msgid "Custom fields"
121
+ msgstr "Uživatelské pole"
122
+
123
+ #: core.php:708
124
+ msgid "Check URLs entered in these custom fields (one per line) :"
125
+ msgstr "Kontrolovat URL adresy v tomto poli (jednu na řádek):"
126
+
127
+ #: core.php:719
128
+ msgid "Advanced"
129
+ msgstr "Pokročilé"
130
+
131
+ #: core.php:725
132
+ msgid "Timeout"
133
+ msgstr "Čas vypršení"
134
+
135
+ #: core.php:731
136
+ #: core.php:787
137
+ #, php-format
138
+ msgid "%s seconds"
139
+ msgstr "%s sekund"
140
+
141
+ #: core.php:740
142
+ msgid "Links that take longer than this to load will be marked as broken."
143
+ msgstr "Odkazy, jejichž načtení bude trvat delší dobu než tuto, budou označeny jako nefunkční."
144
+
145
+ #: core.php:749
146
+ msgid "Custom temporary directory"
147
+ msgstr "Uživatelský dočasný adresář"
148
+
149
+ #: core.php:758
150
+ #: core.php:2792
151
+ msgid "OK"
152
+ msgstr "OK"
153
+
154
+ #: core.php:761
155
+ msgid "Error : This directory isn't writable by PHP."
156
+ msgstr "Chyba: Tento adresář není zapisovatelný jazykem PHP."
157
+
158
+ #: core.php:766
159
+ msgid "Error : This directory doesn't exist."
160
+ msgstr "Chyba: Tento adresář neexistuje."
161
+
162
+ #: core.php:774
163
+ msgid "Set this field if you want the plugin to use a custom directory for its lockfiles. Otherwise, leave it blank."
164
+ msgstr "Nastavte toto pole, pokud chcete použít uživatelský adresář pro ukládání zamykacích souborů. Jinak jej nechte prázdné."
165
+
166
+ #: core.php:781
167
+ msgid "Max. execution time"
168
+ msgstr "Max. čas provádění"
169
+
170
+ #: core.php:798
171
+ msgid "The plugin works by periodically creating a background worker instance that parses your posts looking for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the background instance may run each time before stopping."
172
+ msgstr "Plugin pracuje na principu periodického vytváření pracovní instance na pozadí, která hledá odkazy ve vašich příspěvcích, kontroluje objevené URL adresy a vykonává další časově náročné úkoly. Zde můžete nastavit, jakou maximální dobu bude instance na pozadí pracovat každou periodu, než bude zastavena."
173
+
174
+ #: core.php:808
175
+ msgid "Save Changes"
176
+ msgstr "Uložit změny"
177
+
178
+ #: core.php:821
179
+ msgid "Hide debug info"
180
+ msgstr "Skrýt ladící informace"
181
+
182
+ #: core.php:891
183
+ msgid "Broken"
184
+ msgstr "Nefunkční"
185
+
186
+ #: core.php:893
187
+ msgid "No broken links found"
188
+ msgstr "Nebyly nalezeny žádné nefunkční odkazy."
189
+
190
+ #: core.php:897
191
+ msgid "Redirects"
192
+ msgstr "Přesměrování"
193
+
194
+ #: core.php:898
195
+ msgid "Redirected Links"
196
+ msgstr "Přesměrované odkazy"
197
+
198
+ #: core.php:899
199
+ msgid "No redirects found"
200
+ msgstr "Nebyla nalezena žádná přesměrování"
201
+
202
+ #: core.php:904
203
+ msgid "All"
204
+ msgstr "Všechno"
205
+
206
+ #: core.php:905
207
+ msgid "Detected Links"
208
+ msgstr "Odstraněné odkazy"
209
+
210
+ #: core.php:906
211
+ msgid "No links found (yet)"
212
+ msgstr "Ještě nebyly nalezeny žádné odkazy"
213
+
214
+ #: core.php:933
215
+ #: core.php:1281
216
+ msgid "No links found for your query"
217
+ msgstr "Nebyly nalezeny žádné odkazy na váš dotaz"
218
+
219
+ #: core.php:994
220
+ msgid "You must enter a filter name!"
221
+ msgstr "Musíte zadat jméno filtru!"
222
+
223
+ #: core.php:998
224
+ msgid "Invalid search query."
225
+ msgstr "Nevalidní hledací dotaz."
226
+
227
+ #: core.php:1009
228
+ #, php-format
229
+ msgid "Filter \"%s\" created"
230
+ msgstr "Filtr \"%s\" byl vytvořen."
231
+
232
+ #: core.php:1026
233
+ msgid "Filter ID not specified."
234
+ msgstr "ID filtru nebylo specifikováno."
235
+
236
+ #: core.php:1035
237
+ msgid "Filter deleted"
238
+ msgstr "Filtr byl smazán"
239
+
240
+ #: core.php:1084
241
+ #, php-format
242
+ msgid "Failed to delete post \"%s\" (%d)"
243
+ msgstr "Selhalo smazání příspěvku \"%s\" (%d)"
244
+
245
+ #: core.php:1097
246
+ #, php-format
247
+ msgid "%d post moved to the trash"
248
+ msgid_plural "%d posts moved to the trash"
249
+ msgstr[0] "%d příspěvek byl přesunut do koše"
250
+ msgstr[1] "%d příspěvků bylo přesunuto do koše"
251
+
252
+ #: core.php:1099
253
+ #, php-format
254
+ msgid "%d post deleted"
255
+ msgid_plural "%d posts deleted"
256
+ msgstr[0] "%d příspěvek byl smazán"
257
+ msgstr[1] "%d příspěvků bylo smazán"
258
+
259
+ #: core.php:1134
260
+ #, php-format
261
+ msgid "Failed to delete blogroll link \"%s\" (%d)"
262
+ msgstr "Selhalo smazání odkazu \"%s\" (%d)"
263
+
264
+ #: core.php:1144
265
+ #, php-format
266
+ msgid "%d blogroll link deleted"
267
+ msgid_plural "%d blogroll links deleted"
268
+ msgstr[0] "%d odkaz byl smazán"
269
+ msgstr[1] "%d odkazů bylo smazáno"
270
+
271
+ #: core.php:1153
272
+ msgid "Didn't find anything to delete!"
273
+ msgstr "Nebylo nalezeno nic, co by se mohlo smazat!"
274
+
275
+ #: core.php:1191
276
+ #, php-format
277
+ msgid "%d link removed"
278
+ msgid_plural "%d links removed"
279
+ msgstr[0] "%d odkaz byl odstraněn."
280
+ msgstr[1] "%d odkazů bylo odstraněno"
281
+
282
+ #: core.php:1202
283
+ #, php-format
284
+ msgid "Failed to remove %d link"
285
+ msgid_plural "Failed to remove %d links"
286
+ msgstr[0] "Selhalo odstranění %d odkazu"
287
+ msgstr[1] "Selhalo odstranění %d odkazů"
288
+
289
+ #: core.php:1243
290
+ #, php-format
291
+ msgid "Replaced %d redirect with a direct link"
292
+ msgid_plural "Replaced %d redirects with direct links"
293
+ msgstr[0] "Nahrazeno %d přesměrování přímým odkazem"
294
+ msgstr[1] "Nahrazeno %d přesměrování přímými odkazy"
295
+
296
+ #: core.php:1254
297
+ #, php-format
298
+ msgid "Failed to fix %d redirect"
299
+ msgid_plural "Failed to fix %d redirects"
300
+ msgstr[0] "Selhalo opravení %d přesměrování"
301
+ msgstr[1] "Selhalo opravení %d přesměrování"
302
+
303
+ #: core.php:1264
304
+ msgid "None of the selected links are redirects!"
305
+ msgstr "Žádný z vybraných odkazů není přesměrován!"
306
+
307
+ #: core.php:1279
308
+ #: core.php:1400
309
+ msgid "Search"
310
+ msgstr "Hledat"
311
+
312
+ #: core.php:1280
313
+ msgid "Search Results"
314
+ msgstr "Výsledky hledání"
315
+
316
+ #: core.php:1384
317
+ msgid "Save This Search As a Filter"
318
+ msgstr "Uložit toto hledání jako filtr"
319
+
320
+ #: core.php:1394
321
+ msgid "Delete This Filter"
322
+ msgstr "Smazat tento filtr"
323
+
324
+ #: core.php:1410
325
+ msgid "Link text"
326
+ msgstr "Text odkazu"
327
+
328
+ #: core.php:1413
329
+ #: core.php:1526
330
+ msgid "URL"
331
+ msgstr "URL"
332
+
333
+ #: core.php:1416
334
+ #: core.php:2141
335
+ msgid "HTTP code"
336
+ msgstr "HTTP kód"
337
+
338
+ #: core.php:1419
339
+ msgid "Link status"
340
+ msgstr "Status odkazu"
341
+
342
+ #: core.php:1435
343
+ msgid "Link type"
344
+ msgstr "Typ odkazu"
345
+
346
+ #: core.php:1439
347
+ msgid "Any"
348
+ msgstr "Žádný"
349
+
350
+ #: core.php:1440
351
+ msgid "Normal link"
352
+ msgstr "Normální odkaz"
353
+
354
+ #: core.php:1441
355
+ #: core.php:1602
356
+ msgid "Image"
357
+ msgstr "Obrázek"
358
+
359
+ #: core.php:1442
360
+ #: core.php:1613
361
+ msgid "Custom field"
362
+ msgstr "Uživatelské pole"
363
+
364
+ #: core.php:1443
365
+ #: core.php:1621
366
+ msgid "Bookmark"
367
+ msgstr "Záložka"
368
+
369
+ #: core.php:1456
370
+ msgid "Search Links"
371
+ msgstr "Hledat odkazy"
372
+
373
+ #: core.php:1457
374
+ #: core.php:1654
375
+ msgid "Cancel"
376
+ msgstr "Zrušit"
377
+
378
+ #: core.php:1473
379
+ msgid "Bulk Actions"
380
+ msgstr "Hromadné akce"
381
+
382
+ #: core.php:1474
383
+ #: core.php:1639
384
+ #: core.php:1922
385
+ msgid "Unlink"
386
+ msgstr "Odstranit odkaz"
387
+
388
+ #: core.php:1475
389
+ msgid "Fix redirects"
390
+ msgstr "Opravit přesměrování"
391
+
392
+ #: core.php:1476
393
+ msgid "Delete sources"
394
+ msgstr "Smazat zdroje"
395
+
396
+ #: core.php:1490
397
+ #: core.php:1686
398
+ msgid "Apply"
399
+ msgstr "Aplikovat"
400
+
401
+ #: core.php:1497
402
+ msgid "&laquo;"
403
+ msgstr "&laquo;"
404
+
405
+ #: core.php:1498
406
+ msgid "&raquo;"
407
+ msgstr "&raquo;"
408
+
409
+ #: core.php:1505
410
+ #: core.php:1692
411
+ #, php-format
412
+ msgid "Displaying %s&#8211;%s of <span class=\"current-link-count\">%s</span>"
413
+ msgstr "Zobrazeno %s&#8211;%s z <span class=\"current-link-count\">%s</span>"
414
+
415
+ #: core.php:1524
416
+ msgid "Source"
417
+ msgstr "Zdroj"
418
+
419
+ #: core.php:1525
420
+ msgid "Link Text"
421
+ msgstr "Text odkazu"
422
+
423
+ #: core.php:1557
424
+ #: core.php:1563
425
+ msgid "Edit this post"
426
+ msgstr "Upravit tento příspěvek"
427
+
428
+ #: core.php:1563
429
+ #: core.php:1578
430
+ msgid "Edit"
431
+ msgstr "Upravit"
432
+
433
+ #: core.php:1564
434
+ msgid "Delete this post"
435
+ msgstr "Smazat tento příspěvek"
436
+
437
+ #: core.php:1564
438
+ #, php-format
439
+ msgid ""
440
+ "You are about to delete this post '%s'\n"
441
+ " 'Cancel' to stop, 'OK' to delete."
442
+ msgstr ""
443
+ "Chystáte se smazat tento příspěvek '%s'\n"
444
+ " Klikněte na 'Cancel' pro zrušení, 'OK' pro smazání."
445
+
446
+ #: core.php:1564
447
+ #: core.php:1579
448
+ msgid "Delete"
449
+ msgstr "Smazat"
450
+
451
+ #: core.php:1566
452
+ #, php-format
453
+ msgid "View \"%s\""
454
+ msgstr "Zobrazit \"%s\""
455
+
456
+ #: core.php:1566
457
+ msgid "View"
458
+ msgstr "Zobrazit"
459
+
460
+ #: core.php:1573
461
+ #: core.php:1578
462
+ msgid "Edit this bookmark"
463
+ msgstr "Upravit tuto záložku"
464
+
465
+ #: core.php:1579
466
+ #, php-format
467
+ msgid ""
468
+ "You are about to delete this link '%s'\n"
469
+ " 'Cancel' to stop, 'OK' to delete."
470
+ msgstr ""
471
+ "Chystáte se smazat tento odkaz '%s'\n"
472
+ " Klikněte na 'Cancel' pro zrušení, 'OK' pro smazání."
473
+
474
+ #: core.php:1588
475
+ msgid "[An orphaned link! This is a bug.]"
476
+ msgstr "[Osiřelý odkaz! Toto je chyba programu.]"
477
+
478
+ #: core.php:1636
479
+ msgid "Show more info about this link"
480
+ msgstr "Ukázat více informací o tomto odkazu"
481
+
482
+ #: core.php:1636
483
+ #: core.php:3085
484
+ msgid "Details"
485
+ msgstr "Detaily"
486
+
487
+ #: core.php:1638
488
+ msgid "Remove this link from all posts"
489
+ msgstr "Odstranit tento odkaz ze všech příspěvků"
490
+
491
+ #: core.php:1642
492
+ #: core.php:1952
493
+ #: core.php:1963
494
+ msgid "Excluded"
495
+ msgstr "Vynechán"
496
+
497
+ #: core.php:1644
498
+ msgid "Add this URL to the exclusion list"
499
+ msgstr "Přidat tuto URL adresu do seznamu výjimek"
500
+
501
+ #: core.php:1645
502
+ #: core.php:1966
503
+ msgid "Exclude"
504
+ msgstr "Vynechat"
505
+
506
+ #: core.php:1648
507
+ msgid "Edit link URL"
508
+ msgstr "Upravit URL adresu odkazu"
509
+
510
+ #: core.php:1648
511
+ #: core.php:1863
512
+ #: core.php:1891
513
+ msgid "Edit URL"
514
+ msgstr "Upravit URL"
515
+
516
+ #: core.php:1654
517
+ msgid "Cancel URL editing"
518
+ msgstr "Zrušit upravování URL adresy"
519
+
520
+ #: core.php:1668
521
+ msgid "Remove this link from the list of broken links and mark it as valid"
522
+ msgstr "Odstranit tento odkaz ze seznamu nefunkčních odkazů a označit jako validní"
523
+
524
+ #: core.php:1670
525
+ #: core.php:1755
526
+ msgid "Discard"
527
+ msgstr "Zrušit"
528
+
529
+ #: core.php:1731
530
+ #: core.php:1898
531
+ #: core.php:1935
532
+ msgid "Wait..."
533
+ msgstr "Čekejte..."
534
+
535
+ #: core.php:1789
536
+ msgid "Save URL"
537
+ msgstr "Uložit URL"
538
+
539
+ #: core.php:1799
540
+ msgid "Saving changes..."
541
+ msgstr "Ukládání změn..."
542
+
543
+ #: core.php:2011
544
+ msgid "Enter a name for the new custom filter"
545
+ msgstr "Zadejte jméno pro nový uživatelský filtr"
546
+
547
+ #: core.php:2022
548
+ msgid ""
549
+ "You are about to delete the current filter.\n"
550
+ "'Cancel' to stop, 'OK' to delete"
551
+ msgstr ""
552
+ "Chystáte se smazat současný filtr '%s'\n"
553
+ " Klikněte na 'Cancel' pro zrušení, 'OK' pro smazání."
554
+
555
+ #: core.php:2118
556
+ msgid "Log"
557
+ msgstr "Záznam"
558
+
559
+ #: core.php:2126
560
+ msgid "Post published on"
561
+ msgstr "Příspěvek publikován"
562
+
563
+ #: core.php:2131
564
+ msgid "Link last checked"
565
+ msgstr "Poslední kontrola odkazu"
566
+
567
+ #: core.php:2135
568
+ msgid "Never"
569
+ msgstr "Nikdy"
570
+
571
+ #: core.php:2146
572
+ msgid "Response time"
573
+ msgstr "Čas odpovědi"
574
+
575
+ #: core.php:2148
576
+ #, php-format
577
+ msgid "%2.3f seconds"
578
+ msgstr "%2.3f sekund"
579
+
580
+ #: core.php:2151
581
+ msgid "Final URL"
582
+ msgstr "Konečné URL"
583
+
584
+ #: core.php:2156
585
+ msgid "Redirect count"
586
+ msgstr "Počet přesměrování"
587
+
588
+ #: core.php:2161
589
+ msgid "Instance count"
590
+ msgstr "Počet instancí"
591
+
592
+ #: core.php:2170
593
+ #, php-format
594
+ msgid "This link has failed %d time."
595
+ msgid_plural "This link has failed %d times."
596
+ msgstr[0] "Tento link selhal %d x."
597
+ msgstr[1] "Tento link selhal %d x."
598
+
599
+ #: core.php:2580
600
+ #: core.php:2910
601
+ msgid "This link wasn't checked because a matching keyword was found on your exclusion list."
602
+ msgstr "Tento odkaz nebyl zkontrolován kvůli klíčovému slovu nalezenému ve vašem seznam výjimek."
603
+
604
+ #: core.php:2622
605
+ msgid "View broken links"
606
+ msgstr "Zobrazit nefunkční odkazy"
607
+
608
+ #: core.php:2623
609
+ #, php-format
610
+ msgid "Found %d broken link"
611
+ msgid_plural "Found %d broken links"
612
+ msgstr[0] "Nalezen %d nefunkční odkaz"
613
+ msgstr[1] "Nalezeno %d nefunkčních odkazů"
614
+
615
+ #: core.php:2629
616
+ msgid "No broken links found."
617
+ msgstr "Nebyly nalezeny žádné nefunkční odkazy."
618
+
619
+ #: core.php:2636
620
+ #, php-format
621
+ msgid "%d URL in the work queue"
622
+ msgid_plural "%d URLs in the work queue"
623
+ msgstr[0] "%d URL adresa ve frontě"
624
+ msgstr[1] "%d URL adres ve frontě"
625
+
626
+ #: core.php:2639
627
+ msgid "No URLs in the work queue."
628
+ msgstr "Žádná URL adresa není ve frontě."
629
+
630
+ #: core.php:2645
631
+ #, php-format
632
+ msgid "Detected %d unique URL"
633
+ msgid_plural "Detected %d unique URLs"
634
+ msgstr[0] "Nalezena %d unikátní URL adresa"
635
+ msgstr[1] "Nalezeno %d unikátních URL adres"
636
+
637
+ #: core.php:2646
638
+ #, php-format
639
+ msgid "in %d link"
640
+ msgid_plural "in %d links"
641
+ msgstr[0] "v %d odkazu"
642
+ msgstr[1] "v %d odkazech"
643
+
644
+ #: core.php:2651
645
+ msgid "and still searching..."
646
+ msgstr "a pořád se hledá...."
647
+
648
+ #: core.php:2657
649
+ msgid "Searching your blog for links..."
650
+ msgstr "Hledání odkazů ve vašem blogu..."
651
+
652
+ #: core.php:2659
653
+ msgid "No links detected."
654
+ msgstr "Žádné odkazy nebyly zjištěny"
655
+
656
+ #: core.php:2731
657
+ #: core.php:2763
658
+ #: core.php:2806
659
+ #: core.php:2887
660
+ msgid "You're not allowed to do that!"
661
+ msgstr "Nejste oprávněni toto dělat!"
662
+
663
+ #: core.php:2739
664
+ #: core.php:2773
665
+ #: core.php:2816
666
+ #: core.php:2897
667
+ #, php-format
668
+ msgid "Oops, I can't find the link %d"
669
+ msgstr "Jejda, nemůžu najít odkaz %d"
670
+
671
+ #: core.php:2747
672
+ msgid "This link was manually marked as working by the user."
673
+ msgstr "Tento odkaz byl uživatelem manuálně označen jako funkční"
674
+
675
+ #: core.php:2753
676
+ msgid "Oops, couldn't modify the link!"
677
+ msgstr "Jejda, nemohl jsem upravit odkaz!"
678
+
679
+ #: core.php:2756
680
+ #: core.php:2833
681
+ msgid "Error : link_id not specified"
682
+ msgstr "Chyba: link_id nebyl specifikován"
683
+
684
+ #: core.php:2780
685
+ msgid "Oops, the new URL is invalid!"
686
+ msgstr "Jejda, nová URL adresa je nevalidní!"
687
+
688
+ #: core.php:2789
689
+ msgid "An unexpected error occured!"
690
+ msgstr "Nastala neočekávaná chyba!"
691
+
692
+ #: core.php:2798
693
+ msgid "Error : link_id or new_url not specified"
694
+ msgstr "Chyba: link_id nebo new_url nebylo specifikováno"
695
+
696
+ #: core.php:2823
697
+ #, php-format
698
+ msgid "URL %s was removed."
699
+ msgstr "URL adresa %s byla odstraněna"
700
+
701
+ #: core.php:2827
702
+ msgid "The plugin failed to remove the link."
703
+ msgstr "Plugin selhal při odstraňování odkazu."
704
+
705
+ #: core.php:2842
706
+ msgid "You don't have sufficient privileges to access this information!"
707
+ msgstr "Nemáte dostatečná práva k zpřístupnění těchto informací!"
708
+
709
+ #: core.php:2855
710
+ msgid "Error : link ID not specified"
711
+ msgstr "Chyba: ID odkazu nebylo specifikováno"
712
+
713
+ #: core.php:2879
714
+ #, php-format
715
+ msgid "Failed to load link details (%s)"
716
+ msgstr "Selhalo načtení detailů odkazu (%s)"
717
+
718
+ #: core.php:2917
719
+ #, php-format
720
+ msgid "URL %s added to the exclusion list"
721
+ msgstr "URL adresa %s byla přidána na seznam výjimek"
722
+
723
+ #: core.php:2921
724
+ msgid "Link ID not specified"
725
+ msgstr "ID odkazu nebylo specifikováno"
726
+
727
+ #: core.php:3071
728
+ #, php-format
729
+ msgid "The current temporary directory is not accessible; please <a href=\"%s\">set a different one</a>."
730
+ msgstr "Stávající dočasný adresář je nepřístupný; <a href=\"%s\">zvolte jiný</a>, prosím."
731
+
732
+ #: core.php:3076
733
+ #, php-format
734
+ msgid "Please make the directory <code>%1$s</code> writable by plugins or <a href=\"%2$s\">set a custom temporary directory</a>."
735
+ msgstr "Učiňte adresář <code>%1$s</code> zapisovatelným pro pluginy nebo <a href=\"%2$s\">nastavte uživatelský dočasný adresář</a>."
736
+
737
+ #: core.php:3083
738
+ msgid "Broken Link Checker can't create a lockfile."
739
+ msgstr "Broken Link Checker nemůže vytvořit zamykací soubor."
740
+
741
+ #: core.php:3088
742
+ msgid "The plugin uses a file-based locking mechanism to ensure that only one instance of the resource-heavy link checking algorithm is running at any given time. Unfortunately, BLC can't find a writable directory where it could store the lockfile - it failed to detect the location of your server's temporary directory, and the plugin's own directory isn't writable by PHP. To fix this problem, please make the plugin's directory writable or enter a specify a custom temporary directory in the plugin's settings."
743
+ msgstr "Plugin používá souborově založený zamykací mechanismus k zaručení vykonávání činnosti pouze jedné instance zdrojově náročného odkazy kontrolujícího algoritmu v dané době. Bohužel, BLC nemůže najít zapisovatelný adresář, kde by mohl skladovat zamykací soubory - selhalo zjištění pozice dočasného adresáře vašeho serveru a vlastní adresář pluginu není zapisovatelný jazykem PHP. K opravení tohoto problému učiňte adresář pluginu zapisovatelným nebo zadejte specifický uživatelský dočasný adresář v nastavení pluginu."
744
+
745
+ #: core.php:3108
746
+ msgid "PHP version"
747
+ msgstr "Verze PHP"
748
+
749
+ #: core.php:3114
750
+ msgid "MySQL version"
751
+ msgstr "Verze MySQL"
752
+
753
+ #: core.php:3127
754
+ msgid "You have an old version of CURL. Redirect detection may not work properly."
755
+ msgstr "Máte starou verzi CURL. Detekce přesměrování nemusí fungovat správně."
756
+
757
+ #: core.php:3139
758
+ #: core.php:3155
759
+ #: core.php:3160
760
+ msgid "Not installed"
761
+ msgstr "Nenainstalováno"
762
+
763
+ #: core.php:3142
764
+ msgid "CURL version"
765
+ msgstr "Verze CURL"
766
+
767
+ #: core.php:3148
768
+ msgid "Installed"
769
+ msgstr "Nainstalováno"
770
+
771
+ #: core.php:3161
772
+ msgid "You must have either CURL or Snoopy installed for the plugin to work!"
773
+ msgstr "Musíte mít nainstalovaný buď CURL anebo Snoopy, aby plugin pracoval!"
774
+
775
+ #: core.php:3172
776
+ msgid "On"
777
+ msgstr "Zapnuto"
778
+
779
+ #: core.php:3173
780
+ msgid "Redirects may be detected as broken links when safe_mode is on."
781
+ msgstr "Když je safe_mode zapnutý, může být přesměrování detekováno jako nefunkční odkaz."
782
+
783
+ #: core.php:3178
784
+ #: core.php:3192
785
+ msgid "Off"
786
+ msgstr "Vypnuto"
787
+
788
+ #: core.php:3186
789
+ #, php-format
790
+ msgid "On ( %s )"
791
+ msgstr "Zapnuto ( %s )"
792
+
793
+ #: core.php:3187
794
+ msgid "Redirects may be detected as broken links when open_basedir is on."
795
+ msgstr "Když je open_basedir zapnutý, může být přesměrování detekováno jako nefunkční odkaz."
796
+
797
+ #: core.php:3206
798
+ msgid "Can't create a lockfile. Please specify a custom temporary directory."
799
+ msgstr "Nemůže být vytvořen zamykací soubor. Specifikujte uživatelský dočasný adresář, prosím."
800
+
801
+ #: link-classes.php:212
802
+ #, php-format
803
+ msgid "First try : %d"
804
+ msgstr "První pokus: %d"
805
+
806
+ #: link-classes.php:214
807
+ msgid "First try : 0 (No response)"
808
+ msgstr "První pokus: 0 (žádná reakce)"
809
+
810
+ #: link-classes.php:222
811
+ msgid "Trying a second time with different settings..."
812
+ msgstr "Zkouším podruhé s odlišným nastavením..."
813
+
814
+ #: link-classes.php:237
815
+ #, php-format
816
+ msgid "Second try : %d"
817
+ msgstr "Druhý pokus: %d"
818
+
819
+ #: link-classes.php:239
820
+ msgid "Second try : 0 (No response)"
821
+ msgstr "Druhý pokus: 0 (žádná reakce)"
822
+
823
+ #: link-classes.php:265
824
+ msgid "Using Snoopy"
825
+ msgstr "Používám Snoopy"
826
+
827
+ #: link-classes.php:285
828
+ msgid "Request timed out."
829
+ msgstr "Žádost vypršela."
830
+
831
+ #: link-classes.php:304
832
+ msgid "Link is valid."
833
+ msgstr "Odkaz je v pořádku."
834
+
835
+ #: link-classes.php:309
836
+ msgid "Link is broken."
837
+ msgstr "Odkaz je nefunkční."
838
+
839
+ #: link-classes.php:313
840
+ msgid "Most likely the connection timed out or the domain doesn't exist."
841
+ msgstr "S největší pravděpodobností vypršel časový limit nebo doména neexistuje."
842
+
843
+ #: link-classes.php:354
844
+ #, php-format
845
+ msgid "Error adding link %s : %s"
846
+ msgstr "Chyba přidávání odkazu: %s : %s"
847
+
848
+ #: link-classes.php:374
849
+ #, php-format
850
+ msgid "Error updating link %d : %s"
851
+ msgstr "Chyba aktualizace odkazu: %d : %s"
852
+
853
+ #. Plugin Name of an extension
854
+ msgid "Broken Link Checker"
855
+ msgstr "Broken Link Checker"
856
+
857
+ #. Plugin URI of an extension
858
+ msgid "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
859
+ msgstr "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
860
+
861
+ #. Description of an extension
862
+ msgid "Checks your posts for broken links and missing images and notifies you on the dashboard if any are found."
863
+ msgstr "Kontroluje vaše příspěvky a hledá nefunkční odkazy a obrázky a upozorňuje vás na nástěnce, pokud jsou nějaké nalezeny."
864
+
865
+ #. Author of an extension
866
+ msgid "Janis Elsts"
867
+ msgstr "Janis Elsts"
868
+
869
+ #. Author URI of an extension
870
+ msgid "http://w-shadow.com/blog/"
871
+ msgstr "http://w-shadow.com/blog/"
872
+
languages/broken-link-checker.pot CHANGED
@@ -1,15 +1,15 @@
1
- # Broken Link Checker POT file
2
  # Copyright (C) 2010 Janis Elsts
3
  # This file is distributed under the same license as the Broken Link Checker package.
4
- # Janis Elsts <whiteshadow@w-shadow.com>, 2010.
5
  #
6
  #, fuzzy
7
  msgid ""
8
  msgstr ""
9
- "Project-Id-Version: Broken Link Checker 0.8\n"
10
- "Report-Msgid-Bugs-To: whiteshadow@w-shadow.com\n"
11
- "POT-Creation-Date: 2010-01-24 15:25+0000\n"
12
- "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
  "MIME-Version: 1.0\n"
@@ -17,828 +17,1085 @@ msgstr ""
17
  "Content-Transfer-Encoding: 8bit\n"
18
  "Plural-Forms: nplurals=2; plural=n != 1;\n"
19
 
20
- #: core.php:143 core.php:1835
 
 
 
 
21
  msgid "Loading..."
22
  msgstr ""
23
 
24
- #: core.php:166 core.php:604
25
  msgid "[ Network error ]"
26
  msgstr ""
27
 
28
- #: core.php:191
29
  msgid "Automatically expand the widget if broken links have been detected"
30
  msgstr ""
31
 
32
- #: core.php:375 core.php:384 core.php:414 core.php:426 core.php:1014
33
- #: core.php:1038 core.php:1316
34
  #, php-format
35
- msgid "Database error : %s"
 
 
 
 
 
 
 
 
 
 
 
 
36
  msgstr ""
37
 
38
- #: core.php:452
39
  msgid "Link Checker Settings"
40
  msgstr ""
41
 
42
- #: core.php:453
43
  msgid "Link Checker"
44
  msgstr ""
45
 
46
- #: core.php:459
47
  msgid "View Broken Links"
48
  msgstr ""
49
 
50
- #: core.php:460 core.php:892
51
  msgid "Broken Links"
52
  msgstr ""
53
 
54
- #: core.php:484
55
  msgid "Settings"
56
  msgstr ""
57
 
58
- #: core.php:568
 
 
 
 
 
 
 
 
 
 
 
59
  msgid "Broken Link Checker Options"
60
  msgstr ""
61
 
62
- #: core.php:581
63
  msgid "Status"
64
  msgstr ""
65
 
66
- #: core.php:583 core.php:823
67
  msgid "Show debug info"
68
  msgstr ""
69
 
70
- #: core.php:617
71
  msgid "Re-check all pages"
72
  msgstr ""
73
 
74
- #: core.php:641
75
  msgid "Check each link"
76
  msgstr ""
77
 
78
- #: core.php:646
79
  #, php-format
80
  msgid "Every %s hours"
81
  msgstr ""
82
 
83
- #: core.php:655
84
  msgid ""
85
  "Existing links will be checked this often. New links will usually be checked "
86
  "ASAP."
87
  msgstr ""
88
 
89
- #: core.php:662
90
  msgid "Broken link CSS"
91
  msgstr ""
92
 
93
- #: core.php:667
94
  msgid "Apply <em>class=\"broken_link\"</em> to broken links"
95
  msgstr ""
96
 
97
- #: core.php:679
98
  msgid "Removed link CSS"
99
  msgstr ""
100
 
101
- #: core.php:684
102
  msgid "Apply <em>class=\"removed_link\"</em> to unlinked links"
103
  msgstr ""
104
 
105
- #: core.php:696
106
  msgid "Exclusion list"
107
  msgstr ""
108
 
109
- #: core.php:697
110
  msgid ""
111
  "Don't check links where the URL contains any of these words (one per line) :"
112
  msgstr ""
113
 
114
- #: core.php:707
115
  msgid "Custom fields"
116
  msgstr ""
117
 
118
- #: core.php:708
119
  msgid "Check URLs entered in these custom fields (one per line) :"
120
  msgstr ""
121
 
122
- #: core.php:719
 
 
 
 
 
 
 
 
123
  msgid "Advanced"
124
  msgstr ""
125
 
126
- #: core.php:725
127
  msgid "Timeout"
128
  msgstr ""
129
 
130
- #: core.php:731 core.php:787
131
  #, php-format
132
  msgid "%s seconds"
133
  msgstr ""
134
 
135
- #: core.php:740
136
  msgid "Links that take longer than this to load will be marked as broken."
137
  msgstr ""
138
 
139
- #: core.php:749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  msgid "Custom temporary directory"
141
  msgstr ""
142
 
143
- #: core.php:758 core.php:2792
144
  msgid "OK"
145
  msgstr ""
146
 
147
- #: core.php:761
148
  msgid "Error : This directory isn't writable by PHP."
149
  msgstr ""
150
 
151
- #: core.php:766
152
  msgid "Error : This directory doesn't exist."
153
  msgstr ""
154
 
155
- #: core.php:774
156
  msgid ""
157
  "Set this field if you want the plugin to use a custom directory for its "
158
  "lockfiles. Otherwise, leave it blank."
159
  msgstr ""
160
 
161
- #: core.php:781
162
- msgid "Max. execution time"
163
  msgstr ""
164
 
165
- #: core.php:798
 
 
 
 
 
 
 
166
  msgid ""
167
- "The plugin works by periodically creating a background worker instance that "
168
- "parses your posts looking for links, checks the discovered URLs, and "
169
- "performs other time-consuming tasks. Here you can set for how long, at most, "
170
- "the background instance may run each time before stopping."
171
  msgstr ""
172
 
173
- #: core.php:808
174
  msgid "Save Changes"
175
  msgstr ""
176
 
177
- #: core.php:821
178
  msgid "Hide debug info"
179
  msgstr ""
180
 
181
- #: core.php:891
182
- msgid "Broken"
 
183
  msgstr ""
184
 
185
- #: core.php:893
186
- msgid "No broken links found"
187
  msgstr ""
188
 
189
- #: core.php:897
190
- msgid "Redirects"
191
  msgstr ""
192
 
193
- #: core.php:898
194
- msgid "Redirected Links"
195
  msgstr ""
196
 
197
- #: core.php:899
198
- msgid "No redirects found"
199
  msgstr ""
200
 
201
- #: core.php:904
202
- msgid "All"
203
  msgstr ""
204
 
205
- #: core.php:905
206
- msgid "Detected Links"
207
  msgstr ""
208
 
209
- #: core.php:906
210
- msgid "No links found (yet)"
211
  msgstr ""
212
 
213
- #: core.php:933 core.php:1281
214
- msgid "No links found for your query"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  msgstr ""
216
 
217
- #: core.php:994
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  msgid "You must enter a filter name!"
219
  msgstr ""
220
 
221
- #: core.php:998
222
  msgid "Invalid search query."
223
  msgstr ""
224
 
225
- #: core.php:1009
226
  #, php-format
227
  msgid "Filter \"%s\" created"
228
  msgstr ""
229
 
230
- #: core.php:1026
231
  msgid "Filter ID not specified."
232
  msgstr ""
233
 
234
- #: core.php:1035
235
  msgid "Filter deleted"
236
  msgstr ""
237
 
238
- #: core.php:1084
239
  #, php-format
240
- msgid "Failed to delete post \"%s\" (%d)"
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  msgstr ""
242
 
243
- #: core.php:1097
244
  #, php-format
245
- msgid "%d post moved to the trash"
246
- msgid_plural "%d posts moved to the trash"
247
  msgstr[0] ""
248
  msgstr[1] ""
249
 
250
- #: core.php:1099
251
  #, php-format
252
- msgid "%d post deleted"
253
- msgid_plural "%d posts deleted"
254
  msgstr[0] ""
255
  msgstr[1] ""
256
 
257
- #: core.php:1134
 
 
 
 
258
  #, php-format
259
- msgid "Failed to delete blogroll link \"%s\" (%d)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  msgstr ""
261
 
262
- #: core.php:1144
263
  #, php-format
264
- msgid "%d blogroll link deleted"
265
- msgid_plural "%d blogroll links deleted"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  msgstr[0] ""
267
  msgstr[1] ""
268
 
269
- #: core.php:1153
270
- msgid "Didn't find anything to delete!"
 
 
 
 
271
  msgstr ""
272
 
273
- #: core.php:1191
274
  #, php-format
275
- msgid "%d link removed"
276
- msgid_plural "%d links removed"
277
  msgstr[0] ""
278
  msgstr[1] ""
279
 
280
- #: core.php:1202
 
 
 
 
281
  #, php-format
282
- msgid "Failed to remove %d link"
283
- msgid_plural "Failed to remove %d links"
284
  msgstr[0] ""
285
  msgstr[1] ""
286
 
287
- #: core.php:1243
 
 
 
 
288
  #, php-format
289
- msgid "Replaced %d redirect with a direct link"
290
- msgid_plural "Replaced %d redirects with direct links"
291
  msgstr[0] ""
292
  msgstr[1] ""
293
 
294
- #: core.php:1254
295
  #, php-format
296
- msgid "Failed to fix %d redirect"
297
- msgid_plural "Failed to fix %d redirects"
298
  msgstr[0] ""
299
  msgstr[1] ""
300
 
301
- #: core.php:1264
302
- msgid "None of the selected links are redirects!"
303
  msgstr ""
304
 
305
- #: core.php:1279 core.php:1400
306
- msgid "Search"
307
  msgstr ""
308
 
309
- #: core.php:1280
310
- msgid "Search Results"
311
  msgstr ""
312
 
313
- #: core.php:1384
314
- msgid "Save This Search As a Filter"
315
  msgstr ""
316
 
317
- #: core.php:1394
318
- msgid "Delete This Filter"
 
319
  msgstr ""
320
 
321
- #: core.php:1410
322
- msgid "Link text"
323
  msgstr ""
324
 
325
- #: core.php:1413 core.php:1526
326
- msgid "URL"
327
  msgstr ""
328
 
329
- #: core.php:1416 core.php:2141
330
- msgid "HTTP code"
331
  msgstr ""
332
 
333
- #: core.php:1419
334
- msgid "Link status"
335
  msgstr ""
336
 
337
- #: core.php:1435
338
- msgid "Link type"
339
  msgstr ""
340
 
341
- #: core.php:1439
342
- msgid "Any"
343
  msgstr ""
344
 
345
- #: core.php:1440
346
- msgid "Normal link"
347
  msgstr ""
348
 
349
- #: core.php:1441 core.php:1602
350
- msgid "Image"
351
  msgstr ""
352
 
353
- #: core.php:1442 core.php:1613
354
- msgid "Custom field"
 
355
  msgstr ""
356
 
357
- #: core.php:1443 core.php:1621
358
- msgid "Bookmark"
 
 
 
359
  msgstr ""
360
 
361
- #: core.php:1456
362
- msgid "Search Links"
 
 
 
363
  msgstr ""
364
 
365
- #: core.php:1457 core.php:1654
366
- msgid "Cancel"
367
  msgstr ""
368
 
369
- #: core.php:1473
370
- msgid "Bulk Actions"
 
 
 
 
 
 
 
371
  msgstr ""
372
 
373
- #: core.php:1474 core.php:1639 core.php:1922
374
- msgid "Unlink"
375
  msgstr ""
376
 
377
- #: core.php:1475
378
- msgid "Fix redirects"
379
  msgstr ""
380
 
381
- #: core.php:1476
382
- msgid "Delete sources"
 
383
  msgstr ""
384
 
385
- #: core.php:1490 core.php:1686
386
- msgid "Apply"
387
  msgstr ""
388
 
389
- #: core.php:1497
390
- msgid "&laquo;"
391
  msgstr ""
392
 
393
- #: core.php:1498
394
- msgid "&raquo;"
395
  msgstr ""
396
 
397
- #: core.php:1505 core.php:1692
398
- #, php-format
399
- msgid "Displaying %s&#8211;%s of <span class=\"current-link-count\">%s</span>"
400
  msgstr ""
401
 
402
- #: core.php:1524
403
- msgid "Source"
404
  msgstr ""
405
 
406
- #: core.php:1525
407
- msgid "Link Text"
408
  msgstr ""
409
 
410
- #: core.php:1557 core.php:1563
411
- msgid "Edit this post"
412
  msgstr ""
413
 
414
- #: core.php:1563 core.php:1578
415
- msgid "Edit"
 
416
  msgstr ""
417
 
418
- #: core.php:1564
419
- msgid "Delete this post"
420
  msgstr ""
421
 
422
- #: core.php:1564
423
- #, php-format
424
- msgid ""
425
- "You are about to delete this post '%s'\n"
426
- " 'Cancel' to stop, 'OK' to delete."
427
  msgstr ""
428
 
429
- #: core.php:1564 core.php:1579
430
- msgid "Delete"
 
431
  msgstr ""
432
 
433
- #: core.php:1566
434
  #, php-format
435
- msgid "View \"%s\""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  msgstr ""
437
 
438
- #: core.php:1566
439
- msgid "View"
 
440
  msgstr ""
441
 
442
- #: core.php:1573 core.php:1578
443
- msgid "Edit this bookmark"
 
444
  msgstr ""
445
 
446
- #: core.php:1579
447
  #, php-format
448
- msgid ""
449
- "You are about to delete this link '%s'\n"
450
- " 'Cancel' to stop, 'OK' to delete."
451
  msgstr ""
452
 
453
- #: core.php:1588
454
- msgid "[An orphaned link! This is a bug.]"
455
  msgstr ""
456
 
457
- #: core.php:1636
458
- msgid "Show more info about this link"
459
  msgstr ""
460
 
461
- #: core.php:1636 core.php:3085
462
- msgid "Details"
463
  msgstr ""
464
 
465
- #: core.php:1638
466
- msgid "Remove this link from all posts"
467
  msgstr ""
468
 
469
- #: core.php:1642 core.php:1952 core.php:1963
470
- msgid "Excluded"
 
471
  msgstr ""
472
 
473
- #: core.php:1644
474
- msgid "Add this URL to the exclusion list"
 
 
475
  msgstr ""
476
 
477
- #: core.php:1645 core.php:1966
478
- msgid "Exclude"
479
  msgstr ""
480
 
481
- #: core.php:1648
482
- msgid "Edit link URL"
483
  msgstr ""
484
 
485
- #: core.php:1648 core.php:1863 core.php:1891
486
- msgid "Edit URL"
 
487
  msgstr ""
488
 
489
- #: core.php:1654
490
- msgid "Cancel URL editing"
 
491
  msgstr ""
492
 
493
- #: core.php:1668
494
- msgid "Remove this link from the list of broken links and mark it as valid"
495
  msgstr ""
496
 
497
- #: core.php:1670 core.php:1755
498
- msgid "Discard"
499
  msgstr ""
500
 
501
- #: core.php:1731 core.php:1898 core.php:1935
502
- msgid "Wait..."
 
 
503
  msgstr ""
504
 
505
- #: core.php:1789
506
- msgid "Save URL"
 
 
 
507
  msgstr ""
508
 
509
- #: core.php:1799
510
- msgid "Saving changes..."
511
  msgstr ""
512
 
513
- #: core.php:2011
514
- msgid "Enter a name for the new custom filter"
515
  msgstr ""
516
 
517
- #: core.php:2022
518
- msgid ""
519
- "You are about to delete the current filter.\n"
520
- "'Cancel' to stop, 'OK' to delete"
521
  msgstr ""
522
 
523
- #: core.php:2118
524
- msgid "Log"
525
  msgstr ""
526
 
527
- #: core.php:2126
528
- msgid "Post published on"
529
  msgstr ""
530
 
531
- #: core.php:2131
532
- msgid "Link last checked"
533
  msgstr ""
534
 
535
- #: core.php:2135
536
- msgid "Never"
537
  msgstr ""
538
 
539
- #: core.php:2146
540
- msgid "Response time"
541
  msgstr ""
542
 
543
- #: core.php:2148
544
- #, php-format
545
- msgid "%2.3f seconds"
546
  msgstr ""
547
 
548
- #: core.php:2151
549
- msgid "Final URL"
550
  msgstr ""
551
 
552
- #: core.php:2156
553
- msgid "Redirect count"
554
  msgstr ""
555
 
556
- #: core.php:2161
557
- msgid "Instance count"
 
 
 
 
558
  msgstr ""
559
 
560
- #: core.php:2170
561
  #, php-format
562
- msgid "This link has failed %d time."
563
- msgid_plural "This link has failed %d times."
564
- msgstr[0] ""
565
- msgstr[1] ""
566
 
567
- #: core.php:2580 core.php:2910
568
- msgid ""
569
- "This link wasn't checked because a matching keyword was found on your "
570
- "exclusion list."
571
  msgstr ""
572
 
573
- #: core.php:2622
574
- msgid "View broken links"
575
  msgstr ""
576
 
577
- #: core.php:2623
578
- #, php-format
579
- msgid "Found %d broken link"
580
- msgid_plural "Found %d broken links"
581
- msgstr[0] ""
582
- msgstr[1] ""
583
 
584
- #: core.php:2629
585
- msgid "No broken links found."
586
  msgstr ""
587
 
588
- #: core.php:2636
589
  #, php-format
590
- msgid "%d URL in the work queue"
591
- msgid_plural "%d URLs in the work queue"
592
- msgstr[0] ""
593
- msgstr[1] ""
594
-
595
- #: core.php:2639
596
- msgid "No URLs in the work queue."
597
  msgstr ""
598
 
599
- #: core.php:2645
600
  #, php-format
601
- msgid "Detected %d unique URL"
602
- msgid_plural "Detected %d unique URLs"
603
  msgstr[0] ""
604
  msgstr[1] ""
605
 
606
- #: core.php:2646
607
- #, php-format
608
- msgid "in %d link"
609
- msgid_plural "in %d links"
610
- msgstr[0] ""
611
- msgstr[1] ""
612
 
613
- #: core.php:2651
614
- msgid "and still searching..."
 
615
  msgstr ""
616
 
617
- #: core.php:2657
618
- msgid "Searching your blog for links..."
 
 
 
619
  msgstr ""
620
 
621
- #: core.php:2659
622
- msgid "No links detected."
 
623
  msgstr ""
624
 
625
- #: core.php:2731 core.php:2763 core.php:2806 core.php:2887
626
- msgid "You're not allowed to do that!"
 
627
  msgstr ""
628
 
629
- #: core.php:2739 core.php:2773 core.php:2816 core.php:2897
630
  #, php-format
631
- msgid "Oops, I can't find the link %d"
632
  msgstr ""
633
 
634
- #: core.php:2747
635
- msgid "This link was manually marked as working by the user."
 
636
  msgstr ""
637
 
638
- #: core.php:2753
639
- msgid "Oops, couldn't modify the link!"
640
- msgstr ""
 
 
 
641
 
642
- #: core.php:2756 core.php:2833
643
- msgid "Error : link_id not specified"
 
644
  msgstr ""
645
 
646
- #: core.php:2780
647
- msgid "Oops, the new URL is invalid!"
 
648
  msgstr ""
649
 
650
- #: core.php:2789
651
- msgid "An unexpected error occured!"
652
  msgstr ""
653
 
654
- #: core.php:2798
655
- msgid "Error : link_id or new_url not specified"
656
  msgstr ""
657
 
658
- #: core.php:2823
659
- #, php-format
660
- msgid "URL %s was removed."
661
  msgstr ""
662
 
663
- #: core.php:2827
664
- msgid "The plugin failed to remove the link."
 
665
  msgstr ""
666
 
667
- #: core.php:2842
668
- msgid "You don't have sufficient privileges to access this information!"
669
  msgstr ""
670
 
671
- #: core.php:2855
672
- msgid "Error : link ID not specified"
 
673
  msgstr ""
674
 
675
- #: core.php:2879
676
  #, php-format
677
- msgid "Failed to load link details (%s)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  msgstr ""
679
 
680
- #: core.php:2917
681
  #, php-format
682
- msgid "URL %s added to the exclusion list"
683
  msgstr ""
684
 
685
- #: core.php:2921
686
- msgid "Link ID not specified"
 
 
687
  msgstr ""
688
 
689
- #: core.php:3071
690
- #, php-format
691
- msgid ""
692
- "The current temporary directory is not accessible; please <a href=\"%s\">set "
693
- "a different one</a>."
 
694
  msgstr ""
695
 
696
- #: core.php:3076
 
 
 
 
697
  #, php-format
698
  msgid ""
699
- "Please make the directory <code>%1$s</code> writable by plugins or <a href="
700
- "\"%2$s\">set a custom temporary directory</a>."
701
  msgstr ""
702
 
703
- #: core.php:3083
704
- msgid "Broken Link Checker can't create a lockfile."
 
705
  msgstr ""
706
 
707
- #: core.php:3088
708
- msgid ""
709
- "The plugin uses a file-based locking mechanism to ensure that only one "
710
- "instance of the resource-heavy link checking algorithm is running at any "
711
- "given time. Unfortunately, BLC can't find a writable directory where it "
712
- "could store the lockfile - it failed to detect the location of your server's "
713
- "temporary directory, and the plugin's own directory isn't writable by PHP. "
714
- "To fix this problem, please make the plugin's directory writable or enter a "
715
- "specify a custom temporary directory in the plugin's settings."
716
  msgstr ""
717
 
718
- #: core.php:3108
719
- msgid "PHP version"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  msgstr ""
721
 
722
- #: core.php:3114
723
- msgid "MySQL version"
 
724
  msgstr ""
725
 
726
- #: core.php:3127
727
- msgid ""
728
- "You have an old version of CURL. Redirect detection may not work properly."
729
  msgstr ""
730
 
731
- #: core.php:3139 core.php:3155 core.php:3160
732
- msgid "Not installed"
 
733
  msgstr ""
734
 
735
- #: core.php:3142
736
- msgid "CURL version"
737
  msgstr ""
738
 
739
- #: core.php:3148
740
- msgid "Installed"
741
  msgstr ""
742
 
743
- #: core.php:3161
744
- msgid "You must have either CURL or Snoopy installed for the plugin to work!"
745
  msgstr ""
746
 
747
- #: core.php:3172
748
- msgid "On"
749
  msgstr ""
750
 
751
- #: core.php:3173
752
- msgid "Redirects may be detected as broken links when safe_mode is on."
753
  msgstr ""
754
 
755
- #: core.php:3178 core.php:3192
756
- msgid "Off"
 
757
  msgstr ""
758
 
759
- #: core.php:3186
760
- #, php-format
761
- msgid "On ( %s )"
762
  msgstr ""
763
 
764
- #: core.php:3187
765
- msgid "Redirects may be detected as broken links when open_basedir is on."
766
  msgstr ""
767
 
768
- #: core.php:3206
769
- msgid "Can't create a lockfile. Please specify a custom temporary directory."
770
  msgstr ""
771
 
772
- #: link-classes.php:212
773
- #, php-format
774
- msgid "First try : %d"
775
  msgstr ""
776
 
777
- #: link-classes.php:214
778
- msgid "First try : 0 (No response)"
779
  msgstr ""
780
 
781
- #: link-classes.php:222
782
- msgid "Trying a second time with different settings..."
783
  msgstr ""
784
 
785
- #: link-classes.php:237
786
- #, php-format
787
- msgid "Second try : %d"
788
  msgstr ""
789
 
790
- #: link-classes.php:239
791
- msgid "Second try : 0 (No response)"
792
  msgstr ""
793
 
794
- #: link-classes.php:265
795
- msgid "Using Snoopy"
796
  msgstr ""
797
 
798
- #: link-classes.php:285
799
- msgid "Request timed out."
800
  msgstr ""
801
 
802
- #: link-classes.php:304
803
- msgid "Link is valid."
804
  msgstr ""
805
 
806
- #: link-classes.php:309
807
- msgid "Link is broken."
808
  msgstr ""
809
 
810
- #: link-classes.php:313
811
- msgid "Most likely the connection timed out or the domain doesn't exist."
812
  msgstr ""
813
 
814
- #: link-classes.php:354
815
  #, php-format
816
- msgid "Error adding link %s : %s"
817
  msgstr ""
818
 
819
- #: link-classes.php:374
820
  #, php-format
821
- msgid "Error updating link %d : %s"
822
  msgstr ""
823
 
824
- #. Plugin Name of an extension
825
  msgid "Broken Link Checker"
826
  msgstr ""
827
 
828
- #. Plugin URI of an extension
829
  msgid "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
830
  msgstr ""
831
 
832
- #. Description of an extension
833
  msgid ""
834
- "Checks your posts for broken links and missing images and notifies you on "
835
- "the dashboard if any are found."
836
  msgstr ""
837
 
838
- #. Author of an extension
839
  msgid "Janis Elsts"
840
  msgstr ""
841
 
842
- #. Author URI of an extension
843
  msgid "http://w-shadow.com/blog/"
844
  msgstr ""
1
+ # Translation of the WordPress plugin Broken Link Checker 0.9 by Janis Elsts.
2
  # Copyright (C) 2010 Janis Elsts
3
  # This file is distributed under the same license as the Broken Link Checker package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
5
  #
6
  #, fuzzy
7
  msgid ""
8
  msgstr ""
9
+ "Project-Id-Version: Broken Link Checker 0.9\n"
10
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/broken-link-checker\n"
11
+ "POT-Creation-Date: 2010-04-21 17:01+0000\n"
12
+ "PO-Revision-Date: 2010-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
15
  "MIME-Version: 1.0\n"
17
  "Content-Transfer-Encoding: 8bit\n"
18
  "Plural-Forms: nplurals=2; plural=n != 1;\n"
19
 
20
+ #: broken-link-checker.php:268
21
+ msgid "Once Weekly"
22
+ msgstr ""
23
+
24
+ #: core.php:136 includes/admin/links-page-js.php:21
25
  msgid "Loading..."
26
  msgstr ""
27
 
28
+ #: core.php:159 core.php:669
29
  msgid "[ Network error ]"
30
  msgstr ""
31
 
32
+ #: core.php:184
33
  msgid "Automatically expand the widget if broken links have been detected"
34
  msgstr ""
35
 
36
+ #: core.php:295
 
37
  #, php-format
38
+ msgid "Failed to delete old DB tables. Database error : %s"
39
+ msgstr ""
40
+
41
+ #: core.php:312
42
+ #, php-format
43
+ msgid ""
44
+ "Unexpected error: The plugin doesn't know how to upgrade its database to "
45
+ "version '%d'."
46
+ msgstr ""
47
+
48
+ #: core.php:348 core.php:377 core.php:419 core.php:444
49
+ #, php-format
50
+ msgid "Failed to create table '%s'. Database error: %s"
51
  msgstr ""
52
 
53
+ #: core.php:472
54
  msgid "Link Checker Settings"
55
  msgstr ""
56
 
57
+ #: core.php:473
58
  msgid "Link Checker"
59
  msgstr ""
60
 
61
+ #: core.php:479
62
  msgid "View Broken Links"
63
  msgstr ""
64
 
65
+ #: core.php:480 includes/links.php:771
66
  msgid "Broken Links"
67
  msgstr ""
68
 
69
+ #: core.php:503
70
  msgid "Settings"
71
  msgstr ""
72
 
73
+ #: core.php:513 core.php:1001
74
+ #, php-format
75
+ msgid ""
76
+ "Error: The plugin's database tables are not up to date! (Current version : %"
77
+ "d, expected : %d)"
78
+ msgstr ""
79
+
80
+ #: core.php:626
81
+ msgid "Settings saved."
82
+ msgstr ""
83
+
84
+ #: core.php:634
85
  msgid "Broken Link Checker Options"
86
  msgstr ""
87
 
88
+ #: core.php:647
89
  msgid "Status"
90
  msgstr ""
91
 
92
+ #: core.php:649 core.php:981
93
  msgid "Show debug info"
94
  msgstr ""
95
 
96
+ #: core.php:682
97
  msgid "Re-check all pages"
98
  msgstr ""
99
 
100
+ #: core.php:706
101
  msgid "Check each link"
102
  msgstr ""
103
 
104
+ #: core.php:711
105
  #, php-format
106
  msgid "Every %s hours"
107
  msgstr ""
108
 
109
+ #: core.php:720
110
  msgid ""
111
  "Existing links will be checked this often. New links will usually be checked "
112
  "ASAP."
113
  msgstr ""
114
 
115
+ #: core.php:727
116
  msgid "Broken link CSS"
117
  msgstr ""
118
 
119
+ #: core.php:732
120
  msgid "Apply <em>class=\"broken_link\"</em> to broken links"
121
  msgstr ""
122
 
123
+ #: core.php:744
124
  msgid "Removed link CSS"
125
  msgstr ""
126
 
127
+ #: core.php:749
128
  msgid "Apply <em>class=\"removed_link\"</em> to unlinked links"
129
  msgstr ""
130
 
131
+ #: core.php:761
132
  msgid "Exclusion list"
133
  msgstr ""
134
 
135
+ #: core.php:762
136
  msgid ""
137
  "Don't check links where the URL contains any of these words (one per line) :"
138
  msgstr ""
139
 
140
+ #: core.php:772
141
  msgid "Custom fields"
142
  msgstr ""
143
 
144
+ #: core.php:773
145
  msgid "Check URLs entered in these custom fields (one per line) :"
146
  msgstr ""
147
 
148
+ #: core.php:783
149
+ msgid "E-mail notifications"
150
+ msgstr ""
151
+
152
+ #: core.php:789
153
+ msgid "Send me e-mail notifications about newly detected broken links"
154
+ msgstr ""
155
+
156
+ #: core.php:797
157
  msgid "Advanced"
158
  msgstr ""
159
 
160
+ #: core.php:803
161
  msgid "Timeout"
162
  msgstr ""
163
 
164
+ #: core.php:809 core.php:853
165
  #, php-format
166
  msgid "%s seconds"
167
  msgstr ""
168
 
169
+ #: core.php:818
170
  msgid "Links that take longer than this to load will be marked as broken."
171
  msgstr ""
172
 
173
+ #: core.php:825
174
+ msgid "Link monitor"
175
+ msgstr ""
176
+
177
+ #: core.php:831
178
+ msgid "Run continuously while the Dashboard is open"
179
+ msgstr ""
180
+
181
+ #: core.php:839
182
+ msgid "Run hourly in the background"
183
+ msgstr ""
184
+
185
+ #: core.php:847
186
+ msgid "Max. execution time"
187
+ msgstr ""
188
+
189
+ #: core.php:864
190
+ msgid ""
191
+ "The plugin works by periodically launching a background job that parses your "
192
+ "posts for links, checks the discovered URLs, and performs other time-"
193
+ "consuming tasks. Here you can set for how long, at most, the link monitor "
194
+ "may run each time before stopping."
195
+ msgstr ""
196
+
197
+ #: core.php:874
198
  msgid "Custom temporary directory"
199
  msgstr ""
200
 
201
+ #: core.php:883
202
  msgid "OK"
203
  msgstr ""
204
 
205
+ #: core.php:886
206
  msgid "Error : This directory isn't writable by PHP."
207
  msgstr ""
208
 
209
+ #: core.php:891
210
  msgid "Error : This directory doesn't exist."
211
  msgstr ""
212
 
213
+ #: core.php:899
214
  msgid ""
215
  "Set this field if you want the plugin to use a custom directory for its "
216
  "lockfiles. Otherwise, leave it blank."
217
  msgstr ""
218
 
219
+ #: core.php:906
220
+ msgid "Server load limit"
221
  msgstr ""
222
 
223
+ #: core.php:947
224
+ #, php-format
225
+ msgid ""
226
+ "Link checking will be suspended if the average <a href=\"%s\">server load</"
227
+ "a> rises above this number. Leave this field blank to disable load limiting."
228
+ msgstr ""
229
+
230
+ #: core.php:957
231
  msgid ""
232
+ "Load limiting only works on Linux-like systems where <code>/proc/loadavg</"
233
+ "code> is present and accessible."
 
 
234
  msgstr ""
235
 
236
+ #: core.php:966
237
  msgid "Save Changes"
238
  msgstr ""
239
 
240
+ #: core.php:979
241
  msgid "Hide debug info"
242
  msgstr ""
243
 
244
+ #: core.php:1087 core.php:1409 core.php:1441
245
+ #, php-format
246
+ msgid "Database error : %s"
247
  msgstr ""
248
 
249
+ #: core.php:1162
250
+ msgid "Bulk Actions"
251
  msgstr ""
252
 
253
+ #: core.php:1163
254
+ msgid "Recheck"
255
  msgstr ""
256
 
257
+ #: core.php:1164
258
+ msgid "Fix redirects"
259
  msgstr ""
260
 
261
+ #: core.php:1165 core.php:1308 includes/admin/links-page-js.php:293
262
+ msgid "Unlink"
263
  msgstr ""
264
 
265
+ #: core.php:1166
266
+ msgid "Delete sources"
267
  msgstr ""
268
 
269
+ #: core.php:1180 core.php:1343
270
+ msgid "Apply"
271
  msgstr ""
272
 
273
+ #: core.php:1187
274
+ msgid "&laquo;"
275
  msgstr ""
276
 
277
+ #: core.php:1188
278
+ msgid "&raquo;"
279
+ msgstr ""
280
+
281
+ #: core.php:1195 core.php:1349
282
+ #, php-format
283
+ msgid "Displaying %s&#8211;%s of <span class=\"current-link-count\">%s</span>"
284
+ msgstr ""
285
+
286
+ #: core.php:1214
287
+ msgid "Source"
288
+ msgstr ""
289
+
290
+ #: core.php:1215
291
+ msgid "Link Text"
292
+ msgstr ""
293
+
294
+ #: core.php:1216 includes/admin/search-form.php:42
295
+ msgid "URL"
296
+ msgstr ""
297
+
298
+ #: core.php:1281
299
+ msgid "[An orphaned link! This is a bug.]"
300
+ msgstr ""
301
+
302
+ #: core.php:1305
303
+ msgid "Show more info about this link"
304
+ msgstr ""
305
+
306
+ #: core.php:1305 core.php:2542
307
+ msgid "Details"
308
+ msgstr ""
309
+
310
+ #: core.php:1307
311
+ msgid "Remove this link from all posts"
312
+ msgstr ""
313
+
314
+ #: core.php:1313
315
+ msgid "Remove this link from the list of broken links and mark it as valid"
316
+ msgstr ""
317
+
318
+ #: core.php:1314 includes/admin/links-page-js.php:78
319
+ msgid "Not broken"
320
  msgstr ""
321
 
322
+ #: core.php:1318
323
+ msgid "Edit link URL"
324
+ msgstr ""
325
+
326
+ #: core.php:1318 includes/admin/links-page-js.php:199
327
+ #: includes/admin/links-page-js.php:227
328
+ msgid "Edit URL"
329
+ msgstr ""
330
+
331
+ #: core.php:1324
332
+ msgid "Cancel URL editing"
333
+ msgstr ""
334
+
335
+ #: core.php:1324 includes/admin/search-form.php:87
336
+ msgid "Cancel"
337
+ msgstr ""
338
+
339
+ #: core.php:1392
340
  msgid "You must enter a filter name!"
341
  msgstr ""
342
 
343
+ #: core.php:1396
344
  msgid "Invalid search query."
345
  msgstr ""
346
 
347
+ #: core.php:1404
348
  #, php-format
349
  msgid "Filter \"%s\" created"
350
  msgstr ""
351
 
352
+ #: core.php:1432
353
  msgid "Filter ID not specified."
354
  msgstr ""
355
 
356
+ #: core.php:1438
357
  msgid "Filter deleted"
358
  msgstr ""
359
 
360
+ #: core.php:1486
361
  #, php-format
362
+ msgid "Replaced %d redirect with a direct link"
363
+ msgid_plural "Replaced %d redirects with direct links"
364
+ msgstr[0] ""
365
+ msgstr[1] ""
366
+
367
+ #: core.php:1497
368
+ #, php-format
369
+ msgid "Failed to fix %d redirect"
370
+ msgid_plural "Failed to fix %d redirects"
371
+ msgstr[0] ""
372
+ msgstr[1] ""
373
+
374
+ #: core.php:1507
375
+ msgid "None of the selected links are redirects!"
376
  msgstr ""
377
 
378
+ #: core.php:1553
379
  #, php-format
380
+ msgid "%d link removed"
381
+ msgid_plural "%d links removed"
382
  msgstr[0] ""
383
  msgstr[1] ""
384
 
385
+ #: core.php:1564
386
  #, php-format
387
+ msgid "Failed to remove %d link"
388
+ msgid_plural "Failed to remove %d links"
389
  msgstr[0] ""
390
  msgstr[1] ""
391
 
392
+ #: core.php:1652
393
+ msgid "Didn't find anything to delete!"
394
+ msgstr ""
395
+
396
+ #: core.php:1680
397
  #, php-format
398
+ msgid "%d link scheduled for rechecking"
399
+ msgid_plural "%d links scheduled for rechecking"
400
+ msgstr[0] ""
401
+ msgstr[1] ""
402
+
403
+ #: core.php:1703
404
+ msgid "Post published on"
405
+ msgstr ""
406
+
407
+ #: core.php:1708
408
+ msgid "Link last checked"
409
+ msgstr ""
410
+
411
+ #: core.php:1712
412
+ msgid "Never"
413
+ msgstr ""
414
+
415
+ #: core.php:1718 includes/admin/search-form.php:45
416
+ msgid "HTTP code"
417
+ msgstr ""
418
+
419
+ #: core.php:1723
420
+ msgid "Response time"
421
  msgstr ""
422
 
423
+ #: core.php:1725
424
  #, php-format
425
+ msgid "%2.3f seconds"
426
+ msgstr ""
427
+
428
+ #: core.php:1728
429
+ msgid "Final URL"
430
+ msgstr ""
431
+
432
+ #: core.php:1733
433
+ msgid "Redirect count"
434
+ msgstr ""
435
+
436
+ #: core.php:1738
437
+ msgid "Instance count"
438
+ msgstr ""
439
+
440
+ #: core.php:1747
441
+ #, php-format
442
+ msgid "This link has failed %d time."
443
+ msgid_plural "This link has failed %d times."
444
  msgstr[0] ""
445
  msgstr[1] ""
446
 
447
+ #: core.php:1758
448
+ msgid "Log"
449
+ msgstr ""
450
+
451
+ #: core.php:2070
452
+ msgid "View broken links"
453
  msgstr ""
454
 
455
+ #: core.php:2071
456
  #, php-format
457
+ msgid "Found %d broken link"
458
+ msgid_plural "Found %d broken links"
459
  msgstr[0] ""
460
  msgstr[1] ""
461
 
462
+ #: core.php:2077
463
+ msgid "No broken links found."
464
+ msgstr ""
465
+
466
+ #: core.php:2084
467
  #, php-format
468
+ msgid "%d URL in the work queue"
469
+ msgid_plural "%d URLs in the work queue"
470
  msgstr[0] ""
471
  msgstr[1] ""
472
 
473
+ #: core.php:2087
474
+ msgid "No URLs in the work queue."
475
+ msgstr ""
476
+
477
+ #: core.php:2093
478
  #, php-format
479
+ msgid "Detected %d unique URL"
480
+ msgid_plural "Detected %d unique URLs"
481
  msgstr[0] ""
482
  msgstr[1] ""
483
 
484
+ #: core.php:2094
485
  #, php-format
486
+ msgid "in %d link"
487
+ msgid_plural "in %d links"
488
  msgstr[0] ""
489
  msgstr[1] ""
490
 
491
+ #: core.php:2099
492
+ msgid "and still searching..."
493
  msgstr ""
494
 
495
+ #: core.php:2105
496
+ msgid "Searching your blog for links..."
497
  msgstr ""
498
 
499
+ #: core.php:2107
500
+ msgid "No links detected."
501
  msgstr ""
502
 
503
+ #: core.php:2192 core.php:2228 core.php:2291
504
+ msgid "You're not allowed to do that!"
505
  msgstr ""
506
 
507
+ #: core.php:2200 core.php:2238 core.php:2301
508
+ #, php-format
509
+ msgid "Oops, I can't find the link %d"
510
  msgstr ""
511
 
512
+ #: core.php:2207
513
+ msgid "This link was manually marked as working by the user."
514
  msgstr ""
515
 
516
+ #: core.php:2213
517
+ msgid "Oops, couldn't modify the link!"
518
  msgstr ""
519
 
520
+ #: core.php:2216 core.php:2327
521
+ msgid "Error : link_id not specified"
522
  msgstr ""
523
 
524
+ #: core.php:2248
525
+ msgid "Oops, the new URL is invalid!"
526
  msgstr ""
527
 
528
+ #: core.php:2259 core.php:2310
529
+ msgid "An unexpected error occured!"
530
  msgstr ""
531
 
532
+ #: core.php:2277
533
+ msgid "Error : link_id or new_url not specified"
534
  msgstr ""
535
 
536
+ #: core.php:2336
537
+ msgid "You don't have sufficient privileges to access this information!"
538
  msgstr ""
539
 
540
+ #: core.php:2349
541
+ msgid "Error : link ID not specified"
542
  msgstr ""
543
 
544
+ #: core.php:2360
545
+ #, php-format
546
+ msgid "Failed to load link details (%s)"
547
  msgstr ""
548
 
549
+ #: core.php:2528
550
+ #, php-format
551
+ msgid ""
552
+ "The current temporary directory is not accessible; please <a href=\"%s\">set "
553
+ "a different one</a>."
554
  msgstr ""
555
 
556
+ #: core.php:2533
557
+ #, php-format
558
+ msgid ""
559
+ "Please make the directory <code>%1$s</code> writable by plugins or <a href="
560
+ "\"%2$s\">set a custom temporary directory</a>."
561
  msgstr ""
562
 
563
+ #: core.php:2540
564
+ msgid "Broken Link Checker can't create a lockfile."
565
  msgstr ""
566
 
567
+ #: core.php:2545
568
+ msgid ""
569
+ "The plugin uses a file-based locking mechanism to ensure that only one "
570
+ "instance of the resource-heavy link checking algorithm is running at any "
571
+ "given time. Unfortunately, BLC can't find a writable directory where it "
572
+ "could store the lockfile - it failed to detect the location of your server's "
573
+ "temporary directory, and the plugin's own directory isn't writable by PHP. "
574
+ "To fix this problem, please make the plugin's directory writable or enter a "
575
+ "specify a custom temporary directory in the plugin's settings."
576
  msgstr ""
577
 
578
+ #: core.php:2564
579
+ msgid "PHP version"
580
  msgstr ""
581
 
582
+ #: core.php:2570
583
+ msgid "MySQL version"
584
  msgstr ""
585
 
586
+ #: core.php:2583
587
+ msgid ""
588
+ "You have an old version of CURL. Redirect detection may not work properly."
589
  msgstr ""
590
 
591
+ #: core.php:2595 core.php:2611 core.php:2616
592
+ msgid "Not installed"
593
  msgstr ""
594
 
595
+ #: core.php:2598
596
+ msgid "CURL version"
597
  msgstr ""
598
 
599
+ #: core.php:2604
600
+ msgid "Installed"
601
  msgstr ""
602
 
603
+ #: core.php:2617
604
+ msgid "You must have either CURL or Snoopy installed for the plugin to work!"
 
605
  msgstr ""
606
 
607
+ #: core.php:2628
608
+ msgid "On"
609
  msgstr ""
610
 
611
+ #: core.php:2629
612
+ msgid "Redirects may be detected as broken links when safe_mode is on."
613
  msgstr ""
614
 
615
+ #: core.php:2634 core.php:2648
616
+ msgid "Off"
617
  msgstr ""
618
 
619
+ #: core.php:2642
620
+ #, php-format
621
+ msgid "On ( %s )"
622
  msgstr ""
623
 
624
+ #: core.php:2643
625
+ msgid "Redirects may be detected as broken links when open_basedir is on."
626
  msgstr ""
627
 
628
+ #: core.php:2662
629
+ msgid "Can't create a lockfile. Please specify a custom temporary directory."
 
 
 
630
  msgstr ""
631
 
632
+ #: core.php:2691
633
+ #, php-format
634
+ msgid "[%s] Broken links detected"
635
  msgstr ""
636
 
637
+ #: core.php:2697
638
  #, php-format
639
+ msgid "Broken Link Checker has detected %d new broken link on your site."
640
+ msgid_plural ""
641
+ "Broken Link Checker has detected %d new broken links on your site."
642
+ msgstr[0] ""
643
+ msgstr[1] ""
644
+
645
+ #: core.php:2712
646
+ #, php-format
647
+ msgid "Here's a list of the first %d broken links:"
648
+ msgid_plural "Here's a list of the first %d broken links:"
649
+ msgstr[0] ""
650
+ msgstr[1] ""
651
+
652
+ #: core.php:2720
653
+ msgid "Here's a list of the new broken links: "
654
  msgstr ""
655
 
656
+ #: core.php:2732
657
+ #, php-format
658
+ msgid "Link text : %s"
659
  msgstr ""
660
 
661
+ #: core.php:2733
662
+ #, php-format
663
+ msgid "Link URL : <a href=\"%s\">%s</a>"
664
  msgstr ""
665
 
666
+ #: core.php:2734
667
  #, php-format
668
+ msgid "Source : %s"
 
 
669
  msgstr ""
670
 
671
+ #: core.php:2748
672
+ msgid "You can see all broken links here:"
673
  msgstr ""
674
 
675
+ #: includes/admin/links-page-js.php:40 includes/admin/links-page-js.php:234
676
+ msgid "Wait..."
677
  msgstr ""
678
 
679
+ #: includes/admin/links-page-js.php:109
680
+ msgid "Save URL"
681
  msgstr ""
682
 
683
+ #: includes/admin/links-page-js.php:120
684
+ msgid "Saving changes..."
685
  msgstr ""
686
 
687
+ #: includes/admin/links-page-js.php:166
688
+ #, php-format
689
+ msgid "%d instances of the link were successfully modified."
690
  msgstr ""
691
 
692
+ #: includes/admin/links-page-js.php:172
693
+ #, php-format
694
+ msgid ""
695
+ "However, %d instances couldn't be edited and still point to the old URL."
696
  msgstr ""
697
 
698
+ #: includes/admin/links-page-js.php:178
699
+ msgid "The link could not be modified."
700
  msgstr ""
701
 
702
+ #: includes/admin/links-page-js.php:181 includes/admin/links-page-js.php:285
703
+ msgid "The following error(s) occured :"
704
  msgstr ""
705
 
706
+ #: includes/admin/links-page-js.php:271
707
+ #, php-format
708
+ msgid "%d instances of the link were successfully unlinked."
709
  msgstr ""
710
 
711
+ #: includes/admin/links-page-js.php:277
712
+ #, php-format
713
+ msgid "However, %d instances couldn't be removed."
714
  msgstr ""
715
 
716
+ #: includes/admin/links-page-js.php:282
717
+ msgid "The plugin failed to remove the link."
718
  msgstr ""
719
 
720
+ #: includes/admin/links-page-js.php:337
721
+ msgid "Enter a name for the new custom filter"
722
  msgstr ""
723
 
724
+ #: includes/admin/links-page-js.php:348
725
+ msgid ""
726
+ "You are about to delete the current filter.\n"
727
+ "'Cancel' to stop, 'OK' to delete"
728
  msgstr ""
729
 
730
+ #: includes/admin/links-page-js.php:371
731
+ msgid ""
732
+ "Are you sure you want to delete all posts, bookmarks or other items that "
733
+ "contain any of the selected links? This action can't be undone.\n"
734
+ "'Cancel' to stop, 'OK' to delete"
735
  msgstr ""
736
 
737
+ #: includes/admin/search-form.php:13
738
+ msgid "Save This Search As a Filter"
739
  msgstr ""
740
 
741
+ #: includes/admin/search-form.php:23
742
+ msgid "Delete This Filter"
743
  msgstr ""
744
 
745
+ #: includes/admin/search-form.php:29 includes/links.php:798
746
+ msgid "Search"
 
 
747
  msgstr ""
748
 
749
+ #: includes/admin/search-form.php:39
750
+ msgid "Link text"
751
  msgstr ""
752
 
753
+ #: includes/admin/search-form.php:48
754
+ msgid "Link status"
755
  msgstr ""
756
 
757
+ #: includes/admin/search-form.php:64
758
+ msgid "Link type"
759
  msgstr ""
760
 
761
+ #: includes/admin/search-form.php:68
762
+ msgid "Any"
763
  msgstr ""
764
 
765
+ #: includes/admin/search-form.php:69
766
+ msgid "Normal link"
767
  msgstr ""
768
 
769
+ #: includes/admin/search-form.php:70 includes/parsers/image.php:142
770
+ msgid "Image"
 
771
  msgstr ""
772
 
773
+ #: includes/admin/search-form.php:71 includes/containers/custom_field.php:176
774
+ msgid "Custom field"
775
  msgstr ""
776
 
777
+ #: includes/admin/search-form.php:72 includes/containers/blogroll.php:13
778
+ msgid "Bookmark"
779
  msgstr ""
780
 
781
+ #: includes/admin/search-form.php:73 includes/containers/comment.php:136
782
+ msgid "Comment"
783
+ msgstr ""
784
+
785
+ #: includes/admin/search-form.php:86
786
+ msgid "Search Links"
787
  msgstr ""
788
 
789
+ #: includes/checkers/http.php:182 includes/checkers/http.php:249
790
  #, php-format
791
+ msgid "HTTP code : %d"
792
+ msgstr ""
 
 
793
 
794
+ #: includes/checkers/http.php:184 includes/checkers/http.php:251
795
+ msgid "(No response)"
 
 
796
  msgstr ""
797
 
798
+ #: includes/checkers/http.php:190
799
+ msgid "Most likely the connection timed out or the domain doesn't exist."
800
  msgstr ""
801
 
802
+ #: includes/checkers/http.php:258
803
+ msgid "Request timed out."
804
+ msgstr ""
 
 
 
805
 
806
+ #: includes/checkers/http.php:276
807
+ msgid "Using Snoopy"
808
  msgstr ""
809
 
810
+ #: includes/containers.php:262
811
  #, php-format
812
+ msgid "Container type '%s' not recognized"
 
 
 
 
 
 
813
  msgstr ""
814
 
815
+ #: includes/containers.php:792
816
  #, php-format
817
+ msgid "%d '%s' has been deleted"
818
+ msgid_plural "%d '%s' have been deleted"
819
  msgstr[0] ""
820
  msgstr[1] ""
821
 
822
+ #: includes/containers/blogroll.php:19 includes/containers/blogroll.php:38
823
+ msgid "Edit this bookmark"
824
+ msgstr ""
 
 
 
825
 
826
+ #: includes/containers/blogroll.php:38 includes/containers/comment.php:106
827
+ #: includes/containers/custom_field.php:201 includes/containers/post.php:16
828
+ msgid "Edit"
829
  msgstr ""
830
 
831
+ #: includes/containers/blogroll.php:39
832
+ #, php-format
833
+ msgid ""
834
+ "You are about to delete this link '%s'\n"
835
+ " 'Cancel' to stop, 'OK' to delete."
836
  msgstr ""
837
 
838
+ #: includes/containers/blogroll.php:39
839
+ #: includes/containers/custom_field.php:206 includes/containers/post.php:21
840
+ msgid "Delete"
841
  msgstr ""
842
 
843
+ #: includes/containers/blogroll.php:75 includes/containers/comment.php:36
844
+ #: includes/containers/post.php:86
845
+ msgid "Nothing to update"
846
  msgstr ""
847
 
848
+ #: includes/containers/blogroll.php:89
849
  #, php-format
850
+ msgid "Updating bookmark %d failed"
851
  msgstr ""
852
 
853
+ #: includes/containers/blogroll.php:120
854
+ #, php-format
855
+ msgid "Failed to delete blogroll link \"%s\" (%d)"
856
  msgstr ""
857
 
858
+ #: includes/containers/blogroll.php:280
859
+ #, php-format
860
+ msgid "%d blogroll link deleted"
861
+ msgid_plural "%d blogroll links deleted"
862
+ msgstr[0] ""
863
+ msgstr[1] ""
864
 
865
+ #: includes/containers/comment.php:46
866
+ #, php-format
867
+ msgid "Updating comment %d failed"
868
  msgstr ""
869
 
870
+ #: includes/containers/comment.php:64
871
+ #, php-format
872
+ msgid "Failed to delete comment %d"
873
  msgstr ""
874
 
875
+ #: includes/containers/comment.php:106 includes/containers/comment.php:148
876
+ msgid "Edit comment"
877
  msgstr ""
878
 
879
+ #: includes/containers/comment.php:113
880
+ msgid "Delete Permanently"
881
  msgstr ""
882
 
883
+ #: includes/containers/comment.php:115
884
+ msgid "Move this comment to the trash"
 
885
  msgstr ""
886
 
887
+ #: includes/containers/comment.php:115
888
+ msgctxt "verb"
889
+ msgid "Trash"
890
  msgstr ""
891
 
892
+ #: includes/containers/comment.php:119
893
+ msgid "View comment"
894
  msgstr ""
895
 
896
+ #: includes/containers/comment.php:119
897
+ #: includes/containers/custom_field.php:209 includes/containers/post.php:24
898
+ msgid "View"
899
  msgstr ""
900
 
901
+ #: includes/containers/comment.php:272
902
  #, php-format
903
+ msgid "%d comment moved to the trash"
904
+ msgid_plural "%d comments moved to the trash"
905
+ msgstr[0] ""
906
+ msgstr[1] ""
907
+
908
+ #: includes/containers/comment.php:282
909
+ #, php-format
910
+ msgid "%d comment has been deleted"
911
+ msgid_plural "%d comments have been deleted"
912
+ msgstr[0] ""
913
+ msgstr[1] ""
914
+
915
+ #: includes/containers/custom_field.php:73
916
+ #, php-format
917
+ msgid "Failed to update the meta field '%s' on %s [%d]"
918
  msgstr ""
919
 
920
+ #: includes/containers/custom_field.php:99
921
  #, php-format
922
+ msgid "Failed to delete the meta field '%s' on %s [%d]"
923
  msgstr ""
924
 
925
+ #: includes/containers/custom_field.php:191
926
+ #: includes/containers/custom_field.php:201 includes/containers/post.php:16
927
+ #: includes/containers/post.php:41
928
+ msgid "Edit this post"
929
  msgstr ""
930
 
931
+ #: includes/containers/custom_field.php:204 includes/containers/post.php:19
932
+ msgid "Move this post to the Trash"
933
+ msgstr ""
934
+
935
+ #: includes/containers/custom_field.php:204 includes/containers/post.php:19
936
+ msgid "Trash"
937
  msgstr ""
938
 
939
+ #: includes/containers/custom_field.php:206 includes/containers/post.php:21
940
+ msgid "Delete this post permanently"
941
+ msgstr ""
942
+
943
+ #: includes/containers/custom_field.php:206 includes/containers/post.php:21
944
  #, php-format
945
  msgid ""
946
+ "You are about to delete this post '%s'\n"
947
+ " 'Cancel' to stop, 'OK' to delete."
948
  msgstr ""
949
 
950
+ #: includes/containers/custom_field.php:209 includes/containers/post.php:24
951
+ #, php-format
952
+ msgid "View \"%s\""
953
  msgstr ""
954
 
955
+ #: includes/containers/custom_field.php:248 includes/containers/post.php:127
956
+ #, php-format
957
+ msgid "Failed to delete post \"%s\" (%d)"
 
 
 
 
 
 
958
  msgstr ""
959
 
960
+ #: includes/containers/custom_field.php:479 includes/containers/post.php:298
961
+ #, php-format
962
+ msgid "%d post moved to the trash"
963
+ msgid_plural "%d posts moved to the trash"
964
+ msgstr[0] ""
965
+ msgstr[1] ""
966
+
967
+ #: includes/containers/custom_field.php:481 includes/containers/post.php:300
968
+ #, php-format
969
+ msgid "%d post deleted"
970
+ msgid_plural "%d posts deleted"
971
+ msgstr[0] ""
972
+ msgstr[1] ""
973
+
974
+ #: includes/containers/dummy.php:21 includes/containers/dummy.php:32
975
+ #, php-format
976
+ msgid "I don't know how to edit a '%s' [%d]."
977
  msgstr ""
978
 
979
+ #: includes/containers/post.php:96
980
+ #, php-format
981
+ msgid "Updating post %d failed"
982
  msgstr ""
983
 
984
+ #: includes/instances.php:102 includes/instances.php:148
985
+ #, php-format
986
+ msgid "Container %s[%d] not found"
987
  msgstr ""
988
 
989
+ #: includes/instances.php:111 includes/instances.php:157
990
+ #, php-format
991
+ msgid "Parser '%s' not found."
992
  msgstr ""
993
 
994
+ #: includes/links.php:157
995
+ msgid "The plugin script was terminated while trying to check the link."
996
  msgstr ""
997
 
998
+ #: includes/links.php:201
999
+ msgid "The plugin doesn't know how to check this type of link."
1000
  msgstr ""
1001
 
1002
+ #: includes/links.php:289
1003
+ msgid "Link is valid."
1004
  msgstr ""
1005
 
1006
+ #: includes/links.php:291
1007
+ msgid "Link is broken."
1008
  msgstr ""
1009
 
1010
+ #: includes/links.php:484 includes/links.php:586 includes/links.php:621
1011
+ msgid "Link is not valid"
1012
  msgstr ""
1013
 
1014
+ #: includes/links.php:501
1015
+ msgid ""
1016
+ "This link can not be edited because it is not used anywhere on this site."
1017
  msgstr ""
1018
 
1019
+ #: includes/links.php:527
1020
+ msgid "Failed to create a DB entry for the new URL."
 
1021
  msgstr ""
1022
 
1023
+ #: includes/links.php:599
1024
+ msgid "This link is not a redirect"
1025
  msgstr ""
1026
 
1027
+ #: includes/links.php:648 includes/links.php:685
1028
+ msgid "Couldn't delete the link's database record"
1029
  msgstr ""
1030
 
1031
+ #: includes/links.php:770
1032
+ msgid "Broken"
 
1033
  msgstr ""
1034
 
1035
+ #: includes/links.php:772
1036
+ msgid "No broken links found"
1037
  msgstr ""
1038
 
1039
+ #: includes/links.php:779
1040
+ msgid "Redirects"
1041
  msgstr ""
1042
 
1043
+ #: includes/links.php:780
1044
+ msgid "Redirected Links"
 
1045
  msgstr ""
1046
 
1047
+ #: includes/links.php:781
1048
+ msgid "No redirects found"
1049
  msgstr ""
1050
 
1051
+ #: includes/links.php:789
1052
+ msgid "All"
1053
  msgstr ""
1054
 
1055
+ #: includes/links.php:790
1056
+ msgid "Detected Links"
1057
  msgstr ""
1058
 
1059
+ #: includes/links.php:791
1060
+ msgid "No links found (yet)"
1061
  msgstr ""
1062
 
1063
+ #: includes/links.php:799
1064
+ msgid "Search Results"
1065
  msgstr ""
1066
 
1067
+ #: includes/links.php:800 includes/links.php:843
1068
+ msgid "No links found for your query"
1069
  msgstr ""
1070
 
1071
+ #: includes/parsers.php:151
1072
  #, php-format
1073
+ msgid "Editing is not implemented in the '%s' parser"
1074
  msgstr ""
1075
 
1076
+ #: includes/parsers.php:166
1077
  #, php-format
1078
+ msgid "Unlinking is not implemented in the '%s' parser"
1079
  msgstr ""
1080
 
1081
+ #. Plugin Name of the plugin/theme
1082
  msgid "Broken Link Checker"
1083
  msgstr ""
1084
 
1085
+ #. Plugin URI of the plugin/theme
1086
  msgid "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
1087
  msgstr ""
1088
 
1089
+ #. Description of the plugin/theme
1090
  msgid ""
1091
+ "Checks your blog for broken links and missing images and notifies you on the "
1092
+ "dashboard if any are found."
1093
  msgstr ""
1094
 
1095
+ #. Author of the plugin/theme
1096
  msgid "Janis Elsts"
1097
  msgstr ""
1098
 
1099
+ #. Author URI of the plugin/theme
1100
  msgid "http://w-shadow.com/blog/"
1101
  msgstr ""
link-classes.php DELETED
@@ -1,622 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * @author W-Shadow
5
- * @copyright 2009
6
- */
7
-
8
- if (!class_exists('blcLink')){
9
- class blcLink {
10
-
11
- //Object state
12
- var $is_new = false;
13
- var $last_headers = '';
14
- var $meets_check_threshold = false; //currently unused
15
-
16
- //DB fields
17
- var $link_id = 0;
18
- var $url = '';
19
- var $last_check='0000-00-00 00:00:00';
20
- var $check_count = 0;
21
- var $final_url = '';
22
- var $log = '';
23
- var $http_code = 0;
24
- var $request_duration = 0;
25
- var $timeout = false;
26
- var $redirect_count = 0;
27
-
28
- function __construct($arg = null){
29
- global $wpdb;
30
-
31
- if (is_int($arg)){
32
- //Load a link with ID = $arg from the DB.
33
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id=%d LIMIT 1", $arg);
34
- $arr = $wpdb->get_row( $q, ARRAY_A );
35
-
36
- if ( is_array($arr) ){ //Loaded successfully
37
- $this->set_values($arr);
38
- } else {
39
- //Link not found. The object is invalid.
40
- //I'd throw an error, but that wouldn't be PHP 4 compatible...
41
- }
42
-
43
- } else if (is_string($arg)){
44
- //Load a link with URL = $arg from the DB. Create a new one if the record isn't found.
45
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE url=%s LIMIT 1", $arg);
46
- $arr = $wpdb->get_row( $q, ARRAY_A );
47
-
48
- if ( is_array($arr) ){ //Loaded successfully
49
- $this->set_values($arr);
50
- } else { //Link not found, treat as new
51
- $this->url = $arg;
52
- $this->is_new = true;
53
- }
54
-
55
- } else if (is_array($arg)){
56
- $this->set_values($arg);
57
- //Is this a new link?
58
- $this->is_new = empty($this->link_id);
59
- } else {
60
- $this->is_new = true;
61
- }
62
- }
63
-
64
- function blcLink($arg = null){
65
- $this->__construct($arg);
66
- }
67
-
68
- /**
69
- * blcLink::set_values()
70
- * Set the internal values to the ones provided in an array (doesn't sanitize).
71
- *
72
- * @param array $arr An associative array of values
73
- * @return void
74
- */
75
- function set_values($arr){
76
- foreach( $arr as $key => $value ){
77
- $this->$key = $value;
78
- }
79
- }
80
-
81
- /**
82
- * blcLink::valid()
83
- * Verifies whether the object represents a valid link
84
- *
85
- * @return bool
86
- */
87
- function valid(){
88
- return !empty( $this->url ) && ( !empty($this->link_id) || $this->is_new );
89
- }
90
-
91
- /**
92
- * blcLink::check()
93
- * Check if the link is working.
94
- *
95
- * @return bool
96
- */
97
- function check( $timeout = 40 ){
98
- if ( !$this->valid() ) return false;
99
-
100
- //General note : there is usually no need to save() the result of the check
101
- //in this method because it will be typically called from wsBrokenLinkChecker::work()
102
- //that will call the save() method for us.
103
-
104
- /*
105
- Check for problematic (though not necessarily "broken") links.
106
- If a link has been checked multiple times and still hasn't been marked as
107
- timed-out or broken then probably the checking algorithm is having problems with
108
- that link. Mark it as timed-out and hope the user sorts it out.
109
- */
110
- if ( ($this->check_count >= 3) && ( !$this->timeout ) && ( $this->http_code == BLC_CHECKING ) ) {
111
- $this->timeout = true;
112
- $this->http_code = BLC_TIMEOUT;
113
- $this->last_check = date('Y-m-d H:i:s');
114
- $this->log .= "\r\n[A weird error was detected. This should never happen.]";
115
- return false;
116
- }
117
-
118
- //Update the DB record before actually performing the check.
119
- //Useful if something goes terribly wrong while checkint this particular URL.
120
- //Note : might be unnecessary.
121
- $this->check_count++;
122
- $this->last_check = date('Y-m-d H:i:s');
123
- $this->log = '';
124
- $this->final_url = '';
125
- $this->http_code = BLC_CHECKING;
126
- $this->request_duration = 0;
127
- $this->timeout = false;
128
- $this->redirect_count = 0;
129
- $this->save();
130
-
131
- //Empty some variables before running the check
132
- $this->last_headers = '';
133
-
134
- //Save the URL into a local var; we'll need it later.
135
- $url = $this->url;
136
-
137
- $parts = parse_url($url);
138
- //Only HTTP links are checked. All others are automatically considered okay.
139
- if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') ) {
140
- $this->log .= "URL protocol ($parts[scheme]) is not HTTP(S). This link won't be checked.\n";
141
- $this->http_code = 200;
142
- return true;
143
- }
144
-
145
- //Kill the #anchor if it's present
146
- $anchor_start = strpos($url, '#');
147
- if ( $anchor_start !== false ){
148
- $url = substr($url, 0, $anchor_start);
149
- }
150
-
151
- //******* Use CURL if available ***********
152
- if ( function_exists('curl_init') ) {
153
- $ch = curl_init();
154
- curl_setopt($ch, CURLOPT_URL, blcUtility::urlencodefix($url));
155
- //Masquerade as Internet explorer
156
- curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
157
- //Add a semi-plausible referer header to avoid tripping up some bot traps
158
- curl_setopt($ch, CURLOPT_REFERER, get_option('home'));
159
-
160
- //Redirects don't work when safe mode or open_basedir is enabled.
161
- if ( !blcUtility::is_safe_mode() && !blcUtility::is_open_basedir() ) {
162
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
163
- }
164
- curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
165
-
166
- //Set the timeout
167
- curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
168
-
169
- //Set the proxy configuration. The user can provide this in wp-config.php
170
- if (defined('WP_PROXY_HOST')) {
171
- curl_setopt($ch, CURLOPT_PROXY, WP_PROXY_HOST);
172
- }
173
-
174
- if (defined('WP_PROXY_PORT')) {
175
- curl_setopt($ch, CURLOPT_PROXYPORT, WP_PROXY_PORT);
176
- }
177
-
178
- if (defined('WP_PROXY_USERNAME')){
179
- $auth = WP_PROXY_USERNAME;
180
- if (defined('WP_PROXY_PASSWORD')){
181
- $auth .= ':' . WP_PROXY_PASSWORD;
182
- }
183
- curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth);
184
- }
185
-
186
- //Is this even necessary?
187
- curl_setopt($ch, CURLOPT_FAILONERROR, false);
188
-
189
- $nobody = false;
190
- if( $parts['scheme'] == 'https' ){
191
- //TODO: Redirects don't work with HTTPS
192
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
193
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
194
- } else {
195
- $nobody = true;
196
- curl_setopt($ch, CURLOPT_NOBODY, true); //Use the HEAD method for non-https URLs
197
- }
198
-
199
- //Register a callback function which will process the HTTP header(s).
200
- //It can be called multiple times if the remote server performs a redirect.
201
- curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this,'read_header'));
202
-
203
- //Execute the request
204
- curl_exec($ch);
205
-
206
- $info = curl_getinfo($ch);
207
- $code = intval( $info['http_code'] );
208
-
209
- $this->log .= '=== ';
210
-
211
- if ( $code ){
212
- $this->log .= sprintf( __('First try : %d', 'broken-link-checker'), $code);
213
- } else {
214
- $this->log .= __('First try : 0 (No response)', 'broken-link-checker');
215
- }
216
-
217
- $this->log .= " ===\n\n";
218
-
219
- $this->log .= $this->last_headers."\n";
220
-
221
- if ( (($code<200) || ($code>=400)) && $nobody) {
222
- $this->log .= __("Trying a second time with different settings...", 'broken-link-checker') . "\n";
223
- $this->last_headers = '';
224
-
225
- curl_setopt($ch, CURLOPT_NOBODY, false); //Don't send a HEAD request this time
226
- curl_setopt($ch, CURLOPT_HTTPGET, true); //Switch back to GET instead.
227
- curl_setopt($ch, CURLOPT_RANGE, '0-2047');//But limit the desired response size,
228
- //we don't want to eat the user's bandwidth.
229
- //Run it again
230
- curl_exec($ch);
231
-
232
- $info = curl_getinfo($ch);
233
- $code = intval( $info['http_code'] );
234
-
235
- $this->log .= '=== ';
236
- if ( $code ){
237
- $this->log .= sprintf( __('Second try : %d', 'broken-link-checker'), $code);
238
- } else {
239
- $this->log .= __('Second try : 0 (No response)', 'broken-link-checker');
240
- }
241
- $this->log .= " ===\n\n";
242
-
243
- $this->log .= $this->last_headers."\n";
244
- }
245
-
246
- $this->http_code = $code != 0 ? $code : BLC_TIMEOUT;
247
- $this->final_url = $info['url'];
248
- $this->request_duration = $info['total_time'];
249
- $this->redirect_count = $info['redirect_count'];
250
-
251
- //When safe_mode or open_basedir is enabled CURL will be forbidden from following redirects,
252
- //so redirect_count will be 0 for all URLs. As a workaround, set it to 1 when the HTTP
253
- //response codes indicates a redirect but redirect_count is zero.
254
- //Note to self : Extracting the Location header might also be helpful.
255
- if ( ($this->redirect_count == 0) && ( in_array( $this->http_code, array(301, 302, 307) ) ) ){
256
- $this->redirect_count = 1;
257
- }
258
-
259
-
260
- curl_close($ch);
261
-
262
- } elseif ( class_exists('Snoopy') ) {
263
- //******** Use Snoopy if CURL is not available *********
264
- //Note : Snoopy doesn't work too well with HTTPS URLs.
265
- $this->log .= "<em>(" . __('Using Snoopy', 'broken-link-checker') . ")</em>\n";
266
-
267
- $start_time = microtime_float(true);
268
-
269
- $snoopy = new Snoopy;
270
- $snoopy->read_timeout = $timeout; //read timeout in seconds
271
- $snoopy->maxlength = 1024*5; //load up to 5 kilobytes
272
- $snoopy->fetch($url);
273
-
274
- $this->request_duration = microtime_float(true) - $start_time;
275
-
276
- $this->http_code = $snoopy->status; //HTTP status code (note : Snoopy returns -100 on timeout)
277
- if ( $this->http_code == -100 ){
278
- $this->http_code = BLC_TIMEOUT;
279
- $this->timeout = true;
280
- }
281
-
282
- if ($snoopy->error)
283
- $this->log .= $snoopy->error."\n";
284
- if ($snoopy->timed_out)
285
- $this->log .= __("Request timed out.", 'broken-link-checker') . "\n";
286
-
287
- if ( is_array($snoopy->headers) )
288
- $this->log .= implode("", $snoopy->headers)."\n"; //those headers already contain newlines
289
-
290
- //Redirected?
291
- if ( $snoopy->lastredirectaddr ) {
292
- $this->final_url = $snoopy->lastredirectaddr;
293
- $this->redirect_count = $snoopy->_redirectdepth;
294
- } else {
295
- $this->final_url = $this->url;
296
- }
297
- }
298
-
299
- /*"Good" response codes are anything in the 2XX range (e.g "200 OK") and redirects - the 3XX range.
300
- HTTP 401 Unauthorized is a special case that is considered OK as well. Other errors - the 4XX range -
301
- are treated as "page doesn't exist'". */
302
- //TODO: Treat circular redirects as broken links.
303
- if ( (($this->http_code>=200) && ($this->http_code<400)) || ($this->http_code == 401) ) {
304
- $this->log .= __("Link is valid.", 'broken-link-checker');
305
- //Reset the check count for valid links.
306
- $this->check_count = 0;
307
- return true;
308
- } else {
309
- $this->log .= __("Link is broken.", 'broken-link-checker');
310
- if ( $this->http_code == BLC_TIMEOUT ){
311
- //This is probably a timeout
312
- $this->timeout = true;
313
- $this->log .= "\r\n(" . __("Most likely the connection timed out or the domain doesn't exist.", 'broken-link-checker') . ')';
314
- }
315
- return false;
316
- }
317
- }
318
-
319
- function read_header($ch, $header){
320
- $this->last_headers .= $header;
321
- return strlen($header);
322
- }
323
-
324
- /**
325
- * blcLink::save()
326
- * Save link data to DB.
327
- *
328
- * @return bool True if saved successfully, false otherwise.
329
- */
330
- function save(){
331
- global $wpdb;
332
-
333
- if ( !$this->valid() ) return false;
334
-
335
- if ( $this->is_new ){
336
-
337
- //Insert a new row
338
- $q = "
339
- INSERT INTO {$wpdb->prefix}blc_links
340
- ( url, last_check, check_count, final_url, redirect_count, log, http_code, request_duration, timeout )
341
- VALUES( %s, %s, %d, %s, %d, %s, %d, %f, %d )";
342
- $q = $wpdb->prepare($q, $this->url, $this->last_check, $this->check_count, $this->final_url,
343
- $this->redirect_count, $this->log, $this->http_code, $this->request_duration, (integer)$this->timeout );
344
- $rez = $wpdb->query($q);
345
-
346
- $rez = $rez !== false;
347
-
348
- if ($rez){
349
- $this->link_id = $wpdb->insert_id;
350
- //echo "Link added, ID : {$this->link_id}\r\n<br>";
351
- //If the link was successfully saved then it's no longer "new"
352
- $this->is_new = !$rez;
353
- } else {
354
- printf( __('Error adding link %s : %s', 'broken-link-checker'), $url, $wpdb->last_error );
355
- echo "\r\n<br>";
356
- }
357
-
358
- return $rez;
359
-
360
- } else {
361
-
362
- //Update an existing DB record
363
- $q = "UPDATE {$wpdb->prefix}blc_links SET url=%s, last_check=%s, check_count=%d, final_url=%s,
364
- redirect_count=%d, log=%s, http_code=%d, request_duration=%f, timeout=%d
365
- WHERE link_id=%d";
366
-
367
- $q = $wpdb->prepare($q, $this->url, $this->last_check, $this->check_count, $this->final_url,
368
- $this->redirect_count, $this->log, $this->http_code, $this->request_duration, (integer)$this->timeout, $this->link_id );
369
-
370
- $rez = $wpdb->query($q);
371
- if ( $rez !== false ){
372
- //echo "Link updated, ID : {$this->link_id}\r\n<br>";
373
- } else {
374
- printf( __('Error updating link %d : %s', 'broken-link-checker'), $this->link_id, $wpdb->last_error );
375
- echo "\r\n<br>";
376
- }
377
- return $rez !== false;
378
- }
379
- }
380
-
381
- /**
382
- * blcLink::edit()
383
- * Edit all instances of the link by changing the URL.
384
- *
385
- * Here's how this really works : create a new link with the new URL. Then edit()
386
- * all instances and point them to the new link record. If some instance can't be
387
- * edited they will still point to the old record. The old record is deleted
388
- * if all instances were edited successfully.
389
- *
390
- * @param string $new_url
391
- * @return array An associative array with the new link ID, the number of successfully edited instances and the number of failed edits.
392
- */
393
- function edit($new_url){
394
- if ( !$this->valid() ){
395
- return false;
396
- }
397
-
398
- //FB::info('Changing link '.$this->link_id .' to URL "'.$new_url.'"');
399
-
400
- $instances = $this->get_instances();
401
- //Fail if there are no instances
402
- if (empty($instances)) return false;
403
-
404
- //Load or create a link with the URL = $new_url
405
- $new_link = new blcLink($new_url);
406
- $was_new = $new_link->is_new;
407
- if ($new_link->is_new) {
408
- //FB::log($new_link, 'Saving a new link');
409
- $new_link->save(); //so that we get a valid link_id
410
- }
411
-
412
- if ( empty($new_link->link_id) ){
413
- //FB::error("Failed to create a new link record");
414
- return false;
415
- }
416
-
417
- //Edit each instance.
418
- //FB::info('Editing ' . count($instances) . ' instances');
419
- $cnt_okay = $cnt_error = 0;
420
- foreach ( $instances as $instance ){
421
- if ( $instance->edit( $this->url, $new_url ) ){
422
- $cnt_okay++;
423
- $instance->link_id = $new_link->link_id;
424
- $instance->save();
425
- //FB::info($instance, 'Successfully edited instance ' . $instance->instance_id);
426
- } else {
427
- $cnt_error++;
428
- //FB::error($instance, 'Failed to edit instance ' . $instance->instance_id);
429
- }
430
- }
431
-
432
- //If all instances were edited successfully we can delete the old link record.
433
- //And copy the new link data into this object. UNLESS this link is equal to the new link
434
- //(which should never happen, but whatever)
435
- if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) && ( $this->link_id != $new_link->link_id ) ){
436
- $this->forget( false );
437
-
438
- $this->link_id = $new_link->link_id;
439
- $this->url = $new_link->url;
440
- $this->final_url = $new_link->url;
441
- $this->log = $new_link->log;
442
- $this->http_code = $new_link->http_code;
443
- $this->redirect_count = $new_link->redirect_count;
444
- $this->timeout = $new_link->timeout;
445
- }
446
-
447
- //On the other hand, if no instances could be edited and the $new_link was really new,
448
- //then delete it.
449
- if ( ( $cnt_okay == 0 ) && $was_new ){
450
- $new_link->forget( false );
451
- }
452
-
453
- return array(
454
- 'new_link_id' => $this->link_id,
455
- 'cnt_okay' => $cnt_okay,
456
- 'cnt_error' => $cnt_error,
457
- );
458
- }
459
-
460
- /**
461
- * blcLink::deredirect()
462
- * Edit all of of this link's instances and replace the URL with the URL that it redirects to.
463
- * This method does nothing if the link isn't a redirect.
464
- *
465
- * @return bool|array An associative array with the new_link_id, the number of successfully edited instances (cnt_okay) and the number of failed edits (cnt_error). Returns False on error or if the link is not a redirect.
466
- */
467
- function deredirect(){
468
- if ( !$this->valid() ){
469
- return false;
470
- }
471
-
472
- if ( ($this->redirect_count <= 0) || empty($this->final_url) ){
473
- return false;
474
- }
475
-
476
- return $this->edit($this->final_url);
477
- }
478
-
479
- /**
480
- * blcLink::unlink()
481
- * Delete (unlink) all instances and the link itself
482
- *
483
- * @return bool Returns True on success, False on error
484
- */
485
- function unlink(){
486
- if ( !$this->valid() ){
487
- return false;
488
- }
489
-
490
- //FB::info($this, 'Removing link');
491
-
492
- $instances = $this->get_instances();
493
- //Fail if there are no instances
494
- if (empty($instances)) {
495
- //FB::warn("This link has no instances. Deleting the link.");
496
- return $this->forget( false ) !== false;
497
- }
498
-
499
- //Unlink each instance.
500
- //FB::info('Unlinking ' . count($instances) . ' instances');
501
- $cnt_okay = $cnt_error = 0;
502
- foreach ( $instances as $instance ){
503
- if ( $instance->unlink( $this->url ) ){
504
- $cnt_okay++;
505
- //FB::info( $instance, 'Successfully unlinked instance' );
506
- } else {
507
- $cnt_error++;
508
- //FB::error( $instance, 'Failed to unlink instance' );
509
- }
510
- }
511
-
512
- //If all instances were unlinked successfully we can delete the link record.
513
- if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) ){
514
- //FB::log('Instances removed, deleting the link.');
515
- return $this->forget() !== false;
516
- } else {
517
- //FB::error("Something went wrong. Unlinked instances : $cnt_okay, errors : $cnt_error");
518
- return false;
519
- }
520
- }
521
-
522
- /**
523
- * blcLink::forget()
524
- * Remove the link and instance records from the DB. Doesn't alter posts/etc.
525
- *
526
- * @return mixed 1 on success, 0 if link not found, false on error.
527
- */
528
- function forget($remove_instances = true){
529
- global $wpdb;
530
- if ( !$this->valid() ) return false;
531
-
532
- if ( !empty($this->link_id) ){
533
- //FB::info($this, 'Deleting link from DB');
534
-
535
- if ( $remove_instances ){
536
- //Remove instances, if any
537
- $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE link_id=%d", $this->link_id) );
538
- }
539
-
540
- //Remove the link itself
541
- $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_links WHERE link_id=%d", $this->link_id) );
542
- $this->link_id = 0;
543
-
544
- return $rez;
545
- } else {
546
- return false;
547
- }
548
-
549
- }
550
-
551
- /**
552
- * blcLink::get_instances()
553
- * Get a list of the link's instances
554
- *
555
- * @param integer $max_count The maximum number of instances to return. The default is -1 (no limit)
556
- * @return array An array of instance objects or FALSE on failure.
557
- */
558
- function get_instances($max_count = -1){
559
- global $wpdb;
560
- if ( !$this->valid() || empty($this->link_id) ) return false;
561
-
562
- $limit = $max_count > 0 ? "LIMIT $max_count":'';
563
-
564
- //Get all instances of this link
565
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id=%d $limit", $this->link_id);
566
- $results = $wpdb->get_results($q, ARRAY_A);
567
-
568
- if ( !empty($results) ) {
569
- //Create an object for each instance
570
- $instances = array();
571
- foreach ($results as $result){
572
- //Each source/link type combination has it's own subclass. E.g. _post_image or _blogroll_link.
573
- $classname = 'blcLinkInstance_' . $result['source_type'] . '_' . $result['instance_type'];
574
- $instances[] = new $classname($result);
575
- }
576
- return $instances;
577
- } else {
578
- return false;
579
- }
580
- }
581
-
582
- /**
583
- * blcLink::add_instance()
584
- * Record a new instance of the link.
585
- *
586
- * @param int $source_id
587
- * @param string $source_type
588
- * @param string $link_text
589
- * @param string $instance_type
590
- * @return object The created instance or FALSE on error.
591
- */
592
- function add_instance($source_id, $source_type, $link_text, $instance_type){
593
-
594
- //The link must be saved before an instance can be added
595
- if ($this->is_new) {
596
- if ( !$this->save()) return false;
597
- }
598
-
599
- //Create a new instance tied to this link
600
- $classname = 'blcLinkInstance_' . $source_type . '_' . $instance_type;
601
- if ( !class_exists($classname) ){
602
- $classname = 'blcLinkInstance';
603
- }
604
- $inst = new $classname( array(
605
- 'link_id' => $this->link_id,
606
- 'source_id' => $source_id,
607
- 'source_type' => $source_type,
608
- 'link_text' => $link_text,
609
- 'instance_type' => $instance_type,
610
- ) );
611
-
612
- //Save the instance to the DB
613
- if ( $inst->save() ){
614
- return $inst;
615
- } else {
616
- return false;
617
- };
618
- }
619
- }
620
- } //class_exists
621
-
622
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,18 +1,18 @@
1
  === Broken Link Checker ===
2
  Contributors: whiteshadow
3
- Tags: links, broken, maintenance, blogroll, custom fields, admin
4
- Requires at least: 2.8.0
5
- Tested up to: 3.0-alpha
6
- Stable tag: 0.8.1
7
 
8
- This plugin will check your posts, custom fields and the blogroll for broken links and missing images and notify you if any are found.
9
 
10
  == Description ==
11
  This plugin will monitor your blog looking for broken links and let you know if any are found.
12
 
13
  **Features**
14
 
15
- * Monitors links in your posts, pages, the blogroll, and custom fields (optional).
16
  * Detects links that don't work and missing images.
17
  * Notifies you on the Dashboard if any are found.
18
  * Also detects redirected links.
@@ -25,26 +25,24 @@ This plugin will monitor your blog looking for broken links and let you know if
25
 
26
  **Basic Usage**
27
 
28
- Once installed, the plugin will begin parsing your posts, bookmarks (AKA blogroll), etc and looking for links. Depending on the size of your site this can take a few minutes or even several hours. When parsing is complete the plugin will start checking each link to see if it works. Again, how long this takes depends on how big your site is and how many links there are. You can monitor the progress and set various link checking options in *Settings -> Link Checker*.
29
 
30
- Note : Currently the plugin only runs when you have at least one tab of the Dashboard open. Cron support will likely be added in a later version.
31
 
32
- The broken links, if any are found, will show up in a new tab of WP admin panel - *Tools -> Broken Links*. A notification will also appear in the "Broken Link Checker" widget on the Dashboard. To save display space, you can keep the widget closed and configure it to expand automatically when problematic links are detected.
33
 
34
- The "Broken Links" tab will by default display broken links that have been detected so far. However, you can use the subnavigation links on that page to view redirects or see a listing of all links - working or not - instead.
35
-
36
- There are several actions associated with each link listed -
37
 
38
  * "Details" shows more info about the link. You can also toggle link details by clicking on the "link text" cell.
39
- * "Edit URL" lets you change the URL of that link. If the link is present in more than one place (e.g. both in a post and in the blogroll) then all instances of that URL will be changed.
40
  * "Unlink" removes the link but leaves the link text intact.
41
- * "Exclude" adds the link's URL to the exclusion list. Excluded URLs won't be checked again.
42
- * "Discard" lets you manually mark the link as valid. This is useful if you know it was detected as broken only due to a temporary glitch or similar. The link will still be checked normally later.
43
 
44
  **Translations**
45
 
46
  * Belorussian - [M. Comfi](http://www.comfi.com/)
47
  * Chinese Simplified - [Hank Yang](http://wenzhu.org/)
 
48
  * Danish - [Georg S. Adamsen](http://wordpress.blogos.dk/)
49
  * Dutch - [Gideon van Melle](http://www.gvmelle.com/)
50
  * French - [Whiler](http://blogs.wittwer.fr/whiler/)
@@ -67,14 +65,35 @@ That's it.
67
 
68
  To upgrade your installation
69
 
70
- 1. De-activate the plugin
71
- 1. Get and upload the new files (do steps 1. - 3. from "new installation" instructions)
72
- 1. Reactivate the plugin. Your settings should have been retained from the previous version.
73
 
74
  == Changelog ==
75
 
76
  *This is an automatically generated changelog*
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  = 0.8.1 =
79
  * Updated Italian translation.
80
  * Removed the survey link.
@@ -220,7 +239,7 @@ To upgrade your installation
220
 
221
  = 0.5 =
222
  * This is a near-complete rewrite with a lot of new features.
223
- * Seehttp://w-shadow.com/blog/2009/05/22/broken-link-checker-05/ for details.
224
 
225
  = 0.4.14 =
226
  * Fix false positives when the URL contains an #anchor
1
  === Broken Link Checker ===
2
  Contributors: whiteshadow
3
+ Tags: links, broken, maintenance, blogroll, custom fields, admin, comments, posts
4
+ Requires at least: 2.9.0
5
+ Tested up to: 3.0-beta1
6
+ Stable tag: 0.9
7
 
8
+ This plugin will check your posts, comments and other places for broken links and missing images and notify you if any are found.
9
 
10
  == Description ==
11
  This plugin will monitor your blog looking for broken links and let you know if any are found.
12
 
13
  **Features**
14
 
15
+ * Monitors links in your posts, pages, comments, the blogroll, and custom fields (optional).
16
  * Detects links that don't work and missing images.
17
  * Notifies you on the Dashboard if any are found.
18
  * Also detects redirected links.
25
 
26
  **Basic Usage**
27
 
28
+ Once installed, the plugin will begin parsing your posts, bookmarks (AKA blogroll), etc and looking for links. Depending on the size of your site this can take from a few minutes to several hours. When parsing is complete, the plugin will start checking each link to see if it works. Again, how long this takes depends on how big your site is and how many links there are. You can monitor the progress and tweak various link checking options in *Settings -> Link Checker*.
29
 
30
+ The broken links, if any are found, will show up in a new tab of the WP admin panel - *Tools -> Broken Links*. A notification will also appear in the "Broken Link Checker" widget on the Dashboard. To save display space, you can keep the widget closed and configure it to expand automatically when problematic links are detected.
31
 
32
+ The "Broken Links" tab will by default display a list of broken links that have been detected so far. However, you can use the links on that page to view redirects or see a listing of all links - working or not - instead. You can also create new link filters by performing a search and clicking the "Create Custom Filter" button. For example, this can be used to create a filter that only shows comment links.
33
 
34
+ There are several actions associated with each link. They show up when you move your mouse over to one of the links listed the aforementioned tab -
 
 
35
 
36
  * "Details" shows more info about the link. You can also toggle link details by clicking on the "link text" cell.
37
+ * "Edit URL" lets you change the URL of that link. If the link is present in more than one place (e.g. both in a post and in the blogroll) then all occurences of that URL will be changed.
38
  * "Unlink" removes the link but leaves the link text intact.
39
+ * "Not broken" lets you manually mark a "broken" link as working. This is useful if you know it was incorrectly detected as broken due to a network glitch or a bug. The marked link will still be checked periodically, but the plugin won't consider it broken unless it gets a new result.
 
40
 
41
  **Translations**
42
 
43
  * Belorussian - [M. Comfi](http://www.comfi.com/)
44
  * Chinese Simplified - [Hank Yang](http://wenzhu.org/)
45
+ * Czech - [Lelkoun](http://lelkoun.cz/)
46
  * Danish - [Georg S. Adamsen](http://wordpress.blogos.dk/)
47
  * Dutch - [Gideon van Melle](http://www.gvmelle.com/)
48
  * French - [Whiler](http://blogs.wittwer.fr/whiler/)
65
 
66
  To upgrade your installation
67
 
68
+ 1. Deactivate the plugin
69
+ 1. Retrieve and upload the new files (do steps 1. - 3. from "new installation" instructions)
70
+ 1. Reactivate the plugin. Your settings will be retained from the previous version.
71
 
72
  == Changelog ==
73
 
74
  *This is an automatically generated changelog*
75
 
76
+ = 0.9 =
77
+ * Masquerade as IE 7 when using the Snoopy library to check links. Should prevent some false positives.
78
+ * Fixed relative URL handling (yet again). It'll work this time, honest ;)
79
+ * Fixed post titles being displayed incorrectly on multilingual blogs (props Konstanin Zhilenko)
80
+ * Misc fixes/comments.
81
+ * "Unlink" works properly now.
82
+ * Additional source code comments.
83
+ * Don't try to display icons in email notifications. It didn't work anyway.
84
+ * Use AJAX nonces for additional security.
85
+ * General code cleanup.
86
+ * Email notifications about broken links.
87
+ * "Recheck" bulk action.
88
+ * Check comment links.
89
+ * Suspend checking if the server is overloaded (on by default).
90
+ * Icons for broken links and redirects.
91
+ * Fixed some UI glitches.
92
+ * "Discard" gone, replaced by "Not broken".
93
+ * "Exclude" gone from action links.
94
+ * Better handling of false positives.
95
+ * FTP, mailto:, javascript: and other links with unsupported protocols now show up in the �All links� list.
96
+
97
  = 0.8.1 =
98
  * Updated Italian translation.
99
  * Removed the survey link.
239
 
240
  = 0.5 =
241
  * This is a near-complete rewrite with a lot of new features.
242
+ * See http://w-shadow.com/blog/2009/05/22/broken-link-checker-05/ for details.
243
 
244
  = 0.4.14 =
245
  * Fix false positives when the URL contains an #anchor
uninstall.php CHANGED
@@ -16,7 +16,7 @@ if( defined( 'ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
16
  $mywpdb = $GLOBALS['wpdb'];
17
  if( isset($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" );
20
  }
21
  }
22
 
16
  $mywpdb = $GLOBALS['wpdb'];
17
  if( isset($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
  }
22
 
utility-class.php CHANGED
@@ -67,7 +67,7 @@ class blcUtility {
67
  if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') )
68
  return false;
69
  }
70
-
71
  $url = html_entity_decode($url);
72
  $url = preg_replace(
73
  array('/([\?&]PHPSESSID=\w+)$/i', //remove session ID
@@ -103,12 +103,18 @@ class blcUtility {
103
  return false;
104
  }
105
  if( isset($p["scheme"]) ) return $relative;
 
 
 
 
 
106
 
107
  $parts=(parse_url($absolute));
108
-
109
  if(substr($relative,0,1)=='/') {
110
- $cparts = (explode("/", $relative));
111
- array_shift($cparts);
 
112
  } else {
113
  if(isset($parts['path'])){
114
  $aparts=explode('/',$parts['path']);
@@ -117,20 +123,23 @@ class blcUtility {
117
  } else {
118
  $aparts=array();
119
  }
120
-
121
- $rparts = (explode("/", $relative));
122
-
123
- $cparts = array_merge($aparts, $rparts);
124
- foreach($cparts as $i => $part) {
125
- if($part == '.') {
126
- unset($cparts[$i]);
127
- } else if($part == '..') {
128
- unset($cparts[$i]);
129
- unset($cparts[$i-1]);
130
- }
131
- }
 
 
 
132
  }
133
- $path = implode("/", $cparts);
134
 
135
  $url = '';
136
  if($parts['scheme']) {
@@ -205,6 +214,147 @@ class blcUtility {
205
  return $open_basedir && ( strtolower($open_basedir) != 'none' );
206
  }
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  }//class
209
 
210
  }//class_exists
67
  if ( ($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') )
68
  return false;
69
  }
70
+
71
  $url = html_entity_decode($url);
72
  $url = preg_replace(
73
  array('/([\?&]PHPSESSID=\w+)$/i', //remove session ID
103
  return false;
104
  }
105
  if( isset($p["scheme"]) ) return $relative;
106
+
107
+ //If the relative URL is just a query string, simply attach it to the absolute URL and return
108
+ if ( substr($relative, 0, 1) == '?' ){
109
+ return $absolute . $relative;
110
+ }
111
 
112
  $parts=(parse_url($absolute));
113
+
114
  if(substr($relative,0,1)=='/') {
115
+ //Relative URL starts with a slash => ignore the base path and jump straight to the root.
116
+ $path_segments = explode("/", $relative);
117
+ array_shift($path_segments);
118
  } else {
119
  if(isset($parts['path'])){
120
  $aparts=explode('/',$parts['path']);
123
  } else {
124
  $aparts=array();
125
  }
126
+
127
+ //Merge together the base path & the relative path
128
+ $aparts = array_merge($aparts, explode("/", $relative));
129
+
130
+ //Filter the merged path
131
+ $path_segments = array();
132
+ foreach($aparts as $part){
133
+ if ( $part == '.' ){
134
+ continue; //. = "this directory". It's basically a no-op, so we skip it.
135
+ } elseif ( $part == '..' ) {
136
+ array_pop($path_segments); //.. = one directory up. Remove the last seen path segment.
137
+ } else {
138
+ array_push($path_segments, $part); //Normal directory -> add it to the path.
139
+ }
140
+ }
141
  }
142
+ $path = implode("/", $path_segments);
143
 
144
  $url = '';
145
  if($parts['scheme']) {
214
  return $open_basedir && ( strtolower($open_basedir) != 'none' );
215
  }
216
 
217
+ /**
218
+ * Truncate a string on a specified boundary character.
219
+ *
220
+ * @param string $text The text to truncate.
221
+ * @param integer $max_characters Return no more than $max_characters
222
+ * @param string $break Break on this character. Defaults to space.
223
+ * @param string $pad Pad the truncated string with this string. Defaults to an HTML ellipsis.
224
+ * @return
225
+ */
226
+ function truncate($text, $max_characters = 0, $break = ' ', $pad = '&hellip;'){
227
+ if ( strlen($text) <= $max_characters ){
228
+ return $text;
229
+ }
230
+
231
+ $text = substr($text, 0, $max_characters);
232
+ $break_pos = strrpos($text, $break);
233
+ if ( $break_pos !== false ){
234
+ $text = substr($text, 0, $break_pos);
235
+ }
236
+
237
+ return $text.$pad;
238
+ }
239
+
240
+ /**
241
+ * extract_tags()
242
+ * Extract specific HTML tags and their attributes from a string.
243
+ *
244
+ * You can either specify one tag, an array of tag names, or a regular expression that matches the tag name(s).
245
+ * If multiple tags are specified you must also set the $selfclosing parameter and it must be the same for
246
+ * all specified tags (so you can't extract both normal and self-closing tags in one go).
247
+ *
248
+ * The function returns a numerically indexed array of extracted tags. Each entry is an associative array
249
+ * with these keys :
250
+ * tag_name - the name of the extracted tag, e.g. "a" or "img".
251
+ * offset - the numberic offset of the first character of the tag within the HTML source.
252
+ * contents - the inner HTML of the tag. This is always empty for self-closing tags.
253
+ * attributes - a name -> value array of the tag's attributes, or an empty array if the tag has none.
254
+ * full_tag - the entire matched tag, e.g. '<a href="http://example.com">example.com</a>'. This key
255
+ * will only be present if you set $return_the_entire_tag to true.
256
+ *
257
+ * @param string $html The HTML code to search for tags.
258
+ * @param string|array $tag The tag(s) to extract.
259
+ * @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.
260
+ * @param bool $return_the_entire_tag Return the entire matched tag in 'full_tag' key of the results array.
261
+ * @param string $charset The character set of the HTML code. Defaults to ISO-8859-1.
262
+ *
263
+ * @return array An array of extracted tags, or an empty array if no matching tags were found.
264
+ */
265
+ function extract_tags( $html, $tag, $selfclosing = null, $return_the_entire_tag = false, $charset = 'ISO-8859-1' ){
266
+
267
+ if ( is_array($tag) ){
268
+ $tag = implode('|', $tag);
269
+ }
270
+
271
+ //If the user didn't specify if $tag is a self-closing tag we try to auto-detect it
272
+ //by checking against a list of known self-closing tags.
273
+ $selfclosing_tags = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'col', 'param' );
274
+ if ( is_null($selfclosing) ){
275
+ $selfclosing = in_array( $tag, $selfclosing_tags );
276
+ }
277
+
278
+ //The regexp is different for normal and self-closing tags because I can't figure out
279
+ //how to make a sufficiently robust unified one.
280
+ if ( $selfclosing ){
281
+ $tag_pattern =
282
+ '@<(?P<tag>'.$tag.') # <tag
283
+ (?P<attributes>\s[^>]+)? # attributes, if any
284
+ \s*/?> # /> or just >, being lenient here
285
+ @xsi';
286
+ } else {
287
+ $tag_pattern =
288
+ '@<(?P<tag>'.$tag.') # <tag
289
+ (?P<attributes>\s[^>]+)? # attributes, if any
290
+ \s*> # >
291
+ (?P<contents>.*?) # tag contents
292
+ </(?P=tag)> # the closing </tag>
293
+ @xsi';
294
+ }
295
+
296
+ $attribute_pattern =
297
+ '@
298
+ (?P<name>\w+) # attribute name
299
+ \s*=\s*
300
+ (
301
+ (?P<quote>[\"\'])(?P<value_quoted>.*?)(?P=quote) # a quoted value
302
+ | # or
303
+ (?P<value_unquoted>[^\s"\']+?)(?:\s+|$) # an unquoted value (terminated by whitespace or EOF)
304
+ )
305
+ @xsi';
306
+
307
+ //Find all tags
308
+ if ( !preg_match_all($tag_pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ){
309
+ //Return an empty array if we didn't find anything
310
+ return array();
311
+ }
312
+
313
+ $tags = array();
314
+ foreach ($matches as $match){
315
+
316
+ //Parse tag attributes, if any
317
+ $attributes = array();
318
+ if ( !empty($match['attributes'][0]) ){
319
+
320
+ if ( preg_match_all( $attribute_pattern, $match['attributes'][0], $attribute_data, PREG_SET_ORDER ) ){
321
+ //Turn the attribute data into a name->value array
322
+ foreach($attribute_data as $attr){
323
+ if( !empty($attr['value_quoted']) ){
324
+ $value = $attr['value_quoted'];
325
+ } else if( !empty($attr['value_unquoted']) ){
326
+ $value = $attr['value_unquoted'];
327
+ } else {
328
+ $value = '';
329
+ }
330
+
331
+ //Passing the value through html_entity_decode is handy when you want
332
+ //to extract link URLs or something like that. You might want to remove
333
+ //or modify this call if it doesn't fit your situation.
334
+ $value = html_entity_decode( $value, ENT_QUOTES, $charset );
335
+
336
+ $attributes[$attr['name']] = $value;
337
+ }
338
+ }
339
+
340
+ }
341
+
342
+ $tag = array(
343
+ 'tag_name' => $match['tag'][0],
344
+ 'offset' => $match[0][1],
345
+ 'contents' => !empty($match['contents'])?$match['contents'][0]:'', //empty for self-closing tags
346
+ 'attributes' => $attributes,
347
+ );
348
+ if ( $return_the_entire_tag ){
349
+ $tag['full_tag'] = $match[0][0];
350
+ }
351
+
352
+ $tags[] = $tag;
353
+ }
354
+
355
+ return $tags;
356
+ }
357
+
358
  }//class
359
 
360
  }//class_exists