Broken Link Checker - Version 1.11.8

Version Description

Download this release

Release Info

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

Code changes from version 1.11.5 to 1.11.8

Files changed (64) hide show
  1. broken-link-checker.php +23 -22
  2. core/core.php +3852 -3831
  3. core/init.php +388 -388
  4. css/links-page.css +498 -498
  5. css/options-page.css +242 -242
  6. idn/LICENCE +0 -0
  7. idn/ReadMe.txt +0 -0
  8. idn/idna_convert.class.php +0 -0
  9. idn/transcode_wrapper.php +0 -0
  10. idn/uctc.php +0 -0
  11. images/blue_dot.png +0 -0
  12. images/dailymotion-embed.png +0 -0
  13. images/font-awesome/font-awesome-code.png +0 -0
  14. images/font-awesome/font-awesome-comment-alt.png +0 -0
  15. images/font-awesome/font-awesome-exclamation-sign.png +0 -0
  16. images/font-awesome/font-awesome-link.png +0 -0
  17. images/font-awesome/font-awesome-picture.png +0 -0
  18. images/font-awesome/font-awesome-user.png +0 -0
  19. images/font-awesome/font-awesome-warning-sign.png +0 -0
  20. images/font-awesome/readme.txt +10 -10
  21. images/googlevideo-embed.png +0 -0
  22. images/red_highlight.png +0 -0
  23. images/vimeo-embed.png +0 -0
  24. images/youtube-embed.png +0 -0
  25. images/youtube-iframe.png +0 -0
  26. images/youtube-playlist-embed.png +0 -0
  27. includes/activation.php +114 -114
  28. includes/admin/db-schema.php +96 -96
  29. includes/admin/db-upgrade.php +585 -585
  30. includes/admin/links-page-js.php +945 -945
  31. includes/admin/options-page-js.php +146 -146
  32. includes/admin/search-form.php +116 -116
  33. includes/admin/sidebar.php +13 -13
  34. includes/admin/table-printer.php +0 -0
  35. includes/any-post.php +804 -774
  36. includes/checkers.php +120 -120
  37. includes/config-manager.php +127 -127
  38. includes/containers.php +911 -911
  39. includes/extra-strings.php +0 -0
  40. includes/instances.php +639 -639
  41. includes/link-query.php +866 -866
  42. includes/links.php +1140 -1138
  43. includes/logger.php +188 -188
  44. includes/module-base.php +80 -80
  45. includes/module-manager.php +860 -860
  46. includes/modules.php +34 -34
  47. includes/parsers.php +365 -365
  48. includes/screen-meta-links.php +312 -312
  49. includes/screen-options/screen-options.js +12 -12
  50. includes/screen-options/screen-options.php +297 -297
  51. includes/token-bucket.php +120 -120
  52. includes/transactions-manager.php +4 -0
  53. includes/utility-class.php +417 -417
  54. includes/wp-mutex.php +57 -57
  55. js/jquery.cookie.js +0 -0
  56. js/sprintf.js +55 -55
  57. languages/alternative.broken-link-checker-it_IT.mo +0 -0
  58. languages/alternative.broken-link-checker-it_IT.po +0 -692
  59. languages/broken-link-checker-ar_AR.mo +0 -0
  60. languages/broken-link-checker-ar_AR.po +0 -1668
  61. languages/broken-link-checker-be_BY.mo +0 -0
  62. languages/broken-link-checker-be_BY.po +0 -700
  63. languages/broken-link-checker-cs_CZ.mo +0 -0
  64. languages/broken-link-checker-cs_CZ.po +0 -279
broken-link-checker.php CHANGED
@@ -1,22 +1,23 @@
1
- <?php
2
- /*
3
- Plugin Name: Broken Link Checker
4
- Plugin URI: https://wordpress.org/plugins/broken-link-checker/
5
- Description: Checks your blog for broken links and missing images and notifies you on the dashboard if any are found.
6
- Version: 1.11.5
7
- Author: Janis Elsts, Vladimir Prelovac
8
- Text Domain: broken-link-checker
9
- */
10
-
11
- //Path to this file
12
- if ( !defined('BLC_PLUGIN_FILE') ){
13
- define('BLC_PLUGIN_FILE', __FILE__);
14
- }
15
-
16
- //Path to the plugin's directory
17
- if ( !defined('BLC_DIRECTORY') ){
18
- define('BLC_DIRECTORY', dirname(__FILE__));
19
- }
20
-
21
- //Load the actual plugin
22
- require 'core/init.php';
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Broken Link Checker
4
+ Plugin URI: https://wordpress.org/plugins/broken-link-checker/
5
+ Description: Checks your blog for broken links and missing images and notifies you on the dashboard if any are found.
6
+ Version: 1.11.8
7
+ Author: ManageWP
8
+ Author URI: https://managewp.com
9
+ Text Domain: broken-link-checker
10
+ */
11
+
12
+ //Path to this file
13
+ if ( !defined('BLC_PLUGIN_FILE') ){
14
+ define('BLC_PLUGIN_FILE', __FILE__);
15
+ }
16
+
17
+ //Path to the plugin's directory
18
+ if ( !defined('BLC_DIRECTORY') ){
19
+ define('BLC_DIRECTORY', dirname(__FILE__));
20
+ }
21
+
22
+ //Load the actual plugin
23
+ require 'core/init.php';
core/core.php CHANGED
@@ -1,3831 +1,3852 @@
1
- <?php
2
- /**
3
- * Simple function to replicate PHP 5 behaviour
4
- */
5
- if ( ! function_exists( 'microtime_float' ) ) {
6
- function microtime_float() {
7
- list($usec, $sec) = explode( ' ', microtime() );
8
- return ( (float)$usec + (float)$sec);
9
- }
10
- }
11
-
12
- require BLC_DIRECTORY . '/includes/screen-options/screen-options.php';
13
- require BLC_DIRECTORY . '/includes/screen-meta-links.php';
14
- require BLC_DIRECTORY . '/includes/wp-mutex.php';
15
- require BLC_DIRECTORY . '/includes/transactions-manager.php';
16
-
17
- if (!class_exists('wsBrokenLinkChecker')) {
18
-
19
- class wsBrokenLinkChecker {
20
- var $conf;
21
- var $loader;
22
- var $my_basename = '';
23
-
24
- var $db_version; //The required version of the plugin's DB schema.
25
-
26
- var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
27
-
28
- private $is_textdomain_loaded = false;
29
-
30
- /**
31
- * wsBrokenLinkChecker::wsBrokenLinkChecker()
32
- * Class constructor
33
- *
34
- * @param string $loader The fully qualified filename of the loader script that WP identifies as the "main" plugin file.
35
- * @param blcConfigurationManager $conf An instance of the configuration manager
36
- * @return void
37
- */
38
- function __construct ( $loader, $conf ) {
39
- $this->db_version = BLC_DATABASE_VERSION;
40
-
41
- $this->conf = $conf;
42
- $this->loader = $loader;
43
- $this->my_basename = plugin_basename( $this->loader );
44
-
45
- $this->load_language();
46
-
47
- //Unlike the activation hook, the deactivation callback *can* be registered in this file
48
- //because deactivation happens after this class has already been instantiated (durinng the
49
- //'init' action).
50
- register_deactivation_hook($loader, array($this, 'deactivation'));
51
-
52
- add_action('admin_menu', array($this,'admin_menu'));
53
-
54
- //Load jQuery on Dashboard pages (probably redundant as WP already does that)
55
- add_action('admin_print_scripts', array($this,'admin_print_scripts'));
56
-
57
- //The dashboard widget
58
- add_action('wp_dashboard_setup', array($this, 'hook_wp_dashboard_setup'));
59
-
60
- //AJAXy hooks
61
- add_action( 'wp_ajax_blc_full_status', array($this,'ajax_full_status') );
62
- add_action( 'wp_ajax_blc_dashboard_status', array($this,'ajax_dashboard_status') );
63
- add_action( 'wp_ajax_blc_work', array($this,'ajax_work') );
64
- add_action( 'wp_ajax_blc_discard', array($this,'ajax_discard') );
65
- add_action( 'wp_ajax_blc_edit', array($this,'ajax_edit') );
66
- add_action( 'wp_ajax_blc_link_details', array($this,'ajax_link_details') );
67
- add_action( 'wp_ajax_blc_unlink', array($this,'ajax_unlink') );
68
- add_action( 'wp_ajax_blc_recheck', array($this,'ajax_recheck') );
69
- add_action( 'wp_ajax_blc_deredirect', array($this,'ajax_deredirect') );
70
- add_action( 'wp_ajax_blc_current_load', array($this,'ajax_current_load') );
71
-
72
- add_action( 'wp_ajax_blc_dismiss', array($this, 'ajax_dismiss') );
73
- add_action( 'wp_ajax_blc_undismiss', array($this, 'ajax_undismiss') );
74
-
75
- //Add/remove Cron events
76
- $this->setup_cron_events();
77
-
78
- //Set hooks that listen for our Cron actions
79
- add_action( 'blc_cron_email_notifications', array( $this, 'maybe_send_email_notifications' ) );
80
- add_action( 'blc_cron_check_links', array( $this, 'cron_check_links' ) );
81
- add_action( 'blc_cron_database_maintenance', array( $this, 'database_maintenance' ) );
82
-
83
- //Set the footer hook that will call the worker function via AJAX.
84
- add_action( 'admin_footer', array( $this,'admin_footer' ) );
85
- //Add a "Screen Options" panel to the "Broken Links" page
86
- add_screen_options_panel(
87
- 'blc-screen-options',
88
- '',
89
- array($this, 'screen_options_html'),
90
- 'tools_page_view-broken-links',
91
- array($this, 'ajax_save_screen_options'),
92
- true
93
- );
94
-
95
- //Display an explanatory note on the "Tools -> Broken Links -> Warnings" page.
96
- add_action( 'admin_notices', array( $this, 'show_warnings_section_notice' ) );
97
-
98
- add_filter('cron_schedules', array( $this, 'cron_add_every_10_minutes'));
99
-
100
- }
101
-
102
- /**
103
- * @param $schedules
104
- *
105
- * @return mixed
106
- */
107
- function cron_add_every_10_minutes( $schedules ) {
108
- // Adds once weekly to the existing schedules.
109
- $schedules['10min'] = array(
110
- 'interval' => 600,
111
- 'display' => __('Every 10 minutes')
112
- );
113
-
114
- return $schedules;
115
- }
116
-
117
- /**
118
- * Output the script that runs the link monitor while the Dashboard is open.
119
- *
120
- * @return void
121
- */
122
- function admin_footer(){
123
- if ( !$this->conf->options['run_in_dashboard'] ){
124
- return;
125
- }
126
- $nonce = wp_create_nonce('blc_work');
127
- ?>
128
- <!-- wsblc admin footer -->
129
- <script type='text/javascript'>
130
- (function($){
131
-
132
- //(Re)starts the background worker thread
133
- function blcDoWork(){
134
- $.post(
135
- "<?php echo admin_url('admin-ajax.php'); ?>",
136
- {
137
- 'action' : 'blc_work',
138
- '_ajax_nonce' : '<?php echo esc_js($nonce); ?>'
139
- }
140
- );
141
- }
142
- //Call it the first time
143
- blcDoWork();
144
-
145
- //Then call it periodically every X seconds
146
- setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
147
-
148
- })(jQuery);
149
- </script>
150
- <!-- /wsblc admin footer -->
151
- <?php
152
- }
153
-
154
- /**
155
- * Check if an URL matches the exclusion list.
156
- *
157
- * @param string $url
158
- * @return bool
159
- */
160
- function is_excluded($url){
161
- if (!is_array($this->conf->options['exclusion_list'])) return false;
162
- foreach($this->conf->options['exclusion_list'] as $excluded_word){
163
- if (stristr($url, $excluded_word)){
164
- return true;
165
- }
166
- }
167
- return false;
168
- }
169
-
170
- function dashboard_widget(){
171
- ?>
172
- <p id='wsblc_activity_box'><?php _e( 'Loading...', 'broken-link-checker' ); ?></p>
173
- <script type='text/javascript'>
174
- jQuery( function($){
175
- var blc_was_autoexpanded = false;
176
-
177
- function blcDashboardStatus(){
178
- $.getJSON(
179
- "<?php echo admin_url('admin-ajax.php'); ?>",
180
- {
181
- 'action' : 'blc_dashboard_status',
182
- 'random' : Math.random()
183
- },
184
- function (data){
185
- if ( data && ( typeof(data.text) != 'undefined' ) ) {
186
- $('#wsblc_activity_box').html(data.text);
187
- <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
188
- //Expand the widget if there are broken links.
189
- //Do this only once per pageload so as not to annoy the user.
190
- if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
191
- $('#blc_dashboard_widget.postbox').removeClass('closed');
192
- blc_was_autoexpanded = true;
193
- }
194
- <?php } ?>
195
- } else {
196
- $('#wsblc_activity_box').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
197
- }
198
-
199
- setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
200
- }
201
- );
202
- }
203
-
204
- blcDashboardStatus();//Call it the first time
205
-
206
- } );
207
- </script>
208
- <?php
209
- }
210
-
211
- function dashboard_widget_control(
212
- /** @noinspection PhpUnusedParameterInspection */ $widget_id, $form_inputs = array()
213
- ){
214
- if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
215
- //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
216
- $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
217
- $this->conf->save_options();
218
- }
219
-
220
- ?>
221
- <p><label for="blc-autoexpand">
222
- <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
223
- <?php _e('Automatically expand the widget if broken links have been detected', 'broken-link-checker'); ?>
224
- </label></p>
225
- <?php
226
- }
227
-
228
- function admin_print_scripts(){
229
- //jQuery is used for triggering the link monitor via AJAX when any admin page is open.
230
- wp_enqueue_script('jquery');
231
- }
232
-
233
- function enqueue_settings_scripts(){
234
- //jQuery UI is used on the settings page
235
- wp_enqueue_script('jquery-ui-core'); //Used for background color animation
236
- wp_enqueue_script('jquery-ui-dialog');
237
- wp_enqueue_script('jquery-ui-tabs');
238
- wp_enqueue_script('jquery-cookie', plugins_url('js/jquery.cookie.js', BLC_PLUGIN_FILE)); //Used for storing last widget states, etc
239
- }
240
-
241
- function enqueue_link_page_scripts(){
242
- wp_enqueue_script('jquery-ui-core');
243
- wp_enqueue_script('jquery-ui-dialog'); //Used for the search form
244
- wp_enqueue_script('jquery-color'); //Used for background color animation
245
- wp_enqueue_script('sprintf', plugins_url('js/sprintf.js', BLC_PLUGIN_FILE)); //Used in error messages
246
- }
247
-
248
- /**
249
- * Initiate a full recheck - reparse everything and check all links anew.
250
- *
251
- * @return void
252
- */
253
- function initiate_recheck(){
254
- global $wpdb; /** @var wpdb $wpdb */
255
-
256
- //Delete all discovered instances
257
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
258
-
259
- //Delete all discovered links
260
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
261
-
262
- //Mark all posts, custom fields and bookmarks for processing.
263
- blc_resynch(true);
264
- }
265
-
266
- /**
267
- * A hook executed when the plugin is deactivated.
268
- *
269
- * @return void
270
- */
271
- function deactivation(){
272
- //Remove our Cron events
273
- wp_clear_scheduled_hook('blc_cron_check_links');
274
- wp_clear_scheduled_hook('blc_cron_email_notifications');
275
- wp_clear_scheduled_hook('blc_cron_database_maintenance');
276
- wp_clear_scheduled_hook('blc_cron_check_news'); //Unused event.
277
- //Note the deactivation time for each module. This will help them
278
- //synch up propely if/when the plugin is reactivated.
279
- $moduleManager = blcModuleManager::getInstance();
280
- $the_time = current_time('timestamp');
281
- foreach($moduleManager->get_active_modules() as $module_id => $module){
282
- $this->conf->options['module_deactivated_when'][$module_id] = $the_time;
283
- }
284
- $this->conf->save_options();
285
- }
286
-
287
- /**
288
- * Perform various database maintenance tasks on the plugin's tables.
289
- *
290
- * Removes records that reference disabled containers and parsers,
291
- * deletes invalid instances and links, optimizes tables, etc.
292
- *
293
- * @return void
294
- */
295
- function database_maintenance(){
296
- blcContainerHelper::cleanup_containers();
297
- blc_cleanup_instances();
298
- blc_cleanup_links();
299
-
300
- blcUtility::optimize_database();
301
- }
302
-
303
- /**
304
- * Create the plugin's menu items and enqueue their scripts and CSS.
305
- * Callback for the 'admin_menu' action.
306
- *
307
- * @return void
308
- */
309
- function admin_menu(){
310
- if (current_user_can('manage_options'))
311
- add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
312
-
313
- $options_page_hook = add_options_page(
314
- __('Link Checker Settings', 'broken-link-checker'),
315
- __('Link Checker', 'broken-link-checker'),
316
- 'manage_options',
317
- 'link-checker-settings',array($this, 'options_page')
318
- );
319
-
320
- $menu_title = __('Broken Links', 'broken-link-checker');
321
- if ( $this->conf->options['show_link_count_bubble'] ){
322
- //To make it easier to notice when broken links appear, display the current number of
323
- //broken links in a little bubble notification in the "Broken Links" menu.
324
- //(Similar to how the number of plugin updates and unmoderated comments is displayed).
325
- $blc_link_query = blcLinkQuery::getInstance();
326
- $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
327
- if ( $broken_links > 0 ){
328
- //TODO: Appropriating existing CSS classes for my own purposes is hacky. Fix eventually.
329
- $menu_title .= sprintf(
330
- ' <span class="update-plugins"><span class="update-count blc-menu-bubble">%d</span></span>',
331
- $broken_links
332
- );
333
- }
334
- }
335
- $links_page_hook = add_management_page(
336
- __('View Broken Links', 'broken-link-checker'),
337
- $menu_title,
338
- 'edit_others_posts',
339
- 'view-broken-links',array($this, 'links_page')
340
- );
341
-
342
- //Add plugin-specific scripts and CSS only to the it's own pages
343
- add_action( 'admin_print_styles-' . $options_page_hook, array($this, 'options_page_css') );
344
- add_action( 'admin_print_styles-' . $links_page_hook, array($this, 'links_page_css') );
345
- add_action( 'admin_print_scripts-' . $options_page_hook, array($this, 'enqueue_settings_scripts') );
346
- add_action( 'admin_print_scripts-' . $links_page_hook, array($this, 'enqueue_link_page_scripts') );
347
-
348
- //Make the Settings page link to the link list
349
- add_screen_meta_link(
350
- 'blc-links-page-link',
351
- __('Go to Broken Links', 'broken-link-checker'),
352
- admin_url('tools.php?page=view-broken-links'),
353
- $options_page_hook,
354
- array('style' => 'font-weight: bold;')
355
- );
356
- }
357
-
358
- /**
359
- * plugin_action_links()
360
- * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
361
- * on the plugin list.
362
- *
363
- * @param array $links
364
- * @param string $file
365
- * @return array
366
- */
367
- function plugin_action_links($links, $file) {
368
- if ($file == $this->my_basename)
369
- $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
370
- return $links;
371
- }
372
-
373
- function options_page(){
374
- $moduleManager = blcModuleManager::getInstance();
375
-
376
- //Prior to 1.5.2 (released 2012-05-27), there was a bug that would cause the donation flag to be
377
- //set incorrectly. So we'll unset the flag in that case.
378
- $reset_donation_flag =
379
- ($this->conf->get('first_installation_timestamp', 0) < strtotime('2012-05-27 00:00')) &&
380
- !$this->conf->get('donation_flag_fixed', false);
381
-
382
- if ( $reset_donation_flag) {
383
- $this->conf->set('user_has_donated', false);
384
- $this->conf->set('donation_flag_fixed', true);
385
- $this->conf->save_options();
386
- }
387
-
388
- if (isset($_POST['recheck']) && !empty($_POST['recheck']) ){
389
- $this->initiate_recheck();
390
-
391
- //Redirect back to the settings page
392
- $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
393
- wp_redirect( add_query_arg( array( 'recheck-initiated' => true), $base_url ) );
394
- die();
395
- }
396
-
397
- $available_link_actions = array(
398
- 'edit' => __('Edit URL' , 'broken-link-checker'),
399
- 'delete' => __('Unlink', 'broken-link-checker'),
400
- 'blc-discard-action' => __('Not broken', 'broken-link-checker'),
401
- 'blc-dismiss-action' => __('Dismiss', 'broken-link-checker'),
402
- 'blc-recheck-action' => __('Recheck', 'broken-link-checker'),
403
- 'blc-deredirect-action' => _x('Fix redirect', 'link action; replace one redirect with a direct link', 'broken-link-checker')
404
- );
405
-
406
- if(isset($_POST['submit'])) {
407
- check_admin_referer('link-checker-options');
408
-
409
- $cleanPost = $_POST;
410
- if ( function_exists('wp_magic_quotes') ){
411
- $cleanPost = stripslashes_deep($cleanPost); //Ceterum censeo, WP shouldn't mangle superglobals.
412
- }
413
-
414
- //Activate/deactivate modules
415
- if ( !empty($_POST['module']) ){
416
- $active = array_keys($_POST['module']);
417
- $moduleManager->set_active_modules($active);
418
- }
419
-
420
- //Only post statuses that actually exist can be selected
421
- if ( isset($_POST['enabled_post_statuses']) && is_array($_POST['enabled_post_statuses']) ){
422
- $available_statuses = get_post_stati();
423
- $enabled_post_statuses = array_intersect($_POST['enabled_post_statuses'], $available_statuses);
424
- } else {
425
- $enabled_post_statuses = array();
426
- }
427
- //At least one status must be enabled; defaults to "Published".
428
- if ( empty($enabled_post_statuses) ){
429
- $enabled_post_statuses = array('publish');
430
- }
431
-
432
- //Did the user add/remove any post statuses?
433
- $same_statuses = array_intersect($enabled_post_statuses, $this->conf->options['enabled_post_statuses']);
434
- $post_statuses_changed = (count($same_statuses) != count($enabled_post_statuses))
435
- || (count($same_statuses) !== count($this->conf->options['enabled_post_statuses']));
436
-
437
- $this->conf->options['enabled_post_statuses'] = $enabled_post_statuses;
438
-
439
- //The execution time limit must be above zero
440
- $new_execution_time = intval($_POST['max_execution_time']);
441
- if( $new_execution_time > 0 ){
442
- $this->conf->options['max_execution_time'] = $new_execution_time;
443
- }
444
-
445
- //The check threshold also must be > 0
446
- $new_check_threshold=intval($_POST['check_threshold']);
447
- if( $new_check_threshold > 0 ){
448
- $this->conf->options['check_threshold'] = $new_check_threshold;
449
- }
450
-
451
- $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
452
- $new_broken_link_css = trim($cleanPost['broken_link_css']);
453
- $this->conf->options['broken_link_css'] = $new_broken_link_css;
454
-
455
- $this->conf->options['mark_removed_links'] = !empty($_POST['mark_removed_links']);
456
- $new_removed_link_css = trim($cleanPost['removed_link_css']);
457
- $this->conf->options['removed_link_css'] = $new_removed_link_css;
458
-
459
- $this->conf->options['nofollow_broken_links'] = !empty($_POST['nofollow_broken_links']);
460
-
461
- $this->conf->options['suggestions_enabled'] = !empty($_POST['suggestions_enabled']);
462
-
463
- $this->conf->options['exclusion_list'] = array_filter(
464
- preg_split(
465
- '/[\s\r\n]+/', //split on newlines and whitespace
466
- $cleanPost['exclusion_list'],
467
- -1,
468
- PREG_SPLIT_NO_EMPTY //skip empty values
469
- )
470
- );
471
-
472
- //Parse the custom field list
473
- $new_custom_fields = array_filter(
474
- preg_split( '/[\r\n]+/', $cleanPost['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY )
475
- );
476
-
477
- //Calculate the difference between the old custom field list and the new one (used later)
478
- $diff1 = array_diff( $new_custom_fields, $this->conf->options['custom_fields'] );
479
- $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
480
- $this->conf->options['custom_fields'] = $new_custom_fields;
481
-
482
- //Parse the custom field list
483
- $new_acf_fields = array_filter(preg_split('/[\r\n]+/', $cleanPost['blc_acf_fields'], -1, PREG_SPLIT_NO_EMPTY));
484
-
485
- //Calculate the difference between the old custom field list and the new one (used later)
486
- $acf_fields_diff1 = array_diff($new_acf_fields, $this->conf->options['acf_fields']);
487
- $acf_fields_diff2 = array_diff($this->conf->options['acf_fields'], $new_acf_fields);
488
- $this->conf->options['acf_fields'] = $new_acf_fields;
489
-
490
- //Turning off warnings turns existing warnings into "broken" links.
491
- $warnings_enabled = !empty($_POST['warnings_enabled']);
492
- if ( $this->conf->get('warnings_enabled') && !$warnings_enabled ) {
493
- $this->promote_warnings_to_broken();
494
- }
495
- $this->conf->options['warnings_enabled'] = $warnings_enabled;
496
-
497
- //HTTP timeout
498
- $new_timeout = intval($_POST['timeout']);
499
- if( $new_timeout > 0 ){
500
- $this->conf->options['timeout'] = $new_timeout ;
501
- }
502
-
503
- //Server load limit
504
- if ( isset($_POST['server_load_limit']) ){
505
- $this->conf->options['server_load_limit'] = floatval($_POST['server_load_limit']);
506
- if ( $this->conf->options['server_load_limit'] < 0 ){
507
- $this->conf->options['server_load_limit'] = 0;
508
- }
509
-
510
- $this->conf->options['enable_load_limit'] = $this->conf->options['server_load_limit'] > 0;
511
- }
512
-
513
- //Target resource usage (1% to 100%)
514
- if ( isset($_POST['target_resource_usage']) ) {
515
- $usage = floatval($_POST['target_resource_usage']);
516
- $usage = max(min($usage / 100, 1), 0.01);
517
- $this->conf->options['target_resource_usage'] = $usage;
518
- }
519
-
520
- //When to run the checker
521
- $this->conf->options['run_in_dashboard'] = !empty($_POST['run_in_dashboard']);
522
- $this->conf->options['run_via_cron'] = !empty($_POST['run_via_cron']);
523
-
524
- //Email notifications on/off
525
- $email_notifications = !empty($_POST['send_email_notifications']);
526
- $send_authors_email_notifications = !empty($_POST['send_authors_email_notifications']);
527
-
528
- if (
529
- ($email_notifications && !$this->conf->options['send_email_notifications'])
530
- || ($send_authors_email_notifications && !$this->conf->options['send_authors_email_notifications'])
531
- ){
532
- /*
533
- The plugin should only send notifications about links that have become broken
534
- since the time when email notifications were turned on. If we don't do this,
535
- the first email notification will be sent nigh-immediately and list *all* broken
536
- links that the plugin currently knows about.
537
- */
538
- $this->conf->options['last_notification_sent'] = time();
539
- }
540
- $this->conf->options['send_email_notifications'] = $email_notifications;
541
- $this->conf->options['send_authors_email_notifications'] = $send_authors_email_notifications;
542
-
543
- $this->conf->options['notification_email_address'] = strval($_POST['notification_email_address']);
544
- if ( !filter_var($this->conf->options['notification_email_address'], FILTER_VALIDATE_EMAIL)) {
545
- $this->conf->options['notification_email_address'] = '';
546
- }
547
-
548
- $widget_cap = strval($_POST['dashboard_widget_capability']);
549
- if ( !empty($widget_cap) ) {
550
- $this->conf->options['dashboard_widget_capability'] = $widget_cap;
551
- }
552
-
553
- //Link actions. The user can hide some of them to reduce UI clutter.
554
- $show_link_actions = array();
555
- foreach(array_keys($available_link_actions) as $action) {
556
- $show_link_actions[$action] = isset($_POST['show_link_actions']) &&
557
- !empty($_POST['show_link_actions'][$action]);
558
- }
559
- $this->conf->set('show_link_actions', $show_link_actions);
560
-
561
- //Logging. The plugin can log various events and results for debugging purposes.
562
- $this->conf->options['logging_enabled'] = !empty($_POST['logging_enabled']);
563
- $this->conf->options['custom_log_file_enabled'] = !empty($_POST['custom_log_file_enabled']);
564
-
565
- if ( $this->conf->options['logging_enabled'] ) {
566
- if ( $this->conf->options['custom_log_file_enabled'] ) {
567
- $log_file = strval($cleanPost['log_file']);
568
- } else {
569
- //Default log file is /wp-content/uploads/broken-link-checker/blc-log.txt
570
- $log_directory = self::get_default_log_directory();
571
- $log_file = $log_directory . '/' . self::get_default_log_basename();
572
-
573
- //Attempt to create the log directory.
574
- if ( !is_dir($log_directory) ) {
575
- if ( mkdir($log_directory, 0750) ) {
576
- //Add a .htaccess to hide the log file from site visitors.
577
- file_put_contents($log_directory . '/.htaccess', 'Deny from all');
578
- }
579
- }
580
- }
581
-
582
- $this->conf->options['log_file'] = $log_file;
583
-
584
- //Attempt to create the log file if not already there.
585
- if ( !is_file($log_file) ) {
586
- file_put_contents($log_file, '');
587
- }
588
-
589
- //The log file must be writable.
590
- if ( !is_writable($log_file) || !is_file($log_file) ) {
591
- $this->conf->options['logging_enabled'] = false;
592
- }
593
- }
594
-
595
- //Make settings that affect our Cron events take effect immediately
596
- $this->setup_cron_events();
597
-
598
- $this->conf->save_options();
599
-
600
- /*
601
- If the list of custom fields was modified then we MUST resynchronize or
602
- custom fields linked with existing posts may not be detected. This is somewhat
603
- inefficient.
604
- */
605
- if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
606
- $manager = blcContainerHelper::get_manager('custom_field');
607
- if ( !is_null($manager) ){
608
- $manager->resynch();
609
- blc_got_unsynched_items();
610
- }
611
- }
612
-
613
- /*
614
- If the list of acf fields was modified then we MUST resynchronize or
615
- acf fields linked with existing posts may not be detected. This is somewhat
616
- inefficient.
617
- */
618
- if ( ( count($acf_fields_diff1) > 0 ) || ( count($acf_fields_diff2) > 0 ) ){
619
- $manager = blcContainerHelper::get_manager('acf_field');
620
- if ( !is_null($manager) ){
621
- $manager->resynch();
622
- blc_got_unsynched_items();
623
- }
624
- }
625
-
626
- //Resynchronize posts when the user enables or disables post statuses.
627
- if ( $post_statuses_changed ) {
628
- $overlord = blcPostTypeOverlord::getInstance();
629
- $overlord->enabled_post_statuses = $this->conf->get('enabled_post_statuses', array());
630
- $overlord->resynch('wsh_status_resynch_trigger');
631
-
632
- blc_got_unsynched_items();
633
- blc_cleanup_instances();
634
- blc_cleanup_links();
635
- }
636
-
637
- //Redirect back to the settings page
638
- $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
639
- wp_redirect( add_query_arg( array( 'settings-updated' => true), $base_url ) );
640
- }
641
-
642
- //Show a confirmation message when settings are saved.
643
- if ( !empty($_GET['settings-updated']) ){
644
- echo '<div id="message" class="updated fade"><p><strong>',__('Settings saved.', 'broken-link-checker'), '</strong></p></div>';
645
-
646
- }
647
-
648
- //Show a thank-you message when a donation is made.
649
- if ( !empty($_GET['donated']) ){
650
- echo '<div id="message" class="updated fade"><p><strong>',__('Thank you for your donation!', 'broken-link-checker'), '</strong></p></div>';
651
- $this->conf->set('user_has_donated', true);
652
- $this->conf->save_options();
653
- }
654
-
655
- //Show one when recheck is started, too.
656
- if ( !empty($_GET['recheck-initiated']) ){
657
- echo '<div id="message" class="updated fade"><p><strong>',
658
- __('Complete site recheck started.', 'broken-link-checker'), // -- Yoda
659
- '</strong></p></div>';
660
- }
661
-
662
- //Cull invalid and missing modules
663
- $moduleManager->validate_active_modules();
664
-
665
- $debug = $this->get_debug_info();
666
-
667
- add_filter('blc-module-settings-custom_field', array($this, 'make_custom_field_input'), 10, 2);
668
- add_filter('blc-module-settings-acf_field', array($this, 'make_acf_field_input'), 10, 2);
669
- //Translate and markup-ify module headers for display
670
- $modules = $moduleManager->get_modules_by_category('', true, true);
671
-
672
- //Output the custom broken link/removed link styles for example links
673
- printf(
674
- '<style type="text/css">%s %s</style>',
675
- $this->conf->options['broken_link_css'],
676
- $this->conf->options['removed_link_css']
677
- );
678
-
679
- $section_names = array(
680
- 'general' => __('General', 'broken-link-checker'),
681
- 'where' => __('Look For Links In', 'broken-link-checker'),
682
- 'which' => __('Which Links To Check', 'broken-link-checker'),
683
- 'how' => __('Protocols & APIs', 'broken-link-checker'),
684
- 'advanced' => __('Advanced', 'broken-link-checker'),
685
- );
686
- ?>
687
-
688
- <!--[if lte IE 7]>
689
- <style type="text/css">
690
- /* Simulate inline-block in IE7 */
691
- ul.ui-tabs-nav li {
692
- display: inline;
693
- zoom: 1;
694
- }
695
- </style>
696
- <![endif]-->
697
-
698
- <div class="wrap" id="blc-settings-wrap">
699
- <h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
700
-
701
-
702
- <div id="blc-sidebar">
703
- <div class="metabox-holder">
704
- <?php include BLC_DIRECTORY . '/includes/admin/sidebar.php'; ?>
705
- </div>
706
- </div>
707
-
708
-
709
- <div id="blc-admin-content">
710
-
711
- <form name="link_checker_options" id="link_checker_options" method="post" action="<?php
712
- echo admin_url('options-general.php?page=link-checker-settings&noheader=1');
713
- ?>">
714
- <?php
715
- wp_nonce_field('link-checker-options');
716
- ?>
717
-
718
- <div id="blc-tabs">
719
-
720
- <ul class="hide-if-no-js">
721
- <?php
722
- foreach($section_names as $section_id => $section_name){
723
- printf(
724
- '<li id="tab-button-%s"><a href="#section-%s" title="%s">%s</a></li>',
725
- esc_attr($section_id),
726
- esc_attr($section_id),
727
- esc_attr($section_name),
728
- $section_name
729
- );
730
- }
731
- ?>
732
- </ul>
733
-
734
- <div id="section-general" class="blc-section">
735
- <h3 class="hide-if-js"><?php echo $section_names['general']; ?></h3>
736
-
737
- <table class="form-table">
738
-
739
- <tr valign="top">
740
- <th scope="row">
741
- <?php _e('Status','broken-link-checker'); ?>
742
- <br>
743
- <a href="javascript:void(0)" id="blc-debug-info-toggle"><?php _e('Show debug info', 'broken-link-checker'); ?></a>
744
- </th>
745
- <td>
746
-
747
- <div id='wsblc_full_status'>
748
- <br/><br/><br/>
749
- </div>
750
-
751
- <table id="blc-debug-info">
752
- <?php
753
-
754
- //Output the debug info in a table
755
- foreach( $debug as $key => $value ){
756
- printf (
757
- '<tr valign="top" class="blc-debug-item-%s"><th scope="row">%s</th><td>%s<div class="blc-debug-message">%s</div></td></tr>',
758
- $value['state'],
759
- $key,
760
- $value['value'],
761
- ( array_key_exists('message', $value)?$value['message']:'')
762
- );
763
- }
764
- ?>
765
- </table>
766
-
767
- </td>
768
- </tr>
769
-
770
- <tr valign="top">
771
- <th scope="row"><?php _e('Check each link','broken-link-checker'); ?></th>
772
- <td>
773
-
774
- <?php
775
- printf(
776
- __('Every %s hours','broken-link-checker'),
777
- sprintf(
778
- '<input type="text" name="check_threshold" id="check_threshold" value="%d" size="5" maxlength="5" />',
779
- $this->conf->options['check_threshold']
780
- )
781
- );
782
- ?>
783
- <br/>
784
- <span class="description">
785
- <?php _e('Existing links will be checked this often. New links will usually be checked ASAP.', 'broken-link-checker'); ?>
786
- </span>
787
-
788
- </td>
789
- </tr>
790
-
791
- <tr valign="top">
792
- <th scope="row"><?php _e('E-mail notifications', 'broken-link-checker'); ?></th>
793
- <td>
794
- <p style="margin-top: 0;">
795
- <label for='send_email_notifications'>
796
- <input type="checkbox" name="send_email_notifications" id="send_email_notifications"
797
- <?php if ($this->conf->options['send_email_notifications']) echo ' checked="checked"'; ?>/>
798
- <?php _e('Send me e-mail notifications about newly detected broken links', 'broken-link-checker'); ?>
799
- </label><br />
800
- </p>
801
-
802
- <p>
803
- <label for='send_authors_email_notifications'>
804
- <input type="checkbox" name="send_authors_email_notifications" id="send_authors_email_notifications"
805
- <?php if ($this->conf->options['send_authors_email_notifications']) echo ' checked="checked"'; ?>/>
806
- <?php _e('Send authors e-mail notifications about broken links in their posts', 'broken-link-checker'); ?>
807
- </label><br />
808
- </p>
809
- </td>
810
- </tr>
811
-
812
- <tr valign="top">
813
- <th scope="row"><?php echo __('Notification e-mail address', 'broken-link-checker'); ?></th>
814
- <td>
815
- <p>
816
- <label>
817
- <input
818
- type="text"
819
- name="notification_email_address"
820
- id="notification_email_address"
821
- value="<?php echo esc_attr($this->conf->get('notification_email_address', '')); ?>"
822
- class="regular-text ltr">
823
- </label><br>
824
- <span class="description">
825
- <?php echo __('Leave empty to use the e-mail address specified in Settings &rarr; General.', 'broken-link-checker'); ?>
826
- </span>
827
- </p>
828
- </td>
829
- </tr>
830
-
831
- <tr valign="top">
832
- <th scope="row"><?php _e('Link tweaks','broken-link-checker'); ?></th>
833
- <td>
834
- <p style="margin-top: 0; margin-bottom: 0.5em;">
835
- <label for='mark_broken_links'>
836
- <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
837
- <?php if ($this->conf->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
838
- <?php _e('Apply custom formatting to broken links', 'broken-link-checker'); ?>
839
- </label>
840
- |
841
- <a id="toggle-broken-link-css-editor" href="#" class="blc-toggle-link"><?php
842
- _e('Edit CSS', 'broken-link-checker');
843
- ?></a>
844
- </p>
845
-
846
- <div id="broken-link-css-wrap"<?php
847
- if ( !blcUtility::get_cookie('broken-link-css-wrap', false) ){
848
- echo ' class="hidden"';
849
- }
850
- ?>>
851
- <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'><?php
852
- if( isset($this->conf->options['broken_link_css']) ) {
853
- echo $this->conf->options['broken_link_css'];
854
- }
855
- ?></textarea>
856
- <p class="description"><?php
857
- printf(
858
- __('Example : Lorem ipsum <a %s>broken link</a>, dolor sit amet.', 'broken-link-checker'),
859
- ' href="#" class="broken_link" onclick="return false;"'
860
- );
861
- echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
862
- ?></p>
863
- </div>
864
-
865
- <p style="margin-bottom: 0.5em;">
866
- <label for='mark_removed_links'>
867
- <input type="checkbox" name="mark_removed_links" id="mark_removed_links"
868
- <?php if ($this->conf->options['mark_removed_links']) echo ' checked="checked"'; ?>/>
869
- <?php _e('Apply custom formatting to removed links', 'broken-link-checker'); ?>
870
- </label>
871
- |
872
- <a id="toggle-removed-link-css-editor" href="#" class="blc-toggle-link"><?php
873
- _e('Edit CSS', 'broken-link-checker');
874
- ?></a>
875
- </p>
876
-
877
- <div id="removed-link-css-wrap" <?php
878
- if ( !blcUtility::get_cookie('removed-link-css-wrap', false) ){
879
- echo ' class="hidden"';
880
- }
881
- ?>>
882
- <textarea name="removed_link_css" id="removed_link_css" cols='45' rows='4'><?php
883
- if( isset($this->conf->options['removed_link_css']) )
884
- echo $this->conf->options['removed_link_css'];
885
- ?></textarea>
886
-
887
- <p class="description"><?php
888
- printf(
889
- __('Example : Lorem ipsum <span %s>removed link</span>, dolor sit amet.', 'broken-link-checker'),
890
- ' class="removed_link"'
891
- );
892
- echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
893
- ?>
894
-
895
- </p>
896
- </div>
897
-
898
- <p>
899
- <label for='nofollow_broken_links'>
900
- <input type="checkbox" name="nofollow_broken_links" id="nofollow_broken_links"
901
- <?php if ($this->conf->options['nofollow_broken_links']) echo ' checked="checked"'; ?>/>
902
- <?php _e('Stop search engines from following broken links', 'broken-link-checker'); ?>
903
- </label>
904
- </p>
905
-
906
- <p class="description">
907
- <?php
908
- echo _x(
909
- 'These settings only apply to the content of posts, not comments or custom fields.',
910
- '"Link tweaks" settings',
911
- 'broken-link-checker'
912
- );
913
- ?>
914
- </p>
915
- </td>
916
- </tr>
917
-
918
- <tr valign="top">
919
- <th scope="row"><?php echo _x('Suggestions', 'settings page', 'broken-link-checker'); ?></th>
920
- <td>
921
- <label>
922
- <input type="checkbox" name="suggestions_enabled" id="suggestions_enabled"
923
- <?php checked($this->conf->options['suggestions_enabled']); ?>/>
924
- <?php _e('Suggest alternatives to broken links', 'broken-link-checker'); ?>
925
- </label>
926
- </td>
927
- </tr>
928
-
929
- <tr valign="top">
930
- <th scope="row"><?php echo _x('Warnings', 'settings page', 'broken-link-checker'); ?></th>
931
- <td id="blc_warning_settings">
932
- <label>
933
- <input type="checkbox" name="warnings_enabled" id="warnings_enabled"
934
- <?php checked($this->conf->options['warnings_enabled']); ?>/>
935
- <?php _e('Show uncertain or minor problems as "warnings" instead of "broken"', 'broken-link-checker'); ?>
936
- </label>
937
- <p class="description"><?php
938
- _e('Turning off this option will make the plugin report all problems as broken links.', 'broken-link-checker');
939
- ?></p>
940
- </td>
941
- </tr>
942
-
943
- </table>
944
-
945
- </div>
946
-
947
- <div id="section-where" class="blc-section">
948
- <h3 class="hide-if-js"><?php echo $section_names['where']; ?></h3>
949
-
950
- <table class="form-table">
951
-
952
- <tr valign="top">
953
- <th scope="row"><?php _e('Look for links in', 'broken-link-checker'); ?></th>
954
- <td>
955
- <?php
956
- if ( !empty($modules['container']) ){
957
- uasort($modules['container'], create_function('$a, $b', 'return strcasecmp($a["Name"], $b["Name"]);'));
958
- $this->print_module_list($modules['container'], $this->conf->options);
959
- }
960
- ?>
961
- </td></tr>
962
-
963
- <tr valign="top">
964
- <th scope="row"><?php _e('Post statuses', 'broken-link-checker'); ?></th>
965
- <td>
966
- <?php
967
- $available_statuses = get_post_stati(array('internal' => false), 'objects');
968
-
969
- if ( isset($this->conf->options['enabled_post_statuses']) ){
970
- $enabled_post_statuses = $this->conf->options['enabled_post_statuses'];
971
- } else {
972
- $enabled_post_statuses = array();
973
- }
974
-
975
- foreach($available_statuses as $status => $status_object){
976
- printf(
977
- '<p><label><input type="checkbox" name="enabled_post_statuses[]" value="%s"%s> %s</label></p>',
978
- esc_attr($status),
979
- in_array($status, $enabled_post_statuses)?' checked="checked"':'',
980
- $status_object->label
981
- );
982
- }
983
- ?>
984
- </td></tr>
985
-
986
- </table>
987
-
988
- </div>
989
-
990
-
991
- <div id="section-which" class="blc-section">
992
- <h3 class="hide-if-js"><?php echo $section_names['which']; ?></h3>
993
-
994
- <table class="form-table">
995
-
996
- <tr valign="top">
997
- <th scope="row"><?php _e('Link types', 'broken-link-checker'); ?></th>
998
- <td>
999
- <?php
1000
- if ( !empty($modules['parser']) ){
1001
- $this->print_module_list($modules['parser'], $this->conf->options);
1002
- } else {
1003
- echo __('Error : All link parsers missing!', 'broken-link-checker');
1004
- }
1005
- ?>
1006
- </td>
1007
- </tr>
1008
-
1009
- <tr valign="top">
1010
- <th scope="row"><?php _e('Exclusion list', 'broken-link-checker'); ?></th>
1011
- <td><?php _e("Don't check links where the URL contains any of these words (one per line) :", 'broken-link-checker'); ?><br/>
1012
- <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4'><?php
1013
- if( isset($this->conf->options['exclusion_list']) )
1014
- echo esc_textarea(implode("\n", $this->conf->options['exclusion_list']));
1015
- ?></textarea>
1016
-
1017
- </td>
1018
- </tr>
1019
-
1020
- </table>
1021
- </div>
1022
-
1023
- <div id="section-how" class="blc-section">
1024
- <h3 class="hide-if-js"><?php echo $section_names['how']; ?></h3>
1025
-
1026
- <table class="form-table">
1027
-
1028
- <tr valign="top">
1029
- <th scope="row"><?php _e('Check links using', 'broken-link-checker'); ?></th>
1030
- <td>
1031
- <?php
1032
- if ( !empty($modules['checker']) ){
1033
- $modules['checker'] = array_reverse($modules['checker']);
1034
- $this->print_module_list($modules['checker'], $this->conf->options);
1035
- }
1036
- ?>
1037
- </td></tr>
1038
-
1039
- </table>
1040
- </div>
1041
-
1042
- <div id="section-advanced" class="blc-section">
1043
- <h3 class="hide-if-js"><?php echo $section_names['advanced']; ?></h3>
1044
-
1045
- <table class="form-table">
1046
-
1047
- <tr valign="top">
1048
- <th scope="row"><?php _e('Timeout', 'broken-link-checker'); ?></th>
1049
- <td>
1050
-
1051
- <?php
1052
-
1053
- printf(
1054
- __('%s seconds', 'broken-link-checker'),
1055
- sprintf(
1056
- '<input type="text" name="timeout" id="blc_timeout" value="%d" size="5" maxlength="3" />',
1057
- $this->conf->options['timeout']
1058
- )
1059
- );
1060
-
1061
- ?>
1062
- <br/><span class="description">
1063
- <?php _e('Links that take longer than this to load will be marked as broken.','broken-link-checker'); ?>
1064
- </span>
1065
-
1066
- </td>
1067
- </tr>
1068
-
1069
- <tr valign="top">
1070
- <th scope="row"><?php _e('Link monitor', 'broken-link-checker'); ?></th>
1071
- <td>
1072
-
1073
- <p>
1074
- <label for='run_in_dashboard'>
1075
-
1076
- <input type="checkbox" name="run_in_dashboard" id="run_in_dashboard"
1077
- <?php if ($this->conf->options['run_in_dashboard']) echo ' checked="checked"'; ?>/>
1078
- <?php _e('Run continuously while the Dashboard is open', 'broken-link-checker'); ?>
1079
- </label>
1080
- </p>
1081
-
1082
- <p>
1083
- <label for='run_via_cron'>
1084
- <input type="checkbox" name="run_via_cron" id="run_via_cron"
1085
- <?php if ($this->conf->options['run_via_cron']) echo ' checked="checked"'; ?>/>
1086
- <?php _e('Run hourly in the background', 'broken-link-checker'); ?>
1087
- </label>
1088
- </p>
1089
-
1090
- </td>
1091
- </tr>
1092
-
1093
- <tr valign="top">
1094
- <th scope="row"><?php _e('Show the dashboard widget for', 'broken-link-checker'); ?></th>
1095
- <td>
1096
-
1097
- <?php
1098
- $widget_caps = array(
1099
- _x('Administrator', 'dashboard widget visibility', 'broken-link-checker') => 'manage_options',
1100
- _x('Editor and above', 'dashboard widget visibility', 'broken-link-checker') => 'edit_others_posts',
1101
- _x('Nobody (disables the widget)', 'dashboard widget visibility', 'broken-link-checker') => 'do_not_allow',
1102
- );
1103
-
1104
- foreach($widget_caps as $title => $capability) {
1105
- printf(
1106
- '<p><label><input type="radio" name="dashboard_widget_capability" value="%s"%s> %s</label></p>',
1107
- esc_attr($capability),
1108
- checked($capability, $this->conf->get('dashboard_widget_capability'), false),
1109
- $title
1110
- );
1111
- }
1112
- ?>
1113
- </td>
1114
- </tr>
1115
-
1116
- <tr valign="top">
1117
- <th scope="row"><?php echo _x('Show link actions', 'settings page', 'broken-link-checker'); ?></th>
1118
- <td>
1119
- <?php
1120
- $show_link_actions = $this->conf->get('show_link_actions', array());
1121
- foreach($available_link_actions as $action => $text) {
1122
- $enabled = isset($show_link_actions[$action]) ? (bool)($show_link_actions[$action]) : true;
1123
- printf(
1124
- '<p><label><input type="checkbox" name="show_link_actions[%1$s]" %3$s> %2$s</label></p>',
1125
- $action,
1126
- $text,
1127
- checked($enabled, true, false)
1128
- );
1129
- }
1130
- ?>
1131
- </td>
1132
- </tr>
1133
-
1134
- <tr valign="top">
1135
- <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
1136
- <td>
1137
-
1138
- <?php
1139
-
1140
- printf(
1141
- __('%s seconds', 'broken-link-checker'),
1142
- sprintf(
1143
- '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
1144
- $this->conf->options['max_execution_time']
1145
- )
1146
- );
1147
-
1148
- ?>
1149
- <br/><span class="description">
1150
- <?php
1151
-
1152
- _e('The plugin works by periodically launching a background job that parses your posts for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the link monitor may run each time before stopping.', 'broken-link-checker');
1153
-
1154
- ?>
1155
- </span>
1156
-
1157
- </td>
1158
- </tr>
1159
-
1160
- <tr valign="top">
1161
- <th scope="row"><?php _e('Server load limit', 'broken-link-checker'); ?></th>
1162
- <td>
1163
- <?php
1164
-
1165
- $load = blcUtility::get_server_load();
1166
- $available = !empty($load);
1167
-
1168
- if ( $available ){
1169
- $value = !empty($this->conf->options['server_load_limit'])?sprintf('%.2f', $this->conf->options['server_load_limit']):'';
1170
- printf(
1171
- '<input type="text" name="server_load_limit" id="server_load_limit" value="%s" size="5" maxlength="5"/> ',
1172
- $value
1173
- );
1174
-
1175
- printf(
1176
- __('Current load : %s', 'broken-link-checker'),
1177
- '<span id="wsblc_current_load">...</span>'
1178
- );
1179
- echo '<br/><span class="description">';
1180
- printf(
1181
- __(
1182
- 'Link checking will be suspended if the average <a href="%s">server load</a> rises above this number. Leave this field blank to disable load limiting.',
1183
- 'broken-link-checker'
1184
- ),
1185
- 'http://en.wikipedia.org/wiki/Load_(computing)'
1186
- );
1187
- echo '</span>';
1188
-
1189
- } else {
1190
- echo '<input type="text" disabled="disabled" value="', esc_attr(__('Not available', 'broken-link-checker')), '" size="13"/><br>';
1191
- echo '<span class="description">';
1192
- _e('Load limiting only works on Linux-like systems where <code>/proc/loadavg</code> is present and accessible.', 'broken-link-checker');
1193
- echo '</span>';
1194
- }
1195
- ?>
1196
- </td>
1197
- </tr>
1198
-
1199
- <tr valign="top">
1200
- <th scope="row"><?php _e('Target resource usage', 'broken-link-checker'); ?></th>
1201
- <td>
1202
- <?php
1203
- $target_resource_usage = $this->conf->get('target_resource_usage', 0.25);
1204
- printf(
1205
- '<input name="target_resource_usage" value="%d"
1206
- type="range" min="1" max="100" id="target_resource_usage">',
1207
- $target_resource_usage * 100
1208
- );
1209
- ?>
1210
-
1211
- <span id="target_resource_usage_percent"><?php
1212
- echo sprintf('%.0f%%', $target_resource_usage * 100);
1213
- ?></span>
1214
- </td>
1215
- </tr>
1216
-
1217
- <tr valign="top">
1218
- <th scope="row"><?php _e('Logging', 'broken-link-checker'); ?></th>
1219
- <td>
1220
- <p>
1221
- <label for='logging_enabled'>
1222
- <input type="checkbox" name="logging_enabled" id="logging_enabled"
1223
- <?php checked($this->conf->options['logging_enabled']); ?>/>
1224
- <?php _e('Enable logging', 'broken-link-checker'); ?>
1225
- </label>
1226
- </p>
1227
- </td>
1228
- </tr>
1229
-
1230
- <tr valign="top">
1231
- <th scope="row"><?php _e('Log file location', 'broken-link-checker'); ?></th>
1232
- <td>
1233
-
1234
- <div id="blc-logging-options">
1235
-
1236
- <p>
1237
- <label>
1238
- <input type="radio" name="custom_log_file_enabled" value=""
1239
- <?php checked(!$this->conf->options['custom_log_file_enabled']); ?>>
1240
- <?php echo _x('Default', 'log file location', 'broken-link-checker'); ?>
1241
- </label>
1242
- <br>
1243
- <span class="description">
1244
- <code><?php
1245
- echo self::get_default_log_directory(), '/', self::get_default_log_basename();
1246
- ?></code>
1247
- </span>
1248
- </p>
1249
-
1250
- <p>
1251
- <label>
1252
- <input type="radio" name="custom_log_file_enabled" value="1"
1253
- <?php checked($this->conf->options['custom_log_file_enabled']); ?>>
1254
- <?php echo _x('Custom', 'log file location', 'broken-link-checker'); ?>
1255
- </label>
1256
- <br><input type="text" name="log_file" id="log_file" size="90"
1257
- value="<?php echo esc_attr($this->conf->options['log_file']); ?>">
1258
- </p>
1259
-
1260
- </div>
1261
- </td>
1262
- </tr>
1263
-
1264
-
1265
- <tr valign="top">
1266
- <th scope="row"><?php _e('Forced recheck', 'broken-link-checker'); ?></th>
1267
- <td>
1268
- <input class="button" type="button" name="start-recheck" id="start-recheck"
1269
- value="<?php _e('Re-check all pages', 'broken-link-checker'); ?>" />
1270
- <input type="hidden" name="recheck" value="" id="recheck" />
1271
- <br />
1272
- <span class="description"><?php
1273
- _e('The "Nuclear Option". Click this button to make the plugin empty its link database and recheck the entire site from scratch.', 'broken-link-checker');
1274
-
1275
- ?></span>
1276
- </td>
1277
- </tr>
1278
-
1279
- </table>
1280
- </div>
1281
-
1282
- </div>
1283
-
1284
- <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
1285
- </form>
1286
-
1287
- </div> <!-- First postbox-container -->
1288
-
1289
-
1290
- </div>
1291
-
1292
-
1293
-
1294
- <?php
1295
- //The various JS for this page is stored in a separate file for the purposes readability.
1296
- include dirname($this->loader) . '/includes/admin/options-page-js.php';
1297
- }
1298
-
1299
- /**
1300
- * Output a list of modules and their settings.
1301
- *
1302
- * Each list entry will contain a checkbox that is checked if the module is
1303
- * currently active.
1304
- *
1305
- * @param array $modules Array of modules to display
1306
- * @param array $current_settings
1307
- * @return void
1308
- */
1309
- function print_module_list($modules, $current_settings){
1310
- $moduleManager = blcModuleManager::getInstance();
1311
-
1312
- foreach($modules as $module_id => $module_data){
1313
- $module_id = $module_data['ModuleID'];
1314
-
1315
- $style = $module_data['ModuleHidden']?' style="display:none;"':'';
1316
-
1317
- printf(
1318
- '<div class="module-container" id="module-container-%s"%s>',
1319
- $module_id,
1320
- $style
1321
- );
1322
- $this->print_module_checkbox($module_id, $module_data, $moduleManager->is_active($module_id));
1323
-
1324
- $extra_settings = apply_filters(
1325
- 'blc-module-settings-'.$module_id,
1326
- '',
1327
- $current_settings
1328
- );
1329
-
1330
- if ( !empty($extra_settings) ){
1331
-
1332
- printf(
1333
- ' | <a class="blc-toggle-link toggle-module-settings" id="toggle-module-settings-%s" href="#">%s</a>',
1334
- esc_attr($module_id),
1335
- __('Configure', 'broken-link-checker')
1336
- );
1337
-
1338
- //The plugin remembers the last open/closed state of module configuration boxes
1339
- $box_id = 'module-extra-settings-' . $module_id;
1340
- $show = blcUtility::get_cookie(
1341
- $box_id,
1342
- $moduleManager->is_active($module_id)
1343
- );
1344
-
1345
- printf(
1346
- '<div class="module-extra-settings%s" id="%s">%s</div>',
1347
- $show?'':' hidden',
1348
- $box_id,
1349
- $extra_settings
1350
- );
1351
- }
1352
-
1353
- echo '</div>';
1354
- }
1355
- }
1356
-
1357
- /**
1358
- * Output a checkbox for a module.
1359
- *
1360
- * Generates a simple checkbox that can be used to mark a module as active/inactive.
1361
- * If the specified module can't be deactivated (ModuleAlwaysActive = true), the checkbox
1362
- * will be displayed in a disabled state and a hidden field will be created to make
1363
- * form submissions work correctly.
1364
- *
1365
- * @param string $module_id Module ID.
1366
- * @param array $module_data Associative array of module data.
1367
- * @param bool $active If true, the newly created checkbox will start out checked.
1368
- * @return void
1369
- */
1370
- function print_module_checkbox($module_id, $module_data, $active = false){
1371
- $disabled = false;
1372
- $name_prefix = 'module';
1373
- $label_class = '';
1374
- $active = $active || $module_data['ModuleAlwaysActive'];
1375
-
1376
- if ( $module_data['ModuleAlwaysActive'] ){
1377
- $disabled = true;
1378
- $name_prefix = 'module-always-active';
1379
- }
1380
-
1381
- $checked = $active ? ' checked="checked"':'';
1382
- if ( $disabled ){
1383
- $checked .= ' disabled="disabled"';
1384
- }
1385
-
1386
- printf(
1387
- '<label class="%s">
1388
- <input type="checkbox" name="%s[%s]" id="module-checkbox-%s"%s /> %s
1389
- </label>',
1390
- esc_attr($label_class),
1391
- $name_prefix,
1392
- esc_attr($module_id),
1393
- esc_attr($module_id),
1394
- $checked,
1395
- $module_data['Name']
1396
- );
1397
-
1398
- if ( $module_data['ModuleAlwaysActive'] ){
1399
- printf(
1400
- '<input type="hidden" name="module[%s]" value="on">',
1401
- esc_attr($module_id)
1402
- );
1403
- }
1404
- }
1405
-
1406
- /**
1407
- * Add extra settings to the "Custom fields" entry on the plugin's config. page.
1408
- *
1409
- * Callback for the 'blc-module-settings-custom_field' filter.
1410
- *
1411
- * @param string $html Current extra HTML
1412
- * @param array $current_settings The current plugin configuration.
1413
- * @return string New extra HTML.
1414
- */
1415
- function make_custom_field_input($html, $current_settings){
1416
- $html .= '<span class="description">' .
1417
- __(
1418
- 'Enter the names of custom fields you want to check (one per line). If a field contains HTML code, prefix its name with <code>html:</code>. For example, <code>html:field_name</code>.',
1419
- 'broken-link-checker'
1420
- ) .
1421
- '</span>';
1422
- $html .= '<br><textarea name="blc_custom_fields" id="blc_custom_fields" cols="45" rows="4">';
1423
- if( isset($current_settings['custom_fields']) ){
1424
- $html .= esc_textarea(implode("\n", $current_settings['custom_fields']));
1425
- }
1426
- $html .= '</textarea>';
1427
-
1428
- return $html;
1429
- }
1430
- function make_acf_field_input($html, $current_settings) {
1431
- $html .= '<span class="description">' . __('Enter the keys of acf fields you want to check (one per line). If a field contains HTML code, prefix its name with <code>html:</code>. For example, <code>html:field_586a3eaa4091b</code>.', 'broken-link-checker') . '</span>';
1432
- $html .= '<br><textarea name="blc_acf_fields" id="blc_acf_fields" cols="45" rows="4">';
1433
- if (isset($current_settings['acf_fields'])) {
1434
- $html .= esc_textarea(implode("\n", $current_settings['acf_fields']));
1435
- }
1436
- $html .= '</textarea>';
1437
-
1438
- return $html;
1439
- }
1440
- /**
1441
- * Enqueue CSS file for the plugin's Settings page.
1442
- *
1443
- * @return void
1444
- */
1445
- function options_page_css(){
1446
- wp_enqueue_style('blc-options-page', plugins_url('css/options-page.css', BLC_PLUGIN_FILE), array(), '20141113');
1447
- wp_enqueue_style('dashboard');
1448
- }
1449
-
1450
-
1451
- /**
1452
- * Display the "Broken Links" page, listing links detected by the plugin and their status.
1453
- *
1454
- * @return void
1455
- */
1456
- function links_page(){
1457
- global $wpdb; /* @var wpdb $wpdb */
1458
-
1459
- $blc_link_query = blcLinkQuery::getInstance();
1460
-
1461
- //Cull invalid and missing modules so that we don't get dummy links/instances showing up.
1462
- $moduleManager = blcModuleManager::getInstance();
1463
- $moduleManager->validate_active_modules();
1464
-
1465
- if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
1466
- //Make module headers translatable. They need to be formatted corrrectly and
1467
- //placed in a .php file to be visible to the script(s) that generate .pot files.
1468
- $code = $moduleManager->_build_header_translation_code();
1469
- file_put_contents( dirname($this->loader) . '/includes/extra-strings.php', $code );
1470
- }
1471
-
1472
- $action = !empty($_POST['action'])?$_POST['action']:'';
1473
- if ( intval($action) == -1 ){
1474
- //Try the second bulk actions box
1475
- $action = !empty($_POST['action2'])?$_POST['action2']:'';
1476
- }
1477
-
1478
- //Get the list of link IDs selected via checkboxes
1479
- $selected_links = array();
1480
- if ( isset($_POST['selected_links']) && is_array($_POST['selected_links']) ){
1481
- //Convert all link IDs to integers (non-numeric entries are converted to zero)
1482
- $selected_links = array_map('intval', $_POST['selected_links']);
1483
- //Remove all zeroes
1484
- $selected_links = array_filter($selected_links);
1485
- }
1486
-
1487
- $message = '';
1488
- $msg_class = 'updated';
1489
-
1490
- //Run the selected bulk action, if any
1491
- $force_delete = false;
1492
- switch ( $action ){
1493
- case 'create-custom-filter':
1494
- list($message, $msg_class) = $this->do_create_custom_filter();
1495
- break;
1496
-
1497
- case 'delete-custom-filter':
1498
- list($message, $msg_class) = $this->do_delete_custom_filter();
1499
- break;
1500
-
1501
- /** @noinspection PhpMissingBreakStatementInspection Deliberate fall-through. */
1502
- case 'bulk-delete-sources':
1503
- $force_delete = true;
1504
- case 'bulk-trash-sources':
1505
- list($message, $msg_class) = $this->do_bulk_delete_sources($selected_links, $force_delete);
1506
- break;
1507
-
1508
- case 'bulk-unlink':
1509
- list($message, $msg_class) = $this->do_bulk_unlink($selected_links);
1510
- break;
1511
-
1512
- case 'bulk-deredirect':
1513
- list($message, $msg_class) = $this->do_bulk_deredirect($selected_links);
1514
- break;
1515
-
1516
- case 'bulk-recheck':
1517
- list($message, $msg_class) = $this->do_bulk_recheck($selected_links);
1518
- break;
1519
-
1520
- case 'bulk-not-broken':
1521
- list($message, $msg_class) = $this->do_bulk_discard($selected_links);
1522
- break;
1523
-
1524
- case 'bulk-dismiss':
1525
- list($message, $msg_class) = $this->do_bulk_dismiss($selected_links);
1526
- break;
1527
-
1528
- case 'bulk-edit':
1529
- list($message, $msg_class) = $this->do_bulk_edit($selected_links);
1530
- break;
1531
- }
1532
-
1533
-
1534
- if ( !empty($message) ){
1535
- echo '<div id="message" class="'.$msg_class.' fade"><p>'.$message.'</p></div>';
1536
- }
1537
-
1538
- $start_time = microtime_float();
1539
-
1540
- //Load custom filters, if any
1541
- $blc_link_query->load_custom_filters();
1542
-
1543
- //Calculate the number of links matching each filter
1544
- $blc_link_query->count_filter_results();
1545
-
1546
- //Run the selected filter (defaults to displaying broken links)
1547
- $selected_filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1548
- $current_filter = $blc_link_query->exec_filter(
1549
- $selected_filter_id,
1550
- isset($_GET['paged']) ? intval($_GET['paged']) : 1,
1551
- $this->conf->options['table_links_per_page'],
1552
- 'broken',
1553
- isset($_GET['orderby']) ? $_GET['orderby'] : '',
1554
- isset($_GET['order']) ? $_GET['order'] : ''
1555
- );
1556
-
1557
- //exec_filter() returns an array with filter data, including the actual filter ID that was used.
1558
- $filter_id = $current_filter['filter_id'];
1559
-
1560
- //Error?
1561
- if ( empty($current_filter['links']) && !empty($wpdb->last_error) ){
1562
- printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1563
- }
1564
- ?>
1565
-
1566
- <script type='text/javascript'>
1567
- var blc_current_filter = '<?php echo $filter_id; ?>';
1568
- var blc_is_broken_filter = <?php echo $current_filter['is_broken_filter'] ? 'true' : 'false'; ?>;
1569
- var blc_current_base_filter = '<?php echo esc_js($current_filter['base_filter']); ?>';
1570
- var blc_suggestions_enabled = <?php echo $this->conf->options['suggestions_enabled'] ? 'true' : 'false'; ?>;
1571
- </script>
1572
-
1573
- <div class="wrap">
1574
- <?php
1575
- $blc_link_query->print_filter_heading($current_filter);
1576
- $blc_link_query->print_filter_menu($filter_id);
1577
-
1578
- //Display the "Search" form and associated buttons.
1579
- //The form requires the $filter_id and $current_filter variables to be set.
1580
- include dirname($this->loader) . '/includes/admin/search-form.php';
1581
-
1582
- //If the user has decided to switch the table to a different mode (compact/full),
1583
- //save the new setting.
1584
- if ( isset($_GET['compact']) ){
1585
- $this->conf->options['table_compact'] = (bool)$_GET['compact'];
1586
- $this->conf->save_options();
1587
- }
1588
-
1589
- //Display the links, if any
1590
- if( $current_filter['links'] && ( count($current_filter['links']) > 0 ) ) {
1591
-
1592
- include dirname($this->loader) . '/includes/admin/table-printer.php';
1593
- $table = new blcTablePrinter($this);
1594
- $table->print_table(
1595
- $current_filter,
1596
- $this->conf->options['table_layout'],
1597
- $this->conf->options['table_visible_columns'],
1598
- $this->conf->options['table_compact']
1599
- );
1600
-
1601
- };
1602
- printf('<!-- Total elapsed : %.4f seconds -->', microtime_float() - $start_time);
1603
-
1604
- //Load assorted JS event handlers and other shinies
1605
- include dirname($this->loader) . '/includes/admin/links-page-js.php';
1606
-
1607
- ?></div><?php
1608
- }
1609
-
1610
- /**
1611
- * Create a custom link filter using params passed in $_POST.
1612
- *
1613
- * @uses $_POST
1614
- * @uses $_GET to replace the current filter ID (if any) with that of the newly created filter.
1615
- *
1616
- * @return array Message and the CSS class to apply to the message.
1617
- */
1618
- function do_create_custom_filter(){
1619
- global $wpdb;
1620
-
1621
- //Create a custom filter!
1622
- check_admin_referer( 'create-custom-filter' );
1623
- $msg_class = 'updated';
1624
-
1625
- //Filter name must be set
1626
- if ( empty($_POST['name']) ){
1627
- $message = __("You must enter a filter name!", 'broken-link-checker');
1628
- $msg_class = 'error';
1629
- //Filter parameters (a search query) must also be set
1630
- } elseif ( empty($_POST['params']) ){
1631
- $message = __("Invalid search query.", 'broken-link-checker');
1632
- $msg_class = 'error';
1633
- } else {
1634
- //Save the new filter
1635
- $name = strip_tags(strval($_POST['name']));
1636
- $blc_link_query = blcLinkQuery::getInstance();
1637
- $filter_id = $blc_link_query->create_custom_filter($name, $_POST['params']);
1638
-
1639
- if ( $filter_id ){
1640
- //Saved
1641
- $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $name);
1642
- //A little hack to make the filter active immediately
1643
- $_GET['filter_id'] = $filter_id;
1644
- } else {
1645
- //Error
1646
- $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
1647
- $msg_class = 'error';
1648
- }
1649
- }
1650
-
1651
- return array($message, $msg_class);
1652
- }
1653
-
1654
- /**
1655
- * Delete a custom link filter.
1656
- *
1657
- * @uses $_POST
1658
- *
1659
- * @return array Message and a CSS class to apply to the message.
1660
- */
1661
- function do_delete_custom_filter(){
1662
- //Delete an existing custom filter!
1663
- check_admin_referer( 'delete-custom-filter' );
1664
- $msg_class = 'updated';
1665
-
1666
- //Filter ID must be set
1667
- if ( empty($_POST['filter_id']) ){
1668
- $message = __("Filter ID not specified.", 'broken-link-checker');
1669
- $msg_class = 'error';
1670
- } else {
1671
- //Try to delete the filter
1672
- $blc_link_query = blcLinkQuery::getInstance();
1673
- if ( $blc_link_query->delete_custom_filter($_POST['filter_id']) ){
1674
- //Success
1675
- $message = __('Filter deleted', 'broken-link-checker');
1676
- } else {
1677
- //Either the ID is wrong or there was some other error
1678
- $message = __('Database error : %s', 'broken-link-checker');
1679
- $msg_class = 'error';
1680
- }
1681
- }
1682
-
1683
- return array($message, $msg_class);
1684
- }
1685
-
1686
- /**
1687
- * Modify multiple links to point to their target URLs.
1688
- *
1689
- * @param array $selected_links
1690
- * @return array The message to display and its CSS class.
1691
- */
1692
- function do_bulk_deredirect($selected_links){
1693
- //For all selected links, replace the URL with the final URL that it redirects to.
1694
-
1695
- $message = '';
1696
- $msg_class = 'updated';
1697
-
1698
- check_admin_referer( 'bulk-action' );
1699
-
1700
- if ( count($selected_links) > 0 ) {
1701
- //Fetch all the selected links
1702
- $links = blc_get_links(array(
1703
- 'link_ids' => $selected_links,
1704
- 'purpose' => BLC_FOR_EDITING,
1705
- ));
1706
-
1707
- if ( count($links) > 0 ) {
1708
- $processed_links = 0;
1709
- $failed_links = 0;
1710
-
1711
- //Deredirect all selected links
1712
- foreach($links as $link){
1713
- $rez = $link->deredirect();
1714
- if ( !is_wp_error($rez) && empty($rez['errors'] )){
1715
- $processed_links++;
1716
- } else {
1717
- $failed_links++;
1718
- }
1719
- }
1720
-
1721
- $message = sprintf(
1722
- _n(
1723
- 'Replaced %d redirect with a direct link',
1724
- 'Replaced %d redirects with direct links',
1725
- $processed_links,
1726
- 'broken-link-checker'
1727
- ),
1728
- $processed_links
1729
- );
1730
-
1731
- if ( $failed_links > 0 ) {
1732
- $message .= '<br>' . sprintf(
1733
- _n(
1734
- 'Failed to fix %d redirect',
1735
- 'Failed to fix %d redirects',
1736
- $failed_links,
1737
- 'broken-link-checker'
1738
- ),
1739
- $failed_links
1740
- );
1741
- $msg_class = 'error';
1742
- }
1743
- } else {
1744
- $message = __('None of the selected links are redirects!', 'broken-link-checker');
1745
- }
1746
- }
1747
-
1748
- return array($message, $msg_class);
1749
- }
1750
-
1751
- /**
1752
- * Edit multiple links in one go.
1753
- *
1754
- * @param array $selected_links
1755
- * @return array The message to display and its CSS class.
1756
- */
1757
- function do_bulk_edit($selected_links){
1758
- $message = '';
1759
- $msg_class = 'updated';
1760
-
1761
- check_admin_referer( 'bulk-action' );
1762
-
1763
- $post = $_POST;
1764
- if ( function_exists('wp_magic_quotes') ){
1765
- $post = stripslashes_deep($post); //Ceterum censeo, WP shouldn't mangle superglobals.
1766
- }
1767
-
1768
- $search = isset($post['search']) ? $post['search'] : '';
1769
- $replace = isset($post['replace']) ? $post['replace'] : '';
1770
- $use_regex = !empty($post['regex']);
1771
- $case_sensitive = !empty($post['case_sensitive']);
1772
-
1773
- $delimiter = '`'; //Pick a char that's uncommon in URLs so that escaping won't usually be a problem
1774
- if ( $use_regex ){
1775
- $search = $delimiter . $this->escape_regex_delimiter($search, $delimiter) . $delimiter;
1776
- if ( !$case_sensitive ){
1777
- $search .= 'i';
1778
- }
1779
- } elseif ( !$case_sensitive ) {
1780
- //str_ireplace() would be more appropriate for case-insensitive, non-regexp replacement,
1781
- //but that's only available in PHP5.
1782
- $search = $delimiter . preg_quote($search, $delimiter) . $delimiter . 'i';
1783
- $use_regex = true;
1784
- }
1785
-
1786
- if ( count($selected_links) > 0 ) {
1787
- set_time_limit(300); //In case the user decides to edit hundreds of links at once
1788
-
1789
- //Fetch all the selected links
1790
- $links = blc_get_links(array(
1791
- 'link_ids' => $selected_links,
1792
- 'purpose' => BLC_FOR_EDITING,
1793
- ));
1794
-
1795
- if ( count($links) > 0 ) {
1796
- $processed_links = 0;
1797
- $failed_links = 0;
1798
- $skipped_links = 0;
1799
-
1800
- //Edit the links
1801
- foreach($links as $link){
1802
- if ( $use_regex ){
1803
- $new_url = preg_replace($search, $replace, $link->url);
1804
- } else {
1805
- $new_url = str_replace($search, $replace, $link->url);
1806
- }
1807
-
1808
- if ( $new_url == $link->url ){
1809
- $skipped_links++;
1810
- continue;
1811
- }
1812
-
1813
- $rez = $link->edit($new_url);
1814
- if ( !is_wp_error($rez) && empty($rez['errors'] )){
1815
- $processed_links++;
1816
- } else {
1817
- $failed_links++;
1818
- }
1819
- }
1820
-
1821
- $message .= sprintf(
1822
- _n(
1823
- '%d link updated.',
1824
- '%d links updated.',
1825
- $processed_links,
1826
- 'broken-link-checker'
1827
- ),
1828
- $processed_links
1829
- );
1830
-
1831
- if ( $failed_links > 0 ) {
1832
- $message .= '<br>' . sprintf(
1833
- _n(
1834
- 'Failed to update %d link.',
1835
- 'Failed to update %d links.',
1836
- $failed_links,
1837
- 'broken-link-checker'
1838
- ),
1839
- $failed_links
1840
- );
1841
- $msg_class = 'error';
1842
- }
1843
- }
1844
- }
1845
-
1846
- return array($message, $msg_class);
1847
- }
1848
-
1849
- /**
1850
- * Escape all instances of the $delimiter character with a backslash (unless already escaped).
1851
- *
1852
- * @param string $pattern
1853
- * @param string $delimiter
1854
- * @return string
1855
- */
1856
- private function escape_regex_delimiter($pattern, $delimiter) {
1857
- if ( empty($pattern) ) {
1858
- return '';
1859
- }
1860
-
1861
- $output = '';
1862
- $length = strlen($pattern);
1863
- $escaped = false;
1864
-
1865
- for ($i = 0; $i < $length; $i++) {
1866
- $char = $pattern[$i];
1867
-
1868
- if ( $escaped ) {
1869
- $escaped = false;
1870
- } else {
1871
- if ( $char == '\\' ) {
1872
- $escaped = true;
1873
- } else if ( $char == $delimiter ) {
1874
- $char = '\\' . $char;
1875
- }
1876
- }
1877
-
1878
- $output .= $char;
1879
- }
1880
-
1881
- return $output;
1882
- }
1883
-
1884
- /**
1885
- * Unlink multiple links.
1886
- *
1887
- * @param array $selected_links
1888
- * @return array Message and a CSS classname.
1889
- */
1890
- function do_bulk_unlink($selected_links){
1891
- //Unlink all selected links.
1892
- $message = '';
1893
- $msg_class = 'updated';
1894
-
1895
- check_admin_referer( 'bulk-action' );
1896
-
1897
- if ( count($selected_links) > 0 ) {
1898
-
1899
- //Fetch all the selected links
1900
- $links = blc_get_links(array(
1901
- 'link_ids' => $selected_links,
1902
- 'purpose' => BLC_FOR_EDITING,
1903
- ));
1904
-
1905
- if ( count($links) > 0 ) {
1906
- $processed_links = 0;
1907
- $failed_links = 0;
1908
-
1909
- //Unlink (delete) each one
1910
- foreach($links as $link){
1911
- $rez = $link->unlink();
1912
- if ( ($rez == false) || is_wp_error($rez) ){
1913
- $failed_links++;
1914
- } else {
1915
- $processed_links++;
1916
- }
1917
- }
1918
-
1919
- //This message is slightly misleading - it doesn't account for the fact that
1920
- //a link can be present in more than one post.
1921
- $message = sprintf(
1922
- _n(
1923
- '%d link removed',
1924
- '%d links removed',
1925
- $processed_links,
1926
- 'broken-link-checker'
1927
- ),
1928
- $processed_links
1929
- );
1930
-
1931
- if ( $failed_links > 0 ) {
1932
- $message .= '<br>' . sprintf(
1933
- _n(
1934
- 'Failed to remove %d link',
1935
- 'Failed to remove %d links',
1936
- $failed_links,
1937
- 'broken-link-checker'
1938
- ),
1939
- $failed_links
1940
- );
1941
- $msg_class = 'error';
1942
- }
1943
- }
1944
- }
1945
-
1946
- return array($message, $msg_class);
1947
- }
1948
-
1949
- /**
1950
- * Delete or trash posts, bookmarks and other items that contain any of the specified links.
1951
- *
1952
- * Will prefer moving stuff to trash to permanent deletion. If it encounters an item that
1953
- * can't be moved to the trash, it will skip that item by default.
1954
- *
1955
- * @param array $selected_links An array of link IDs
1956
- * @param bool $force_delete Whether to bypass trash and force deletion. Defaults to false.
1957
- * @return array Confirmation message and its CSS class.
1958
- */
1959
- function do_bulk_delete_sources($selected_links, $force_delete = false){
1960
- $message = '';
1961
- $msg_class = 'updated';
1962
-
1963
- //Delete posts, blogroll entries and any other link containers that contain any of the selected links.
1964
- //
1965
- //Note that once all containers containing a particular link have been deleted,
1966
- //there is no need to explicitly delete the link record itself. The hooks attached to
1967
- //the actions that execute when something is deleted (e.g. "post_deleted") will
1968
- //take care of that.
1969
-
1970
- check_admin_referer( 'bulk-action' );
1971
-
1972
- if ( count($selected_links) > 0 ) {
1973
- $messages = array();
1974
-
1975
- //Fetch all the selected links
1976
- $links = blc_get_links(array(
1977
- 'link_ids' => $selected_links,
1978
- 'load_instances' => true,
1979
- ));
1980
-
1981
- //Make a list of all containers associated with these links, with each container
1982
- //listed only once.
1983
- $containers = array();
1984
- foreach($links as $link){ /* @var blcLink $link */
1985
- $instances = $link->get_instances();
1986
- foreach($instances as $instance){ /* @var blcLinkInstance $instance */
1987
- $key = $instance->container_type . '|' . $instance->container_id;
1988
- $containers[$key] = array($instance->container_type, $instance->container_id);
1989
- }
1990
- }
1991
-
1992
- //Instantiate the containers
1993
- $containers = blcContainerHelper::get_containers($containers);
1994
-
1995
- //Delete/trash their associated entities
1996
- $deleted = array();
1997
- $skipped = array();
1998
- foreach($containers as $container){ /* @var blcContainer $container */
1999
- if ( !$container->current_user_can_delete() ){
2000
- continue;
2001
- }
2002
-
2003
- if ( $force_delete ){
2004
- $rez = $container->delete_wrapped_object();
2005
- } else {
2006
- if ( $container->can_be_trashed() ){
2007
- $rez = $container->trash_wrapped_object();
2008
- } else {
2009
- $skipped[] = $container;
2010
- continue;
2011
- }
2012
- }
2013
-
2014
- if ( is_wp_error($rez) ){ /* @var WP_Error $rez */
2015
- //Record error messages for later display
2016
- $messages[] = $rez->get_error_message();
2017
- $msg_class = 'error';
2018
- } else {
2019
- //Keep track of how many of each type were deleted.
2020
- $container_type = $container->container_type;
2021
- if ( isset($deleted[$container_type]) ){
2022
- $deleted[$container_type]++;
2023
- } else {
2024
- $deleted[$container_type] = 1;
2025
- }
2026
- }
2027
- }
2028
-
2029
- //Generate delete confirmation messages
2030
- foreach($deleted as $container_type => $number){
2031
- if ( $force_delete ){
2032
- $messages[] = blcContainerHelper::ui_bulk_delete_message($container_type, $number);
2033
- } else {
2034
- $messages[] = blcContainerHelper::ui_bulk_trash_message($container_type, $number);
2035
- }
2036
-
2037
- }
2038
-
2039
- //If some items couldn't be trashed, let the user know
2040
- if ( count($skipped) > 0 ){
2041
- $message = sprintf(
2042
- _n(
2043
- "%d item was skipped because it can't be moved to the Trash. You need to delete it manually.",
2044
- "%d items were skipped because they can't be moved to the Trash. You need to delete them manually.",
2045
- count($skipped)
2046
- ),
2047
- count($skipped)
2048
- );
2049
- $message .= '<br><ul>';
2050
- foreach($skipped as $container){
2051
- $message .= sprintf(
2052
- '<li>%s</li>',
2053
- $container->ui_get_source('')
2054
- );
2055
- }
2056
- $message .= '</ul>';
2057
-
2058
- $messages[] = $message;
2059
- }
2060
-
2061
- if ( count($messages) > 0 ){
2062
- $message = implode('<p>', $messages);
2063
- } else {
2064
- $message = __("Didn't find anything to delete!", 'broken-link-checker');
2065
- $msg_class = 'error';
2066
- }
2067
- }
2068
-
2069
- return array($message, $msg_class);
2070
- }
2071
-
2072
- /**
2073
- * Mark multiple links as unchecked.
2074
- *
2075
- * @param array $selected_links An array of link IDs
2076
- * @return array Confirmation nessage and the CSS class to use with that message.
2077
- */
2078
- function do_bulk_recheck($selected_links){
2079
- /** @var wpdb $wpdb */
2080
- global $wpdb;
2081
-
2082
- $message = '';
2083
- $msg_class = 'updated';
2084
-
2085
- check_admin_referer('bulk-action');
2086
-
2087
- if ( count($selected_links) > 0 ){
2088
- $q = "UPDATE {$wpdb->prefix}blc_links
2089
- SET last_check_attempt = '0000-00-00 00:00:00'
2090
- WHERE link_id IN (".implode(', ', $selected_links).")";
2091
- $changes = $wpdb->query($q);
2092
-
2093
- $message = sprintf(
2094
- _n(
2095
- "%d link scheduled for rechecking",
2096
- "%d links scheduled for rechecking",
2097
- $changes,
2098
- 'broken-link-checker'
2099
- ),
2100
- $changes
2101
- );
2102
- }
2103
-
2104
- return array($message, $msg_class);
2105
- }
2106
-
2107
-
2108
- /**
2109
- * Mark multiple links as not broken.
2110
- *
2111
- * @param array $selected_links An array of link IDs
2112
- * @return array Confirmation nessage and the CSS class to use with that message.
2113
- */
2114
- function do_bulk_discard($selected_links){
2115
- check_admin_referer( 'bulk-action' );
2116
-
2117
- $messages = array();
2118
- $msg_class = 'updated';
2119
- $processed_links = 0;
2120
-
2121
- if ( count($selected_links) > 0 ){
2122
- foreach($selected_links as $link_id){
2123
- //Load the link
2124
- $link = new blcLink( intval($link_id) );
2125
-
2126
- //Skip links that don't actually exist
2127
- if ( !$link->valid() ){
2128
- continue;
2129
- }
2130
-
2131
- //Skip links that weren't actually detected as broken
2132
- if ( !$link->broken && !$link->warning ){
2133
- continue;
2134
- }
2135
-
2136
- //Make it appear "not broken"
2137
- $link->broken = false;
2138
- $link->warning = false;
2139
- $link->false_positive = true;
2140
- $link->last_check_attempt = time();
2141
- $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2142
-
2143
- $link->isOptionLinkChanged = true;
2144
- //Save the changes
2145
- if ( $link->save() ){
2146
- $processed_links++;
2147
- } else {
2148
- $messages[] = sprintf(
2149
- __("Couldn't modify link %d", 'broken-link-checker'),
2150
- $link_id
2151
- );
2152
- $msg_class = 'error';
2153
- }
2154
- }
2155
- }
2156
-
2157
- if ( $processed_links > 0 ){
2158
- $messages[] = sprintf(
2159
- _n(
2160
- '%d link marked as not broken',
2161
- '%d links marked as not broken',
2162
- $processed_links,
2163
- 'broken-link-checker'
2164
- ),
2165
- $processed_links
2166
- );
2167
- }
2168
-
2169
- return array(implode('<br>', $messages), $msg_class);
2170
- }
2171
-
2172
- /**
2173
- * Dismiss multiple links.
2174
- *
2175
- * @param array $selected_links An array of link IDs
2176
- * @return array Confirmation message and the CSS class to use with that message.
2177
- */
2178
- function do_bulk_dismiss($selected_links){
2179
- check_admin_referer( 'bulk-action' );
2180
-
2181
- $messages = array();
2182
- $msg_class = 'updated';
2183
- $processed_links = 0;
2184
-
2185
- if ( count($selected_links) > 0 ){
2186
- foreach($selected_links as $link_id){
2187
- //Load the link
2188
- $link = new blcLink( intval($link_id) );
2189
-
2190
- //Skip links that don't actually exist
2191
- if ( !$link->valid() ){
2192
- continue;
2193
- }
2194
-
2195
- //We can only dismiss broken links and redirects.
2196
- if ( !($link->broken || $link->warning || ($link->redirect_count > 0)) ){
2197
- continue;
2198
- }
2199
-
2200
- $link->dismissed = true;
2201
-
2202
- $link->isOptionLinkChanged = true;
2203
- //Save the changes
2204
- if ( $link->save() ){
2205
- $processed_links++;
2206
- } else {
2207
- $messages[] = sprintf(
2208
- __("Couldn't modify link %d", 'broken-link-checker'),
2209
- $link_id
2210
- );
2211
- $msg_class = 'error';
2212
- }
2213
- }
2214
- }
2215
-
2216
- if ( $processed_links > 0 ){
2217
- $messages[] = sprintf(
2218
- _n(
2219
- '%d link dismissed',
2220
- '%d links dismissed',
2221
- $processed_links,
2222
- 'broken-link-checker'
2223
- ),
2224
- $processed_links
2225
- );
2226
- }
2227
-
2228
- return array(implode('<br>', $messages), $msg_class);
2229
- }
2230
-
2231
-
2232
- /**
2233
- * Enqueue CSS files for the "Broken Links" page
2234
- *
2235
- * @return void
2236
- */
2237
- function links_page_css(){
2238
- wp_enqueue_style('blc-links-page', plugins_url('css/links-page.css', $this->loader), array(), '20141113-2');
2239
- }
2240
-
2241
- /**
2242
- * Show an admin notice that explains what the "Warnings" section under "Tools -> Broken Links" does.
2243
- * The user can hide the notice.
2244
- */
2245
- public function show_warnings_section_notice() {
2246
- $is_warnings_section = isset($_GET['filter_id'])
2247
- && ($_GET['filter_id'] === 'warnings')
2248
- && isset($_GET['page'])
2249
- && ($_GET['page'] === 'view-broken-links');
2250
-
2251
- if ( !($is_warnings_section && current_user_can('edit_others_posts')) ) {
2252
- return;
2253
- }
2254
-
2255
- //Let the user hide the notice.
2256
- $conf = blc_get_configuration();
2257
- $notice_name = 'show_warnings_section_hint';
2258
-
2259
- if ( isset($_GET[$notice_name]) && is_numeric($_GET[$notice_name]) ) {
2260
- $conf->set($notice_name, (bool)$_GET[$notice_name]);
2261
- $conf->save_options();
2262
- }
2263
- if ( !$conf->get($notice_name, true) ) {
2264
- return;
2265
- }
2266
-
2267
- printf(
2268
- '<div class="updated">
2269
- <p>%1$s</p>
2270
- <p>
2271
- <a href="%2$s">%3$s</a> |
2272
- <a href="%4$s">%5$s</a>
2273
- <p>
2274
- </div>',
2275
- __(
2276
- 'The "Warnings" page lists problems that are probably temporary or suspected to be false positives.<br> Warnings that persist for a long time will usually be reclassified as broken links.',
2277
- 'broken-link-checker'
2278
- ),
2279
- esc_attr(add_query_arg($notice_name, '0')),
2280
- _x(
2281
- 'Hide notice',
2282
- 'admin notice under Tools - Broken links - Warnings',
2283
- 'broken-link-checker'
2284
- ),
2285
- esc_attr(admin_url('options-general.php?page=link-checker-settings#blc_warning_settings')),
2286
- _x(
2287
- 'Change warning settings',
2288
- 'a link from the admin notice under Tools - Broken links - Warnings',
2289
- 'broken-link-checker'
2290
- )
2291
- );
2292
- }
2293
-
2294
- /**
2295
- * Generate the HTML for the plugin's Screen Options panel.
2296
- *
2297
- * @return string
2298
- */
2299
- function screen_options_html(){
2300
- //Update the links-per-page setting when "Apply" is clicked
2301
- if ( isset($_POST['per_page']) && is_numeric($_POST['per_page']) ) {
2302
- check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
2303
- $per_page = intval($_POST['per_page']);
2304
- if ( ($per_page >= 1) && ($per_page <= 500) ){
2305
- $this->conf->options['table_links_per_page'] = $per_page;
2306
- $this->conf->save_options();
2307
- }
2308
- }
2309
-
2310
- //Let the user show/hide individual table columns
2311
- $html = '<h5>' . __('Table columns', 'broken-link-checker') . '</h5>';
2312
-
2313
- include dirname($this->loader) . '/includes/admin/table-printer.php';
2314
- $table = new blcTablePrinter($this);
2315
- $available_columns = $table->get_layout_columns($this->conf->options['table_layout']);
2316
-
2317
- $html .= '<div id="blc-column-selector" class="metabox-prefs">';
2318
-
2319
- foreach( $available_columns as $column_id => $data ){
2320
- $html .= sprintf(
2321
- '<label><input type="checkbox" name="visible_columns[%s]"%s>%s</label>',
2322
- esc_attr($column_id),
2323
- in_array($column_id, $this->conf->options['table_visible_columns']) ? ' checked="checked"' : '',
2324
- $data['heading']
2325
- );
2326
- }
2327
-
2328
- $html .= '</div>';
2329
-
2330
- $html .= '<h5>' . __('Show on screen', 'broken-link-checker') . '</h5>';
2331
- $html .= '<div class="screen-options">';
2332
- $html .= sprintf(
2333
- '<input type="text" name="per_page" maxlength="3" value="%d" class="screen-per-page" id="blc_links_per_page" />
2334
- <label for="blc_links_per_page">%s</label>
2335
- <input type="button" class="button" value="%s" id="blc-per-page-apply-button" /><br />',
2336
- $this->conf->options['table_links_per_page'],
2337
- __('links', 'broken-link-checker'),
2338
- __('Apply')
2339
- );
2340
- $html .= '</div>';
2341
-
2342
- $html .= '<h5>' . __('Misc', 'broken-link-checker') . '</h5>';
2343
- $html .= '<div class="screen-options">';
2344
- /*
2345
- Display a checkbox in "Screen Options" that lets the user highlight links that
2346
- have been broken for at least X days.
2347
- */
2348
- $html .= sprintf(
2349
- '<label><input type="checkbox" id="highlight_permanent_failures" name="highlight_permanent_failures"%s> ',
2350
- $this->conf->options['highlight_permanent_failures'] ? ' checked="checked"' : ''
2351
- );
2352
- $input_box = sprintf(
2353
- '</label><input type="text" name="failure_duration_threshold" id="failure_duration_threshold" value="%d" size="2"><label for="highlight_permanent_failures">',
2354
- $this->conf->options['failure_duration_threshold']
2355
- );
2356
- $html .= sprintf(
2357
- __('Highlight links broken for at least %s days', 'broken-link-checker'),
2358
- $input_box
2359
- );
2360
- $html .= '</label>';
2361
-
2362
- //Display a checkbox for turning colourful link status messages on/off
2363
- $html .= sprintf(
2364
- '<br/><label><input type="checkbox" id="table_color_code_status" name="table_color_code_status"%s> %s</label>',
2365
- $this->conf->options['table_color_code_status'] ? ' checked="checked"' : '',
2366
- __('Color-code status codes', 'broken-link-checker')
2367
- );
2368
-
2369
- $html .= '</div>';
2370
-
2371
- return $html;
2372
- }
2373
-
2374
- /**
2375
- * AJAX callback for saving the "Screen Options" panel settings
2376
- *
2377
- * @param array $form
2378
- * @return void
2379
- */
2380
- function ajax_save_screen_options($form){
2381
- if ( !current_user_can('edit_others_posts') ){
2382
- die( json_encode( array(
2383
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2384
- )));
2385
- }
2386
-
2387
- $this->conf->options['highlight_permanent_failures'] = !empty($form['highlight_permanent_failures']);
2388
- $this->conf->options['table_color_code_status'] = !empty($form['table_color_code_status']);
2389
-
2390
- $failure_duration_threshold = intval($form['failure_duration_threshold']);
2391
- if ( $failure_duration_threshold >=1 ){
2392
- $this->conf->options['failure_duration_threshold'] = $failure_duration_threshold;
2393
- }
2394
-
2395
- if ( isset($form['visible_columns']) && is_array($form['visible_columns']) ){
2396
- $this->conf->options['table_visible_columns'] = array_keys($form['visible_columns']);
2397
- }
2398
-
2399
- $this->conf->save_options();
2400
- die('1');
2401
- }
2402
-
2403
- function start_timer(){
2404
- $this->execution_start_time = microtime_float();
2405
- }
2406
-
2407
- function execution_time(){
2408
- return microtime_float() - $this->execution_start_time;
2409
- }
2410
-
2411
- /**
2412
- * The main worker function that does all kinds of things.
2413
- *
2414
- * @return void
2415
- */
2416
- function work(){
2417
- global $blclog;
2418
-
2419
- //Close the session to prevent lock-ups.
2420
- //PHP sessions are blocking. session_start() will wait until all other scripts that are using the same session
2421
- //are finished. As a result, a long-running script that unintentionally keeps the session open can cause
2422
- //the entire site to "lock up" for the current user/browser. WordPress itself doesn't use sessions, but some
2423
- //plugins do, so we should explicitly close the session (if any) before starting the worker.
2424
- if ( session_id() != '' ) {
2425
- session_write_close();
2426
- }
2427
-
2428
- if ( !$this->acquire_lock() ){
2429
- //FB::warn("Another instance of BLC is already working. Stop.");
2430
- $blclog->info('Another instance of BLC is already working. Stop.');
2431
- return;
2432
- }
2433
-
2434
- if ( $this->server_too_busy() ){
2435
- //FB::warn("Server is too busy. Stop.");
2436
- $blclog->warn('Server load is too high, stopping.');
2437
- return;
2438
- }
2439
-
2440
- $this->start_timer();
2441
- $blclog->info('work() starts');
2442
-
2443
- $max_execution_time = $this->conf->options['max_execution_time'];
2444
-
2445
- /*****************************************
2446
- Preparation
2447
- ******************************************/
2448
- // Check for safe mode
2449
- if( blcUtility::is_safe_mode() ){
2450
- // Do it the safe mode way - obey the existing max_execution_time setting
2451
- $t = ini_get('max_execution_time');
2452
- if ($t && ($t < $max_execution_time))
2453
- $max_execution_time = $t-1;
2454
- } else {
2455
- // Do it the regular way
2456
- @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
2457
- }
2458
-
2459
- //Don't stop the script when the connection is closed
2460
- ignore_user_abort( true );
2461
-
2462
- //Close the connection as per http://www.php.net/manual/en/features.connection-handling.php#71172
2463
- //This reduces resource usage.
2464
- //(Disable when debugging or you won't get the FirePHP output)
2465
- if (
2466
- !headers_sent()
2467
- && (defined('DOING_AJAX') && constant('DOING_AJAX'))
2468
- && (!defined('BLC_DEBUG') || !constant('BLC_DEBUG'))
2469
- ){
2470
- @ob_end_clean(); //Discard the existing buffer, if any
2471
- header("Connection: close");
2472
- ob_start();
2473
- echo ('Connection closed'); //This could be anything
2474
- $size = ob_get_length();
2475
- header("Content-Length: $size");
2476
- ob_end_flush(); // Strange behaviour, will not work
2477
- flush(); // Unless both are called !
2478
- }
2479
-
2480
- //Load modules for this context
2481
- $moduleManager = blcModuleManager::getInstance();
2482
- $moduleManager->load_modules('work');
2483
-
2484
- $target_usage_fraction = $this->conf->get('target_resource_usage', 0.25);
2485
- //Target usage must be between 1% and 100%.
2486
- $target_usage_fraction = max(min($target_usage_fraction, 1), 0.01);
2487
-
2488
-
2489
- /*****************************************
2490
- Parse posts and bookmarks
2491
- ******************************************/
2492
-
2493
- $orphans_possible = false;
2494
- $still_need_resynch = $this->conf->options['need_resynch'];
2495
-
2496
- if ( $still_need_resynch ) {
2497
-
2498
- //FB::log("Looking for containers that need parsing...");
2499
- $max_containers_per_query = 50;
2500
-
2501
- $start = microtime(true);
2502
- $containers = blcContainerHelper::get_unsynched_containers($max_containers_per_query);
2503
- $get_containers_time = microtime(true) - $start;
2504
-
2505
- while( !empty($containers) ){
2506
- //FB::log($containers, 'Found containers');
2507
- $this->sleep_to_maintain_ratio($get_containers_time, $target_usage_fraction);
2508
-
2509
- foreach($containers as $container){
2510
- $synch_start_time = microtime(true);
2511
-
2512
- //FB::log($container, "Parsing container");
2513
- $container->synch();
2514
-
2515
- $synch_elapsed_time = microtime(true) - $synch_start_time;
2516
- $blclog->info(sprintf(
2517
- 'Parsed container %s[%s] in %.2f ms',
2518
- $container->container_type,
2519
- $container->container_id,
2520
- $synch_elapsed_time * 1000
2521
- ));
2522
-
2523
- //Check if we still have some execution time left
2524
- if( $this->execution_time() > $max_execution_time ){
2525
- //FB::log('The allotted execution time has run out');
2526
- blc_cleanup_links();
2527
- $this->release_lock();
2528
- return;
2529
- }
2530
-
2531
- //Check if the server isn't overloaded
2532
- if ( $this->server_too_busy() ){
2533
- //FB::log('Server overloaded, bailing out.');
2534
- blc_cleanup_links();
2535
- $this->release_lock();
2536
- return;
2537
- }
2538
-
2539
- //Intentionally slow down parsing to reduce the load on the server. Basically,
2540
- //we work $target_usage_fraction of the time and sleep the rest of the time.
2541
- $this->sleep_to_maintain_ratio($synch_elapsed_time, $target_usage_fraction);
2542
- }
2543
- $orphans_possible = true;
2544
-
2545
- $start = microtime(true);
2546
- $containers = blcContainerHelper::get_unsynched_containers($max_containers_per_query);
2547
- $get_containers_time = microtime(true) - $start;
2548
- }
2549
-
2550
- //FB::log('No unparsed items found.');
2551
- $still_need_resynch = false;
2552
-
2553
- } else {
2554
- //FB::log('Resynch not required.');
2555
- }
2556
-
2557
- /******************************************
2558
- Resynch done?
2559
- *******************************************/
2560
- if ( $this->conf->options['need_resynch'] && !$still_need_resynch ){
2561
- $this->conf->options['need_resynch'] = $still_need_resynch;
2562
- $this->conf->save_options();
2563
- }
2564
-
2565
- /******************************************
2566
- Remove orphaned links
2567
- *******************************************/
2568
-
2569
- if ( $orphans_possible ) {
2570
- $start = microtime(true);
2571
-
2572
- $blclog->info('Removing orphaned links.');
2573
- blc_cleanup_links();
2574
-
2575
- $get_links_time = microtime(true) - $start;
2576
- $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2577
- }
2578
-
2579
- //Check if we still have some execution time left
2580
- if( $this->execution_time() > $max_execution_time ){
2581
- //FB::log('The allotted execution time has run out');
2582
- $blclog->info('The allotted execution time has run out.');
2583
- $this->release_lock();
2584
- return;
2585
- }
2586
-
2587
- if ( $this->server_too_busy() ){
2588
- //FB::log('Server overloaded, bailing out.');
2589
- $blclog->info('Server load too high, stopping.');
2590
- $this->release_lock();
2591
- return;
2592
- }
2593
-
2594
- /*****************************************
2595
- Check links
2596
- ******************************************/
2597
- $max_links_per_query = 30;
2598
-
2599
- $start = microtime(true);
2600
- $links = $this->get_links_to_check($max_links_per_query);
2601
- $get_links_time = microtime(true) - $start;
2602
-
2603
- while ( $links ){
2604
- $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2605
-
2606
- //Some unchecked links found
2607
- //FB::log("Checking ".count($links)." link(s)");
2608
- $blclog->info("Checking ".count($links)." link(s)");
2609
-
2610
- //Randomizing the array reduces the chances that we'll get several links to the same domain in a row.
2611
- shuffle($links);
2612
-
2613
- $transactionManager = TransactionManager::getInstance();
2614
- $transactionManager->start();
2615
-
2616
- foreach ($links as $link) {
2617
- //Does this link need to be checked? Excluded links aren't checked, but their URLs are still
2618
- //tested periodically to see if they're still on the exclusion list.
2619
- if ( !$this->is_excluded( $link->url ) ) {
2620
- //Check the link.
2621
- //FB::log($link->url, "Checking link {$link->link_id}");
2622
- $link->check( true );
2623
- } else {
2624
- //FB::info("The URL {$link->url} is excluded, skipping link {$link->link_id}.");
2625
- $link->last_check_attempt = time();
2626
- $link->save();
2627
- }
2628
-
2629
- //Check if we still have some execution time left
2630
- if( $this->execution_time() > $max_execution_time ){
2631
- //FB::log('The allotted execution time has run out');
2632
- $blclog->info('The allotted execution time has run out.');
2633
- $this->release_lock();
2634
- return;
2635
- }
2636
-
2637
- //Check if the server isn't overloaded
2638
- if ( $this->server_too_busy() ){
2639
- //FB::log('Server overloaded, bailing out.');
2640
- $blclog->info('Server load too high, stopping.');
2641
- $this->release_lock();
2642
- return;
2643
- }
2644
- }
2645
- $transactionManager->commit();
2646
-
2647
- $start = microtime(true);
2648
- $links = $this->get_links_to_check($max_links_per_query);
2649
- $get_links_time = microtime(true) - $start;
2650
- }
2651
- //FB::log('No links need to be checked right now.');
2652
-
2653
- $this->release_lock();
2654
- $blclog->info('work(): All done.');
2655
- //FB::log('All done.');
2656
- }
2657
-
2658
- /**
2659
- * Sleep long enough to maintain the required $ratio between $elapsed_time and total runtime.
2660
- *
2661
- * For example, if $ratio is 0.25 and $elapsed_time is 1 second, this method will sleep for 3 seconds.
2662
- * Total runtime = 1 + 3 = 4, ratio = 1 / 4 = 0.25.
2663
- *
2664
- * @param float $elapsed_time
2665
- * @param float $ratio
2666
- */
2667
- private function sleep_to_maintain_ratio($elapsed_time, $ratio) {
2668
- if ( ($ratio <= 0) || ($ratio > 1) ) {
2669
- return;
2670
- }
2671
- $sleep_time = $elapsed_time * ((1 / $ratio) - 1);
2672
- if ($sleep_time > 0.0001) {
2673
- /*global $blclog;
2674
- $blclog->debug(sprintf(
2675
- 'Task took %.2f ms, sleeping for %.2f ms',
2676
- $elapsed_time * 1000,
2677
- $sleep_time * 1000
2678
- ));*/
2679
- usleep($sleep_time * 1000000);
2680
- }
2681
- }
2682
-
2683
- /**
2684
- * This function is called when the plugin's cron hook executes.
2685
- * Its only purpose is to invoke the worker function.
2686
- *
2687
- * @uses wsBrokenLinkChecker::work()
2688
- *
2689
- * @return void
2690
- */
2691
- function cron_check_links(){
2692
- $this->work();
2693
- }
2694
-
2695
- /**
2696
- * Retrieve links that need to be checked or re-checked.
2697
- *
2698
- * @param integer $max_results The maximum number of links to return. Defaults to 0 = no limit.
2699
- * @param bool $count_only If true, only the number of found links will be returned, not the links themselves.
2700
- * @return int|blcLink[]
2701
- */
2702
- function get_links_to_check($max_results = 0, $count_only = false){
2703
- global $wpdb; /* @var wpdb $wpdb */
2704
-
2705
- $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2706
- $recheck_threshold = date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2707
-
2708
- //FB::log('Looking for links to check (threshold : '.$check_threshold.', recheck_threshold : '.$recheck_threshold.')...');
2709
-
2710
- //Select some links that haven't been checked for a long time or
2711
- //that are broken and need to be re-checked again. Links that are
2712
- //marked as "being checked" and have been that way for several minutes
2713
- //can also be considered broken/buggy, so those will be selected
2714
- //as well.
2715
-
2716
- //Only check links that have at least one valid instance (i.e. an instance exists and
2717
- //it corresponds to one of the currently loaded container/parser types).
2718
- $manager = blcModuleManager::getInstance();
2719
- $loaded_containers = $manager->get_escaped_ids('container');
2720
- $loaded_parsers = $manager->get_escaped_ids('parser');
2721
-
2722
- //Note : This is a slow query, but AFAIK there is no way to speed it up.
2723
- //I could put an index on last_check_attempt, but that value is almost
2724
- //certainly unique for each row so it wouldn't be much better than a full table scan.
2725
- if ( $count_only ){
2726
- $q = "SELECT COUNT(DISTINCT links.link_id)\n";
2727
- } else {
2728
- $q = "SELECT DISTINCT links.*\n";
2729
- }
2730
- $q .= "FROM {$wpdb->prefix}blc_links AS links
2731
- INNER JOIN {$wpdb->prefix}blc_instances AS instances USING(link_id)
2732
- WHERE
2733
- (
2734
- ( last_check_attempt < %s )
2735
- OR
2736
- (
2737
- (broken = 1 OR being_checked = 1)
2738
- AND may_recheck = 1
2739
- AND check_count < %d
2740
- AND last_check_attempt < %s
2741
- )
2742
- )
2743
-
2744
- AND
2745
- ( instances.container_type IN ({$loaded_containers}) )
2746
- AND ( instances.parser_type IN ({$loaded_parsers}) )
2747
- ";
2748
-
2749
- if ( !$count_only ){
2750
- $q .= "\nORDER BY last_check_attempt ASC\n";
2751
- if ( !empty($max_results) ){
2752
- $q .= "LIMIT " . intval($max_results);
2753
- }
2754
- }
2755
-
2756
- $link_q = $wpdb->prepare(
2757
- $q,
2758
- $check_threshold,
2759
- $this->conf->options['recheck_count'],
2760
- $recheck_threshold
2761
- );
2762
- //FB::log($link_q, "Find links to check");
2763
- //$blclog->debug("Find links to check: \n" . $link_q);
2764
-
2765
- //If we just need the number of links, retrieve it and return
2766
- if ( $count_only ){
2767
- return $wpdb->get_var($link_q);
2768
- }
2769
-
2770
- //Fetch the link data
2771
- $link_data = $wpdb->get_results($link_q, ARRAY_A);
2772
- if ( empty($link_data) ){
2773
- return array();
2774
- }
2775
-
2776
- //Instantiate blcLink objects for all fetched links
2777
- $links = array();
2778
- foreach($link_data as $data){
2779
- $links[] = new blcLink($data);
2780
- }
2781
-
2782
- return $links;
2783
- }
2784
-
2785
- /**
2786
- * Output the current link checker status in JSON format.
2787
- * Ajax hook for the 'blc_full_status' action.
2788
- *
2789
- * @return void
2790
- */
2791
- function ajax_full_status( ){
2792
- $status = $this->get_status();
2793
- $text = $this->status_text( $status );
2794
-
2795
- echo json_encode( array(
2796
- 'text' => $text,
2797
- 'status' => $status,
2798
- ) );
2799
-
2800
- die();
2801
- }
2802
-
2803
- /**
2804
- * Generates a status message based on the status info in $status
2805
- *
2806
- * @param array $status
2807
- * @return string
2808
- */
2809
- function status_text( $status ){
2810
- $text = '';
2811
-
2812
- if( $status['broken_links'] > 0 ){
2813
- $text .= sprintf(
2814
- "<a href='%s' title='" . __('View broken links', 'broken-link-checker') . "'><strong>".
2815
- _n('Found %d broken link', 'Found %d broken links', $status['broken_links'], 'broken-link-checker') .
2816
- "</strong></a>",
2817
- esc_attr(admin_url('tools.php?page=view-broken-links')),
2818
- $status['broken_links']
2819
- );
2820
- } else {
2821
- $text .= __("No broken links found.", 'broken-link-checker');
2822
- }
2823
-
2824
- $text .= "<br/>";
2825
-
2826
- if( $status['unchecked_links'] > 0) {
2827
- $text .= sprintf(
2828
- _n('%d URL in the work queue', '%d URLs in the work queue', $status['unchecked_links'], 'broken-link-checker'),
2829
- $status['unchecked_links'] );
2830
- } else {
2831
- $text .= __("No URLs in the work queue.", 'broken-link-checker');
2832
- }
2833
-
2834
- $text .= "<br/>";
2835
- if ( $status['known_links'] > 0 ){
2836
- $url_count = sprintf(
2837
- _nx('%d unique URL', '%d unique URLs', $status['known_links'], 'for the "Detected X unique URLs in Y links" message', 'broken-link-checker'),
2838
- $status['known_links']
2839
- );
2840
- $link_count = sprintf(
2841
- _nx('%d link', '%d links', $status['known_instances'], 'for the "Detected X unique URLs in Y links" message', 'broken-link-checker'),
2842
- $status['known_instances']
2843
- );
2844
-
2845
- if ($this->conf->options['need_resynch']){
2846
- $text .= sprintf(
2847
- __('Detected %1$s in %2$s and still searching...', 'broken-link-checker'),
2848
- $url_count,
2849
- $link_count
2850
- );
2851
- } else {
2852
- $text .= sprintf(
2853
- __('Detected %1$s in %2$s.', 'broken-link-checker'),
2854
- $url_count,
2855
- $link_count
2856
- );
2857
- }
2858
- } else {
2859
- if ($this->conf->options['need_resynch']){
2860
- $text .= __('Searching your blog for links...', 'broken-link-checker');
2861
- } else {
2862
- $text .= __('No links detected.', 'broken-link-checker');
2863
- }
2864
- }
2865
-
2866
- return $text;
2867
- }
2868
-
2869
- /**
2870
- * @uses wsBrokenLinkChecker::ajax_full_status()
2871
- *
2872
- * @return void
2873
- */
2874
- function ajax_dashboard_status(){
2875
- //Just display the full status.
2876
- $this->ajax_full_status();
2877
- }
2878
-
2879
- /**
2880
- * Output the current average server load (over the last one-minute period).
2881
- * Called via AJAX.
2882
- *
2883
- * @return void
2884
- */
2885
- function ajax_current_load(){
2886
- $load = blcUtility::get_server_load();
2887
- if ( empty($load) ){
2888
- die( _x('Unknown', 'current load', 'broken-link-checker') );
2889
- }
2890
-
2891
- $one_minute = reset($load);
2892
- printf('%.2f', $one_minute);
2893
- die();
2894
- }
2895
-
2896
- /**
2897
- * Returns an array with various status information about the plugin. Array key reference:
2898
- * check_threshold - date/time; links checked before this threshold should be checked again.
2899
- * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
2900
- * known_links - the number of detected unique URLs (a misleading name, yes).
2901
- * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
2902
- * broken_links - the number of detected broken links.
2903
- * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
2904
- *
2905
- * @return array
2906
- */
2907
- function get_status(){
2908
- $blc_link_query = blcLinkQuery::getInstance();
2909
-
2910
- $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2911
- $recheck_threshold=date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2912
-
2913
- $known_links = blc_get_links(array('count_only' => true));
2914
- $known_instances = blc_get_usable_instance_count();
2915
-
2916
- $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
2917
-
2918
- $unchecked_links = $this->get_links_to_check(0, true);
2919
-
2920
- return array(
2921
- 'check_threshold' => $check_threshold,
2922
- 'recheck_threshold' => $recheck_threshold,
2923
- 'known_links' => $known_links,
2924
- 'known_instances' => $known_instances,
2925
- 'broken_links' => $broken_links,
2926
- 'unchecked_links' => $unchecked_links,
2927
- );
2928
- }
2929
-
2930
- function ajax_work(){
2931
- check_ajax_referer('blc_work');
2932
-
2933
- //Run the worker function
2934
- $this->work();
2935
- die();
2936
- }
2937
-
2938
- /**
2939
- * AJAX hook for the "Not broken" button. Marks a link as broken and as a likely false positive.
2940
- *
2941
- * @return void
2942
- */
2943
- function ajax_discard(){
2944
- if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_discard', false, false)){
2945
- die( __("You're not allowed to do that!", 'broken-link-checker') );
2946
- }
2947
-
2948
- if ( isset($_POST['link_id']) ){
2949
- //Load the link
2950
- $link = new blcLink( intval($_POST['link_id']) );
2951
-
2952
- if ( !$link->valid() ){
2953
- printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2954
- die();
2955
- }
2956
- //Make it appear "not broken"
2957
- $link->broken = false;
2958
- $link->warning = false;
2959
- $link->false_positive = true;
2960
- $link->last_check_attempt = time();
2961
- $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2962
-
2963
- $link->isOptionLinkChanged = true;
2964
- //Save the changes
2965
- if ( $link->save() ){
2966
- die( "OK" );
2967
- } else {
2968
- die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
2969
- }
2970
- } else {
2971
- die( __("Error : link_id not specified", 'broken-link-checker') );
2972
- }
2973
- }
2974
-
2975
- public function ajax_dismiss(){
2976
- $this->ajax_set_link_dismissed(true);
2977
- }
2978
-
2979
- public function ajax_undismiss(){
2980
- $this->ajax_set_link_dismissed(false);
2981
- }
2982
-
2983
- private function ajax_set_link_dismissed($dismiss){
2984
- $action = $dismiss ? 'blc_dismiss' : 'blc_undismiss';
2985
-
2986
- if (!current_user_can('edit_others_posts') || !check_ajax_referer($action, false, false)){
2987
- die( __("You're not allowed to do that!", 'broken-link-checker') );
2988
- }
2989
-
2990
- if ( isset($_POST['link_id']) ){
2991
- //Load the link
2992
- $link = new blcLink( intval($_POST['link_id']) );
2993
-
2994
- if ( !$link->valid() ){
2995
- printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2996
- die();
2997
- }
2998
-
2999
- $link->dismissed = $dismiss;
3000
-
3001
- //Save the changes
3002
- $link->isOptionLinkChanged = true;
3003
- if ( $link->save() ){
3004
- die( "OK" );
3005
- } else {
3006
- die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
3007
- }
3008
- } else {
3009
- die( __("Error : link_id not specified", 'broken-link-checker') );
3010
- }
3011
- }
3012
-
3013
- /**
3014
- * AJAX hook for the inline link editor on Tools -> Broken Links.
3015
- *
3016
- * @return void
3017
- */
3018
- function ajax_edit(){
3019
- if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_edit', false, false)){
3020
- die( json_encode( array(
3021
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3022
- )));
3023
- }
3024
-
3025
- if ( empty($_POST['link_id']) || empty($_POST['new_url']) || !is_numeric($_POST['link_id']) ) {
3026
- die( json_encode( array(
3027
- 'error' => __("Error : link_id or new_url not specified", 'broken-link-checker')
3028
- )));
3029
- }
3030
-
3031
- //Load the link
3032
- $link = new blcLink( intval($_POST['link_id']) );
3033
-
3034
- if ( !$link->valid() ){
3035
- die( json_encode( array(
3036
- 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
3037
- )));
3038
- }
3039
-
3040
- //Validate the new URL.
3041
- $new_url = stripslashes($_POST['new_url']);
3042
- $parsed = @parse_url($new_url);
3043
- if ( !$parsed ){
3044
- die( json_encode( array(
3045
- 'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
3046
- )));
3047
- }
3048
-
3049
- if ( !current_user_can('unfiltered_html') ) {
3050
- //Disallow potentially dangerous URLs like "javascript:...".
3051
- $protocols = wp_allowed_protocols();
3052
- $good_protocol_url = wp_kses_bad_protocol($new_url, $protocols);
3053
- if ( $new_url != $good_protocol_url ) {
3054
- die( json_encode( array(
3055
- 'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
3056
- )));
3057
- }
3058
- }
3059
-
3060
- $new_text = (isset($_POST['new_text']) && is_string($_POST['new_text'])) ? stripslashes($_POST['new_text']) : null;
3061
- if ( $new_text === '' ) {
3062
- $new_text = null;
3063
- }
3064
- if ( !empty($new_text) && !current_user_can('unfiltered_html') ) {
3065
- $new_text = stripslashes(wp_filter_post_kses(addslashes($new_text))); //wp_filter_post_kses expects slashed data.
3066
- }
3067
-
3068
- $rez = $link->edit($new_url, $new_text);
3069
- if ( $rez === false ){
3070
- die( json_encode( array(
3071
- 'error' => __("An unexpected error occurred!", 'broken-link-checker')
3072
- )));
3073
- } else {
3074
- $new_link = $rez['new_link']; /** @var blcLink $new_link */
3075
- $new_status = $new_link->analyse_status();
3076
- $ui_link_text = null;
3077
- if ( isset($new_text) ) {
3078
- $instances = $new_link->get_instances();
3079
- if ( !empty($instances) ) {
3080
- $first_instance = reset($instances);
3081
- $ui_link_text = $first_instance->ui_get_link_text();
3082
- }
3083
- }
3084
-
3085
- $response = array(
3086
- 'new_link_id' => $rez['new_link_id'],
3087
- 'cnt_okay' => $rez['cnt_okay'],
3088
- 'cnt_error' => $rez['cnt_error'],
3089
-
3090
- 'status_text' => $new_status['text'],
3091
- 'status_code' => $new_status['code'],
3092
- 'http_code' => empty($new_link->http_code) ? '' : $new_link->http_code,
3093
- 'redirect_count' => $new_link->redirect_count,
3094
-
3095
- 'url' => $new_link->url,
3096
- 'escaped_url' => esc_url_raw($new_link->url),
3097
- 'final_url' => $new_link->final_url,
3098
- 'link_text' => isset($new_text) ? $new_text : null,
3099
- 'ui_link_text' => isset($new_text) ? $ui_link_text : null,
3100
-
3101
- 'errors' => array(),
3102
- );
3103
- //url, status text, status code, link text, editable link text
3104
-
3105
-
3106
- foreach($rez['errors'] as $error){ /** @var $error WP_Error */
3107
- array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
3108
- }
3109
- die( json_encode($response) );
3110
- }
3111
- }
3112
-
3113
- /**
3114
- * AJAX hook for the "Unlink" action links in Tools -> Broken Links.
3115
- * Removes the specified link from all posts and other supported items.
3116
- *
3117
- * @return void
3118
- */
3119
- function ajax_unlink(){
3120
- if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_unlink', false, false)){
3121
- die( json_encode( array(
3122
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3123
- )));
3124
- }
3125
-
3126
- if ( isset($_POST['link_id']) ){
3127
- //Load the link
3128
- $link = new blcLink( intval($_POST['link_id']) );
3129
-
3130
- if ( !$link->valid() ){
3131
- die( json_encode( array(
3132
- 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
3133
- )));
3134
- }
3135
-
3136
- //Try and unlink it
3137
- $rez = $link->unlink();
3138
-
3139
- if ( $rez === false ){
3140
- die( json_encode( array(
3141
- 'error' => __("An unexpected error occured!", 'broken-link-checker')
3142
- )));
3143
- } else {
3144
- $response = array(
3145
- 'cnt_okay' => $rez['cnt_okay'],
3146
- 'cnt_error' => $rez['cnt_error'],
3147
- 'errors' => array(),
3148
- );
3149
- foreach($rez['errors'] as $error){ /** @var WP_Error $error */
3150
- array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
3151
- }
3152
-
3153
- die( json_encode($response) );
3154
- }
3155
-
3156
- } else {
3157
- die( json_encode( array(
3158
- 'error' => __("Error : link_id not specified", 'broken-link-checker')
3159
- )));
3160
- }
3161
- }
3162
-
3163
- public function ajax_deredirect() {
3164
- if ( !current_user_can('edit_others_posts') || !check_ajax_referer('blc_deredirect', false, false) ){
3165
- die( json_encode( array(
3166
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3167
- )));
3168
- }
3169
-
3170
- if ( !isset($_POST['link_id']) || !is_numeric($_POST['link_id']) ) {
3171
- die( json_encode( array(
3172
- 'error' => __("Error : link_id not specified", 'broken-link-checker')
3173
- )));
3174
- }
3175
-
3176
- $id = intval($_POST['link_id']);
3177
- $link = new blcLink($id);
3178
-
3179
- if ( !$link->valid() ){
3180
- die( json_encode( array(
3181
- 'error' => sprintf(__("Oops, I can't find the link %d", 'broken-link-checker'), $id)
3182
- )));
3183
- }
3184
-
3185
- //The actual task is simple; it's error handling that's complicated.
3186
- $result = $link->deredirect();
3187
- if ( is_wp_error($result) ) {
3188
- die( json_encode( array(
3189
- 'error' => sprintf('%s [%s]', $result->get_error_message(), $result->get_error_code())
3190
- )));
3191
- }
3192
-
3193
- $link = $result['new_link'] /** @var blcLink $link */;
3194
-
3195
- $status = $link->analyse_status();
3196
- $response = array(
3197
- 'url' => $link->url,
3198
- 'escaped_url' => esc_url_raw($link->url),
3199
- 'new_link_id' => $result['new_link_id'],
3200
-
3201
- 'status_text' => $status['text'],
3202
- 'status_code' => $status['code'],
3203
- 'http_code' => empty($link->http_code) ? '' : $link->http_code,
3204
- 'redirect_count' => $link->redirect_count,
3205
- 'final_url' => $link->final_url,
3206
-
3207
- 'cnt_okay' => $result['cnt_okay'],
3208
- 'cnt_error' => $result['cnt_error'],
3209
- 'errors' => array(),
3210
- );
3211
-
3212
- //Convert WP_Error's to simple strings.
3213
- if ( !empty($result['errors']) ) {
3214
- foreach($result['errors'] as $error) { /** @var WP_Error $error */
3215
- $response['errors'][] = $error->get_error_message();
3216
- }
3217
- }
3218
-
3219
- die(json_encode($response));
3220
- }
3221
-
3222
- /**
3223
- * AJAX hook for the "Recheck" action.
3224
- */
3225
- public function ajax_recheck() {
3226
- if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_recheck', false, false)){
3227
- die( json_encode( array(
3228
- 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3229
- )));
3230
- }
3231
-
3232
- if ( !isset($_POST['link_id']) || !is_numeric($_POST['link_id']) ) {
3233
- die( json_encode( array(
3234
- 'error' => __("Error : link_id not specified", 'broken-link-checker')
3235
- )));
3236
- }
3237
-
3238
- $id = intval($_POST['link_id']);
3239
- $link = new blcLink($id);
3240
-
3241
- if ( !$link->valid() ){
3242
- die( json_encode( array(
3243
- 'error' => sprintf(__("Oops, I can't find the link %d", 'broken-link-checker'), $id)
3244
- )));
3245
- }
3246
-
3247
- //In case the immediate check fails, this will ensure the link is checked during the next work() run.
3248
- $link->last_check_attempt = 0;
3249
- $link->isOptionLinkChanged = true;
3250
- $link->save();
3251
-
3252
- //Check the link and save the results.
3253
- $link->check(true);
3254
-
3255
- $status = $link->analyse_status();
3256
- $response = array(
3257
- 'status_text' => $status['text'],
3258
- 'status_code' => $status['code'],
3259
- 'http_code' => empty($link->http_code) ? '' : $link->http_code,
3260
- 'redirect_count' => $link->redirect_count,
3261
- 'final_url' => $link->final_url,
3262
- );
3263
-
3264
- die(json_encode($response));
3265
- }
3266
-
3267
- function ajax_link_details(){
3268
- global $wpdb; /* @var wpdb $wpdb */
3269
-
3270
- if (!current_user_can('edit_others_posts')){
3271
- die( __("You don't have sufficient privileges to access this information!", 'broken-link-checker') );
3272
- }
3273
-
3274
- //FB::log("Loading link details via AJAX");
3275
-
3276
- if ( isset($_GET['link_id']) ){
3277
- //FB::info("Link ID found in GET");
3278
- $link_id = intval($_GET['link_id']);
3279
- } else if ( isset($_POST['link_id']) ){
3280
- //FB::info("Link ID found in POST");
3281
- $link_id = intval($_POST['link_id']);
3282
- } else {
3283
- //FB::error('Link ID not specified, you hacking bastard.');
3284
- die( __('Error : link ID not specified', 'broken-link-checker') );
3285
- }
3286
-
3287
- //Load the link.
3288
- $link = new blcLink($link_id);
3289
-
3290
- if ( !$link->is_new ){
3291
- //FB::info($link, 'Link loaded');
3292
- if ( !class_exists('blcTablePrinter') ){
3293
- require dirname($this->loader) . '/includes/admin/table-printer.php';
3294
- }
3295
- blcTablePrinter::details_row_contents($link);
3296
- die();
3297
- } else {
3298
- printf( __('Failed to load link details (%s)', 'broken-link-checker'), $wpdb->last_error );
3299
- die();
3300
- }
3301
- }
3302
-
3303
- /**
3304
- * Acquire an exclusive lock.
3305
- * If we already hold a lock, it will be released and a new one will be acquired.
3306
- *
3307
- * @return bool
3308
- */
3309
- function acquire_lock(){
3310
- return WPMutex::acquire('blc_lock');
3311
- }
3312
-
3313
- /**
3314
- * Relese our exclusive lock.
3315
- * Does nothing if the lock has already been released.
3316
- *
3317
- * @return bool
3318
- */
3319
- function release_lock(){
3320
- return WPMutex::release('blc_lock');
3321
- }
3322
-
3323
- /**
3324
- * Check if server is currently too overloaded to run the link checker.
3325
- *
3326
- * @return bool
3327
- */
3328
- function server_too_busy(){
3329
- if ( !$this->conf->options['enable_load_limit'] || !isset($this->conf->options['server_load_limit']) ){
3330
- return false;
3331
- }
3332
-
3333
- $loads = blcUtility::get_server_load();
3334
- if ( empty($loads) ){
3335
- return false;
3336
- }
3337
- $one_minute = floatval(reset($loads));
3338
-
3339
- return $one_minute > $this->conf->options['server_load_limit'];
3340
- }
3341
-
3342
- /**
3343
- * Register BLC's Dashboard widget
3344
- *
3345
- * @return void
3346
- */
3347
- function hook_wp_dashboard_setup(){
3348
- $show_widget = current_user_can($this->conf->get('dashboard_widget_capability', 'edit_others_posts'));
3349
- if ( function_exists( 'wp_add_dashboard_widget' ) && $show_widget ) {
3350
- wp_add_dashboard_widget(
3351
- 'blc_dashboard_widget',
3352
- __('Broken Link Checker', 'broken-link-checker'),
3353
- array( $this, 'dashboard_widget' ),
3354
- array( $this, 'dashboard_widget_control' )
3355
- );
3356
- }
3357
- }
3358
-
3359
- /**
3360
- * Collect various debugging information and return it in an associative array
3361
- *
3362
- * @return array
3363
- */
3364
- function get_debug_info(){
3365
- /** @var wpdb $wpdb */
3366
- global $wpdb;
3367
-
3368
- //Collect some information that's useful for debugging
3369
- $debug = array();
3370
-
3371
- //PHP version. Any one is fine as long as WP supports it.
3372
- $debug[ __('PHP version', 'broken-link-checker') ] = array(
3373
- 'state' => 'ok',
3374
- 'value' => phpversion(),
3375
- );
3376
-
3377
- //MySQL version
3378
- $debug[ __('MySQL version', 'broken-link-checker') ] = array(
3379
- 'state' => 'ok',
3380
- 'value' => $wpdb->db_version(),
3381
- );
3382
-
3383
- //CURL presence and version
3384
- if ( function_exists('curl_version') ){
3385
- $version = curl_version();
3386
-
3387
- if ( version_compare( $version['version'], '7.16.0', '<=' ) ){
3388
- $data = array(
3389
- 'state' => 'warning',
3390
- 'value' => $version['version'],
3391
- 'message' => __('You have an old version of CURL. Redirect detection may not work properly.', 'broken-link-checker'),
3392
- );
3393
- } else {
3394
- $data = array(
3395
- 'state' => 'ok',
3396
- 'value' => $version['version'],
3397
- );
3398
- }
3399
-
3400
- } else {
3401
- $data = array(
3402
- 'state' => 'warning',
3403
- 'value' => __('Not installed', 'broken-link-checker'),
3404
- );
3405
- }
3406
- $debug[ __('CURL version', 'broken-link-checker') ] = $data;
3407
-
3408
- //Snoopy presence
3409
- if ( class_exists('Snoopy') || file_exists(ABSPATH. WPINC . '/class-snoopy.php') ){
3410
- $data = array(
3411
- 'state' => 'ok',
3412
- 'value' => __('Installed', 'broken-link-checker'),
3413
- );
3414
- } else {
3415
- //No Snoopy? This should never happen, but if it does we *must* have CURL.
3416
- if ( function_exists('curl_init') ){
3417
- $data = array(
3418
- 'state' => 'ok',
3419
- 'value' => __('Not installed', 'broken-link-checker'),
3420
- );
3421
- } else {
3422
- $data = array(
3423
- 'state' => 'error',
3424
- 'value' => __('Not installed', 'broken-link-checker'),
3425
- 'message' => __('You must have either CURL or Snoopy installed for the plugin to work!', 'broken-link-checker'),
3426
- );
3427
- }
3428
-
3429
- }
3430
- $debug['Snoopy'] = $data;
3431
-
3432
- //Safe_mode status
3433
- if ( blcUtility::is_safe_mode() ){
3434
- $debug['Safe mode'] = array(
3435
- 'state' => 'warning',
3436
- 'value' => __('On', 'broken-link-checker'),
3437
- 'message' => __('Redirects may be detected as broken links when safe_mode is on.', 'broken-link-checker'),
3438
- );
3439
- } else {
3440
- $debug['Safe mode'] = array(
3441
- 'state' => 'ok',
3442
- 'value' => __('Off', 'broken-link-checker'),
3443
- );
3444
- }
3445
-
3446
- //Open_basedir status
3447
- if ( blcUtility::is_open_basedir() ){
3448
- $debug['open_basedir'] = array(
3449
- 'state' => 'warning',
3450
- 'value' => sprintf( __('On ( %s )', 'broken-link-checker'), ini_get('open_basedir') ),
3451
- 'message' => __('Redirects may be detected as broken links when open_basedir is on.', 'broken-link-checker'),
3452
- );
3453
- } else {
3454
- $debug['open_basedir'] = array(
3455
- 'state' => 'ok',
3456
- 'value' => __('Off', 'broken-link-checker'),
3457
- );
3458
- }
3459
-
3460
- //Default PHP execution time limit
3461
- $debug['Default PHP execution time limit'] = array(
3462
- 'state' => 'ok',
3463
- 'value' => sprintf(__('%s seconds'), ini_get('max_execution_time')),
3464
- );
3465
-
3466
- //Database character set. Usually it's UTF-8. Setting it to something else can cause problems
3467
- //unless the site owner really knows what they're doing.
3468
- $charset = $wpdb->get_charset_collate();
3469
- $debug[ __('Database character set', 'broken-link-checker') ] = array(
3470
- 'state' => 'ok',
3471
- 'value' => !empty($charset) ? $charset : '-',
3472
- );
3473
-
3474
- //Resynch flag.
3475
- $debug['Resynch. flag'] = array(
3476
- 'state' => 'ok',
3477
- 'value' => sprintf('%d', $this->conf->options['need_resynch'] ? '1 (resynch. required)' : '0 (resynch. not required)'),
3478
- );
3479
-
3480
- //Synch records
3481
- $synch_records = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch"));
3482
- $data = array(
3483
- 'state' => 'ok',
3484
- 'value' => sprintf('%d', $synch_records),
3485
- );
3486
- if ( $synch_records == 0 ){
3487
- $data['state'] = 'warning';
3488
- $data['message'] = __('If this value is zero even after several page reloads you have probably encountered a bug.', 'broken-link-checker');
3489
- }
3490
- $debug['Synch. records'] = $data;
3491
-
3492
- //Total links and instances (including invalid ones)
3493
- $all_links = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_links"));
3494
- $all_instances = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_instances"));
3495
-
3496
- //Show the number of unparsed containers. Useful for debugging. For performance,
3497
- //this is only shown when we have no links/instances yet.
3498
- if( ($all_links == 0) && ($all_instances == 0) ){
3499
- $unparsed_items = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch WHERE synched=0"));
3500
- $debug['Unparsed items'] = array(
3501
- 'state' => 'warning',
3502
- 'value' => $unparsed_items,
3503
- );
3504
- }
3505
-
3506
- //Links & instances
3507
- if ( ($all_links > 0) && ($all_instances > 0) ){
3508
- $debug['Link records'] = array(
3509
- 'state' => 'ok',
3510
- 'value' => sprintf('%d (%d)', $all_links, $all_instances),
3511
- );
3512
- } else {
3513
- $debug['Link records'] = array(
3514
- 'state' => 'warning',
3515
- 'value' => sprintf('%d (%d)', $all_links, $all_instances),
3516
- );
3517
- }
3518
-
3519
- //Email notifications.
3520
- if ( $this->conf->options['last_notification_sent'] ) {
3521
- $notificationDebug = array(
3522
- 'value' => date('Y-m-d H:i:s T', $this->conf->options['last_notification_sent']),
3523
- 'state' => 'ok',
3524
- );
3525
- } else {
3526
- $notificationDebug = array(
3527
- 'value' => 'Never',
3528
- 'state' => $this->conf->options['send_email_notifications'] ? 'ok' : 'warning',
3529
- );
3530
- }
3531
- $debug['Last email notification'] = $notificationDebug;
3532
-
3533
- if ( isset($this->conf->options['last_email']) ) {
3534
- $email = $this->conf->options['last_email'];
3535
- $debug['Last email sent'] = array(
3536
- 'state' => 'ok',
3537
- 'value' => sprintf(
3538
- '"%s" on %s (%s)',
3539
- htmlentities($email['subject']),
3540
- date('Y-m-d H:i:s T', $email['timestamp']),
3541
- $email['success'] ? 'success' : 'failure'
3542
- )
3543
- );
3544
- }
3545
-
3546
-
3547
- //Installation log
3548
- $logger = new blcCachedOptionLogger('blc_installation_log');
3549
- $installation_log = $logger->get_messages();
3550
- if ( !empty($installation_log) ){
3551
- $debug['Installation log'] = array(
3552
- 'state' => $this->conf->options['installation_complete'] ? 'ok' : 'error',
3553
- 'value' => implode("<br>\n", $installation_log),
3554
- );
3555
- } else {
3556
- $debug['Installation log'] = array(
3557
- 'state' => 'warning',
3558
- 'value' => 'No installation log found found.',
3559
- );
3560
- }
3561
-
3562
- return $debug;
3563
- }
3564
-
3565
- function maybe_send_email_notifications() {
3566
- global $wpdb; /** @var wpdb $wpdb */
3567
-
3568
- if ( !($this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications']) ){
3569
- return;
3570
- }
3571
-
3572
- //Find links that have been detected as broken since the last sent notification.
3573
- $last_notification = date('Y-m-d H:i:s', $this->conf->options['last_notification_sent']);
3574
- $where = $wpdb->prepare('( first_failure >= %s )', $last_notification);
3575
-
3576
- $links = blc_get_links(array(
3577
- 's_filter' => 'broken',
3578
- 'where_expr' => $where,
3579
- 'load_instances' => true,
3580
- 'load_containers' => true,
3581
- 'load_wrapped_objects' => $this->conf->options['send_authors_email_notifications'],
3582
- 'max_results' => 0,
3583
- ));
3584
-
3585
- if ( empty($links) ){
3586
- return;
3587
- }
3588
-
3589
- //Send the admin/maintainer an email notification.
3590
- $email = $this->conf->get('notification_email_address');
3591
- if ( empty($email) ) {
3592
- //Default to the admin email.
3593
- $email = get_option('admin_email');
3594
- }
3595
- if ( $this->conf->options['send_email_notifications'] && !empty($email) ) {
3596
- $this->send_admin_notification($links, $email);
3597
- }
3598
-
3599
- //Send notifications to post authors
3600
- if ( $this->conf->options['send_authors_email_notifications'] ) {
3601
- $this->send_authors_notifications($links);
3602
- }
3603
-
3604
- $this->conf->options['last_notification_sent'] = time();
3605
- $this->conf->save_options();
3606
- }
3607
-
3608
- function send_admin_notification($links, $email) {
3609
- //Prepare email message
3610
- $subject = sprintf(
3611
- __("[%s] Broken links detected", 'broken-link-checker'),
3612
- html_entity_decode(get_option('blogname'), ENT_QUOTES)
3613
- );
3614
-
3615
- $body = sprintf(
3616
- _n(
3617
- "Broken Link Checker has detected %d new broken link on your site.",
3618
- "Broken Link Checker has detected %d new broken links on your site.",
3619
- count($links),
3620
- 'broken-link-checker'
3621
- ),
3622
- count($links)
3623
- );
3624
- $body .= "<br>";
3625
-
3626
- $instances = array();
3627
- foreach($links as $link) { /* @var blcLink $link */
3628
- $instances = array_merge($instances, $link->get_instances());
3629
- }
3630
- $body .= $this->build_instance_list_for_email($instances);
3631
-
3632
- if ( $this->is_textdomain_loaded && is_rtl() ) {
3633
- $body = '<div dir="rtl">' . $body . '</div>';
3634
- }
3635
-
3636
- $this->send_html_email($email, $subject, $body);
3637
- }
3638
-
3639
- function build_instance_list_for_email($instances, $max_displayed_links = 5, $add_admin_link = true){
3640
- if ( $max_displayed_links === null ) {
3641
- $max_displayed_links = 5;
3642
- }
3643
-
3644
- $result = '';
3645
- if ( count($instances) > $max_displayed_links ){
3646
- $line = sprintf(
3647
- _n(
3648
- "Here's a list of the first %d broken links:",
3649
- "Here's a list of the first %d broken links:",
3650
- $max_displayed_links,
3651
- 'broken-link-checker'
3652
- ),
3653
- $max_displayed_links
3654
- );
3655
- } else {
3656
- $line = __("Here's a list of the new broken links: ", 'broken-link-checker');
3657
- }
3658
-
3659
- $result .= "<p>$line</p>";
3660
-
3661
- //Show up to $max_displayed_links broken link instances right in the email.
3662
- $displayed = 0;
3663
- foreach($instances as $instance){ /* @var blcLinkInstance $instance */
3664
- $pieces = array(
3665
- sprintf( __('Link text : %s', 'broken-link-checker'), $instance->ui_get_link_text('email') ),
3666
- sprintf( __('Link URL : <a href="%s">%s</a>', 'broken-link-checker'), htmlentities($instance->get_url()), blcUtility::truncate($instance->get_url(), 70, '') ),
3667
- sprintf( __('Source : %s', 'broken-link-checker'), $instance->ui_get_source('email') ),
3668
- );
3669
-
3670
- $link_entry = implode("<br>", $pieces);
3671
- $result .= "$link_entry<br><br>";
3672
-
3673
- $displayed++;
3674
- if ( $displayed >= $max_displayed_links ){
3675
- break;
3676
- }
3677
- }
3678
-
3679
- //Add a link to the "Broken Links" tab.
3680
- if ( $add_admin_link ) {
3681
- $result .= __("You can see all broken links here:", 'broken-link-checker') . "<br>";
3682
- $result .= sprintf('<a href="%1$s">%1$s</a>', admin_url('tools.php?page=view-broken-links'));
3683
- }
3684
-
3685
- return $result;
3686
- }
3687
-
3688
- function send_html_email($email_address, $subject, $body) {
3689
- //Need to override the default 'text/plain' content type to send a HTML email.
3690
- add_filter('wp_mail_content_type', array($this, 'override_mail_content_type'));
3691
-
3692
- //Let auto-responders and similar software know this is an auto-generated email
3693
- //that they shouldn't respond to.
3694
- $headers = array('Auto-Submitted: auto-generated');
3695
-
3696
- $success = wp_mail($email_address, $subject, $body, $headers);
3697
-
3698
- //Remove the override so that it doesn't interfere with other plugins that might
3699
- //want to send normal plaintext emails.
3700
- remove_filter('wp_mail_content_type', array($this, 'override_mail_content_type'));
3701
-
3702
- $this->conf->options['last_email'] = array(
3703
- 'subject' => $subject,
3704
- 'timestamp' => time(),
3705
- 'success' => $success,
3706
- );
3707
- $this->conf->save_options();
3708
-
3709
- return $success;
3710
- }
3711
-
3712
- function send_authors_notifications($links) {
3713
- $authorInstances = array();
3714
- foreach($links as $link){ /* @var blcLink $link */
3715
- foreach($link->get_instances() as $instance){ /* @var blcLinkInstance $instance */
3716
- $container = $instance->get_container(); /** @var blcContainer $container */
3717
- if ( empty($container) || !($container instanceof blcAnyPostContainer) ) {
3718
- continue;
3719
- }
3720
- $post = $container->get_wrapped_object(); /** @var StdClass $post */
3721
- if ( !array_key_exists($post->post_author, $authorInstances) ) {
3722
- $authorInstances[$post->post_author] = array();
3723
- }
3724
- $authorInstances[$post->post_author][] = $instance;
3725
- }
3726
- }
3727
-
3728
- foreach($authorInstances as $author_id => $instances) {
3729
- $subject = sprintf(
3730
- __("[%s] Broken links detected", 'broken-link-checker'),
3731
- html_entity_decode(get_option('blogname'), ENT_QUOTES)
3732
- );
3733
-
3734
- $body = sprintf(
3735
- _n(
3736
- "Broken Link Checker has detected %d new broken link in your posts.",
3737
- "Broken Link Checker has detected %d new broken links in your posts.",
3738
- count($instances),
3739
- 'broken-link-checker'
3740
- ),
3741
- count($instances)
3742
- );
3743
- $body .= "<br>";
3744
-
3745
- $author = get_user_by('id', $author_id); /** @var WP_User $author */
3746
- $body .= $this->build_instance_list_for_email($instances, null, $author->has_cap('edit_others_posts'));
3747
-
3748
- if ( $this->is_textdomain_loaded && is_rtl() ) {
3749
- $body = '<div dir="rtl">' . $body . '</div>';
3750
- }
3751
-
3752
- $this->send_html_email($author->user_email, $subject, $body);
3753
- }
3754
- }
3755
-
3756
- function override_mail_content_type(/** @noinspection PhpUnusedParameterInspection */ $content_type){
3757
- return 'text/html';
3758
- }
3759
-
3760
- /**
3761
- * Promote all links with the "warning" status to "broken".
3762
- */
3763
- private function promote_warnings_to_broken() {
3764
- global $wpdb; /** @var wpdb $wpdb */
3765
- $wpdb->update(
3766
- $wpdb->prefix . 'blc_links',
3767
- array(
3768
- 'broken' => 1,
3769
- 'warning' => 0,
3770
- ),
3771
- array(
3772
- 'warning' => 1,
3773
- ),
3774
- '%d'
3775
- );
3776
- }
3777
-
3778
- /**
3779
- * Install or uninstall the plugin's Cron events based on current settings.
3780
- *
3781
- * @uses wsBrokenLinkChecker::$conf Uses $conf->options to determine if events need to be (un)installed.
3782
- *
3783
- * @return void
3784
- */
3785
- function setup_cron_events(){
3786
-
3787
- //Link monitor
3788
- if ( $this->conf->options['run_via_cron'] ){
3789
- if (!wp_next_scheduled('blc_cron_check_links')) {
3790
- wp_schedule_event( time(), '10min', 'blc_cron_check_links' );
3791
- }
3792
- } else {
3793
- wp_clear_scheduled_hook('blc_cron_check_links');
3794
- }
3795
-
3796
- //Email notifications about broken links
3797
- if ( $this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications'] ){
3798
- if ( !wp_next_scheduled('blc_cron_email_notifications') ){
3799
- wp_schedule_event(time(), $this->conf->options['notification_schedule'], 'blc_cron_email_notifications');
3800
- }
3801
- } else {
3802
- wp_clear_scheduled_hook('blc_cron_email_notifications');
3803
- }
3804
-
3805
- //Run database maintenance every two weeks or so
3806
- if ( !wp_next_scheduled('blc_cron_database_maintenance') ){
3807
- wp_schedule_event(time(), 'daily', 'blc_cron_database_maintenance');
3808
- }
3809
- }
3810
-
3811
- /**
3812
- * Load the plugin's textdomain.
3813
- *
3814
- * @return void
3815
- */
3816
- function load_language(){
3817
- $this->is_textdomain_loaded = load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
3818
- }
3819
-
3820
- protected static function get_default_log_directory() {
3821
- $uploads = wp_upload_dir();
3822
- return $uploads['basedir'] . '/broken-link-checker';
3823
- }
3824
-
3825
- protected static function get_default_log_basename() {
3826
- return 'blc-log.txt';
3827
- }
3828
-
3829
- }//class ends here
3830
-
3831
- } // if class_exists...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Simple function to replicate PHP 5 behaviour
4
+ */
5
+ if ( ! function_exists( 'microtime_float' ) ) {
6
+ function microtime_float() {
7
+ list($usec, $sec) = explode( ' ', microtime() );
8
+ return ( (float)$usec + (float)$sec);
9
+ }
10
+ }
11
+
12
+ require BLC_DIRECTORY . '/includes/screen-options/screen-options.php';
13
+ require BLC_DIRECTORY . '/includes/screen-meta-links.php';
14
+ require BLC_DIRECTORY . '/includes/wp-mutex.php';
15
+ require BLC_DIRECTORY . '/includes/transactions-manager.php';
16
+
17
+ if (!class_exists('wsBrokenLinkChecker')) {
18
+
19
+ class wsBrokenLinkChecker {
20
+ var $conf;
21
+ var $loader;
22
+ var $my_basename = '';
23
+
24
+ var $db_version; //The required version of the plugin's DB schema.
25
+
26
+ var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
27
+
28
+ private $is_textdomain_loaded = false;
29
+
30
+ /**
31
+ * wsBrokenLinkChecker::wsBrokenLinkChecker()
32
+ * Class constructor
33
+ *
34
+ * @param string $loader The fully qualified filename of the loader script that WP identifies as the "main" plugin file.
35
+ * @param blcConfigurationManager $conf An instance of the configuration manager
36
+ * @return void
37
+ */
38
+ function __construct ( $loader, $conf ) {
39
+ $this->db_version = BLC_DATABASE_VERSION;
40
+
41
+ $this->conf = $conf;
42
+ $this->loader = $loader;
43
+ $this->my_basename = plugin_basename( $this->loader );
44
+
45
+ $this->load_language();
46
+
47
+ //Unlike the activation hook, the deactivation callback *can* be registered in this file
48
+ //because deactivation happens after this class has already been instantiated (durinng the
49
+ //'init' action).
50
+ register_deactivation_hook($loader, array($this, 'deactivation'));
51
+
52
+ add_action('admin_menu', array($this,'admin_menu'));
53
+
54
+ //Load jQuery on Dashboard pages (probably redundant as WP already does that)
55
+ add_action('admin_print_scripts', array($this,'admin_print_scripts'));
56
+
57
+ //The dashboard widget
58
+ add_action('wp_dashboard_setup', array($this, 'hook_wp_dashboard_setup'));
59
+
60
+ //AJAXy hooks
61
+ add_action( 'wp_ajax_blc_full_status', array($this,'ajax_full_status') );
62
+ add_action( 'wp_ajax_blc_dashboard_status', array($this,'ajax_dashboard_status') );
63
+ add_action( 'wp_ajax_blc_work', array($this,'ajax_work') );
64
+ add_action( 'wp_ajax_blc_discard', array($this,'ajax_discard') );
65
+ add_action( 'wp_ajax_blc_edit', array($this,'ajax_edit') );
66
+ add_action( 'wp_ajax_blc_link_details', array($this,'ajax_link_details') );
67
+ add_action( 'wp_ajax_blc_unlink', array($this,'ajax_unlink') );
68
+ add_action( 'wp_ajax_blc_recheck', array($this,'ajax_recheck') );
69
+ add_action( 'wp_ajax_blc_deredirect', array($this,'ajax_deredirect') );
70
+ add_action( 'wp_ajax_blc_current_load', array($this,'ajax_current_load') );
71
+
72
+ add_action( 'wp_ajax_blc_dismiss', array($this, 'ajax_dismiss') );
73
+ add_action( 'wp_ajax_blc_undismiss', array($this, 'ajax_undismiss') );
74
+
75
+ //Add/remove Cron events
76
+ add_filter( 'cron_schedules', array( $this, 'cron_add_every_10_minutes' ) );
77
+ $this->setup_cron_events();
78
+
79
+ //Set hooks that listen for our Cron actions
80
+ add_action( 'blc_cron_email_notifications', array( $this, 'maybe_send_email_notifications' ) );
81
+ add_action( 'blc_cron_check_links', array( $this, 'cron_check_links' ) );
82
+ add_action( 'blc_cron_database_maintenance', array( $this, 'database_maintenance' ) );
83
+
84
+ //Set the footer hook that will call the worker function via AJAX.
85
+ add_action( 'admin_footer', array( $this,'admin_footer' ) );
86
+ //Add a "Screen Options" panel to the "Broken Links" page
87
+ add_screen_options_panel(
88
+ 'blc-screen-options',
89
+ '',
90
+ array($this, 'screen_options_html'),
91
+ 'tools_page_view-broken-links',
92
+ array($this, 'ajax_save_screen_options'),
93
+ true
94
+ );
95
+
96
+ //Display an explanatory note on the "Tools -> Broken Links -> Warnings" page.
97
+ add_action( 'admin_notices', array( $this, 'show_warnings_section_notice' ) );
98
+
99
+
100
+ }
101
+
102
+ /**
103
+ * @param $schedules
104
+ *
105
+ * @return mixed
106
+ */
107
+ function cron_add_every_10_minutes( $schedules ) {
108
+ // Adds once weekly to the existing schedules.
109
+ $schedules['10min'] = array(
110
+ 'interval' => 600,
111
+ 'display' => __('Every 10 minutes')
112
+ );
113
+
114
+ return $schedules;
115
+ }
116
+
117
+ /**
118
+ * Output the script that runs the link monitor while the Dashboard is open.
119
+ *
120
+ * @return void
121
+ */
122
+ function admin_footer(){
123
+ if ( !$this->conf->options['run_in_dashboard'] ){
124
+ return;
125
+ }
126
+ $nonce = wp_create_nonce('blc_work');
127
+ ?>
128
+ <!-- wsblc admin footer -->
129
+ <script type='text/javascript'>
130
+ (function($){
131
+
132
+ //(Re)starts the background worker thread
133
+ function blcDoWork(){
134
+ $.post(
135
+ "<?php echo admin_url('admin-ajax.php'); ?>",
136
+ {
137
+ 'action' : 'blc_work',
138
+ '_ajax_nonce' : '<?php echo esc_js($nonce); ?>'
139
+ }
140
+ );
141
+ }
142
+ //Call it the first time
143
+ blcDoWork();
144
+
145
+ //Then call it periodically every X seconds
146
+ setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
147
+
148
+ })(jQuery);
149
+ </script>
150
+ <!-- /wsblc admin footer -->
151
+ <?php
152
+ }
153
+
154
+ /**
155
+ * Check if an URL matches the exclusion list.
156
+ *
157
+ * @param string $url
158
+ * @return bool
159
+ */
160
+ function is_excluded($url){
161
+ if (!is_array($this->conf->options['exclusion_list'])) return false;
162
+ foreach($this->conf->options['exclusion_list'] as $excluded_word){
163
+ if (stristr($url, $excluded_word)){
164
+ return true;
165
+ }
166
+ }
167
+ return false;
168
+ }
169
+
170
+ function dashboard_widget(){
171
+ ?>
172
+ <p id='wsblc_activity_box'><?php _e( 'Loading...', 'broken-link-checker' ); ?></p>
173
+ <script type='text/javascript'>
174
+ jQuery( function($){
175
+ var blc_was_autoexpanded = false;
176
+
177
+ function blcDashboardStatus(){
178
+ $.getJSON(
179
+ "<?php echo admin_url('admin-ajax.php'); ?>",
180
+ {
181
+ 'action' : 'blc_dashboard_status',
182
+ 'random' : Math.random()
183
+ },
184
+ function (data){
185
+ if ( data && ( typeof(data.text) != 'undefined' ) ) {
186
+ $('#wsblc_activity_box').html(data.text);
187
+ <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
188
+ //Expand the widget if there are broken links.
189
+ //Do this only once per pageload so as not to annoy the user.
190
+ if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
191
+ $('#blc_dashboard_widget.postbox').removeClass('closed');
192
+ blc_was_autoexpanded = true;
193
+ }
194
+ <?php } ?>
195
+ } else {
196
+ $('#wsblc_activity_box').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
197
+ }
198
+
199
+ setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
200
+ }
201
+ );
202
+ }
203
+
204
+ blcDashboardStatus();//Call it the first time
205
+
206
+ } );
207
+ </script>
208
+ <?php
209
+ }
210
+
211
+ function dashboard_widget_control(
212
+ /** @noinspection PhpUnusedParameterInspection */ $widget_id, $form_inputs = array()
213
+ ){
214
+ if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
215
+ //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
216
+ $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
217
+ $this->conf->save_options();
218
+ }
219
+
220
+ ?>
221
+ <p><label for="blc-autoexpand">
222
+ <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
223
+ <?php _e('Automatically expand the widget if broken links have been detected', 'broken-link-checker'); ?>
224
+ </label></p>
225
+ <?php
226
+ }
227
+
228
+ function admin_print_scripts(){
229
+ //jQuery is used for triggering the link monitor via AJAX when any admin page is open.
230
+ wp_enqueue_script('jquery');
231
+ }
232
+
233
+ function enqueue_settings_scripts(){
234
+ //jQuery UI is used on the settings page
235
+ wp_enqueue_script('jquery-ui-core'); //Used for background color animation
236
+ wp_enqueue_script('jquery-ui-dialog');
237
+ wp_enqueue_script('jquery-ui-tabs');
238
+ wp_enqueue_script('jquery-cookie', plugins_url('js/jquery.cookie.js', BLC_PLUGIN_FILE)); //Used for storing last widget states, etc
239
+ }
240
+
241
+ function enqueue_link_page_scripts(){
242
+ wp_enqueue_script('jquery-ui-core');
243
+ wp_enqueue_script('jquery-ui-dialog'); //Used for the search form
244
+ wp_enqueue_script('jquery-color'); //Used for background color animation
245
+ wp_enqueue_script('sprintf', plugins_url('js/sprintf.js', BLC_PLUGIN_FILE)); //Used in error messages
246
+ }
247
+
248
+ /**
249
+ * Initiate a full recheck - reparse everything and check all links anew.
250
+ *
251
+ * @return void
252
+ */
253
+ function initiate_recheck(){
254
+ global $wpdb; /** @var wpdb $wpdb */
255
+
256
+ //Delete all discovered instances
257
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
258
+
259
+ //Delete all discovered links
260
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
261
+
262
+ //Mark all posts, custom fields and bookmarks for processing.
263
+ blc_resynch(true);
264
+ }
265
+
266
+ /**
267
+ * A hook executed when the plugin is deactivated.
268
+ *
269
+ * @return void
270
+ */
271
+ function deactivation(){
272
+ //Remove our Cron events
273
+ wp_clear_scheduled_hook('blc_cron_check_links');
274
+ wp_clear_scheduled_hook('blc_cron_email_notifications');
275
+ wp_clear_scheduled_hook('blc_cron_database_maintenance');
276
+ wp_clear_scheduled_hook('blc_cron_check_news'); //Unused event.
277
+ //Note the deactivation time for each module. This will help them
278
+ //synch up propely if/when the plugin is reactivated.
279
+ $moduleManager = blcModuleManager::getInstance();
280
+ $the_time = current_time('timestamp');
281
+ foreach($moduleManager->get_active_modules() as $module_id => $module){
282
+ $this->conf->options['module_deactivated_when'][$module_id] = $the_time;
283
+ }
284
+ $this->conf->save_options();
285
+ }
286
+
287
+ /**
288
+ * Perform various database maintenance tasks on the plugin's tables.
289
+ *
290
+ * Removes records that reference disabled containers and parsers,
291
+ * deletes invalid instances and links, optimizes tables, etc.
292
+ *
293
+ * @return void
294
+ */
295
+ function database_maintenance(){
296
+ blcContainerHelper::cleanup_containers();
297
+ blc_cleanup_instances();
298
+ blc_cleanup_links();
299
+
300
+ blcUtility::optimize_database();
301
+ }
302
+
303
+ /**
304
+ * Create the plugin's menu items and enqueue their scripts and CSS.
305
+ * Callback for the 'admin_menu' action.
306
+ *
307
+ * @return void
308
+ */
309
+ function admin_menu(){
310
+ if (current_user_can('manage_options'))
311
+ add_filter('plugin_action_links', array($this, 'plugin_action_links'), 10, 2);
312
+
313
+ $options_page_hook = add_options_page(
314
+ __('Link Checker Settings', 'broken-link-checker'),
315
+ __('Link Checker', 'broken-link-checker'),
316
+ 'manage_options',
317
+ 'link-checker-settings',array($this, 'options_page')
318
+ );
319
+
320
+ $menu_title = __('Broken Links', 'broken-link-checker');
321
+ if ( $this->conf->options['show_link_count_bubble'] ){
322
+ //To make it easier to notice when broken links appear, display the current number of
323
+ //broken links in a little bubble notification in the "Broken Links" menu.
324
+ //(Similar to how the number of plugin updates and unmoderated comments is displayed).
325
+ $blc_link_query = blcLinkQuery::getInstance();
326
+ $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
327
+ if ( $broken_links > 0 ){
328
+ //TODO: Appropriating existing CSS classes for my own purposes is hacky. Fix eventually.
329
+ $menu_title .= sprintf(
330
+ ' <span class="update-plugins"><span class="update-count blc-menu-bubble">%d</span></span>',
331
+ $broken_links
332
+ );
333
+ }
334
+ }
335
+ $links_page_hook = add_management_page(
336
+ __('View Broken Links', 'broken-link-checker'),
337
+ $menu_title,
338
+ 'edit_others_posts',
339
+ 'view-broken-links',array($this, 'links_page')
340
+ );
341
+
342
+ //Add plugin-specific scripts and CSS only to the it's own pages
343
+ add_action( 'admin_print_styles-' . $options_page_hook, array($this, 'options_page_css') );
344
+ add_action( 'admin_print_styles-' . $links_page_hook, array($this, 'links_page_css') );
345
+ add_action( 'admin_print_scripts-' . $options_page_hook, array($this, 'enqueue_settings_scripts') );
346
+ add_action( 'admin_print_scripts-' . $links_page_hook, array($this, 'enqueue_link_page_scripts') );
347
+
348
+ //Make the Settings page link to the link list
349
+ add_screen_meta_link(
350
+ 'blc-links-page-link',
351
+ __('Go to Broken Links', 'broken-link-checker'),
352
+ admin_url('tools.php?page=view-broken-links'),
353
+ $options_page_hook,
354
+ array('style' => 'font-weight: bold;')
355
+ );
356
+ }
357
+
358
+ /**
359
+ * plugin_action_links()
360
+ * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
361
+ * on the plugin list.
362
+ *
363
+ * @param array $links
364
+ * @param string $file
365
+ * @return array
366
+ */
367
+ function plugin_action_links($links, $file) {
368
+ if ($file == $this->my_basename)
369
+ $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
370
+ return $links;
371
+ }
372
+
373
+ function options_page(){
374
+ $moduleManager = blcModuleManager::getInstance();
375
+
376
+ //Prior to 1.5.2 (released 2012-05-27), there was a bug that would cause the donation flag to be
377
+ //set incorrectly. So we'll unset the flag in that case.
378
+ $reset_donation_flag =
379
+ ($this->conf->get('first_installation_timestamp', 0) < strtotime('2012-05-27 00:00')) &&
380
+ !$this->conf->get('donation_flag_fixed', false);
381
+
382
+ if ( $reset_donation_flag) {
383
+ $this->conf->set('user_has_donated', false);
384
+ $this->conf->set('donation_flag_fixed', true);
385
+ $this->conf->save_options();
386
+ }
387
+
388
+ if (isset($_POST['recheck']) && !empty($_POST['recheck']) ){
389
+ $this->initiate_recheck();
390
+
391
+ //Redirect back to the settings page
392
+ $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
393
+ wp_redirect( add_query_arg( array( 'recheck-initiated' => true), $base_url ) );
394
+ die();
395
+ }
396
+
397
+ $available_link_actions = array(
398
+ 'edit' => __('Edit URL' , 'broken-link-checker'),
399
+ 'delete' => __('Unlink', 'broken-link-checker'),
400
+ 'blc-discard-action' => __('Not broken', 'broken-link-checker'),
401
+ 'blc-dismiss-action' => __('Dismiss', 'broken-link-checker'),
402
+ 'blc-recheck-action' => __('Recheck', 'broken-link-checker'),
403
+ 'blc-deredirect-action' => _x('Fix redirect', 'link action; replace one redirect with a direct link', 'broken-link-checker')
404
+ );
405
+
406
+ if(isset($_POST['submit'])) {
407
+ check_admin_referer('link-checker-options');
408
+
409
+ $cleanPost = $_POST;
410
+ if ( function_exists('wp_magic_quotes') ){
411
+ $cleanPost = stripslashes_deep($cleanPost); //Ceterum censeo, WP shouldn't mangle superglobals.
412
+ }
413
+
414
+ //Activate/deactivate modules
415
+ if ( !empty($_POST['module']) ){
416
+ $active = array_keys($_POST['module']);
417
+ $moduleManager->set_active_modules($active);
418
+ }
419
+
420
+ //Only post statuses that actually exist can be selected
421
+ if ( isset($_POST['enabled_post_statuses']) && is_array($_POST['enabled_post_statuses']) ){
422
+ $available_statuses = get_post_stati();
423
+ $enabled_post_statuses = array_intersect($_POST['enabled_post_statuses'], $available_statuses);
424
+ } else {
425
+ $enabled_post_statuses = array();
426
+ }
427
+ //At least one status must be enabled; defaults to "Published".
428
+ if ( empty($enabled_post_statuses) ){
429
+ $enabled_post_statuses = array('publish');
430
+ }
431
+
432
+ //Did the user add/remove any post statuses?
433
+ $same_statuses = array_intersect($enabled_post_statuses, $this->conf->options['enabled_post_statuses']);
434
+ $post_statuses_changed = (count($same_statuses) != count($enabled_post_statuses))
435
+ || (count($same_statuses) !== count($this->conf->options['enabled_post_statuses']));
436
+
437
+ $this->conf->options['enabled_post_statuses'] = $enabled_post_statuses;
438
+
439
+ //The execution time limit must be above zero
440
+ $new_execution_time = intval($_POST['max_execution_time']);
441
+ if( $new_execution_time > 0 ){
442
+ $this->conf->options['max_execution_time'] = $new_execution_time;
443
+ }
444
+
445
+ //The check threshold also must be > 0
446
+ $new_check_threshold=intval($_POST['check_threshold']);
447
+ if( $new_check_threshold > 0 ){
448
+ $this->conf->options['check_threshold'] = $new_check_threshold;
449
+ }
450
+
451
+ $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
452
+ $new_broken_link_css = trim($cleanPost['broken_link_css']);
453
+ $this->conf->options['broken_link_css'] = $new_broken_link_css;
454
+
455
+ $this->conf->options['mark_removed_links'] = !empty($_POST['mark_removed_links']);
456
+ $new_removed_link_css = trim($cleanPost['removed_link_css']);
457
+ $this->conf->options['removed_link_css'] = $new_removed_link_css;
458
+
459
+ $this->conf->options['nofollow_broken_links'] = !empty($_POST['nofollow_broken_links']);
460
+
461
+ $this->conf->options['suggestions_enabled'] = !empty($_POST['suggestions_enabled']);
462
+
463
+ $this->conf->options['exclusion_list'] = array_filter(
464
+ preg_split(
465
+ '/[\s\r\n]+/', //split on newlines and whitespace
466
+ $cleanPost['exclusion_list'],
467
+ -1,
468
+ PREG_SPLIT_NO_EMPTY //skip empty values
469
+ )
470
+ );
471
+
472
+ //Parse the custom field list
473
+ $new_custom_fields = array_filter(
474
+ preg_split( '/[\r\n]+/', $cleanPost['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY )
475
+ );
476
+
477
+ //Calculate the difference between the old custom field list and the new one (used later)
478
+ $diff1 = array_diff( $new_custom_fields, $this->conf->options['custom_fields'] );
479
+ $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
480
+ $this->conf->options['custom_fields'] = $new_custom_fields;
481
+
482
+ //Parse the custom field list
483
+ $new_acf_fields = array_filter(preg_split('/[\r\n]+/', $cleanPost['blc_acf_fields'], -1, PREG_SPLIT_NO_EMPTY));
484
+
485
+ //Calculate the difference between the old custom field list and the new one (used later)
486
+ $acf_fields_diff1 = array_diff($new_acf_fields, $this->conf->options['acf_fields']);
487
+ $acf_fields_diff2 = array_diff($this->conf->options['acf_fields'], $new_acf_fields);
488
+ $this->conf->options['acf_fields'] = $new_acf_fields;
489
+
490
+ //Turning off warnings turns existing warnings into "broken" links.
491
+ $warnings_enabled = !empty($_POST['warnings_enabled']);
492
+ if ( $this->conf->get('warnings_enabled') && !$warnings_enabled ) {
493
+ $this->promote_warnings_to_broken();
494
+ }
495
+ $this->conf->options['warnings_enabled'] = $warnings_enabled;
496
+
497
+ //HTTP timeout
498
+ $new_timeout = intval($_POST['timeout']);
499
+ if( $new_timeout > 0 ){
500
+ $this->conf->options['timeout'] = $new_timeout ;
501
+ }
502
+
503
+ //Server load limit
504
+ if ( isset($_POST['server_load_limit']) ){
505
+ $this->conf->options['server_load_limit'] = floatval($_POST['server_load_limit']);
506
+ if ( $this->conf->options['server_load_limit'] < 0 ){
507
+ $this->conf->options['server_load_limit'] = 0;
508
+ }
509
+
510
+ $this->conf->options['enable_load_limit'] = $this->conf->options['server_load_limit'] > 0;
511
+ }
512
+
513
+ //Target resource usage (1% to 100%)
514
+ if ( isset($_POST['target_resource_usage']) ) {
515
+ $usage = floatval($_POST['target_resource_usage']);
516
+ $usage = max(min($usage / 100, 1), 0.01);
517
+ $this->conf->options['target_resource_usage'] = $usage;
518
+ }
519
+
520
+ //When to run the checker
521
+ $this->conf->options['run_in_dashboard'] = !empty($_POST['run_in_dashboard']);
522
+ $this->conf->options['run_via_cron'] = !empty($_POST['run_via_cron']);
523
+
524
+ //Email notifications on/off
525
+ $email_notifications = !empty($_POST['send_email_notifications']);
526
+ $send_authors_email_notifications = !empty($_POST['send_authors_email_notifications']);
527
+
528
+ if (
529
+ ($email_notifications && !$this->conf->options['send_email_notifications'])
530
+ || ($send_authors_email_notifications && !$this->conf->options['send_authors_email_notifications'])
531
+ ){
532
+ /*
533
+ The plugin should only send notifications about links that have become broken
534
+ since the time when email notifications were turned on. If we don't do this,
535
+ the first email notification will be sent nigh-immediately and list *all* broken
536
+ links that the plugin currently knows about.
537
+ */
538
+ $this->conf->options['last_notification_sent'] = time();
539
+ }
540
+ $this->conf->options['send_email_notifications'] = $email_notifications;
541
+ $this->conf->options['send_authors_email_notifications'] = $send_authors_email_notifications;
542
+
543
+ $this->conf->options['notification_email_address'] = strval($_POST['notification_email_address']);
544
+ if ( !filter_var($this->conf->options['notification_email_address'], FILTER_VALIDATE_EMAIL)) {
545
+ $this->conf->options['notification_email_address'] = '';
546
+ }
547
+
548
+ $widget_cap = strval($_POST['dashboard_widget_capability']);
549
+ if ( !empty($widget_cap) ) {
550
+ $this->conf->options['dashboard_widget_capability'] = $widget_cap;
551
+ }
552
+
553
+ //Link actions. The user can hide some of them to reduce UI clutter.
554
+ $show_link_actions = array();
555
+ foreach(array_keys($available_link_actions) as $action) {
556
+ $show_link_actions[$action] = isset($_POST['show_link_actions']) &&
557
+ !empty($_POST['show_link_actions'][$action]);
558
+ }
559
+ $this->conf->set('show_link_actions', $show_link_actions);
560
+
561
+ //Logging. The plugin can log various events and results for debugging purposes.
562
+ $this->conf->options['logging_enabled'] = !empty($_POST['logging_enabled']);
563
+ $this->conf->options['custom_log_file_enabled'] = !empty($_POST['custom_log_file_enabled']);
564
+
565
+ if ( $this->conf->options['logging_enabled'] ) {
566
+ if ( $this->conf->options['custom_log_file_enabled'] ) {
567
+ $log_file = strval($cleanPost['log_file']);
568
+ } else {
569
+ //Default log file is /wp-content/uploads/broken-link-checker/blc-log.txt
570
+ $log_directory = self::get_default_log_directory();
571
+ $log_file = $log_directory . '/' . self::get_default_log_basename();
572
+
573
+ //Attempt to create the log directory.
574
+ if ( !is_dir($log_directory) ) {
575
+ if ( mkdir($log_directory, 0750) ) {
576
+ //Add a .htaccess to hide the log file from site visitors.
577
+ file_put_contents($log_directory . '/.htaccess', 'Deny from all');
578
+ }
579
+ }
580
+ }
581
+
582
+ $this->conf->options['log_file'] = $log_file;
583
+
584
+ //Attempt to create the log file if not already there.
585
+ if ( !is_file($log_file) ) {
586
+ file_put_contents($log_file, '');
587
+ }
588
+
589
+ //The log file must be writable.
590
+ if ( !is_writable($log_file) || !is_file($log_file) ) {
591
+ $this->conf->options['logging_enabled'] = false;
592
+ }
593
+ }
594
+
595
+ //Make settings that affect our Cron events take effect immediately
596
+ $this->setup_cron_events();
597
+
598
+ $this->conf->save_options();
599
+
600
+ /*
601
+ If the list of custom fields was modified then we MUST resynchronize or
602
+ custom fields linked with existing posts may not be detected. This is somewhat
603
+ inefficient.
604
+ */
605
+ if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
606
+ $manager = blcContainerHelper::get_manager('custom_field');
607
+ if ( !is_null($manager) ){
608
+ $manager->resynch();
609
+ blc_got_unsynched_items();
610
+ }
611
+ }
612
+
613
+ /*
614
+ If the list of acf fields was modified then we MUST resynchronize or
615
+ acf fields linked with existing posts may not be detected. This is somewhat
616
+ inefficient.
617
+ */
618
+ if ( ( count($acf_fields_diff1) > 0 ) || ( count($acf_fields_diff2) > 0 ) ){
619
+ $manager = blcContainerHelper::get_manager('acf_field');
620
+ if ( !is_null($manager) ){
621
+ $manager->resynch();
622
+ blc_got_unsynched_items();
623
+ }
624
+ }
625
+
626
+ //Resynchronize posts when the user enables or disables post statuses.
627
+ if ( $post_statuses_changed ) {
628
+ $overlord = blcPostTypeOverlord::getInstance();
629
+ $overlord->enabled_post_statuses = $this->conf->get('enabled_post_statuses', array());
630
+ $overlord->resynch('wsh_status_resynch_trigger');
631
+
632
+ blc_got_unsynched_items();
633
+ blc_cleanup_instances();
634
+ blc_cleanup_links();
635
+ }
636
+
637
+ //Redirect back to the settings page
638
+ $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
639
+ wp_redirect( add_query_arg( array( 'settings-updated' => true), $base_url ) );
640
+ }
641
+
642
+ //Show a confirmation message when settings are saved.
643
+ if ( !empty($_GET['settings-updated']) ){
644
+ echo '<div id="message" class="updated fade"><p><strong>',__('Settings saved.', 'broken-link-checker'), '</strong></p></div>';
645
+
646
+ }
647
+
648
+ //Show a thank-you message when a donation is made.
649
+ if ( !empty($_GET['donated']) ){
650
+ echo '<div id="message" class="updated fade"><p><strong>',__('Thank you for your donation!', 'broken-link-checker'), '</strong></p></div>';
651
+ $this->conf->set('user_has_donated', true);
652
+ $this->conf->save_options();
653
+ }
654
+
655
+ //Show one when recheck is started, too.
656
+ if ( !empty($_GET['recheck-initiated']) ){
657
+ echo '<div id="message" class="updated fade"><p><strong>',
658
+ __('Complete site recheck started.', 'broken-link-checker'), // -- Yoda
659
+ '</strong></p></div>';
660
+ }
661
+
662
+ //Cull invalid and missing modules
663
+ $moduleManager->validate_active_modules();
664
+
665
+ $debug = $this->get_debug_info();
666
+
667
+ add_filter('blc-module-settings-custom_field', array($this, 'make_custom_field_input'), 10, 2);
668
+ add_filter('blc-module-settings-acf_field', array($this, 'make_acf_field_input'), 10, 2);
669
+ //Translate and markup-ify module headers for display
670
+ $modules = $moduleManager->get_modules_by_category('', true, true);
671
+
672
+ //Output the custom broken link/removed link styles for example links
673
+ printf(
674
+ '<style type="text/css">%s %s</style>',
675
+ $this->conf->options['broken_link_css'],
676
+ $this->conf->options['removed_link_css']
677
+ );
678
+
679
+ $section_names = array(
680
+ 'general' => __('General', 'broken-link-checker'),
681
+ 'where' => __('Look For Links In', 'broken-link-checker'),
682
+ 'which' => __('Which Links To Check', 'broken-link-checker'),
683
+ 'how' => __('Protocols & APIs', 'broken-link-checker'),
684
+ 'advanced' => __('Advanced', 'broken-link-checker'),
685
+ );
686
+ ?>
687
+
688
+ <!--[if lte IE 7]>
689
+ <style type="text/css">
690
+ /* Simulate inline-block in IE7 */
691
+ ul.ui-tabs-nav li {
692
+ display: inline;
693
+ zoom: 1;
694
+ }
695
+ </style>
696
+ <![endif]-->
697
+
698
+ <div class="wrap" id="blc-settings-wrap">
699
+ <h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
700
+
701
+
702
+ <div id="blc-sidebar">
703
+ <div class="metabox-holder">
704
+ <?php include BLC_DIRECTORY . '/includes/admin/sidebar.php'; ?>
705
+ </div>
706
+ </div>
707
+
708
+
709
+ <div id="blc-admin-content">
710
+
711
+ <form name="link_checker_options" id="link_checker_options" method="post" action="<?php
712
+ echo admin_url('options-general.php?page=link-checker-settings&noheader=1');
713
+ ?>">
714
+ <?php
715
+ wp_nonce_field('link-checker-options');
716
+ ?>
717
+
718
+ <div id="blc-tabs">
719
+
720
+ <ul class="hide-if-no-js">
721
+ <?php
722
+ foreach($section_names as $section_id => $section_name){
723
+ printf(
724
+ '<li id="tab-button-%s"><a href="#section-%s" title="%s">%s</a></li>',
725
+ esc_attr($section_id),
726
+ esc_attr($section_id),
727
+ esc_attr($section_name),
728
+ $section_name
729
+ );
730
+ }
731
+ ?>
732
+ </ul>
733
+
734
+ <div id="section-general" class="blc-section">
735
+ <h3 class="hide-if-js"><?php echo $section_names['general']; ?></h3>
736
+
737
+ <table class="form-table">
738
+
739
+ <tr valign="top">
740
+ <th scope="row">
741
+ <?php _e('Status','broken-link-checker'); ?>
742
+ <br>
743
+ <a href="javascript:void(0)" id="blc-debug-info-toggle"><?php _e('Show debug info', 'broken-link-checker'); ?></a>
744
+ </th>
745
+ <td>
746
+
747
+ <div id='wsblc_full_status'>
748
+ <br/><br/><br/>
749
+ </div>
750
+
751
+ <table id="blc-debug-info">
752
+ <?php
753
+
754
+ //Output the debug info in a table
755
+ foreach( $debug as $key => $value ){
756
+ printf (
757
+ '<tr valign="top" class="blc-debug-item-%s"><th scope="row">%s</th><td>%s<div class="blc-debug-message">%s</div></td></tr>',
758
+ $value['state'],
759
+ $key,
760
+ $value['value'],
761
+ ( array_key_exists('message', $value)?$value['message']:'')
762
+ );
763
+ }
764
+ ?>
765
+ </table>
766
+
767
+ </td>
768
+ </tr>
769
+
770
+ <tr valign="top">
771
+ <th scope="row"><?php _e('Check each link','broken-link-checker'); ?></th>
772
+ <td>
773
+
774
+ <?php
775
+ printf(
776
+ __('Every %s hours','broken-link-checker'),
777
+ sprintf(
778
+ '<input type="text" name="check_threshold" id="check_threshold" value="%d" size="5" maxlength="5" />',
779
+ $this->conf->options['check_threshold']
780
+ )
781
+ );
782
+ ?>
783
+ <br/>
784
+ <span class="description">
785
+ <?php _e('Existing links will be checked this often. New links will usually be checked ASAP.', 'broken-link-checker'); ?>
786
+ </span>
787
+
788
+ </td>
789
+ </tr>
790
+
791
+ <tr valign="top">
792
+ <th scope="row"><?php _e('E-mail notifications', 'broken-link-checker'); ?></th>
793
+ <td>
794
+ <p style="margin-top: 0;">
795
+ <label for='send_email_notifications'>
796
+ <input type="checkbox" name="send_email_notifications" id="send_email_notifications"
797
+ <?php if ($this->conf->options['send_email_notifications']) echo ' checked="checked"'; ?>/>
798
+ <?php _e('Send me e-mail notifications about newly detected broken links', 'broken-link-checker'); ?>
799
+ </label><br />
800
+ </p>
801
+
802
+ <p>
803
+ <label for='send_authors_email_notifications'>
804
+ <input type="checkbox" name="send_authors_email_notifications" id="send_authors_email_notifications"
805
+ <?php if ($this->conf->options['send_authors_email_notifications']) echo ' checked="checked"'; ?>/>
806
+ <?php _e('Send authors e-mail notifications about broken links in their posts', 'broken-link-checker'); ?>
807
+ </label><br />
808
+ </p>
809
+ </td>
810
+ </tr>
811
+
812
+ <tr valign="top">
813
+ <th scope="row"><?php echo __('Notification e-mail address', 'broken-link-checker'); ?></th>
814
+ <td>
815
+ <p>
816
+ <label>
817
+ <input
818
+ type="text"
819
+ name="notification_email_address"
820
+ id="notification_email_address"
821
+ value="<?php echo esc_attr($this->conf->get('notification_email_address', '')); ?>"
822
+ class="regular-text ltr">
823
+ </label><br>
824
+ <span class="description">
825
+ <?php echo __('Leave empty to use the e-mail address specified in Settings &rarr; General.', 'broken-link-checker'); ?>
826
+ </span>
827
+ </p>
828
+ </td>
829
+ </tr>
830
+
831
+ <tr valign="top">
832
+ <th scope="row"><?php _e('Link tweaks','broken-link-checker'); ?></th>
833
+ <td>
834
+ <p style="margin-top: 0; margin-bottom: 0.5em;">
835
+ <label for='mark_broken_links'>
836
+ <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
837
+ <?php if ($this->conf->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
838
+ <?php _e('Apply custom formatting to broken links', 'broken-link-checker'); ?>
839
+ </label>
840
+ |
841
+ <a id="toggle-broken-link-css-editor" href="#" class="blc-toggle-link"><?php
842
+ _e('Edit CSS', 'broken-link-checker');
843
+ ?></a>
844
+ </p>
845
+
846
+ <div id="broken-link-css-wrap"<?php
847
+ if ( !blcUtility::get_cookie('broken-link-css-wrap', false) ){
848
+ echo ' class="hidden"';
849
+ }
850
+ ?>>
851
+ <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'><?php
852
+ if( isset($this->conf->options['broken_link_css']) ) {
853
+ echo $this->conf->options['broken_link_css'];
854
+ }
855
+ ?></textarea>
856
+ <p class="description"><?php
857
+ printf(
858
+ __('Example : Lorem ipsum <a %s>broken link</a>, dolor sit amet.', 'broken-link-checker'),
859
+ ' href="#" class="broken_link" onclick="return false;"'
860
+ );
861
+ echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
862
+ ?></p>
863
+ </div>
864
+
865
+ <p style="margin-bottom: 0.5em;">
866
+ <label for='mark_removed_links'>
867
+ <input type="checkbox" name="mark_removed_links" id="mark_removed_links"
868
+ <?php if ($this->conf->options['mark_removed_links']) echo ' checked="checked"'; ?>/>
869
+ <?php _e('Apply custom formatting to removed links', 'broken-link-checker'); ?>
870
+ </label>
871
+ |
872
+ <a id="toggle-removed-link-css-editor" href="#" class="blc-toggle-link"><?php
873
+ _e('Edit CSS', 'broken-link-checker');
874
+ ?></a>
875
+ </p>
876
+
877
+ <div id="removed-link-css-wrap" <?php
878
+ if ( !blcUtility::get_cookie('removed-link-css-wrap', false) ){
879
+ echo ' class="hidden"';
880
+ }
881
+ ?>>
882
+ <textarea name="removed_link_css" id="removed_link_css" cols='45' rows='4'><?php
883
+ if( isset($this->conf->options['removed_link_css']) )
884
+ echo $this->conf->options['removed_link_css'];
885
+ ?></textarea>
886
+
887
+ <p class="description"><?php
888
+ printf(
889
+ __('Example : Lorem ipsum <span %s>removed link</span>, dolor sit amet.', 'broken-link-checker'),
890
+ ' class="removed_link"'
891
+ );
892
+ echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
893
+ ?>
894
+
895
+ </p>
896
+ </div>
897
+
898
+ <p>
899
+ <label for='nofollow_broken_links'>
900
+ <input type="checkbox" name="nofollow_broken_links" id="nofollow_broken_links"
901
+ <?php if ($this->conf->options['nofollow_broken_links']) echo ' checked="checked"'; ?>/>
902
+ <?php _e('Stop search engines from following broken links', 'broken-link-checker'); ?>
903
+ </label>
904
+ </p>
905
+
906
+ <p class="description">
907
+ <?php
908
+ echo _x(
909
+ 'These settings only apply to the content of posts, not comments or custom fields.',
910
+ '"Link tweaks" settings',
911
+ 'broken-link-checker'
912
+ );
913
+ ?>
914
+ </p>
915
+ </td>
916
+ </tr>
917
+
918
+ <tr valign="top">
919
+ <th scope="row"><?php echo _x('Suggestions', 'settings page', 'broken-link-checker'); ?></th>
920
+ <td>
921
+ <label>
922
+ <input type="checkbox" name="suggestions_enabled" id="suggestions_enabled"
923
+ <?php checked($this->conf->options['suggestions_enabled']); ?>/>
924
+ <?php _e('Suggest alternatives to broken links', 'broken-link-checker'); ?>
925
+ </label>
926
+ </td>
927
+ </tr>
928
+
929
+ <tr valign="top">
930
+ <th scope="row"><?php echo _x('Warnings', 'settings page', 'broken-link-checker'); ?></th>
931
+ <td id="blc_warning_settings">
932
+ <label>
933
+ <input type="checkbox" name="warnings_enabled" id="warnings_enabled"
934
+ <?php checked($this->conf->options['warnings_enabled']); ?>/>
935
+ <?php _e('Show uncertain or minor problems as "warnings" instead of "broken"', 'broken-link-checker'); ?>
936
+ </label>
937
+ <p class="description"><?php
938
+ _e('Turning off this option will make the plugin report all problems as broken links.', 'broken-link-checker');
939
+ ?></p>
940
+ </td>
941
+ </tr>
942
+
943
+ </table>
944
+
945
+ </div>
946
+
947
+ <div id="section-where" class="blc-section">
948
+ <h3 class="hide-if-js"><?php echo $section_names['where']; ?></h3>
949
+
950
+ <table class="form-table">
951
+
952
+ <tr valign="top">
953
+ <th scope="row"><?php _e('Look for links in', 'broken-link-checker'); ?></th>
954
+ <td>
955
+ <?php
956
+ if ( !empty($modules['container']) ){
957
+ uasort($modules['container'], create_function('$a, $b', 'return strcasecmp($a["Name"], $b["Name"]);'));
958
+ $this->print_module_list($modules['container'], $this->conf->options);
959
+ }
960
+ ?>
961
+ </td></tr>
962
+
963
+ <tr valign="top">
964
+ <th scope="row"><?php _e('Post statuses', 'broken-link-checker'); ?></th>
965
+ <td>
966
+ <?php
967
+ $available_statuses = get_post_stati(array('internal' => false), 'objects');
968
+
969
+ if ( isset($this->conf->options['enabled_post_statuses']) ){
970
+ $enabled_post_statuses = $this->conf->options['enabled_post_statuses'];
971
+ } else {
972
+ $enabled_post_statuses = array();
973
+ }
974
+
975
+ foreach($available_statuses as $status => $status_object){
976
+ printf(
977
+ '<p><label><input type="checkbox" name="enabled_post_statuses[]" value="%s"%s> %s</label></p>',
978
+ esc_attr($status),
979
+ in_array($status, $enabled_post_statuses)?' checked="checked"':'',
980
+ $status_object->label
981
+ );
982
+ }
983
+ ?>
984
+ </td></tr>
985
+
986
+ </table>
987
+
988
+ </div>
989
+
990
+
991
+ <div id="section-which" class="blc-section">
992
+ <h3 class="hide-if-js"><?php echo $section_names['which']; ?></h3>
993
+
994
+ <table class="form-table">
995
+
996
+ <tr valign="top">
997
+ <th scope="row"><?php _e('Link types', 'broken-link-checker'); ?></th>
998
+ <td>
999
+ <?php
1000
+ if ( !empty($modules['parser']) ){
1001
+ $this->print_module_list($modules['parser'], $this->conf->options);
1002
+ } else {
1003
+ echo __('Error : All link parsers missing!', 'broken-link-checker');
1004
+ }
1005
+ ?>
1006
+ </td>
1007
+ </tr>
1008
+
1009
+ <tr valign="top">
1010
+ <th scope="row"><?php _e('Exclusion list', 'broken-link-checker'); ?></th>
1011
+ <td><?php _e("Don't check links where the URL contains any of these words (one per line) :", 'broken-link-checker'); ?><br/>
1012
+ <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4'><?php
1013
+ if( isset($this->conf->options['exclusion_list']) )
1014
+ echo esc_textarea(implode("\n", $this->conf->options['exclusion_list']));
1015
+ ?></textarea>
1016
+
1017
+ </td>
1018
+ </tr>
1019
+
1020
+ </table>
1021
+ </div>
1022
+
1023
+ <div id="section-how" class="blc-section">
1024
+ <h3 class="hide-if-js"><?php echo $section_names['how']; ?></h3>
1025
+
1026
+ <table class="form-table">
1027
+
1028
+ <tr valign="top">
1029
+ <th scope="row"><?php _e('Check links using', 'broken-link-checker'); ?></th>
1030
+ <td>
1031
+ <?php
1032
+ if ( !empty($modules['checker']) ){
1033
+ $modules['checker'] = array_reverse($modules['checker']);
1034
+ $this->print_module_list($modules['checker'], $this->conf->options);
1035
+ }
1036
+ ?>
1037
+ </td></tr>
1038
+
1039
+ </table>
1040
+ </div>
1041
+
1042
+ <div id="section-advanced" class="blc-section">
1043
+ <h3 class="hide-if-js"><?php echo $section_names['advanced']; ?></h3>
1044
+
1045
+ <table class="form-table">
1046
+
1047
+ <tr valign="top">
1048
+ <th scope="row"><?php _e('Timeout', 'broken-link-checker'); ?></th>
1049
+ <td>
1050
+
1051
+ <?php
1052
+
1053
+ printf(
1054
+ __('%s seconds', 'broken-link-checker'),
1055
+ sprintf(
1056
+ '<input type="text" name="timeout" id="blc_timeout" value="%d" size="5" maxlength="3" />',
1057
+ $this->conf->options['timeout']
1058
+ )
1059
+ );
1060
+
1061
+ ?>
1062
+ <br/><span class="description">
1063
+ <?php _e('Links that take longer than this to load will be marked as broken.','broken-link-checker'); ?>
1064
+ </span>
1065
+
1066
+ </td>
1067
+ </tr>
1068
+
1069
+ <tr valign="top">
1070
+ <th scope="row"><?php _e('Link monitor', 'broken-link-checker'); ?></th>
1071
+ <td>
1072
+
1073
+ <p>
1074
+ <label for='run_in_dashboard'>
1075
+
1076
+ <input type="checkbox" name="run_in_dashboard" id="run_in_dashboard"
1077
+ <?php if ($this->conf->options['run_in_dashboard']) echo ' checked="checked"'; ?>/>
1078
+ <?php _e('Run continuously while the Dashboard is open', 'broken-link-checker'); ?>
1079
+ </label>
1080
+ </p>
1081
+
1082
+ <p>
1083
+ <label for='run_via_cron'>
1084
+ <input type="checkbox" name="run_via_cron" id="run_via_cron"
1085
+ <?php if ($this->conf->options['run_via_cron']) echo ' checked="checked"'; ?>/>
1086
+ <?php _e('Run hourly in the background', 'broken-link-checker'); ?>
1087
+ </label>
1088
+ </p>
1089
+
1090
+ </td>
1091
+ </tr>
1092
+
1093
+ <tr valign="top">
1094
+ <th scope="row"><?php _e('Show the dashboard widget for', 'broken-link-checker'); ?></th>
1095
+ <td>
1096
+
1097
+ <?php
1098
+ $widget_caps = array(
1099
+ _x('Administrator', 'dashboard widget visibility', 'broken-link-checker') => 'manage_options',
1100
+ _x('Editor and above', 'dashboard widget visibility', 'broken-link-checker') => 'edit_others_posts',
1101
+ _x('Nobody (disables the widget)', 'dashboard widget visibility', 'broken-link-checker') => 'do_not_allow',
1102
+ );
1103
+
1104
+ foreach($widget_caps as $title => $capability) {
1105
+ printf(
1106
+ '<p><label><input type="radio" name="dashboard_widget_capability" value="%s"%s> %s</label></p>',
1107
+ esc_attr($capability),
1108
+ checked($capability, $this->conf->get('dashboard_widget_capability'), false),
1109
+ $title
1110
+ );
1111
+ }
1112
+ ?>
1113
+ </td>
1114
+ </tr>
1115
+
1116
+ <tr valign="top">
1117
+ <th scope="row"><?php echo _x('Show link actions', 'settings page', 'broken-link-checker'); ?></th>
1118
+ <td>
1119
+ <?php
1120
+ $show_link_actions = $this->conf->get('show_link_actions', array());
1121
+ foreach($available_link_actions as $action => $text) {
1122
+ $enabled = isset($show_link_actions[$action]) ? (bool)($show_link_actions[$action]) : true;
1123
+ printf(
1124
+ '<p><label><input type="checkbox" name="show_link_actions[%1$s]" %3$s> %2$s</label></p>',
1125
+ $action,
1126
+ $text,
1127
+ checked($enabled, true, false)
1128
+ );
1129
+ }
1130
+ ?>
1131
+ </td>
1132
+ </tr>
1133
+
1134
+ <tr valign="top">
1135
+ <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
1136
+ <td>
1137
+
1138
+ <?php
1139
+
1140
+ printf(
1141
+ __('%s seconds', 'broken-link-checker'),
1142
+ sprintf(
1143
+ '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
1144
+ $this->conf->options['max_execution_time']
1145
+ )
1146
+ );
1147
+
1148
+ ?>
1149
+ <br/><span class="description">
1150
+ <?php
1151
+
1152
+ _e('The plugin works by periodically launching a background job that parses your posts for links, checks the discovered URLs, and performs other time-consuming tasks. Here you can set for how long, at most, the link monitor may run each time before stopping.', 'broken-link-checker');
1153
+
1154
+ ?>
1155
+ </span>
1156
+
1157
+ </td>
1158
+ </tr>
1159
+
1160
+ <tr valign="top">
1161
+ <th scope="row"><?php _e('Server load limit', 'broken-link-checker'); ?></th>
1162
+ <td>
1163
+ <?php
1164
+
1165
+ $load = blcUtility::get_server_load();
1166
+ $available = !empty($load);
1167
+
1168
+ if ( $available ){
1169
+ $value = !empty($this->conf->options['server_load_limit'])?sprintf('%.2f', $this->conf->options['server_load_limit']):'';
1170
+ printf(
1171
+ '<input type="text" name="server_load_limit" id="server_load_limit" value="%s" size="5" maxlength="5"/> ',
1172
+ $value
1173
+ );
1174
+
1175
+ printf(
1176
+ __('Current load : %s', 'broken-link-checker'),
1177
+ '<span id="wsblc_current_load">...</span>'
1178
+ );
1179
+ echo '<br/><span class="description">';
1180
+ printf(
1181
+ __(
1182
+ 'Link checking will be suspended if the average <a href="%s">server load</a> rises above this number. Leave this field blank to disable load limiting.',
1183
+ 'broken-link-checker'
1184
+ ),
1185
+ 'http://en.wikipedia.org/wiki/Load_(computing)'
1186
+ );
1187
+ echo '</span>';
1188
+
1189
+ } else {
1190
+ echo '<input type="text" disabled="disabled" value="', esc_attr(__('Not available', 'broken-link-checker')), '" size="13"/><br>';
1191
+ echo '<span class="description">';
1192
+ _e('Load limiting only works on Linux-like systems where <code>/proc/loadavg</code> is present and accessible.', 'broken-link-checker');
1193
+ echo '</span>';
1194
+ }
1195
+ ?>
1196
+ </td>
1197
+ </tr>
1198
+
1199
+ <tr valign="top">
1200
+ <th scope="row"><?php _e('Target resource usage', 'broken-link-checker'); ?></th>
1201
+ <td>
1202
+ <?php
1203
+ $target_resource_usage = $this->conf->get('target_resource_usage', 0.25);
1204
+ printf(
1205
+ '<input name="target_resource_usage" value="%d"
1206
+ type="range" min="1" max="100" id="target_resource_usage">',
1207
+ $target_resource_usage * 100
1208
+ );
1209
+ ?>
1210
+
1211
+ <span id="target_resource_usage_percent"><?php
1212
+ echo sprintf('%.0f%%', $target_resource_usage * 100);
1213
+ ?></span>
1214
+ </td>
1215
+ </tr>
1216
+
1217
+ <tr valign="top">
1218
+ <th scope="row"><?php _e('Logging', 'broken-link-checker'); ?></th>
1219
+ <td>
1220
+ <p>
1221
+ <label for='logging_enabled'>
1222
+ <input type="checkbox" name="logging_enabled" id="logging_enabled"
1223
+ <?php checked($this->conf->options['logging_enabled']); ?>/>
1224
+ <?php _e('Enable logging', 'broken-link-checker'); ?>
1225
+ </label>
1226
+ </p>
1227
+ </td>
1228
+ </tr>
1229
+
1230
+ <tr valign="top">
1231
+ <th scope="row"><?php _e('Log file location', 'broken-link-checker'); ?></th>
1232
+ <td>
1233
+
1234
+ <div id="blc-logging-options">
1235
+
1236
+ <p>
1237
+ <label>
1238
+ <input type="radio" name="custom_log_file_enabled" value=""
1239
+ <?php checked(!$this->conf->options['custom_log_file_enabled']); ?>>
1240
+ <?php echo _x('Default', 'log file location', 'broken-link-checker'); ?>
1241
+ </label>
1242
+ <br>
1243
+ <span class="description">
1244
+ <code><?php
1245
+ echo self::get_default_log_directory(), '/', self::get_default_log_basename();
1246
+ ?></code>
1247
+ </span>
1248
+ </p>
1249
+
1250
+ <p>
1251
+ <label>
1252
+ <input type="radio" name="custom_log_file_enabled" value="1"
1253
+ <?php checked($this->conf->options['custom_log_file_enabled']); ?>>
1254
+ <?php echo _x('Custom', 'log file location', 'broken-link-checker'); ?>
1255
+ </label>
1256
+ <br><input type="text" name="log_file" id="log_file" size="90"
1257
+ value="<?php echo esc_attr($this->conf->options['log_file']); ?>">
1258
+ </p>
1259
+
1260
+ </div>
1261
+ </td>
1262
+ </tr>
1263
+
1264
+
1265
+ <tr valign="top">
1266
+ <th scope="row"><?php _e('Forced recheck', 'broken-link-checker'); ?></th>
1267
+ <td>
1268
+ <input class="button" type="button" name="start-recheck" id="start-recheck"
1269
+ value="<?php _e('Re-check all pages', 'broken-link-checker'); ?>" />
1270
+ <input type="hidden" name="recheck" value="" id="recheck" />
1271
+ <br />
1272
+ <span class="description"><?php
1273
+ _e('The "Nuclear Option". Click this button to make the plugin empty its link database and recheck the entire site from scratch.', 'broken-link-checker');
1274
+
1275
+ ?></span>
1276
+ </td>
1277
+ </tr>
1278
+
1279
+ </table>
1280
+ </div>
1281
+
1282
+ </div>
1283
+
1284
+ <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
1285
+ </form>
1286
+
1287
+ </div> <!-- First postbox-container -->
1288
+
1289
+
1290
+ </div>
1291
+
1292
+
1293
+
1294
+ <?php
1295
+ //The various JS for this page is stored in a separate file for the purposes readability.
1296
+ include dirname($this->loader) . '/includes/admin/options-page-js.php';
1297
+ }
1298
+
1299
+ /**
1300
+ * Output a list of modules and their settings.
1301
+ *
1302
+ * Each list entry will contain a checkbox that is checked if the module is
1303
+ * currently active.
1304
+ *
1305
+ * @param array $modules Array of modules to display
1306
+ * @param array $current_settings
1307
+ * @return void
1308
+ */
1309
+ function print_module_list($modules, $current_settings){
1310
+ $moduleManager = blcModuleManager::getInstance();
1311
+
1312
+ foreach($modules as $module_id => $module_data){
1313
+ $module_id = $module_data['ModuleID'];
1314
+
1315
+ $style = $module_data['ModuleHidden']?' style="display:none;"':'';
1316
+
1317
+ printf(
1318
+ '<div class="module-container" id="module-container-%s"%s>',
1319
+ $module_id,
1320
+ $style
1321
+ );
1322
+ $this->print_module_checkbox($module_id, $module_data, $moduleManager->is_active($module_id));
1323
+
1324
+ $extra_settings = apply_filters(
1325
+ 'blc-module-settings-'.$module_id,
1326
+ '',
1327
+ $current_settings
1328
+ );
1329
+
1330
+ if ( !empty($extra_settings) ){
1331
+
1332
+ printf(
1333
+ ' | <a class="blc-toggle-link toggle-module-settings" id="toggle-module-settings-%s" href="#">%s</a>',
1334
+ esc_attr($module_id),
1335
+ __('Configure', 'broken-link-checker')
1336
+ );
1337
+
1338
+ //The plugin remembers the last open/closed state of module configuration boxes
1339
+ $box_id = 'module-extra-settings-' . $module_id;
1340
+ $show = blcUtility::get_cookie(
1341
+ $box_id,
1342
+ $moduleManager->is_active($module_id)
1343
+ );
1344
+
1345
+ printf(
1346
+ '<div class="module-extra-settings%s" id="%s">%s</div>',
1347
+ $show?'':' hidden',
1348
+ $box_id,
1349
+ $extra_settings
1350
+ );
1351
+ }
1352
+
1353
+ echo '</div>';
1354
+ }
1355
+ }
1356
+
1357
+ /**
1358
+ * Output a checkbox for a module.
1359
+ *
1360
+ * Generates a simple checkbox that can be used to mark a module as active/inactive.
1361
+ * If the specified module can't be deactivated (ModuleAlwaysActive = true), the checkbox
1362
+ * will be displayed in a disabled state and a hidden field will be created to make
1363
+ * form submissions work correctly.
1364
+ *
1365
+ * @param string $module_id Module ID.
1366
+ * @param array $module_data Associative array of module data.
1367
+ * @param bool $active If true, the newly created checkbox will start out checked.
1368
+ * @return void
1369
+ */
1370
+ function print_module_checkbox($module_id, $module_data, $active = false){
1371
+ $disabled = false;
1372
+ $name_prefix = 'module';
1373
+ $label_class = '';
1374
+ $active = $active || $module_data['ModuleAlwaysActive'];
1375
+
1376
+ if ( $module_data['ModuleAlwaysActive'] ){
1377
+ $disabled = true;
1378
+ $name_prefix = 'module-always-active';
1379
+ }
1380
+
1381
+ $checked = $active ? ' checked="checked"':'';
1382
+ if ( $disabled ){
1383
+ $checked .= ' disabled="disabled"';
1384
+ }
1385
+
1386
+ printf(
1387
+ '<label class="%s">
1388
+ <input type="checkbox" name="%s[%s]" id="module-checkbox-%s"%s /> %s
1389
+ </label>',
1390
+ esc_attr($label_class),
1391
+ $name_prefix,
1392
+ esc_attr($module_id),
1393
+ esc_attr($module_id),
1394
+ $checked,
1395
+ $module_data['Name']
1396
+ );
1397
+
1398
+ if ( $module_data['ModuleAlwaysActive'] ){
1399
+ printf(
1400
+ '<input type="hidden" name="module[%s]" value="on">',
1401
+ esc_attr($module_id)
1402
+ );
1403
+ }
1404
+ }
1405
+
1406
+ /**
1407
+ * Add extra settings to the "Custom fields" entry on the plugin's config. page.
1408
+ *
1409
+ * Callback for the 'blc-module-settings-custom_field' filter.
1410
+ *
1411
+ * @param string $html Current extra HTML
1412
+ * @param array $current_settings The current plugin configuration.
1413
+ * @return string New extra HTML.
1414
+ */
1415
+ function make_custom_field_input($html, $current_settings){
1416
+ $html .= '<span class="description">' .
1417
+ __(
1418
+ 'Enter the names of custom fields you want to check (one per line). If a field contains HTML code, prefix its name with <code>html:</code>. For example, <code>html:field_name</code>.',
1419
+ 'broken-link-checker'
1420
+ ) .
1421
+ '</span>';
1422
+ $html .= '<br><textarea name="blc_custom_fields" id="blc_custom_fields" cols="45" rows="4">';
1423
+ if( isset($current_settings['custom_fields']) ){
1424
+ $html .= esc_textarea(implode("\n", $current_settings['custom_fields']));
1425
+ }
1426
+ $html .= '</textarea>';
1427
+
1428
+ return $html;
1429
+ }
1430
+ function make_acf_field_input($html, $current_settings) {
1431
+ $html .= '<span class="description">' . __('Enter the keys of acf fields you want to check (one per line). If a field contains HTML code, prefix its name with <code>html:</code>. For example, <code>html:field_586a3eaa4091b</code>.', 'broken-link-checker') . '</span>';
1432
+ $html .= '<br><textarea name="blc_acf_fields" id="blc_acf_fields" cols="45" rows="4">';
1433
+ if (isset($current_settings['acf_fields'])) {
1434
+ $html .= esc_textarea(implode("\n", $current_settings['acf_fields']));
1435
+ }
1436
+ $html .= '</textarea>';
1437
+
1438
+ return $html;
1439
+ }
1440
+ /**
1441
+ * Enqueue CSS file for the plugin's Settings page.
1442
+ *
1443
+ * @return void
1444
+ */
1445
+ function options_page_css(){
1446
+ wp_enqueue_style('blc-options-page', plugins_url('css/options-page.css', BLC_PLUGIN_FILE), array(), '20141113');
1447
+ wp_enqueue_style('dashboard');
1448
+ }
1449
+
1450
+
1451
+ /**
1452
+ * Display the "Broken Links" page, listing links detected by the plugin and their status.
1453
+ *
1454
+ * @return void
1455
+ */
1456
+ function links_page(){
1457
+ global $wpdb; /* @var wpdb $wpdb */
1458
+
1459
+ $blc_link_query = blcLinkQuery::getInstance();
1460
+
1461
+ //Cull invalid and missing modules so that we don't get dummy links/instances showing up.
1462
+ $moduleManager = blcModuleManager::getInstance();
1463
+ $moduleManager->validate_active_modules();
1464
+
1465
+ if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
1466
+ //Make module headers translatable. They need to be formatted corrrectly and
1467
+ //placed in a .php file to be visible to the script(s) that generate .pot files.
1468
+ $code = $moduleManager->_build_header_translation_code();
1469
+ file_put_contents( dirname($this->loader) . '/includes/extra-strings.php', $code );
1470
+ }
1471
+
1472
+ $action = !empty($_POST['action'])?$_POST['action']:'';
1473
+ if ( intval($action) == -1 ){
1474
+ //Try the second bulk actions box
1475
+ $action = !empty($_POST['action2'])?$_POST['action2']:'';
1476
+ }
1477
+
1478
+ //Get the list of link IDs selected via checkboxes
1479
+ $selected_links = array();
1480
+ if ( isset($_POST['selected_links']) && is_array($_POST['selected_links']) ){
1481
+ //Convert all link IDs to integers (non-numeric entries are converted to zero)
1482
+ $selected_links = array_map('intval', $_POST['selected_links']);
1483
+ //Remove all zeroes
1484
+ $selected_links = array_filter($selected_links);
1485
+ }
1486
+
1487
+ $message = '';
1488
+ $msg_class = 'updated';
1489
+
1490
+ //Run the selected bulk action, if any
1491
+ $force_delete = false;
1492
+ switch ( $action ){
1493
+ case 'create-custom-filter':
1494
+ list($message, $msg_class) = $this->do_create_custom_filter();
1495
+ break;
1496
+
1497
+ case 'delete-custom-filter':
1498
+ list($message, $msg_class) = $this->do_delete_custom_filter();
1499
+ break;
1500
+
1501
+ /** @noinspection PhpMissingBreakStatementInspection Deliberate fall-through. */
1502
+ case 'bulk-delete-sources':
1503
+ $force_delete = true;
1504
+ case 'bulk-trash-sources':
1505
+ list($message, $msg_class) = $this->do_bulk_delete_sources($selected_links, $force_delete);
1506
+ break;
1507
+
1508
+ case 'bulk-unlink':
1509
+ list($message, $msg_class) = $this->do_bulk_unlink($selected_links);
1510
+ break;
1511
+
1512
+ case 'bulk-deredirect':
1513
+ list($message, $msg_class) = $this->do_bulk_deredirect($selected_links);
1514
+ break;
1515
+
1516
+ case 'bulk-recheck':
1517
+ list($message, $msg_class) = $this->do_bulk_recheck($selected_links);
1518
+ break;
1519
+
1520
+ case 'bulk-not-broken':
1521
+ list($message, $msg_class) = $this->do_bulk_discard($selected_links);
1522
+ break;
1523
+
1524
+ case 'bulk-dismiss':
1525
+ list($message, $msg_class) = $this->do_bulk_dismiss($selected_links);
1526
+ break;
1527
+
1528
+ case 'bulk-edit':
1529
+ list($message, $msg_class) = $this->do_bulk_edit($selected_links);
1530
+ break;
1531
+ }
1532
+
1533
+
1534
+ if ( !empty($message) ){
1535
+ echo '<div id="message" class="'.$msg_class.' fade"><p>'.$message.'</p></div>';
1536
+ }
1537
+
1538
+ $start_time = microtime_float();
1539
+
1540
+ //Load custom filters, if any
1541
+ $blc_link_query->load_custom_filters();
1542
+
1543
+ //Calculate the number of links matching each filter
1544
+ $blc_link_query->count_filter_results();
1545
+
1546
+ //Run the selected filter (defaults to displaying broken links)
1547
+ $selected_filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
1548
+ $current_filter = $blc_link_query->exec_filter(
1549
+ $selected_filter_id,
1550
+ isset($_GET['paged']) ? intval($_GET['paged']) : 1,
1551
+ $this->conf->options['table_links_per_page'],
1552
+ 'broken',
1553
+ isset($_GET['orderby']) ? $_GET['orderby'] : '',
1554
+ isset($_GET['order']) ? $_GET['order'] : ''
1555
+ );
1556
+
1557
+ //exec_filter() returns an array with filter data, including the actual filter ID that was used.
1558
+ $filter_id = $current_filter['filter_id'];
1559
+
1560
+ //Error?
1561
+ if ( empty($current_filter['links']) && !empty($wpdb->last_error) ){
1562
+ printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
1563
+ }
1564
+ ?>
1565
+
1566
+ <script type='text/javascript'>
1567
+ var blc_current_filter = '<?php echo $filter_id; ?>';
1568
+ var blc_is_broken_filter = <?php echo $current_filter['is_broken_filter'] ? 'true' : 'false'; ?>;
1569
+ var blc_current_base_filter = '<?php echo esc_js($current_filter['base_filter']); ?>';
1570
+ var blc_suggestions_enabled = <?php echo $this->conf->options['suggestions_enabled'] ? 'true' : 'false'; ?>;
1571
+ </script>
1572
+
1573
+ <div class="wrap">
1574
+ <?php
1575
+ $blc_link_query->print_filter_heading($current_filter);
1576
+ $blc_link_query->print_filter_menu($filter_id);
1577
+
1578
+ //Display the "Search" form and associated buttons.
1579
+ //The form requires the $filter_id and $current_filter variables to be set.
1580
+ include dirname($this->loader) . '/includes/admin/search-form.php';
1581
+
1582
+ //If the user has decided to switch the table to a different mode (compact/full),
1583
+ //save the new setting.
1584
+ if ( isset($_GET['compact']) ){
1585
+ $this->conf->options['table_compact'] = (bool)$_GET['compact'];
1586
+ $this->conf->save_options();
1587
+ }
1588
+
1589
+ //Display the links, if any
1590
+ if( $current_filter['links'] && ( count($current_filter['links']) > 0 ) ) {
1591
+
1592
+ include dirname($this->loader) . '/includes/admin/table-printer.php';
1593
+ $table = new blcTablePrinter($this);
1594
+ $table->print_table(
1595
+ $current_filter,
1596
+ $this->conf->options['table_layout'],
1597
+ $this->conf->options['table_visible_columns'],
1598
+ $this->conf->options['table_compact']
1599
+ );
1600
+
1601
+ };
1602
+ printf('<!-- Total elapsed : %.4f seconds -->', microtime_float() - $start_time);
1603
+
1604
+ //Load assorted JS event handlers and other shinies
1605
+ include dirname($this->loader) . '/includes/admin/links-page-js.php';
1606
+
1607
+ ?></div><?php
1608
+ }
1609
+
1610
+ /**
1611
+ * Create a custom link filter using params passed in $_POST.
1612
+ *
1613
+ * @uses $_POST
1614
+ * @uses $_GET to replace the current filter ID (if any) with that of the newly created filter.
1615
+ *
1616
+ * @return array Message and the CSS class to apply to the message.
1617
+ */
1618
+ function do_create_custom_filter(){
1619
+ global $wpdb;
1620
+
1621
+ //Create a custom filter!
1622
+ check_admin_referer( 'create-custom-filter' );
1623
+ $msg_class = 'updated';
1624
+
1625
+ //Filter name must be set
1626
+ if ( empty($_POST['name']) ){
1627
+ $message = __("You must enter a filter name!", 'broken-link-checker');
1628
+ $msg_class = 'error';
1629
+ //Filter parameters (a search query) must also be set
1630
+ } elseif ( empty($_POST['params']) ){
1631
+ $message = __("Invalid search query.", 'broken-link-checker');
1632
+ $msg_class = 'error';
1633
+ } else {
1634
+ //Save the new filter
1635
+ $name = strip_tags(strval($_POST['name']));
1636
+ $blc_link_query = blcLinkQuery::getInstance();
1637
+ $filter_id = $blc_link_query->create_custom_filter($name, $_POST['params']);
1638
+
1639
+ if ( $filter_id ){
1640
+ //Saved
1641
+ $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $name);
1642
+ //A little hack to make the filter active immediately
1643
+ $_GET['filter_id'] = $filter_id;
1644
+ } else {
1645
+ //Error
1646
+ $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
1647
+ $msg_class = 'error';
1648
+ }
1649
+ }
1650
+
1651
+ return array($message, $msg_class);
1652
+ }
1653
+
1654
+ /**
1655
+ * Delete a custom link filter.
1656
+ *
1657
+ * @uses $_POST
1658
+ *
1659
+ * @return array Message and a CSS class to apply to the message.
1660
+ */
1661
+ function do_delete_custom_filter(){
1662
+ //Delete an existing custom filter!
1663
+ check_admin_referer( 'delete-custom-filter' );
1664
+ $msg_class = 'updated';
1665
+
1666
+ //Filter ID must be set
1667
+ if ( empty($_POST['filter_id']) ){
1668
+ $message = __("Filter ID not specified.", 'broken-link-checker');
1669
+ $msg_class = 'error';
1670
+ } else {
1671
+ //Try to delete the filter
1672
+ $blc_link_query = blcLinkQuery::getInstance();
1673
+ if ( $blc_link_query->delete_custom_filter($_POST['filter_id']) ){
1674
+ //Success
1675
+ $message = __('Filter deleted', 'broken-link-checker');
1676
+ } else {
1677
+ //Either the ID is wrong or there was some other error
1678
+ $message = __('Database error : %s', 'broken-link-checker');
1679
+ $msg_class = 'error';
1680
+ }
1681
+ }
1682
+
1683
+ return array($message, $msg_class);
1684
+ }
1685
+
1686
+ /**
1687
+ * Modify multiple links to point to their target URLs.
1688
+ *
1689
+ * @param array $selected_links
1690
+ * @return array The message to display and its CSS class.
1691
+ */
1692
+ function do_bulk_deredirect($selected_links){
1693
+ //For all selected links, replace the URL with the final URL that it redirects to.
1694
+
1695
+ $message = '';
1696
+ $msg_class = 'updated';
1697
+
1698
+ check_admin_referer( 'bulk-action' );
1699
+
1700
+ if ( count($selected_links) > 0 ) {
1701
+ //Fetch all the selected links
1702
+ $links = blc_get_links(array(
1703
+ 'link_ids' => $selected_links,
1704
+ 'purpose' => BLC_FOR_EDITING,
1705
+ ));
1706
+
1707
+ if ( count($links) > 0 ) {
1708
+ $processed_links = 0;
1709
+ $failed_links = 0;
1710
+
1711
+ //Deredirect all selected links
1712
+ foreach($links as $link){
1713
+ $rez = $link->deredirect();
1714
+ if ( !is_wp_error($rez) && empty($rez['errors'] )){
1715
+ $processed_links++;
1716
+ } else {
1717
+ $failed_links++;
1718
+ }
1719
+ }
1720
+
1721
+ $message = sprintf(
1722
+ _n(
1723
+ 'Replaced %d redirect with a direct link',
1724
+ 'Replaced %d redirects with direct links',
1725
+ $processed_links,
1726
+ 'broken-link-checker'
1727
+ ),
1728
+ $processed_links
1729
+ );
1730
+
1731
+ if ( $failed_links > 0 ) {
1732
+ $message .= '<br>' . sprintf(
1733
+ _n(
1734
+ 'Failed to fix %d redirect',
1735
+ 'Failed to fix %d redirects',
1736
+ $failed_links,
1737
+ 'broken-link-checker'
1738
+ ),
1739
+ $failed_links
1740
+ );
1741
+ $msg_class = 'error';
1742
+ }
1743
+ } else {
1744
+ $message = __('None of the selected links are redirects!', 'broken-link-checker');
1745
+ }
1746
+ }
1747
+
1748
+ return array($message, $msg_class);
1749
+ }
1750
+
1751
+ /**
1752
+ * Edit multiple links in one go.
1753
+ *
1754
+ * @param array $selected_links
1755
+ * @return array The message to display and its CSS class.
1756
+ */
1757
+ function do_bulk_edit($selected_links){
1758
+ $message = '';
1759
+ $msg_class = 'updated';
1760
+
1761
+ check_admin_referer( 'bulk-action' );
1762
+
1763
+ $post = $_POST;
1764
+ if ( function_exists('wp_magic_quotes') ){
1765
+ $post = stripslashes_deep($post); //Ceterum censeo, WP shouldn't mangle superglobals.
1766
+ }
1767
+
1768
+ $search = isset($post['search']) ? $post['search'] : '';
1769
+ $replace = isset($post['replace']) ? $post['replace'] : '';
1770
+ $use_regex = !empty($post['regex']);
1771
+ $case_sensitive = !empty($post['case_sensitive']);
1772
+
1773
+ $delimiter = '`'; //Pick a char that's uncommon in URLs so that escaping won't usually be a problem
1774
+ if ( $use_regex ){
1775
+ $search = $delimiter . $this->escape_regex_delimiter($search, $delimiter) . $delimiter;
1776
+ if ( !$case_sensitive ){
1777
+ $search .= 'i';
1778
+ }
1779
+ } elseif ( !$case_sensitive ) {
1780
+ //str_ireplace() would be more appropriate for case-insensitive, non-regexp replacement,
1781
+ //but that's only available in PHP5.
1782
+ $search = $delimiter . preg_quote($search, $delimiter) . $delimiter . 'i';
1783
+ $use_regex = true;
1784
+ }
1785
+
1786
+ if ( count($selected_links) > 0 ) {
1787
+ set_time_limit(300); //In case the user decides to edit hundreds of links at once
1788
+
1789
+ //Fetch all the selected links
1790
+ $links = blc_get_links(array(
1791
+ 'link_ids' => $selected_links,
1792
+ 'purpose' => BLC_FOR_EDITING,
1793
+ ));
1794
+
1795
+ if ( count($links) > 0 ) {
1796
+ $processed_links = 0;
1797
+ $failed_links = 0;
1798
+ $skipped_links = 0;
1799
+
1800
+ //Edit the links
1801
+ foreach($links as $link){
1802
+ if ( $use_regex ){
1803
+ $new_url = preg_replace($search, $replace, $link->url);
1804
+ } else {
1805
+ $new_url = str_replace($search, $replace, $link->url);
1806
+ }
1807
+
1808
+ if ( $new_url == $link->url ){
1809
+ $skipped_links++;
1810
+ continue;
1811
+ }
1812
+
1813
+ $rez = $link->edit($new_url);
1814
+ if ( !is_wp_error($rez) && empty($rez['errors'] )){
1815
+ $processed_links++;
1816
+ } else {
1817
+ $failed_links++;
1818
+ }
1819
+ }
1820
+
1821
+ $message .= sprintf(
1822
+ _n(
1823
+ '%d link updated.',
1824
+ '%d links updated.',
1825
+ $processed_links,
1826
+ 'broken-link-checker'
1827
+ ),
1828
+ $processed_links
1829
+ );
1830
+
1831
+ if ( $failed_links > 0 ) {
1832
+ $message .= '<br>' . sprintf(
1833
+ _n(
1834
+ 'Failed to update %d link.',
1835
+ 'Failed to update %d links.',
1836
+ $failed_links,
1837
+ 'broken-link-checker'
1838
+ ),
1839
+ $failed_links
1840
+ );
1841
+ $msg_class = 'error';
1842
+ }
1843
+ }
1844
+ }
1845
+
1846
+ return array($message, $msg_class);
1847
+ }
1848
+
1849
+ /**
1850
+ * Escape all instances of the $delimiter character with a backslash (unless already escaped).
1851
+ *
1852
+ * @param string $pattern
1853
+ * @param string $delimiter
1854
+ * @return string
1855
+ */
1856
+ private function escape_regex_delimiter($pattern, $delimiter) {
1857
+ if ( empty($pattern) ) {
1858
+ return '';
1859
+ }
1860
+
1861
+ $output = '';
1862
+ $length = strlen($pattern);
1863
+ $escaped = false;
1864
+
1865
+ for ($i = 0; $i < $length; $i++) {
1866
+ $char = $pattern[$i];
1867
+
1868
+ if ( $escaped ) {
1869
+ $escaped = false;
1870
+ } else {
1871
+ if ( $char == '\\' ) {
1872
+ $escaped = true;
1873
+ } else if ( $char == $delimiter ) {
1874
+ $char = '\\' . $char;
1875
+ }
1876
+ }
1877
+
1878
+ $output .= $char;
1879
+ }
1880
+
1881
+ return $output;
1882
+ }
1883
+
1884
+ /**
1885
+ * Unlink multiple links.
1886
+ *
1887
+ * @param array $selected_links
1888
+ * @return array Message and a CSS classname.
1889
+ */
1890
+ function do_bulk_unlink($selected_links){
1891
+ //Unlink all selected links.
1892
+ $message = '';
1893
+ $msg_class = 'updated';
1894
+
1895
+ check_admin_referer( 'bulk-action' );
1896
+
1897
+ if ( count($selected_links) > 0 ) {
1898
+
1899
+ //Fetch all the selected links
1900
+ $links = blc_get_links(array(
1901
+ 'link_ids' => $selected_links,
1902
+ 'purpose' => BLC_FOR_EDITING,
1903
+ ));
1904
+
1905
+ if ( count($links) > 0 ) {
1906
+ $processed_links = 0;
1907
+ $failed_links = 0;
1908
+
1909
+ //Unlink (delete) each one
1910
+ foreach($links as $link){
1911
+ $rez = $link->unlink();
1912
+ if ( ($rez == false) || is_wp_error($rez) ){
1913
+ $failed_links++;
1914
+ } else {
1915
+ $processed_links++;
1916
+ }
1917
+ }
1918
+
1919
+ //This message is slightly misleading - it doesn't account for the fact that
1920
+ //a link can be present in more than one post.
1921
+ $message = sprintf(
1922
+ _n(
1923
+ '%d link removed',
1924
+ '%d links removed',
1925
+ $processed_links,
1926
+ 'broken-link-checker'
1927
+ ),
1928
+ $processed_links
1929
+ );
1930
+
1931
+ if ( $failed_links > 0 ) {
1932
+ $message .= '<br>' . sprintf(
1933
+ _n(
1934
+ 'Failed to remove %d link',
1935
+ 'Failed to remove %d links',
1936
+ $failed_links,
1937
+ 'broken-link-checker'
1938
+ ),
1939
+ $failed_links
1940
+ );
1941
+ $msg_class = 'error';
1942
+ }
1943
+ }
1944
+ }
1945
+
1946
+ return array($message, $msg_class);
1947
+ }
1948
+
1949
+ /**
1950
+ * Delete or trash posts, bookmarks and other items that contain any of the specified links.
1951
+ *
1952
+ * Will prefer moving stuff to trash to permanent deletion. If it encounters an item that
1953
+ * can't be moved to the trash, it will skip that item by default.
1954
+ *
1955
+ * @param array $selected_links An array of link IDs
1956
+ * @param bool $force_delete Whether to bypass trash and force deletion. Defaults to false.
1957
+ * @return array Confirmation message and its CSS class.
1958
+ */
1959
+ function do_bulk_delete_sources($selected_links, $force_delete = false){
1960
+ $message = '';
1961
+ $msg_class = 'updated';
1962
+
1963
+ //Delete posts, blogroll entries and any other link containers that contain any of the selected links.
1964
+ //
1965
+ //Note that once all containers containing a particular link have been deleted,
1966
+ //there is no need to explicitly delete the link record itself. The hooks attached to
1967
+ //the actions that execute when something is deleted (e.g. "post_deleted") will
1968
+ //take care of that.
1969
+
1970
+ check_admin_referer( 'bulk-action' );
1971
+
1972
+ if ( count($selected_links) > 0 ) {
1973
+ $messages = array();
1974
+
1975
+ //Fetch all the selected links
1976
+ $links = blc_get_links(array(
1977
+ 'link_ids' => $selected_links,
1978
+ 'load_instances' => true,
1979
+ ));
1980
+
1981
+ //Make a list of all containers associated with these links, with each container
1982
+ //listed only once.
1983
+ $containers = array();
1984
+ foreach($links as $link){ /* @var blcLink $link */
1985
+ $instances = $link->get_instances();
1986
+ foreach($instances as $instance){ /* @var blcLinkInstance $instance */
1987
+ $key = $instance->container_type . '|' . $instance->container_id;
1988
+ $containers[$key] = array($instance->container_type, $instance->container_id);
1989
+ }
1990
+ }
1991
+
1992
+ //Instantiate the containers
1993
+ $containers = blcContainerHelper::get_containers($containers);
1994
+
1995
+ //Delete/trash their associated entities
1996
+ $deleted = array();
1997
+ $skipped = array();
1998
+ foreach($containers as $container){ /* @var blcContainer $container */
1999
+ if ( !$container->current_user_can_delete() ){
2000
+ continue;
2001
+ }
2002
+
2003
+ if ( $force_delete ){
2004
+ $rez = $container->delete_wrapped_object();
2005
+ } else {
2006
+ if ( $container->can_be_trashed() ){
2007
+ $rez = $container->trash_wrapped_object();
2008
+ } else {
2009
+ $skipped[] = $container;
2010
+ continue;
2011
+ }
2012
+ }
2013
+
2014
+ if ( is_wp_error($rez) ){ /* @var WP_Error $rez */
2015
+ //Record error messages for later display
2016
+ $messages[] = $rez->get_error_message();
2017
+ $msg_class = 'error';
2018
+ } else {
2019
+ //Keep track of how many of each type were deleted.
2020
+ $container_type = $container->container_type;
2021
+ if ( isset($deleted[$container_type]) ){
2022
+ $deleted[$container_type]++;
2023
+ } else {
2024
+ $deleted[$container_type] = 1;
2025
+ }
2026
+ }
2027
+ }
2028
+
2029
+ //Generate delete confirmation messages
2030
+ foreach($deleted as $container_type => $number){
2031
+ if ( $force_delete ){
2032
+ $messages[] = blcContainerHelper::ui_bulk_delete_message($container_type, $number);
2033
+ } else {
2034
+ $messages[] = blcContainerHelper::ui_bulk_trash_message($container_type, $number);
2035
+ }
2036
+
2037
+ }
2038
+
2039
+ //If some items couldn't be trashed, let the user know
2040
+ if ( count($skipped) > 0 ){
2041
+ $message = sprintf(
2042
+ _n(
2043
+ "%d item was skipped because it can't be moved to the Trash. You need to delete it manually.",
2044
+ "%d items were skipped because they can't be moved to the Trash. You need to delete them manually.",
2045
+ count($skipped)
2046
+ ),
2047
+ count($skipped)
2048
+ );
2049
+ $message .= '<br><ul>';
2050
+ foreach($skipped as $container){
2051
+ $message .= sprintf(
2052
+ '<li>%s</li>',
2053
+ $container->ui_get_source('')
2054
+ );
2055
+ }
2056
+ $message .= '</ul>';
2057
+
2058
+ $messages[] = $message;
2059
+ }
2060
+
2061
+ if ( count($messages) > 0 ){
2062
+ $message = implode('<p>', $messages);
2063
+ } else {
2064
+ $message = __("Didn't find anything to delete!", 'broken-link-checker');
2065
+ $msg_class = 'error';
2066
+ }
2067
+ }
2068
+
2069
+ return array($message, $msg_class);
2070
+ }
2071
+
2072
+ /**
2073
+ * Mark multiple links as unchecked.
2074
+ *
2075
+ * @param array $selected_links An array of link IDs
2076
+ * @return array Confirmation nessage and the CSS class to use with that message.
2077
+ */
2078
+ function do_bulk_recheck($selected_links){
2079
+ /** @var wpdb $wpdb */
2080
+ global $wpdb;
2081
+
2082
+ $message = '';
2083
+ $msg_class = 'updated';
2084
+
2085
+ check_admin_referer('bulk-action');
2086
+
2087
+ if ( count($selected_links) > 0 ){
2088
+ $q = "UPDATE {$wpdb->prefix}blc_links
2089
+ SET last_check_attempt = '0000-00-00 00:00:00'
2090
+ WHERE link_id IN (".implode(', ', $selected_links).")";
2091
+ $changes = $wpdb->query($q);
2092
+
2093
+ $message = sprintf(
2094
+ _n(
2095
+ "%d link scheduled for rechecking",
2096
+ "%d links scheduled for rechecking",
2097
+ $changes,
2098
+ 'broken-link-checker'
2099
+ ),
2100
+ $changes
2101
+ );
2102
+ }
2103
+
2104
+ return array($message, $msg_class);
2105
+ }
2106
+
2107
+
2108
+ /**
2109
+ * Mark multiple links as not broken.
2110
+ *
2111
+ * @param array $selected_links An array of link IDs
2112
+ * @return array Confirmation nessage and the CSS class to use with that message.
2113
+ */
2114
+ function do_bulk_discard($selected_links){
2115
+ check_admin_referer( 'bulk-action' );
2116
+
2117
+ $messages = array();
2118
+ $msg_class = 'updated';
2119
+ $processed_links = 0;
2120
+
2121
+ if ( count($selected_links) > 0 ){
2122
+ $transactionManager = TransactionManager::getInstance();
2123
+ $transactionManager->start();
2124
+ foreach($selected_links as $link_id){
2125
+ //Load the link
2126
+ $link = new blcLink( intval($link_id) );
2127
+
2128
+ //Skip links that don't actually exist
2129
+ if ( !$link->valid() ){
2130
+ continue;
2131
+ }
2132
+
2133
+ //Skip links that weren't actually detected as broken
2134
+ if ( !$link->broken && !$link->warning ){
2135
+ continue;
2136
+ }
2137
+
2138
+ //Make it appear "not broken"
2139
+ $link->broken = false;
2140
+ $link->warning = false;
2141
+ $link->false_positive = true;
2142
+ $link->last_check_attempt = time();
2143
+ $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2144
+
2145
+ $link->isOptionLinkChanged = true;
2146
+ //Save the changes
2147
+ if ( $link->save() ){
2148
+ $processed_links++;
2149
+ } else {
2150
+ $messages[] = sprintf(
2151
+ __("Couldn't modify link %d", 'broken-link-checker'),
2152
+ $link_id
2153
+ );
2154
+ $msg_class = 'error';
2155
+ }
2156
+ }
2157
+ }
2158
+
2159
+ if ( $processed_links > 0 ){
2160
+ $transactionManager->commit();
2161
+ $messages[] = sprintf(
2162
+ _n(
2163
+ '%d link marked as not broken',
2164
+ '%d links marked as not broken',
2165
+ $processed_links,
2166
+ 'broken-link-checker'
2167
+ ),
2168
+ $processed_links
2169
+ );
2170
+ }
2171
+
2172
+ return array(implode('<br>', $messages), $msg_class);
2173
+ }
2174
+
2175
+ /**
2176
+ * Dismiss multiple links.
2177
+ *
2178
+ * @param array $selected_links An array of link IDs
2179
+ * @return array Confirmation message and the CSS class to use with that message.
2180
+ */
2181
+ function do_bulk_dismiss($selected_links){
2182
+ check_admin_referer( 'bulk-action' );
2183
+
2184
+ $messages = array();
2185
+ $msg_class = 'updated';
2186
+ $processed_links = 0;
2187
+
2188
+ if ( count($selected_links) > 0 ){
2189
+ $transactionManager = TransactionManager::getInstance();
2190
+ $transactionManager->start();
2191
+ foreach($selected_links as $link_id){
2192
+ //Load the link
2193
+ $link = new blcLink( intval($link_id) );
2194
+
2195
+ //Skip links that don't actually exist
2196
+ if ( !$link->valid() ){
2197
+ continue;
2198
+ }
2199
+
2200
+ //We can only dismiss broken links and redirects.
2201
+ if ( !($link->broken || $link->warning || ($link->redirect_count > 0)) ){
2202
+ continue;
2203
+ }
2204
+
2205
+ $link->dismissed = true;
2206
+
2207
+ $link->isOptionLinkChanged = true;
2208
+ //Save the changes
2209
+ if ( $link->save() ){
2210
+ $processed_links++;
2211
+ } else {
2212
+ $messages[] = sprintf(
2213
+ __("Couldn't modify link %d", 'broken-link-checker'),
2214
+ $link_id
2215
+ );
2216
+ $msg_class = 'error';
2217
+ }
2218
+ }
2219
+ }
2220
+
2221
+ if ( $processed_links > 0 ){
2222
+ $transactionManager->commit();
2223
+ $messages[] = sprintf(
2224
+ _n(
2225
+ '%d link dismissed',
2226
+ '%d links dismissed',
2227
+ $processed_links,
2228
+ 'broken-link-checker'
2229
+ ),
2230
+ $processed_links
2231
+ );
2232
+ }
2233
+
2234
+ return array(implode('<br>', $messages), $msg_class);
2235
+ }
2236
+
2237
+
2238
+ /**
2239
+ * Enqueue CSS files for the "Broken Links" page
2240
+ *
2241
+ * @return void
2242
+ */
2243
+ function links_page_css(){
2244
+ wp_enqueue_style('blc-links-page', plugins_url('css/links-page.css', $this->loader), array(), '20141113-2');
2245
+ }
2246
+
2247
+ /**
2248
+ * Show an admin notice that explains what the "Warnings" section under "Tools -> Broken Links" does.
2249
+ * The user can hide the notice.
2250
+ */
2251
+ public function show_warnings_section_notice() {
2252
+ $is_warnings_section = isset($_GET['filter_id'])
2253
+ && ($_GET['filter_id'] === 'warnings')
2254
+ && isset($_GET['page'])
2255
+ && ($_GET['page'] === 'view-broken-links');
2256
+
2257
+ if ( !($is_warnings_section && current_user_can('edit_others_posts')) ) {
2258
+ return;
2259
+ }
2260
+
2261
+ //Let the user hide the notice.
2262
+ $conf = blc_get_configuration();
2263
+ $notice_name = 'show_warnings_section_hint';
2264
+
2265
+ if ( isset($_GET[$notice_name]) && is_numeric($_GET[$notice_name]) ) {
2266
+ $conf->set($notice_name, (bool)$_GET[$notice_name]);
2267
+ $conf->save_options();
2268
+ }
2269
+ if ( !$conf->get($notice_name, true) ) {
2270
+ return;
2271
+ }
2272
+
2273
+ printf(
2274
+ '<div class="updated">
2275
+ <p>%1$s</p>
2276
+ <p>
2277
+ <a href="%2$s">%3$s</a> |
2278
+ <a href="%4$s">%5$s</a>
2279
+ <p>
2280
+ </div>',
2281
+ __(
2282
+ 'The "Warnings" page lists problems that are probably temporary or suspected to be false positives.<br> Warnings that persist for a long time will usually be reclassified as broken links.',
2283
+ 'broken-link-checker'
2284
+ ),
2285
+ esc_attr(add_query_arg($notice_name, '0')),
2286
+ _x(
2287
+ 'Hide notice',
2288
+ 'admin notice under Tools - Broken links - Warnings',
2289
+ 'broken-link-checker'
2290
+ ),
2291
+ esc_attr(admin_url('options-general.php?page=link-checker-settings#blc_warning_settings')),
2292
+ _x(
2293
+ 'Change warning settings',
2294
+ 'a link from the admin notice under Tools - Broken links - Warnings',
2295
+ 'broken-link-checker'
2296
+ )
2297
+ );
2298
+ }
2299
+
2300
+ /**
2301
+ * Generate the HTML for the plugin's Screen Options panel.
2302
+ *
2303
+ * @return string
2304
+ */
2305
+ function screen_options_html(){
2306
+ //Update the links-per-page setting when "Apply" is clicked
2307
+ if ( isset($_POST['per_page']) && is_numeric($_POST['per_page']) ) {
2308
+ check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
2309
+ $per_page = intval($_POST['per_page']);
2310
+ if ( ($per_page >= 1) && ($per_page <= 500) ){
2311
+ $this->conf->options['table_links_per_page'] = $per_page;
2312
+ $this->conf->save_options();
2313
+ }
2314
+ }
2315
+
2316
+ //Let the user show/hide individual table columns
2317
+ $html = '<h5>' . __('Table columns', 'broken-link-checker') . '</h5>';
2318
+
2319
+ include dirname($this->loader) . '/includes/admin/table-printer.php';
2320
+ $table = new blcTablePrinter($this);
2321
+ $available_columns = $table->get_layout_columns($this->conf->options['table_layout']);
2322
+
2323
+ $html .= '<div id="blc-column-selector" class="metabox-prefs">';
2324
+
2325
+ foreach( $available_columns as $column_id => $data ){
2326
+ $html .= sprintf(
2327
+ '<label><input type="checkbox" name="visible_columns[%s]"%s>%s</label>',
2328
+ esc_attr($column_id),
2329
+ in_array($column_id, $this->conf->options['table_visible_columns']) ? ' checked="checked"' : '',
2330
+ $data['heading']
2331
+ );
2332
+ }
2333
+
2334
+ $html .= '</div>';
2335
+
2336
+ $html .= '<h5>' . __('Show on screen', 'broken-link-checker') . '</h5>';
2337
+ $html .= '<div class="screen-options">';
2338
+ $html .= sprintf(
2339
+ '<input type="text" name="per_page" maxlength="3" value="%d" class="screen-per-page" id="blc_links_per_page" />
2340
+ <label for="blc_links_per_page">%s</label>
2341
+ <input type="button" class="button" value="%s" id="blc-per-page-apply-button" /><br />',
2342
+ $this->conf->options['table_links_per_page'],
2343
+ __('links', 'broken-link-checker'),
2344
+ __('Apply')
2345
+ );
2346
+ $html .= '</div>';
2347
+
2348
+ $html .= '<h5>' . __('Misc', 'broken-link-checker') . '</h5>';
2349
+ $html .= '<div class="screen-options">';
2350
+ /*
2351
+ Display a checkbox in "Screen Options" that lets the user highlight links that
2352
+ have been broken for at least X days.
2353
+ */
2354
+ $html .= sprintf(
2355
+ '<label><input type="checkbox" id="highlight_permanent_failures" name="highlight_permanent_failures"%s> ',
2356
+ $this->conf->options['highlight_permanent_failures'] ? ' checked="checked"' : ''
2357
+ );
2358
+ $input_box = sprintf(
2359
+ '</label><input type="text" name="failure_duration_threshold" id="failure_duration_threshold" value="%d" size="2"><label for="highlight_permanent_failures">',
2360
+ $this->conf->options['failure_duration_threshold']
2361
+ );
2362
+ $html .= sprintf(
2363
+ __('Highlight links broken for at least %s days', 'broken-link-checker'),
2364
+ $input_box
2365
+ );
2366
+ $html .= '</label>';
2367
+
2368
+ //Display a checkbox for turning colourful link status messages on/off
2369
+ $html .= sprintf(
2370
+ '<br/><label><input type="checkbox" id="table_color_code_status" name="table_color_code_status"%s> %s</label>',
2371
+ $this->conf->options['table_color_code_status'] ? ' checked="checked"' : '',
2372
+ __('Color-code status codes', 'broken-link-checker')
2373
+ );
2374
+
2375
+ $html .= '</div>';
2376
+
2377
+ return $html;
2378
+ }
2379
+
2380
+ /**
2381
+ * AJAX callback for saving the "Screen Options" panel settings
2382
+ *
2383
+ * @param array $form
2384
+ * @return void
2385
+ */
2386
+ function ajax_save_screen_options($form){
2387
+ if ( !current_user_can('edit_others_posts') ){
2388
+ die( json_encode( array(
2389
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
2390
+ )));
2391
+ }
2392
+
2393
+ $this->conf->options['highlight_permanent_failures'] = !empty($form['highlight_permanent_failures']);
2394
+ $this->conf->options['table_color_code_status'] = !empty($form['table_color_code_status']);
2395
+
2396
+ $failure_duration_threshold = intval($form['failure_duration_threshold']);
2397
+ if ( $failure_duration_threshold >=1 ){
2398
+ $this->conf->options['failure_duration_threshold'] = $failure_duration_threshold;
2399
+ }
2400
+
2401
+ if ( isset($form['visible_columns']) && is_array($form['visible_columns']) ){
2402
+ $this->conf->options['table_visible_columns'] = array_keys($form['visible_columns']);
2403
+ }
2404
+
2405
+ $this->conf->save_options();
2406
+ die('1');
2407
+ }
2408
+
2409
+ function start_timer(){
2410
+ $this->execution_start_time = microtime_float();
2411
+ }
2412
+
2413
+ function execution_time(){
2414
+ return microtime_float() - $this->execution_start_time;
2415
+ }
2416
+
2417
+ /**
2418
+ * The main worker function that does all kinds of things.
2419
+ *
2420
+ * @return void
2421
+ */
2422
+ function work(){
2423
+ global $blclog;
2424
+
2425
+ //Close the session to prevent lock-ups.
2426
+ //PHP sessions are blocking. session_start() will wait until all other scripts that are using the same session
2427
+ //are finished. As a result, a long-running script that unintentionally keeps the session open can cause
2428
+ //the entire site to "lock up" for the current user/browser. WordPress itself doesn't use sessions, but some
2429
+ //plugins do, so we should explicitly close the session (if any) before starting the worker.
2430
+ if ( session_id() != '' ) {
2431
+ session_write_close();
2432
+ }
2433
+
2434
+ if ( !$this->acquire_lock() ){
2435
+ //FB::warn("Another instance of BLC is already working. Stop.");
2436
+ $blclog->info('Another instance of BLC is already working. Stop.');
2437
+ return;
2438
+ }
2439
+
2440
+ if ( $this->server_too_busy() ){
2441
+ //FB::warn("Server is too busy. Stop.");
2442
+ $blclog->warn('Server load is too high, stopping.');
2443
+ return;
2444
+ }
2445
+
2446
+ $this->start_timer();
2447
+ $blclog->info('work() starts');
2448
+
2449
+ $max_execution_time = $this->conf->options['max_execution_time'];
2450
+
2451
+ /*****************************************
2452
+ Preparation
2453
+ ******************************************/
2454
+ // Check for safe mode
2455
+ if( blcUtility::is_safe_mode() ){
2456
+ // Do it the safe mode way - obey the existing max_execution_time setting
2457
+ $t = ini_get('max_execution_time');
2458
+ if ($t && ($t < $max_execution_time))
2459
+ $max_execution_time = $t-1;
2460
+ } else {
2461
+ // Do it the regular way
2462
+ @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
2463
+ }
2464
+
2465
+ //Don't stop the script when the connection is closed
2466
+ ignore_user_abort( true );
2467
+
2468
+ //Close the connection as per http://www.php.net/manual/en/features.connection-handling.php#71172
2469
+ //This reduces resource usage.
2470
+ //(Disable when debugging or you won't get the FirePHP output)
2471
+ if (
2472
+ !headers_sent()
2473
+ && (defined('DOING_AJAX') && constant('DOING_AJAX'))
2474
+ && (!defined('BLC_DEBUG') || !constant('BLC_DEBUG'))
2475
+ ){
2476
+ @ob_end_clean(); //Discard the existing buffer, if any
2477
+ header("Connection: close");
2478
+ ob_start();
2479
+ echo ('Connection closed'); //This could be anything
2480
+ $size = ob_get_length();
2481
+ header("Content-Length: $size");
2482
+ ob_end_flush(); // Strange behaviour, will not work
2483
+ flush(); // Unless both are called !
2484
+ }
2485
+
2486
+ //Load modules for this context
2487
+ $moduleManager = blcModuleManager::getInstance();
2488
+ $moduleManager->load_modules('work');
2489
+
2490
+ $target_usage_fraction = $this->conf->get('target_resource_usage', 0.25);
2491
+ //Target usage must be between 1% and 100%.
2492
+ $target_usage_fraction = max(min($target_usage_fraction, 1), 0.01);
2493
+
2494
+
2495
+ /*****************************************
2496
+ Parse posts and bookmarks
2497
+ ******************************************/
2498
+
2499
+ $orphans_possible = false;
2500
+ $still_need_resynch = $this->conf->options['need_resynch'];
2501
+
2502
+ if ( $still_need_resynch ) {
2503
+
2504
+ //FB::log("Looking for containers that need parsing...");
2505
+ $max_containers_per_query = 50;
2506
+
2507
+ $start = microtime(true);
2508
+ $containers = blcContainerHelper::get_unsynched_containers($max_containers_per_query);
2509
+ $get_containers_time = microtime(true) - $start;
2510
+
2511
+ while( !empty($containers) ){
2512
+ //FB::log($containers, 'Found containers');
2513
+ $this->sleep_to_maintain_ratio($get_containers_time, $target_usage_fraction);
2514
+
2515
+ foreach($containers as $container){
2516
+ $synch_start_time = microtime(true);
2517
+
2518
+ //FB::log($container, "Parsing container");
2519
+ $container->synch();
2520
+
2521
+ $synch_elapsed_time = microtime(true) - $synch_start_time;
2522
+ $blclog->info(sprintf(
2523
+ 'Parsed container %s[%s] in %.2f ms',
2524
+ $container->container_type,
2525
+ $container->container_id,
2526
+ $synch_elapsed_time * 1000
2527
+ ));
2528
+
2529
+ //Check if we still have some execution time left
2530
+ if( $this->execution_time() > $max_execution_time ){
2531
+ //FB::log('The allotted execution time has run out');
2532
+ blc_cleanup_links();
2533
+ $this->release_lock();
2534
+ return;
2535
+ }
2536
+
2537
+ //Check if the server isn't overloaded
2538
+ if ( $this->server_too_busy() ){
2539
+ //FB::log('Server overloaded, bailing out.');
2540
+ blc_cleanup_links();
2541
+ $this->release_lock();
2542
+ return;
2543
+ }
2544
+
2545
+ //Intentionally slow down parsing to reduce the load on the server. Basically,
2546
+ //we work $target_usage_fraction of the time and sleep the rest of the time.
2547
+ $this->sleep_to_maintain_ratio($synch_elapsed_time, $target_usage_fraction);
2548
+ }
2549
+ $orphans_possible = true;
2550
+
2551
+ $start = microtime(true);
2552
+ $containers = blcContainerHelper::get_unsynched_containers($max_containers_per_query);
2553
+ $get_containers_time = microtime(true) - $start;
2554
+ }
2555
+
2556
+ //FB::log('No unparsed items found.');
2557
+ $still_need_resynch = false;
2558
+
2559
+ } else {
2560
+ //FB::log('Resynch not required.');
2561
+ }
2562
+
2563
+ /******************************************
2564
+ Resynch done?
2565
+ *******************************************/
2566
+ if ( $this->conf->options['need_resynch'] && !$still_need_resynch ){
2567
+ $this->conf->options['need_resynch'] = $still_need_resynch;
2568
+ $this->conf->save_options();
2569
+ }
2570
+
2571
+ /******************************************
2572
+ Remove orphaned links
2573
+ *******************************************/
2574
+
2575
+ if ( $orphans_possible ) {
2576
+ $start = microtime(true);
2577
+
2578
+ $blclog->info('Removing orphaned links.');
2579
+ blc_cleanup_links();
2580
+
2581
+ $get_links_time = microtime(true) - $start;
2582
+ $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2583
+ }
2584
+
2585
+ //Check if we still have some execution time left
2586
+ if( $this->execution_time() > $max_execution_time ){
2587
+ //FB::log('The allotted execution time has run out');
2588
+ $blclog->info('The allotted execution time has run out.');
2589
+ $this->release_lock();
2590
+ return;
2591
+ }
2592
+
2593
+ if ( $this->server_too_busy() ){
2594
+ //FB::log('Server overloaded, bailing out.');
2595
+ $blclog->info('Server load too high, stopping.');
2596
+ $this->release_lock();
2597
+ return;
2598
+ }
2599
+
2600
+ /*****************************************
2601
+ Check links
2602
+ ******************************************/
2603
+ $max_links_per_query = 30;
2604
+
2605
+ $start = microtime(true);
2606
+ $links = $this->get_links_to_check($max_links_per_query);
2607
+ $get_links_time = microtime(true) - $start;
2608
+
2609
+ while ( $links ){
2610
+ $this->sleep_to_maintain_ratio($get_links_time, $target_usage_fraction);
2611
+
2612
+ //Some unchecked links found
2613
+ //FB::log("Checking ".count($links)." link(s)");
2614
+ $blclog->info("Checking ".count($links)." link(s)");
2615
+
2616
+ //Randomizing the array reduces the chances that we'll get several links to the same domain in a row.
2617
+ shuffle($links);
2618
+
2619
+ $transactionManager = TransactionManager::getInstance();
2620
+ $transactionManager->start();
2621
+
2622
+ foreach ($links as $link) {
2623
+ //Does this link need to be checked? Excluded links aren't checked, but their URLs are still
2624
+ //tested periodically to see if they're still on the exclusion list.
2625
+ if ( !$this->is_excluded( $link->url ) ) {
2626
+ //Check the link.
2627
+ //FB::log($link->url, "Checking link {$link->link_id}");
2628
+ $link->check( true );
2629
+ } else {
2630
+ //FB::info("The URL {$link->url} is excluded, skipping link {$link->link_id}.");
2631
+ $link->last_check_attempt = time();
2632
+ $link->save();
2633
+ }
2634
+
2635
+ //Check if we still have some execution time left
2636
+ if( $this->execution_time() > $max_execution_time ){
2637
+ $transactionManager->commit();
2638
+ //FB::log('The allotted execution time has run out');
2639
+ $blclog->info('The allotted execution time has run out.');
2640
+ $this->release_lock();
2641
+ return;
2642
+ }
2643
+
2644
+ //Check if the server isn't overloaded
2645
+ if ( $this->server_too_busy() ){
2646
+ $transactionManager->commit();
2647
+ //FB::log('Server overloaded, bailing out.');
2648
+ $blclog->info('Server load too high, stopping.');
2649
+ $this->release_lock();
2650
+ return;
2651
+ }
2652
+ }
2653
+ $transactionManager->commit();
2654
+
2655
+ $start = microtime(true);
2656
+ $links = $this->get_links_to_check($max_links_per_query);
2657
+ $get_links_time = microtime(true) - $start;
2658
+ }
2659
+ //FB::log('No links need to be checked right now.');
2660
+
2661
+ $this->release_lock();
2662
+ $blclog->info('work(): All done.');
2663
+ //FB::log('All done.');
2664
+ }
2665
+
2666
+ /**
2667
+ * Sleep long enough to maintain the required $ratio between $elapsed_time and total runtime.
2668
+ *
2669
+ * For example, if $ratio is 0.25 and $elapsed_time is 1 second, this method will sleep for 3 seconds.
2670
+ * Total runtime = 1 + 3 = 4, ratio = 1 / 4 = 0.25.
2671
+ *
2672
+ * @param float $elapsed_time
2673
+ * @param float $ratio
2674
+ */
2675
+ private function sleep_to_maintain_ratio($elapsed_time, $ratio) {
2676
+ if ( ($ratio <= 0) || ($ratio > 1) ) {
2677
+ return;
2678
+ }
2679
+ $sleep_time = $elapsed_time * ((1 / $ratio) - 1);
2680
+ if ($sleep_time > 0.0001) {
2681
+ /*global $blclog;
2682
+ $blclog->debug(sprintf(
2683
+ 'Task took %.2f ms, sleeping for %.2f ms',
2684
+ $elapsed_time * 1000,
2685
+ $sleep_time * 1000
2686
+ ));*/
2687
+ usleep($sleep_time * 1000000);
2688
+ }
2689
+ }
2690
+
2691
+ /**
2692
+ * This function is called when the plugin's cron hook executes.
2693
+ * Its only purpose is to invoke the worker function.
2694
+ *
2695
+ * @uses wsBrokenLinkChecker::work()
2696
+ *
2697
+ * @return void
2698
+ */
2699
+ function cron_check_links(){
2700
+ $this->work();
2701
+ }
2702
+
2703
+ /**
2704
+ * Retrieve links that need to be checked or re-checked.
2705
+ *
2706
+ * @param integer $max_results The maximum number of links to return. Defaults to 0 = no limit.
2707
+ * @param bool $count_only If true, only the number of found links will be returned, not the links themselves.
2708
+ * @return int|blcLink[]
2709
+ */
2710
+ function get_links_to_check($max_results = 0, $count_only = false){
2711
+ global $wpdb; /* @var wpdb $wpdb */
2712
+
2713
+ $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2714
+ $recheck_threshold = date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2715
+
2716
+ //FB::log('Looking for links to check (threshold : '.$check_threshold.', recheck_threshold : '.$recheck_threshold.')...');
2717
+
2718
+ //Select some links that haven't been checked for a long time or
2719
+ //that are broken and need to be re-checked again. Links that are
2720
+ //marked as "being checked" and have been that way for several minutes
2721
+ //can also be considered broken/buggy, so those will be selected
2722
+ //as well.
2723
+
2724
+ //Only check links that have at least one valid instance (i.e. an instance exists and
2725
+ //it corresponds to one of the currently loaded container/parser types).
2726
+ $manager = blcModuleManager::getInstance();
2727
+ $loaded_containers = $manager->get_escaped_ids('container');
2728
+ $loaded_parsers = $manager->get_escaped_ids('parser');
2729
+
2730
+ //Note : This is a slow query, but AFAIK there is no way to speed it up.
2731
+ //I could put an index on last_check_attempt, but that value is almost
2732
+ //certainly unique for each row so it wouldn't be much better than a full table scan.
2733
+ if ( $count_only ){
2734
+ $q = "SELECT COUNT(DISTINCT links.link_id)\n";
2735
+ } else {
2736
+ $q = "SELECT DISTINCT links.*\n";
2737
+ }
2738
+ $q .= "FROM {$wpdb->prefix}blc_links AS links
2739
+ INNER JOIN {$wpdb->prefix}blc_instances AS instances USING(link_id)
2740
+ WHERE
2741
+ (
2742
+ ( last_check_attempt < %s )
2743
+ OR
2744
+ (
2745
+ (broken = 1 OR being_checked = 1)
2746
+ AND may_recheck = 1
2747
+ AND check_count < %d
2748
+ AND last_check_attempt < %s
2749
+ )
2750
+ )
2751
+
2752
+ AND
2753
+ ( instances.container_type IN ({$loaded_containers}) )
2754
+ AND ( instances.parser_type IN ({$loaded_parsers}) )
2755
+ ";
2756
+
2757
+ if ( !$count_only ){
2758
+ $q .= "\nORDER BY last_check_attempt ASC\n";
2759
+ if ( !empty($max_results) ){
2760
+ $q .= "LIMIT " . intval($max_results);
2761
+ }
2762
+ }
2763
+
2764
+ $link_q = $wpdb->prepare(
2765
+ $q,
2766
+ $check_threshold,
2767
+ $this->conf->options['recheck_count'],
2768
+ $recheck_threshold
2769
+ );
2770
+ //FB::log($link_q, "Find links to check");
2771
+ //$blclog->debug("Find links to check: \n" . $link_q);
2772
+
2773
+ //If we just need the number of links, retrieve it and return
2774
+ if ( $count_only ){
2775
+ return $wpdb->get_var($link_q);
2776
+ }
2777
+
2778
+ //Fetch the link data
2779
+ $link_data = $wpdb->get_results($link_q, ARRAY_A);
2780
+ if ( empty($link_data) ){
2781
+ return array();
2782
+ }
2783
+
2784
+ //Instantiate blcLink objects for all fetched links
2785
+ $links = array();
2786
+ foreach($link_data as $data){
2787
+ $links[] = new blcLink($data);
2788
+ }
2789
+
2790
+ return $links;
2791
+ }
2792
+
2793
+ /**
2794
+ * Output the current link checker status in JSON format.
2795
+ * Ajax hook for the 'blc_full_status' action.
2796
+ *
2797
+ * @return void
2798
+ */
2799
+ function ajax_full_status( ){
2800
+ $status = $this->get_status();
2801
+ $text = $this->status_text( $status );
2802
+
2803
+ echo json_encode( array(
2804
+ 'text' => $text,
2805
+ 'status' => $status,
2806
+ ) );
2807
+
2808
+ die();
2809
+ }
2810
+
2811
+ /**
2812
+ * Generates a status message based on the status info in $status
2813
+ *
2814
+ * @param array $status
2815
+ * @return string
2816
+ */
2817
+ function status_text( $status ){
2818
+ $text = '';
2819
+
2820
+ if( $status['broken_links'] > 0 ){
2821
+ $text .= sprintf(
2822
+ "<a href='%s' title='" . __('View broken links', 'broken-link-checker') . "'><strong>".
2823
+ _n('Found %d broken link', 'Found %d broken links', $status['broken_links'], 'broken-link-checker') .
2824
+ "</strong></a>",
2825
+ esc_attr(admin_url('tools.php?page=view-broken-links')),
2826
+ $status['broken_links']
2827
+ );
2828
+ } else {
2829
+ $text .= __("No broken links found.", 'broken-link-checker');
2830
+ }
2831
+
2832
+ $text .= "<br/>";
2833
+
2834
+ if( $status['unchecked_links'] > 0) {
2835
+ $text .= sprintf(
2836
+ _n('%d URL in the work queue', '%d URLs in the work queue', $status['unchecked_links'], 'broken-link-checker'),
2837
+ $status['unchecked_links'] );
2838
+ } else {
2839
+ $text .= __("No URLs in the work queue.", 'broken-link-checker');
2840
+ }
2841
+
2842
+ $text .= "<br/>";
2843
+ if ( $status['known_links'] > 0 ){
2844
+ $url_count = sprintf(
2845
+ _nx('%d unique URL', '%d unique URLs', $status['known_links'], 'for the "Detected X unique URLs in Y links" message', 'broken-link-checker'),
2846
+ $status['known_links']
2847
+ );
2848
+ $link_count = sprintf(
2849
+ _nx('%d link', '%d links', $status['known_instances'], 'for the "Detected X unique URLs in Y links" message', 'broken-link-checker'),
2850
+ $status['known_instances']
2851
+ );
2852
+
2853
+ if ($this->conf->options['need_resynch']){
2854
+ $text .= sprintf(
2855
+ __('Detected %1$s in %2$s and still searching...', 'broken-link-checker'),
2856
+ $url_count,
2857
+ $link_count
2858
+ );
2859
+ } else {
2860
+ $text .= sprintf(
2861
+ __('Detected %1$s in %2$s.', 'broken-link-checker'),
2862
+ $url_count,
2863
+ $link_count
2864
+ );
2865
+ }
2866
+ } else {
2867
+ if ($this->conf->options['need_resynch']){
2868
+ $text .= __('Searching your blog for links...', 'broken-link-checker');
2869
+ } else {
2870
+ $text .= __('No links detected.', 'broken-link-checker');
2871
+ }
2872
+ }
2873
+
2874
+ return $text;
2875
+ }
2876
+
2877
+ /**
2878
+ * @uses wsBrokenLinkChecker::ajax_full_status()
2879
+ *
2880
+ * @return void
2881
+ */
2882
+ function ajax_dashboard_status(){
2883
+ //Just display the full status.
2884
+ $this->ajax_full_status();
2885
+ }
2886
+
2887
+ /**
2888
+ * Output the current average server load (over the last one-minute period).
2889
+ * Called via AJAX.
2890
+ *
2891
+ * @return void
2892
+ */
2893
+ function ajax_current_load(){
2894
+ $load = blcUtility::get_server_load();
2895
+ if ( empty($load) ){
2896
+ die( _x('Unknown', 'current load', 'broken-link-checker') );
2897
+ }
2898
+
2899
+ $one_minute = reset($load);
2900
+ printf('%.2f', $one_minute);
2901
+ die();
2902
+ }
2903
+
2904
+ /**
2905
+ * Returns an array with various status information about the plugin. Array key reference:
2906
+ * check_threshold - date/time; links checked before this threshold should be checked again.
2907
+ * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
2908
+ * known_links - the number of detected unique URLs (a misleading name, yes).
2909
+ * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
2910
+ * broken_links - the number of detected broken links.
2911
+ * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
2912
+ *
2913
+ * @return array
2914
+ */
2915
+ function get_status(){
2916
+ $blc_link_query = blcLinkQuery::getInstance();
2917
+
2918
+ $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
2919
+ $recheck_threshold=date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
2920
+
2921
+ $known_links = blc_get_links(array('count_only' => true));
2922
+ $known_instances = blc_get_usable_instance_count();
2923
+
2924
+ $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
2925
+
2926
+ $unchecked_links = $this->get_links_to_check(0, true);
2927
+
2928
+ return array(
2929
+ 'check_threshold' => $check_threshold,
2930
+ 'recheck_threshold' => $recheck_threshold,
2931
+ 'known_links' => $known_links,
2932
+ 'known_instances' => $known_instances,
2933
+ 'broken_links' => $broken_links,
2934
+ 'unchecked_links' => $unchecked_links,
2935
+ );
2936
+ }
2937
+
2938
+ function ajax_work(){
2939
+ check_ajax_referer('blc_work');
2940
+
2941
+ //Run the worker function
2942
+ $this->work();
2943
+ die();
2944
+ }
2945
+
2946
+ /**
2947
+ * AJAX hook for the "Not broken" button. Marks a link as broken and as a likely false positive.
2948
+ *
2949
+ * @return void
2950
+ */
2951
+ function ajax_discard(){
2952
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_discard', false, false)){
2953
+ die( __("You're not allowed to do that!", 'broken-link-checker') );
2954
+ }
2955
+
2956
+ if ( isset($_POST['link_id']) ){
2957
+ //Load the link
2958
+ $link = new blcLink( intval($_POST['link_id']) );
2959
+
2960
+ if ( !$link->valid() ){
2961
+ printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
2962
+ die();
2963
+ }
2964
+ //Make it appear "not broken"
2965
+ $link->broken = false;
2966
+ $link->warning = false;
2967
+ $link->false_positive = true;
2968
+ $link->last_check_attempt = time();
2969
+ $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
2970
+
2971
+ $link->isOptionLinkChanged = true;
2972
+
2973
+ $transactionManager = TransactionManager::getInstance();
2974
+ $transactionManager->start();
2975
+
2976
+ //Save the changes
2977
+ if ( $link->save() ){
2978
+ $transactionManager->commit();
2979
+ die( "OK" );
2980
+ } else {
2981
+ die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
2982
+ }
2983
+ } else {
2984
+ die( __("Error : link_id not specified", 'broken-link-checker') );
2985
+ }
2986
+ }
2987
+
2988
+ public function ajax_dismiss(){
2989
+ $this->ajax_set_link_dismissed(true);
2990
+ }
2991
+
2992
+ public function ajax_undismiss(){
2993
+ $this->ajax_set_link_dismissed(false);
2994
+ }
2995
+
2996
+ private function ajax_set_link_dismissed($dismiss){
2997
+ $action = $dismiss ? 'blc_dismiss' : 'blc_undismiss';
2998
+
2999
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer($action, false, false)){
3000
+ die( __("You're not allowed to do that!", 'broken-link-checker') );
3001
+ }
3002
+
3003
+ if ( isset($_POST['link_id']) ){
3004
+ //Load the link
3005
+ $link = new blcLink( intval($_POST['link_id']) );
3006
+
3007
+ if ( !$link->valid() ){
3008
+ printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
3009
+ die();
3010
+ }
3011
+
3012
+ $link->dismissed = $dismiss;
3013
+
3014
+ //Save the changes
3015
+ $link->isOptionLinkChanged = true;
3016
+ $transactionManager = TransactionManager::getInstance();
3017
+ $transactionManager->start();
3018
+ if ( $link->save() ){
3019
+ $transactionManager->commit();
3020
+ die( "OK" );
3021
+ } else {
3022
+ die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
3023
+ }
3024
+ } else {
3025
+ die( __("Error : link_id not specified", 'broken-link-checker') );
3026
+ }
3027
+ }
3028
+
3029
+ /**
3030
+ * AJAX hook for the inline link editor on Tools -> Broken Links.
3031
+ *
3032
+ * @return void
3033
+ */
3034
+ function ajax_edit(){
3035
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_edit', false, false)){
3036
+ die( json_encode( array(
3037
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3038
+ )));
3039
+ }
3040
+
3041
+ if ( empty($_POST['link_id']) || empty($_POST['new_url']) || !is_numeric($_POST['link_id']) ) {
3042
+ die( json_encode( array(
3043
+ 'error' => __("Error : link_id or new_url not specified", 'broken-link-checker')
3044
+ )));
3045
+ }
3046
+
3047
+ //Load the link
3048
+ $link = new blcLink( intval($_POST['link_id']) );
3049
+
3050
+ if ( !$link->valid() ){
3051
+ die( json_encode( array(
3052
+ 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
3053
+ )));
3054
+ }
3055
+
3056
+ //Validate the new URL.
3057
+ $new_url = stripslashes($_POST['new_url']);
3058
+ $parsed = @parse_url($new_url);
3059
+ if ( !$parsed ){
3060
+ die( json_encode( array(
3061
+ 'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
3062
+ )));
3063
+ }
3064
+
3065
+ if ( !current_user_can('unfiltered_html') ) {
3066
+ //Disallow potentially dangerous URLs like "javascript:...".
3067
+ $protocols = wp_allowed_protocols();
3068
+ $good_protocol_url = wp_kses_bad_protocol($new_url, $protocols);
3069
+ if ( $new_url != $good_protocol_url ) {
3070
+ die( json_encode( array(
3071
+ 'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
3072
+ )));
3073
+ }
3074
+ }
3075
+
3076
+ $new_text = (isset($_POST['new_text']) && is_string($_POST['new_text'])) ? stripslashes($_POST['new_text']) : null;
3077
+ if ( $new_text === '' ) {
3078
+ $new_text = null;
3079
+ }
3080
+ if ( !empty($new_text) && !current_user_can('unfiltered_html') ) {
3081
+ $new_text = stripslashes(wp_filter_post_kses(addslashes($new_text))); //wp_filter_post_kses expects slashed data.
3082
+ }
3083
+
3084
+ $rez = $link->edit($new_url, $new_text);
3085
+ if ( $rez === false ){
3086
+ die( json_encode( array(
3087
+ 'error' => __("An unexpected error occurred!", 'broken-link-checker')
3088
+ )));
3089
+ } else {
3090
+ $new_link = $rez['new_link']; /** @var blcLink $new_link */
3091
+ $new_status = $new_link->analyse_status();
3092
+ $ui_link_text = null;
3093
+ if ( isset($new_text) ) {
3094
+ $instances = $new_link->get_instances();
3095
+ if ( !empty($instances) ) {
3096
+ $first_instance = reset($instances);
3097
+ $ui_link_text = $first_instance->ui_get_link_text();
3098
+ }
3099
+ }
3100
+
3101
+ $response = array(
3102
+ 'new_link_id' => $rez['new_link_id'],
3103
+ 'cnt_okay' => $rez['cnt_okay'],
3104
+ 'cnt_error' => $rez['cnt_error'],
3105
+
3106
+ 'status_text' => $new_status['text'],
3107
+ 'status_code' => $new_status['code'],
3108
+ 'http_code' => empty($new_link->http_code) ? '' : $new_link->http_code,
3109
+ 'redirect_count' => $new_link->redirect_count,
3110
+
3111
+ 'url' => $new_link->url,
3112
+ 'escaped_url' => esc_url_raw($new_link->url),
3113
+ 'final_url' => $new_link->final_url,
3114
+ 'link_text' => isset($new_text) ? $new_text : null,
3115
+ 'ui_link_text' => isset($new_text) ? $ui_link_text : null,
3116
+
3117
+ 'errors' => array(),
3118
+ );
3119
+ //url, status text, status code, link text, editable link text
3120
+
3121
+
3122
+ foreach($rez['errors'] as $error){ /** @var $error WP_Error */
3123
+ array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
3124
+ }
3125
+ die( json_encode($response) );
3126
+ }
3127
+ }
3128
+
3129
+ /**
3130
+ * AJAX hook for the "Unlink" action links in Tools -> Broken Links.
3131
+ * Removes the specified link from all posts and other supported items.
3132
+ *
3133
+ * @return void
3134
+ */
3135
+ function ajax_unlink(){
3136
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_unlink', false, false)){
3137
+ die( json_encode( array(
3138
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3139
+ )));
3140
+ }
3141
+
3142
+ if ( isset($_POST['link_id']) ){
3143
+ //Load the link
3144
+ $link = new blcLink( intval($_POST['link_id']) );
3145
+
3146
+ if ( !$link->valid() ){
3147
+ die( json_encode( array(
3148
+ 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
3149
+ )));
3150
+ }
3151
+
3152
+ //Try and unlink it
3153
+ $rez = $link->unlink();
3154
+
3155
+ if ( $rez === false ){
3156
+ die( json_encode( array(
3157
+ 'error' => __("An unexpected error occured!", 'broken-link-checker')
3158
+ )));
3159
+ } else {
3160
+ $response = array(
3161
+ 'cnt_okay' => $rez['cnt_okay'],
3162
+ 'cnt_error' => $rez['cnt_error'],
3163
+ 'errors' => array(),
3164
+ );
3165
+ foreach($rez['errors'] as $error){ /** @var WP_Error $error */
3166
+ array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
3167
+ }
3168
+
3169
+ die( json_encode($response) );
3170
+ }
3171
+
3172
+ } else {
3173
+ die( json_encode( array(
3174
+ 'error' => __("Error : link_id not specified", 'broken-link-checker')
3175
+ )));
3176
+ }
3177
+ }
3178
+
3179
+ public function ajax_deredirect() {
3180
+ if ( !current_user_can('edit_others_posts') || !check_ajax_referer('blc_deredirect', false, false) ){
3181
+ die( json_encode( array(
3182
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3183
+ )));
3184
+ }
3185
+
3186
+ if ( !isset($_POST['link_id']) || !is_numeric($_POST['link_id']) ) {
3187
+ die( json_encode( array(
3188
+ 'error' => __("Error : link_id not specified", 'broken-link-checker')
3189
+ )));
3190
+ }
3191
+
3192
+ $id = intval($_POST['link_id']);
3193
+ $link = new blcLink($id);
3194
+
3195
+ if ( !$link->valid() ){
3196
+ die( json_encode( array(
3197
+ 'error' => sprintf(__("Oops, I can't find the link %d", 'broken-link-checker'), $id)
3198
+ )));
3199
+ }
3200
+
3201
+ //The actual task is simple; it's error handling that's complicated.
3202
+ $result = $link->deredirect();
3203
+ if ( is_wp_error($result) ) {
3204
+ die( json_encode( array(
3205
+ 'error' => sprintf('%s [%s]', $result->get_error_message(), $result->get_error_code())
3206
+ )));
3207
+ }
3208
+
3209
+ $link = $result['new_link'] /** @var blcLink $link */;
3210
+
3211
+ $status = $link->analyse_status();
3212
+ $response = array(
3213
+ 'url' => $link->url,
3214
+ 'escaped_url' => esc_url_raw($link->url),
3215
+ 'new_link_id' => $result['new_link_id'],
3216
+
3217
+ 'status_text' => $status['text'],
3218
+ 'status_code' => $status['code'],
3219
+ 'http_code' => empty($link->http_code) ? '' : $link->http_code,
3220
+ 'redirect_count' => $link->redirect_count,
3221
+ 'final_url' => $link->final_url,
3222
+
3223
+ 'cnt_okay' => $result['cnt_okay'],
3224
+ 'cnt_error' => $result['cnt_error'],
3225
+ 'errors' => array(),
3226
+ );
3227
+
3228
+ //Convert WP_Error's to simple strings.
3229
+ if ( !empty($result['errors']) ) {
3230
+ foreach($result['errors'] as $error) { /** @var WP_Error $error */
3231
+ $response['errors'][] = $error->get_error_message();
3232
+ }
3233
+ }
3234
+
3235
+ die(json_encode($response));
3236
+ }
3237
+
3238
+ /**
3239
+ * AJAX hook for the "Recheck" action.
3240
+ */
3241
+ public function ajax_recheck() {
3242
+ if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_recheck', false, false)){
3243
+ die( json_encode( array(
3244
+ 'error' => __("You're not allowed to do that!", 'broken-link-checker')
3245
+ )));
3246
+ }
3247
+
3248
+ if ( !isset($_POST['link_id']) || !is_numeric($_POST['link_id']) ) {
3249
+ die( json_encode( array(
3250
+ 'error' => __("Error : link_id not specified", 'broken-link-checker')
3251
+ )));
3252
+ }
3253
+
3254
+ $id = intval($_POST['link_id']);
3255
+ $link = new blcLink($id);
3256
+
3257
+ if ( !$link->valid() ){
3258
+ die( json_encode( array(
3259
+ 'error' => sprintf(__("Oops, I can't find the link %d", 'broken-link-checker'), $id)
3260
+ )));
3261
+ }
3262
+
3263
+ $transactionManager = TransactionManager::getInstance();
3264
+ $transactionManager->start();
3265
+
3266
+ //In case the immediate check fails, this will ensure the link is checked during the next work() run.
3267
+ $link->last_check_attempt = 0;
3268
+ $link->isOptionLinkChanged = true;
3269
+ $link->save();
3270
+
3271
+ //Check the link and save the results.
3272
+ $link->check(true);
3273
+
3274
+ $transactionManager->commit();
3275
+
3276
+ $status = $link->analyse_status();
3277
+ $response = array(
3278
+ 'status_text' => $status['text'],
3279
+ 'status_code' => $status['code'],
3280
+ 'http_code' => empty($link->http_code) ? '' : $link->http_code,
3281
+ 'redirect_count' => $link->redirect_count,
3282
+ 'final_url' => $link->final_url,
3283
+ );
3284
+
3285
+ die(json_encode($response));
3286
+ }
3287
+
3288
+ function ajax_link_details(){
3289
+ global $wpdb; /* @var wpdb $wpdb */
3290
+
3291
+ if (!current_user_can('edit_others_posts')){
3292
+ die( __("You don't have sufficient privileges to access this information!", 'broken-link-checker') );
3293
+ }
3294
+
3295
+ //FB::log("Loading link details via AJAX");
3296
+
3297
+ if ( isset($_GET['link_id']) ){
3298
+ //FB::info("Link ID found in GET");
3299
+ $link_id = intval($_GET['link_id']);
3300
+ } else if ( isset($_POST['link_id']) ){
3301
+ //FB::info("Link ID found in POST");
3302
+ $link_id = intval($_POST['link_id']);
3303
+ } else {
3304
+ //FB::error('Link ID not specified, you hacking bastard.');
3305
+ die( __('Error : link ID not specified', 'broken-link-checker') );
3306
+ }
3307
+
3308
+ //Load the link.
3309
+ $link = new blcLink($link_id);
3310
+
3311
+ if ( !$link->is_new ){
3312
+ //FB::info($link, 'Link loaded');
3313
+ if ( !class_exists('blcTablePrinter') ){
3314
+ require dirname($this->loader) . '/includes/admin/table-printer.php';
3315
+ }
3316
+ blcTablePrinter::details_row_contents($link);
3317
+ die();
3318
+ } else {
3319
+ printf( __('Failed to load link details (%s)', 'broken-link-checker'), $wpdb->last_error );
3320
+ die();
3321
+ }
3322
+ }
3323
+
3324
+ /**
3325
+ * Acquire an exclusive lock.
3326
+ * If we already hold a lock, it will be released and a new one will be acquired.
3327
+ *
3328
+ * @return bool
3329
+ */
3330
+ function acquire_lock(){
3331
+ return WPMutex::acquire('blc_lock');
3332
+ }
3333
+
3334
+ /**
3335
+ * Relese our exclusive lock.
3336
+ * Does nothing if the lock has already been released.
3337
+ *
3338
+ * @return bool
3339
+ */
3340
+ function release_lock(){
3341
+ return WPMutex::release('blc_lock');
3342
+ }
3343
+
3344
+ /**
3345
+ * Check if server is currently too overloaded to run the link checker.
3346
+ *
3347
+ * @return bool
3348
+ */
3349
+ function server_too_busy(){
3350
+ if ( !$this->conf->options['enable_load_limit'] || !isset($this->conf->options['server_load_limit']) ){
3351
+ return false;
3352
+ }
3353
+
3354
+ $loads = blcUtility::get_server_load();
3355
+ if ( empty($loads) ){
3356
+ return false;
3357
+ }
3358
+ $one_minute = floatval(reset($loads));
3359
+
3360
+ return $one_minute > $this->conf->options['server_load_limit'];
3361
+ }
3362
+
3363
+ /**
3364
+ * Register BLC's Dashboard widget
3365
+ *
3366
+ * @return void
3367
+ */
3368
+ function hook_wp_dashboard_setup(){
3369
+ $show_widget = current_user_can($this->conf->get('dashboard_widget_capability', 'edit_others_posts'));
3370
+ if ( function_exists( 'wp_add_dashboard_widget' ) && $show_widget ) {
3371
+ wp_add_dashboard_widget(
3372
+ 'blc_dashboard_widget',
3373
+ __('Broken Link Checker', 'broken-link-checker'),
3374
+ array( $this, 'dashboard_widget' ),
3375
+ array( $this, 'dashboard_widget_control' )
3376
+ );
3377
+ }
3378
+ }
3379
+
3380
+ /**
3381
+ * Collect various debugging information and return it in an associative array
3382
+ *
3383
+ * @return array
3384
+ */
3385
+ function get_debug_info(){
3386
+ /** @var wpdb $wpdb */
3387
+ global $wpdb;
3388
+
3389
+ //Collect some information that's useful for debugging
3390
+ $debug = array();
3391
+
3392
+ //PHP version. Any one is fine as long as WP supports it.
3393
+ $debug[ __('PHP version', 'broken-link-checker') ] = array(
3394
+ 'state' => 'ok',
3395
+ 'value' => phpversion(),
3396
+ );
3397
+
3398
+ //MySQL version
3399
+ $debug[ __('MySQL version', 'broken-link-checker') ] = array(
3400
+ 'state' => 'ok',
3401
+ 'value' => $wpdb->db_version(),
3402
+ );
3403
+
3404
+ //CURL presence and version
3405
+ if ( function_exists('curl_version') ){
3406
+ $version = curl_version();
3407
+
3408
+ if ( version_compare( $version['version'], '7.16.0', '<=' ) ){
3409
+ $data = array(
3410
+ 'state' => 'warning',
3411
+ 'value' => $version['version'],
3412
+ 'message' => __('You have an old version of CURL. Redirect detection may not work properly.', 'broken-link-checker'),
3413
+ );
3414
+ } else {
3415
+ $data = array(
3416
+ 'state' => 'ok',
3417
+ 'value' => $version['version'],
3418
+ );
3419
+ }
3420
+
3421
+ } else {
3422
+ $data = array(
3423
+ 'state' => 'warning',
3424
+ 'value' => __('Not installed', 'broken-link-checker'),
3425
+ );
3426
+ }
3427
+ $debug[ __('CURL version', 'broken-link-checker') ] = $data;
3428
+
3429
+ //Snoopy presence
3430
+ if ( class_exists('Snoopy') || file_exists(ABSPATH. WPINC . '/class-snoopy.php') ){
3431
+ $data = array(
3432
+ 'state' => 'ok',
3433
+ 'value' => __('Installed', 'broken-link-checker'),
3434
+ );
3435
+ } else {
3436
+ //No Snoopy? This should never happen, but if it does we *must* have CURL.
3437
+ if ( function_exists('curl_init') ){
3438
+ $data = array(
3439
+ 'state' => 'ok',
3440
+ 'value' => __('Not installed', 'broken-link-checker'),
3441
+ );
3442
+ } else {
3443
+ $data = array(
3444
+ 'state' => 'error',
3445
+ 'value' => __('Not installed', 'broken-link-checker'),
3446
+ 'message' => __('You must have either CURL or Snoopy installed for the plugin to work!', 'broken-link-checker'),
3447
+ );
3448
+ }
3449
+
3450
+ }
3451
+ $debug['Snoopy'] = $data;
3452
+
3453
+ //Safe_mode status
3454
+ if ( blcUtility::is_safe_mode() ){
3455
+ $debug['Safe mode'] = array(
3456
+ 'state' => 'warning',
3457
+ 'value' => __('On', 'broken-link-checker'),
3458
+ 'message' => __('Redirects may be detected as broken links when safe_mode is on.', 'broken-link-checker'),
3459
+ );
3460
+ } else {
3461
+ $debug['Safe mode'] = array(
3462
+ 'state' => 'ok',
3463
+ 'value' => __('Off', 'broken-link-checker'),
3464
+ );
3465
+ }
3466
+
3467
+ //Open_basedir status
3468
+ if ( blcUtility::is_open_basedir() ){
3469
+ $debug['open_basedir'] = array(
3470
+ 'state' => 'warning',
3471
+ 'value' => sprintf( __('On ( %s )', 'broken-link-checker'), ini_get('open_basedir') ),
3472
+ 'message' => __('Redirects may be detected as broken links when open_basedir is on.', 'broken-link-checker'),
3473
+ );
3474
+ } else {
3475
+ $debug['open_basedir'] = array(
3476
+ 'state' => 'ok',
3477
+ 'value' => __('Off', 'broken-link-checker'),
3478
+ );
3479
+ }
3480
+
3481
+ //Default PHP execution time limit
3482
+ $debug['Default PHP execution time limit'] = array(
3483
+ 'state' => 'ok',
3484
+ 'value' => sprintf(__('%s seconds'), ini_get('max_execution_time')),
3485
+ );
3486
+
3487
+ //Database character set. Usually it's UTF-8. Setting it to something else can cause problems
3488
+ //unless the site owner really knows what they're doing.
3489
+ $charset = $wpdb->get_charset_collate();
3490
+ $debug[ __('Database character set', 'broken-link-checker') ] = array(
3491
+ 'state' => 'ok',
3492
+ 'value' => !empty($charset) ? $charset : '-',
3493
+ );
3494
+
3495
+ //Resynch flag.
3496
+ $debug['Resynch. flag'] = array(
3497
+ 'state' => 'ok',
3498
+ 'value' => sprintf('%d', $this->conf->options['need_resynch'] ? '1 (resynch. required)' : '0 (resynch. not required)'),
3499
+ );
3500
+
3501
+ //Synch records
3502
+ $synch_records = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch"));
3503
+ $data = array(
3504
+ 'state' => 'ok',
3505
+ 'value' => sprintf('%d', $synch_records),
3506
+ );
3507
+ if ( $synch_records == 0 ){
3508
+ $data['state'] = 'warning';
3509
+ $data['message'] = __('If this value is zero even after several page reloads you have probably encountered a bug.', 'broken-link-checker');
3510
+ }
3511
+ $debug['Synch. records'] = $data;
3512
+
3513
+ //Total links and instances (including invalid ones)
3514
+ $all_links = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_links"));
3515
+ $all_instances = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_instances"));
3516
+
3517
+ //Show the number of unparsed containers. Useful for debugging. For performance,
3518
+ //this is only shown when we have no links/instances yet.
3519
+ if( ($all_links == 0) && ($all_instances == 0) ){
3520
+ $unparsed_items = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch WHERE synched=0"));
3521
+ $debug['Unparsed items'] = array(
3522
+ 'state' => 'warning',
3523
+ 'value' => $unparsed_items,
3524
+ );
3525
+ }
3526
+
3527
+ //Links & instances
3528
+ if ( ($all_links > 0) && ($all_instances > 0) ){
3529
+ $debug['Link records'] = array(
3530
+ 'state' => 'ok',
3531
+ 'value' => sprintf('%d (%d)', $all_links, $all_instances),
3532
+ );
3533
+ } else {
3534
+ $debug['Link records'] = array(
3535
+ 'state' => 'warning',
3536
+ 'value' => sprintf('%d (%d)', $all_links, $all_instances),
3537
+ );
3538
+ }
3539
+
3540
+ //Email notifications.
3541
+ if ( $this->conf->options['last_notification_sent'] ) {
3542
+ $notificationDebug = array(
3543
+ 'value' => date('Y-m-d H:i:s T', $this->conf->options['last_notification_sent']),
3544
+ 'state' => 'ok',
3545
+ );
3546
+ } else {
3547
+ $notificationDebug = array(
3548
+ 'value' => 'Never',
3549
+ 'state' => $this->conf->options['send_email_notifications'] ? 'ok' : 'warning',
3550
+ );
3551
+ }
3552
+ $debug['Last email notification'] = $notificationDebug;
3553
+
3554
+ if ( isset($this->conf->options['last_email']) ) {
3555
+ $email = $this->conf->options['last_email'];
3556
+ $debug['Last email sent'] = array(
3557
+ 'state' => 'ok',
3558
+ 'value' => sprintf(
3559
+ '"%s" on %s (%s)',
3560
+ htmlentities($email['subject']),
3561
+ date('Y-m-d H:i:s T', $email['timestamp']),
3562
+ $email['success'] ? 'success' : 'failure'
3563
+ )
3564
+ );
3565
+ }
3566
+
3567
+
3568
+ //Installation log
3569
+ $logger = new blcCachedOptionLogger('blc_installation_log');
3570
+ $installation_log = $logger->get_messages();
3571
+ if ( !empty($installation_log) ){
3572
+ $debug['Installation log'] = array(
3573
+ 'state' => $this->conf->options['installation_complete'] ? 'ok' : 'error',
3574
+ 'value' => implode("<br>\n", $installation_log),
3575
+ );
3576
+ } else {
3577
+ $debug['Installation log'] = array(
3578
+ 'state' => 'warning',
3579
+ 'value' => 'No installation log found found.',
3580
+ );
3581
+ }
3582
+
3583
+ return $debug;
3584
+ }
3585
+
3586
+ function maybe_send_email_notifications() {
3587
+ global $wpdb; /** @var wpdb $wpdb */
3588
+
3589
+ if ( !($this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications']) ){
3590
+ return;
3591
+ }
3592
+
3593
+ //Find links that have been detected as broken since the last sent notification.
3594
+ $last_notification = date('Y-m-d H:i:s', $this->conf->options['last_notification_sent']);
3595
+ $where = $wpdb->prepare('( first_failure >= %s )', $last_notification);
3596
+
3597
+ $links = blc_get_links(array(
3598
+ 's_filter' => 'broken',
3599
+ 'where_expr' => $where,
3600
+ 'load_instances' => true,
3601
+ 'load_containers' => true,
3602
+ 'load_wrapped_objects' => $this->conf->options['send_authors_email_notifications'],
3603
+ 'max_results' => 0,
3604
+ ));
3605
+
3606
+ if ( empty($links) ){
3607
+ return;
3608
+ }
3609
+
3610
+ //Send the admin/maintainer an email notification.
3611
+ $email = $this->conf->get('notification_email_address');
3612
+ if ( empty($email) ) {
3613
+ //Default to the admin email.
3614
+ $email = get_option('admin_email');
3615
+ }
3616
+ if ( $this->conf->options['send_email_notifications'] && !empty($email) ) {
3617
+ $this->send_admin_notification($links, $email);
3618
+ }
3619
+
3620
+ //Send notifications to post authors
3621
+ if ( $this->conf->options['send_authors_email_notifications'] ) {
3622
+ $this->send_authors_notifications($links);
3623
+ }
3624
+
3625
+ $this->conf->options['last_notification_sent'] = time();
3626
+ $this->conf->save_options();
3627
+ }
3628
+
3629
+ function send_admin_notification($links, $email) {
3630
+ //Prepare email message
3631
+ $subject = sprintf(
3632
+ __("[%s] Broken links detected", 'broken-link-checker'),
3633
+ html_entity_decode(get_option('blogname'), ENT_QUOTES)
3634
+ );
3635
+
3636
+ $body = sprintf(
3637
+ _n(
3638
+ "Broken Link Checker has detected %d new broken link on your site.",
3639
+ "Broken Link Checker has detected %d new broken links on your site.",
3640
+ count($links),
3641
+ 'broken-link-checker'
3642
+ ),
3643
+ count($links)
3644
+ );
3645
+ $body .= "<br>";
3646
+
3647
+ $instances = array();
3648
+ foreach($links as $link) { /* @var blcLink $link */
3649
+ $instances = array_merge($instances, $link->get_instances());
3650
+ }
3651
+ $body .= $this->build_instance_list_for_email($instances);
3652
+
3653
+ if ( $this->is_textdomain_loaded && is_rtl() ) {
3654
+ $body = '<div dir="rtl">' . $body . '</div>';
3655
+ }
3656
+
3657
+ $this->send_html_email($email, $subject, $body);
3658
+ }
3659
+
3660
+ function build_instance_list_for_email($instances, $max_displayed_links = 5, $add_admin_link = true){
3661
+ if ( $max_displayed_links === null ) {
3662
+ $max_displayed_links = 5;
3663
+ }
3664
+
3665
+ $result = '';
3666
+ if ( count($instances) > $max_displayed_links ){
3667
+ $line = sprintf(
3668
+ _n(
3669
+ "Here's a list of the first %d broken links:",
3670
+ "Here's a list of the first %d broken links:",
3671
+ $max_displayed_links,
3672
+ 'broken-link-checker'
3673
+ ),
3674
+ $max_displayed_links
3675
+ );
3676
+ } else {
3677
+ $line = __("Here's a list of the new broken links: ", 'broken-link-checker');
3678
+ }
3679
+
3680
+ $result .= "<p>$line</p>";
3681
+
3682
+ //Show up to $max_displayed_links broken link instances right in the email.
3683
+ $displayed = 0;
3684
+ foreach($instances as $instance){ /* @var blcLinkInstance $instance */
3685
+ $pieces = array(
3686
+ sprintf( __('Link text : %s', 'broken-link-checker'), $instance->ui_get_link_text('email') ),
3687
+ sprintf( __('Link URL : <a href="%s">%s</a>', 'broken-link-checker'), htmlentities($instance->get_url()), blcUtility::truncate($instance->get_url(), 70, '') ),
3688
+ sprintf( __('Source : %s', 'broken-link-checker'), $instance->ui_get_source('email') ),
3689
+ );
3690
+
3691
+ $link_entry = implode("<br>", $pieces);
3692
+ $result .= "$link_entry<br><br>";
3693
+
3694
+ $displayed++;
3695
+ if ( $displayed >= $max_displayed_links ){
3696
+ break;
3697
+ }
3698
+ }
3699
+
3700
+ //Add a link to the "Broken Links" tab.
3701
+ if ( $add_admin_link ) {
3702
+ $result .= __("You can see all broken links here:", 'broken-link-checker') . "<br>";
3703
+ $result .= sprintf('<a href="%1$s">%1$s</a>', admin_url('tools.php?page=view-broken-links'));
3704
+ }
3705
+
3706
+ return $result;
3707
+ }
3708
+
3709
+ function send_html_email($email_address, $subject, $body) {
3710
+ //Need to override the default 'text/plain' content type to send a HTML email.
3711
+ add_filter('wp_mail_content_type', array($this, 'override_mail_content_type'));
3712
+
3713
+ //Let auto-responders and similar software know this is an auto-generated email
3714
+ //that they shouldn't respond to.
3715
+ $headers = array('Auto-Submitted: auto-generated');
3716
+
3717
+ $success = wp_mail($email_address, $subject, $body, $headers);
3718
+
3719
+ //Remove the override so that it doesn't interfere with other plugins that might
3720
+ //want to send normal plaintext emails.
3721
+ remove_filter('wp_mail_content_type', array($this, 'override_mail_content_type'));
3722
+
3723
+ $this->conf->options['last_email'] = array(
3724
+ 'subject' => $subject,
3725
+ 'timestamp' => time(),
3726
+ 'success' => $success,
3727
+ );
3728
+ $this->conf->save_options();
3729
+
3730
+ return $success;
3731
+ }
3732
+
3733
+ function send_authors_notifications($links) {
3734
+ $authorInstances = array();
3735
+ foreach($links as $link){ /* @var blcLink $link */
3736
+ foreach($link->get_instances() as $instance){ /* @var blcLinkInstance $instance */
3737
+ $container = $instance->get_container(); /** @var blcContainer $container */
3738
+ if ( empty($container) || !($container instanceof blcAnyPostContainer) ) {
3739
+ continue;
3740
+ }
3741
+ $post = $container->get_wrapped_object(); /** @var StdClass $post */
3742
+ if ( !array_key_exists($post->post_author, $authorInstances) ) {
3743
+ $authorInstances[$post->post_author] = array();
3744
+ }
3745
+ $authorInstances[$post->post_author][] = $instance;
3746
+ }
3747
+ }
3748
+
3749
+ foreach($authorInstances as $author_id => $instances) {
3750
+ $subject = sprintf(
3751
+ __("[%s] Broken links detected", 'broken-link-checker'),
3752
+ html_entity_decode(get_option('blogname'), ENT_QUOTES)
3753
+ );
3754
+
3755
+ $body = sprintf(
3756
+ _n(
3757
+ "Broken Link Checker has detected %d new broken link in your posts.",
3758
+ "Broken Link Checker has detected %d new broken links in your posts.",
3759
+ count($instances),
3760
+ 'broken-link-checker'
3761
+ ),
3762
+ count($instances)
3763
+ );
3764
+ $body .= "<br>";
3765
+
3766
+ $author = get_user_by('id', $author_id); /** @var WP_User $author */
3767
+ $body .= $this->build_instance_list_for_email($instances, null, $author->has_cap('edit_others_posts'));
3768
+
3769
+ if ( $this->is_textdomain_loaded && is_rtl() ) {
3770
+ $body = '<div dir="rtl">' . $body . '</div>';
3771
+ }
3772
+
3773
+ $this->send_html_email($author->user_email, $subject, $body);
3774
+ }
3775
+ }
3776
+
3777
+ function override_mail_content_type(/** @noinspection PhpUnusedParameterInspection */ $content_type){
3778
+ return 'text/html';
3779
+ }
3780
+
3781
+ /**
3782
+ * Promote all links with the "warning" status to "broken".
3783
+ */
3784
+ private function promote_warnings_to_broken() {
3785
+ global $wpdb; /** @var wpdb $wpdb */
3786
+ $wpdb->update(
3787
+ $wpdb->prefix . 'blc_links',
3788
+ array(
3789
+ 'broken' => 1,
3790
+ 'warning' => 0,
3791
+ ),
3792
+ array(
3793
+ 'warning' => 1,
3794
+ ),
3795
+ '%d'
3796
+ );
3797
+ }
3798
+
3799
+ /**
3800
+ * Install or uninstall the plugin's Cron events based on current settings.
3801
+ *
3802
+ * @uses wsBrokenLinkChecker::$conf Uses $conf->options to determine if events need to be (un)installed.
3803
+ *
3804
+ * @return void
3805
+ */
3806
+ function setup_cron_events(){
3807
+
3808
+ //Link monitor
3809
+ if ( $this->conf->options['run_via_cron'] ){
3810
+ if (!wp_next_scheduled('blc_cron_check_links')) {
3811
+ wp_schedule_event( time(), '10min', 'blc_cron_check_links' );
3812
+ }
3813
+ } else {
3814
+ wp_clear_scheduled_hook('blc_cron_check_links');
3815
+ }
3816
+
3817
+ //Email notifications about broken links
3818
+ if ( $this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications'] ){
3819
+ if ( !wp_next_scheduled('blc_cron_email_notifications') ){
3820
+ wp_schedule_event(time(), $this->conf->options['notification_schedule'], 'blc_cron_email_notifications');
3821
+ }
3822
+ } else {
3823
+ wp_clear_scheduled_hook('blc_cron_email_notifications');
3824
+ }
3825
+
3826
+ //Run database maintenance every two weeks or so
3827
+ if ( !wp_next_scheduled('blc_cron_database_maintenance') ){
3828
+ wp_schedule_event(time(), 'daily', 'blc_cron_database_maintenance');
3829
+ }
3830
+ }
3831
+
3832
+ /**
3833
+ * Load the plugin's textdomain.
3834
+ *
3835
+ * @return void
3836
+ */
3837
+ function load_language(){
3838
+ $this->is_textdomain_loaded = load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
3839
+ }
3840
+
3841
+ protected static function get_default_log_directory() {
3842
+ $uploads = wp_upload_dir();
3843
+ return $uploads['basedir'] . '/broken-link-checker';
3844
+ }
3845
+
3846
+ protected static function get_default_log_basename() {
3847
+ return 'blc-log.txt';
3848
+ }
3849
+
3850
+ }//class ends here
3851
+
3852
+ } // if class_exists...
core/init.php CHANGED
@@ -1,389 +1,389 @@
1
- <?php
2
- //To prevent conflicts, only one version of the plugin can be activated at any given time.
3
- if ( defined('BLC_ACTIVE') ){
4
- trigger_error(
5
- 'Another version of Broken Link Checker is already active. Please deactivate it before activating this one.',
6
- E_USER_ERROR
7
- );
8
- } else {
9
-
10
- define('BLC_ACTIVE', true);
11
-
12
- //Fail fast if the WP version is unsupported. The $wp_version variable may be obfuscated by other
13
- //plugins, so use function detection to determine the version. get_post_stati was introduced in WP 3.0.0
14
- if ( !function_exists('get_post_stati') ){
15
- trigger_error(
16
- 'This version of Broken Link Checker requires WordPress 3.0 or later!',
17
- E_USER_ERROR
18
- );
19
- }
20
-
21
- /***********************************************
22
- Debugging stuff
23
- ************************************************/
24
-
25
- //define('BLC_DEBUG', true);
26
-
27
-
28
-
29
- /***********************************************
30
- Constants
31
- ************************************************/
32
-
33
- /*
34
- For performance, some internal APIs used for retrieving multiple links, instances or containers
35
- can take an optional "$purpose" argument. Those APIs will try to use this argument to pre-load
36
- any DB data required for the specified purpose ahead of time.
37
-
38
- For example, if you're loading a bunch of link containers for the purposes of parsing them and
39
- thus set $purpose to BLC_FOR_PARSING, the relevant container managers will (if applicable) precache
40
- the parse-able fields in each returned container object. Still, setting $purpose to any particular
41
- value does not *guarantee* any data will be preloaded - it's only a suggestion that it should.
42
-
43
- The currently supported values for the $purpose argument are :
44
- */
45
- define('BLC_FOR_EDITING', 'edit');
46
- define('BLC_FOR_PARSING', 'parse');
47
- define('BLC_FOR_DISPLAY', 'display');
48
-
49
- define('BLC_DATABASE_VERSION', 9);
50
-
51
- /***********************************************
52
- Configuration
53
- ************************************************/
54
-
55
- //Load and initialize the plugin's configuration
56
- require BLC_DIRECTORY . '/includes/config-manager.php';
57
-
58
- global $blc_config_manager;
59
- $blc_config_manager = new blcConfigurationManager(
60
- //Save the plugin's configuration into this DB option
61
- 'wsblc_options',
62
- //Initialize default settings
63
- array(
64
- 'max_execution_time' => 7*60, //(in seconds) How long the worker instance may run, at most.
65
- 'check_threshold' => 72, //(in hours) Check each link every 72 hours.
66
-
67
- 'recheck_count' => 3, //How many times a broken link should be re-checked.
68
- 'recheck_threshold' => 30*60, //(in seconds) Re-check broken links after 30 minutes.
69
-
70
- 'run_in_dashboard' => true, //Run the link checker algo. continuously while the Dashboard is open.
71
- 'run_via_cron' => true, //Run it hourly via WordPress pseudo-cron.
72
-
73
- 'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
74
- 'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
75
- 'nofollow_broken_links' => false, //Whether to add rel="nofollow" to broken links in posts.
76
-
77
- 'mark_removed_links' => false, //Whether to add the removed_link class when un-linking a link.
78
- 'removed_link_css' => ".removed_link, a.removed_link {\n\ttext-decoration: line-through;\n}",
79
-
80
- 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
81
-
82
- 'send_email_notifications' => true, //Whether to send the admin email notifications about broken links
83
- 'send_authors_email_notifications' => false, //Whether to send post authors notifications about broken links in their posts.
84
- 'notification_email_address' => '', //If set, send email notifications to this address instead of the admin.
85
- 'notification_schedule' => 'daily', //How often (at most) notifications will be sent. Possible values : 'daily', 'weekly'
86
- 'last_notification_sent' => 0, //When the last email notification was sent (Unix timestamp)
87
-
88
- 'suggestions_enabled' => true, //Whether to suggest alternative URLs for broken links.
89
-
90
- 'warnings_enabled' => true, //Try to automatically detect temporary problems and false positives,
91
- //and report them as "Warnings" instead of broken links.
92
-
93
- 'server_load_limit' => null, //Stop parsing stuff & checking links if the 1-minute load average
94
- //goes over this value. Only works on Linux servers. 0 = no limit.
95
- 'enable_load_limit' => true, //Enable/disable load monitoring.
96
-
97
- 'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
98
- 'acf_fields' => array(), //List of custom fields that can contain URLs and should be checked.
99
- 'enabled_post_statuses' => array('publish'), //Only check posts that match one of these statuses
100
-
101
- 'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
102
- 'dashboard_widget_capability' => 'edit_others_posts', //Only display the widget to users who have this capability
103
- 'show_link_count_bubble' => true, //Display a notification bubble in the menu when broken links are found
104
-
105
- 'table_layout' => 'flexible', //The layout of the link table. Possible values : 'classic', 'flexible'
106
- 'table_compact' => true, //Compact table mode on/off
107
- 'table_visible_columns' => array('new-url', 'status', 'used-in', 'new-link-text',),
108
- 'table_links_per_page' => 30,
109
- 'table_color_code_status' => true, //Color-code link status text
110
-
111
- 'need_resynch' => false, //[Internal flag] True if there are unparsed items.
112
- 'current_db_version' => 0, //The currently set-up version of the plugin's tables
113
-
114
- 'timeout' => 30, //(in seconds) Links that take longer than this to respond will be treated as broken.
115
-
116
- 'highlight_permanent_failures' => false,//Highlight links that have appear to be permanently broken (in Tools -> Broken Links).
117
- 'failure_duration_threshold' => 3, //(days) Assume a link is permanently broken if it still hasn't
118
- //recovered after this many days.
119
- 'logging_enabled' => false,
120
- 'log_file' => '',
121
- 'custom_log_file_enabled' => false,
122
-
123
- 'installation_complete' => false,
124
- 'installation_flag_cleared_on' => 0,
125
- 'installation_flag_set_on' => 0,
126
-
127
- 'user_has_donated' => false, //Whether the user has donated to the plugin.
128
- 'donation_flag_fixed' => false,
129
-
130
- //Visible link actions.
131
- 'show_link_actions' => array('blc-deredirect-action' => false),
132
- )
133
- );
134
-
135
- /***********************************************
136
- Logging
137
- ************************************************/
138
-
139
- include BLC_DIRECTORY . '/includes/logger.php';
140
-
141
- global $blclog;
142
- if ($blc_config_manager->get('logging_enabled', false) && is_writable($blc_config_manager->get('log_file'))) {
143
- $blclog = new blcFileLogger($blc_config_manager->get('log_file'));
144
- } else {
145
- $blclog = new blcDummyLogger;
146
- }
147
-
148
- /*
149
- if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
150
- //Load FirePHP for debug logging
151
- if ( !class_exists('FB') && file_exists(BLC_DIRECTORY . '/FirePHPCore/fb.php4') ) {
152
- require_once BLC_DIRECTORY . '/FirePHPCore/fb.php4';
153
- }
154
- //FB::setEnabled(false);
155
- }
156
- //to comment out all calls : (^[^\/]*)(FB::) -> $1\/\/$2
157
- //to uncomment : \/\/(\s*FB::) -> $1
158
- //*/
159
-
160
- /***********************************************
161
- Global functions
162
- ************************************************/
163
-
164
- /**
165
- * Get the configuration object used by Broken Link Checker.
166
- *
167
- * @return blcConfigurationManager
168
- */
169
- function blc_get_configuration(){
170
- return $GLOBALS['blc_config_manager'];
171
- }
172
-
173
- /**
174
- * Notify the link checker that there are unsynched items
175
- * that might contain links (e.g. a new or edited post).
176
- *
177
- * @return void
178
- */
179
- function blc_got_unsynched_items(){
180
- $conf = blc_get_configuration();
181
-
182
- if ( !$conf->options['need_resynch'] ){
183
- $conf->options['need_resynch'] = true;
184
- $conf->save_options();
185
- }
186
- }
187
-
188
- /**
189
- * (Re)create synchronization records for all containers and mark them all as unparsed.
190
- *
191
- * @param bool $forced If true, the plugin will recreate all synch. records from scratch.
192
- * @return void
193
- */
194
- function blc_resynch( $forced = false ){
195
- global $wpdb, $blclog; /* @var wpdb $wpdb */
196
-
197
- if ( $forced ){
198
- $blclog->info('... Forced resynchronization initiated');
199
-
200
- //Drop all synchronization records
201
- $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
202
- } else {
203
- $blclog->info('... Resynchronization initiated');
204
- }
205
-
206
- //Remove invalid DB entries
207
- blc_cleanup_database();
208
-
209
- //(Re)create and update synch. records for all container types.
210
- $blclog->info('... (Re)creating container records');
211
- blcContainerHelper::resynch($forced);
212
-
213
- $blclog->info('... Setting resync. flags');
214
- blc_got_unsynched_items();
215
-
216
- //All done.
217
- $blclog->info('Database resynchronization complete.');
218
- }
219
-
220
- /**
221
- * Delete synch. records, instances and links that refer to missing or invalid items.
222
- *
223
- * @return void
224
- */
225
- function blc_cleanup_database(){
226
- global $blclog;
227
-
228
- //Delete synch. records for container types that don't exist
229
- $blclog->info('... Deleting invalid container records');
230
- blcContainerHelper::cleanup_containers();
231
-
232
- //Delete invalid instances
233
- $blclog->info('... Deleting invalid link instances');
234
- blc_cleanup_instances();
235
-
236
- //Delete orphaned links
237
- $blclog->info('... Deleting orphaned links');
238
- blc_cleanup_links();
239
- }
240
-
241
- /***********************************************
242
- Utility hooks
243
- ************************************************/
244
-
245
- /**
246
- * Add a weekly Cron schedule for email notifications
247
- * and a bimonthly schedule for database maintenance.
248
- *
249
- * @param array $schedules Existing Cron schedules.
250
- * @return array
251
- */
252
- function blc_cron_schedules($schedules){
253
- if ( !isset($schedules['weekly']) ){
254
- $schedules['weekly'] = array(
255
- 'interval' => 604800, //7 days
256
- 'display' => __('Once Weekly')
257
- );
258
- }
259
- if ( !isset($schedules['bimonthly']) ){
260
- $schedules['bimonthly'] = array(
261
- 'interval' => 15*24*2600, //15 days
262
- 'display' => __('Twice a Month')
263
- );
264
- }
265
-
266
- return $schedules;
267
- }
268
- add_filter('cron_schedules', 'blc_cron_schedules');
269
-
270
- /***********************************************
271
- Main functionality
272
- ************************************************/
273
-
274
- //Execute the installation/upgrade script when the plugin is activated.
275
- function blc_activation_hook(){
276
- require BLC_DIRECTORY . '/includes/activation.php';
277
- }
278
- register_activation_hook(BLC_PLUGIN_FILE, 'blc_activation_hook');
279
-
280
- //Load the plugin if installed successfully
281
- if ( $blc_config_manager->options['installation_complete'] ){
282
- function blc_init(){
283
- global $blc_module_manager, $blc_config_manager, $ws_link_checker;
284
-
285
- static $init_done = false;
286
- if ( $init_done ){
287
- return;
288
- }
289
- $init_done = true;
290
-
291
- //Ensure the database is up to date
292
- if ($blc_config_manager->options['current_db_version'] != BLC_DATABASE_VERSION) {
293
- require_once BLC_DIRECTORY . '/includes/admin/db-upgrade.php';
294
- blcDatabaseUpgrader::upgrade_database(); //Also updates the DB ver. in options['current_db_version'].
295
- }
296
-
297
- //Load the base classes and utilities
298
- require_once BLC_DIRECTORY . '/includes/links.php';
299
- require_once BLC_DIRECTORY . '/includes/link-query.php';
300
- require_once BLC_DIRECTORY . '/includes/instances.php';
301
- require_once BLC_DIRECTORY . '/includes/utility-class.php';
302
-
303
- //Load the module subsystem
304
- require_once BLC_DIRECTORY . '/includes/modules.php';
305
-
306
- //Load the modules that want to be executed in all contexts
307
- $blc_module_manager->load_modules();
308
-
309
- if ( is_admin() || defined('DOING_CRON') ){
310
-
311
- //It's an admin-side or Cron request. Load the core.
312
- require_once BLC_DIRECTORY . '/core/core.php';
313
- $ws_link_checker = new wsBrokenLinkChecker( BLC_PLUGIN_FILE, $blc_config_manager );
314
-
315
- } else {
316
-
317
- //This is user-side request, so we don't need to load the core.
318
- //We might need to inject the CSS for removed links, though.
319
- if ( $blc_config_manager->options['mark_removed_links'] && !empty($blc_config_manager->options['removed_link_css']) ){
320
- function blc_print_removed_link_css(){
321
- global $blc_config_manager;
322
- echo '<style type="text/css">',$blc_config_manager->options['removed_link_css'],'</style>';
323
- }
324
- add_action('wp_head', 'blc_print_removed_link_css');
325
- }
326
- }
327
- }
328
- add_action('init', 'blc_init', 2000);
329
- } else {
330
- //Display installation errors (if any) on the Dashboard.
331
- function blc_print_installation_errors(){
332
- global $blc_config_manager, $wpdb; /** @var wpdb $wpdb */
333
- if ( $blc_config_manager->options['installation_complete'] ) {
334
- return;
335
- }
336
-
337
- $messages = array(
338
- '<strong>' . __('Broken Link Checker installation failed. Try deactivating and then reactivating the plugin.', 'broken-link-checker') . '</strong>',
339
- );
340
-
341
- if ( is_multisite() && is_plugin_active_for_network(plugin_basename(BLC_PLUGIN_FILE)) ) {
342
- $messages[] = __('Please activate the plugin separately on each site. Network activation is not supported.', 'broken-link-checker');
343
- $messages[] = '';
344
- }
345
-
346
- if ( ! $blc_config_manager->db_option_loaded ) {
347
- $messages[] = sprintf(
348
- '<strong>Failed to load plugin settings from the "%s" option.</strong>',
349
- $blc_config_manager->option_name
350
- );
351
- $messages[] = '';
352
-
353
- $serialized_config = $wpdb->get_var(
354
- sprintf(
355
- 'SELECT option_value FROM `%s` WHERE option_name = "%s"',
356
- $wpdb->options,
357
- $blc_config_manager->option_name
358
- )
359
- );
360
-
361
- if ( $serialized_config === null ) {
362
- $messages[] = "Option doesn't exist in the {$wpdb->options} table.";
363
- } else {
364
- $messages[] = "Option exists in the {$wpdb->options} table and has the following value:";
365
- $messages[] = '';
366
- $messages[] = '<textarea cols="120" rows="20">' . htmlentities($serialized_config) . '</textarea>';
367
- }
368
-
369
- } else {
370
- $logger = new blcCachedOptionLogger('blc_installation_log');
371
- $messages = array_merge(
372
- $messages,
373
- array(
374
- 'installation_complete = ' . (isset($blc_config_manager->options['installation_complete']) ? intval($blc_config_manager->options['installation_complete']) : 'no value'),
375
- 'installation_flag_cleared_on = ' . $blc_config_manager->options['installation_flag_cleared_on'],
376
- 'installation_flag_set_on = ' . $blc_config_manager->options['installation_flag_set_on'],
377
- '',
378
- '<em>Installation log follows :</em>'
379
- ),
380
- $logger->get_messages()
381
- );
382
- }
383
-
384
- echo "<div class='error'><p>", implode("<br>\n", $messages), "</p></div>";
385
- }
386
- add_action('admin_notices', 'blc_print_installation_errors');
387
- }
388
-
389
  }
1
+ <?php
2
+ //To prevent conflicts, only one version of the plugin can be activated at any given time.
3
+ if ( defined('BLC_ACTIVE') ){
4
+ trigger_error(
5
+ 'Another version of Broken Link Checker is already active. Please deactivate it before activating this one.',
6
+ E_USER_ERROR
7
+ );
8
+ } else {
9
+
10
+ define('BLC_ACTIVE', true);
11
+
12
+ //Fail fast if the WP version is unsupported. The $wp_version variable may be obfuscated by other
13
+ //plugins, so use function detection to determine the version. get_post_stati was introduced in WP 3.0.0
14
+ if ( !function_exists('get_post_stati') ){
15
+ trigger_error(
16
+ 'This version of Broken Link Checker requires WordPress 3.0 or later!',
17
+ E_USER_ERROR
18
+ );
19
+ }
20
+
21
+ /***********************************************
22
+ Debugging stuff
23
+ ************************************************/
24
+
25
+ //define('BLC_DEBUG', true);
26
+
27
+
28
+
29
+ /***********************************************
30
+ Constants
31
+ ************************************************/
32
+
33
+ /*
34
+ For performance, some internal APIs used for retrieving multiple links, instances or containers
35
+ can take an optional "$purpose" argument. Those APIs will try to use this argument to pre-load
36
+ any DB data required for the specified purpose ahead of time.
37
+
38
+ For example, if you're loading a bunch of link containers for the purposes of parsing them and
39
+ thus set $purpose to BLC_FOR_PARSING, the relevant container managers will (if applicable) precache
40
+ the parse-able fields in each returned container object. Still, setting $purpose to any particular
41
+ value does not *guarantee* any data will be preloaded - it's only a suggestion that it should.
42
+
43
+ The currently supported values for the $purpose argument are :
44
+ */
45
+ define('BLC_FOR_EDITING', 'edit');
46
+ define('BLC_FOR_PARSING', 'parse');
47
+ define('BLC_FOR_DISPLAY', 'display');
48
+
49
+ define('BLC_DATABASE_VERSION', 9);
50
+
51
+ /***********************************************
52
+ Configuration
53
+ ************************************************/
54
+
55
+ //Load and initialize the plugin's configuration
56
+ require BLC_DIRECTORY . '/includes/config-manager.php';
57
+
58
+ global $blc_config_manager;
59
+ $blc_config_manager = new blcConfigurationManager(
60
+ //Save the plugin's configuration into this DB option
61
+ 'wsblc_options',
62
+ //Initialize default settings
63
+ array(
64
+ 'max_execution_time' => 7*60, //(in seconds) How long the worker instance may run, at most.
65
+ 'check_threshold' => 72, //(in hours) Check each link every 72 hours.
66
+
67
+ 'recheck_count' => 3, //How many times a broken link should be re-checked.
68
+ 'recheck_threshold' => 30*60, //(in seconds) Re-check broken links after 30 minutes.
69
+
70
+ 'run_in_dashboard' => true, //Run the link checker algo. continuously while the Dashboard is open.
71
+ 'run_via_cron' => true, //Run it hourly via WordPress pseudo-cron.
72
+
73
+ 'mark_broken_links' => true, //Whether to add the broken_link class to broken links in posts.
74
+ 'broken_link_css' => ".broken_link, a.broken_link {\n\ttext-decoration: line-through;\n}",
75
+ 'nofollow_broken_links' => false, //Whether to add rel="nofollow" to broken links in posts.
76
+
77
+ 'mark_removed_links' => false, //Whether to add the removed_link class when un-linking a link.
78
+ 'removed_link_css' => ".removed_link, a.removed_link {\n\ttext-decoration: line-through;\n}",
79
+
80
+ 'exclusion_list' => array(), //Links that contain a substring listed in this array won't be checked.
81
+
82
+ 'send_email_notifications' => true, //Whether to send the admin email notifications about broken links
83
+ 'send_authors_email_notifications' => false, //Whether to send post authors notifications about broken links in their posts.
84
+ 'notification_email_address' => '', //If set, send email notifications to this address instead of the admin.
85
+ 'notification_schedule' => 'daily', //How often (at most) notifications will be sent. Possible values : 'daily', 'weekly'
86
+ 'last_notification_sent' => 0, //When the last email notification was sent (Unix timestamp)
87
+
88
+ 'suggestions_enabled' => true, //Whether to suggest alternative URLs for broken links.
89
+
90
+ 'warnings_enabled' => true, //Try to automatically detect temporary problems and false positives,
91
+ //and report them as "Warnings" instead of broken links.
92
+
93
+ 'server_load_limit' => null, //Stop parsing stuff & checking links if the 1-minute load average
94
+ //goes over this value. Only works on Linux servers. 0 = no limit.
95
+ 'enable_load_limit' => true, //Enable/disable load monitoring.
96
+
97
+ 'custom_fields' => array(), //List of custom fields that can contain URLs and should be checked.
98
+ 'acf_fields' => array(), //List of custom fields that can contain URLs and should be checked.
99
+ 'enabled_post_statuses' => array('publish'), //Only check posts that match one of these statuses
100
+
101
+ 'autoexpand_widget' => true, //Autoexpand the Dashboard widget if broken links are detected
102
+ 'dashboard_widget_capability' => 'edit_others_posts', //Only display the widget to users who have this capability
103
+ 'show_link_count_bubble' => true, //Display a notification bubble in the menu when broken links are found
104
+
105
+ 'table_layout' => 'flexible', //The layout of the link table. Possible values : 'classic', 'flexible'
106
+ 'table_compact' => true, //Compact table mode on/off
107
+ 'table_visible_columns' => array('new-url', 'status', 'used-in', 'new-link-text',),
108
+ 'table_links_per_page' => 30,
109
+ 'table_color_code_status' => true, //Color-code link status text
110
+
111
+ 'need_resynch' => false, //[Internal flag] True if there are unparsed items.
112
+ 'current_db_version' => 0, //The currently set-up version of the plugin's tables
113
+
114
+ 'timeout' => 30, //(in seconds) Links that take longer than this to respond will be treated as broken.
115
+
116
+ 'highlight_permanent_failures' => false,//Highlight links that have appear to be permanently broken (in Tools -> Broken Links).
117
+ 'failure_duration_threshold' => 3, //(days) Assume a link is permanently broken if it still hasn't
118
+ //recovered after this many days.
119
+ 'logging_enabled' => false,
120
+ 'log_file' => '',
121
+ 'custom_log_file_enabled' => false,
122
+
123
+ 'installation_complete' => false,
124
+ 'installation_flag_cleared_on' => 0,
125
+ 'installation_flag_set_on' => 0,
126
+
127
+ 'user_has_donated' => false, //Whether the user has donated to the plugin.
128
+ 'donation_flag_fixed' => false,
129
+
130
+ //Visible link actions.
131
+ 'show_link_actions' => array('blc-deredirect-action' => false),
132
+ )
133
+ );
134
+
135
+ /***********************************************
136
+ Logging
137
+ ************************************************/
138
+
139
+ include BLC_DIRECTORY . '/includes/logger.php';
140
+
141
+ global $blclog;
142
+ if ($blc_config_manager->get('logging_enabled', false) && is_writable($blc_config_manager->get('log_file'))) {
143
+ $blclog = new blcFileLogger($blc_config_manager->get('log_file'));
144
+ } else {
145
+ $blclog = new blcDummyLogger;
146
+ }
147
+
148
+ /*
149
+ if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
150
+ //Load FirePHP for debug logging
151
+ if ( !class_exists('FB') && file_exists(BLC_DIRECTORY . '/FirePHPCore/fb.php4') ) {
152
+ require_once BLC_DIRECTORY . '/FirePHPCore/fb.php4';
153
+ }
154
+ //FB::setEnabled(false);
155
+ }
156
+ //to comment out all calls : (^[^\/]*)(FB::) -> $1\/\/$2
157
+ //to uncomment : \/\/(\s*FB::) -> $1
158
+ //*/
159
+
160
+ /***********************************************
161
+ Global functions
162
+ ************************************************/
163
+
164
+ /**
165
+ * Get the configuration object used by Broken Link Checker.
166
+ *
167
+ * @return blcConfigurationManager
168
+ */
169
+ function blc_get_configuration(){
170
+ return $GLOBALS['blc_config_manager'];
171
+ }
172
+
173
+ /**
174
+ * Notify the link checker that there are unsynched items
175
+ * that might contain links (e.g. a new or edited post).
176
+ *
177
+ * @return void
178
+ */
179
+ function blc_got_unsynched_items(){
180
+ $conf = blc_get_configuration();
181
+
182
+ if ( !$conf->options['need_resynch'] ){
183
+ $conf->options['need_resynch'] = true;
184
+ $conf->save_options();
185
+ }
186
+ }
187
+
188
+ /**
189
+ * (Re)create synchronization records for all containers and mark them all as unparsed.
190
+ *
191
+ * @param bool $forced If true, the plugin will recreate all synch. records from scratch.
192
+ * @return void
193
+ */
194
+ function blc_resynch( $forced = false ){
195
+ global $wpdb, $blclog; /* @var wpdb $wpdb */
196
+
197
+ if ( $forced ){
198
+ $blclog->info('... Forced resynchronization initiated');
199
+
200
+ //Drop all synchronization records
201
+ $wpdb->query("TRUNCATE {$wpdb->prefix}blc_synch");
202
+ } else {
203
+ $blclog->info('... Resynchronization initiated');
204
+ }
205
+
206
+ //Remove invalid DB entries
207
+ blc_cleanup_database();
208
+
209
+ //(Re)create and update synch. records for all container types.
210
+ $blclog->info('... (Re)creating container records');
211
+ blcContainerHelper::resynch($forced);
212
+
213
+ $blclog->info('... Setting resync. flags');
214
+ blc_got_unsynched_items();
215
+
216
+ //All done.
217
+ $blclog->info('Database resynchronization complete.');
218
+ }
219
+
220
+ /**
221
+ * Delete synch. records, instances and links that refer to missing or invalid items.
222
+ *
223
+ * @return void
224
+ */
225
+ function blc_cleanup_database(){
226
+ global $blclog;
227
+
228
+ //Delete synch. records for container types that don't exist
229
+ $blclog->info('... Deleting invalid container records');
230
+ blcContainerHelper::cleanup_containers();
231
+
232
+ //Delete invalid instances
233
+ $blclog->info('... Deleting invalid link instances');
234
+ blc_cleanup_instances();
235
+
236
+ //Delete orphaned links
237
+ $blclog->info('... Deleting orphaned links');
238
+ blc_cleanup_links();
239
+ }
240
+
241
+ /***********************************************
242
+ Utility hooks
243
+ ************************************************/
244
+
245
+ /**
246
+ * Add a weekly Cron schedule for email notifications
247
+ * and a bimonthly schedule for database maintenance.
248
+ *
249
+ * @param array $schedules Existing Cron schedules.
250
+ * @return array
251
+ */
252
+ function blc_cron_schedules($schedules){
253
+ if ( !isset($schedules['weekly']) ){
254
+ $schedules['weekly'] = array(
255
+ 'interval' => 604800, //7 days
256
+ 'display' => __('Once Weekly')
257
+ );
258
+ }
259
+ if ( !isset($schedules['bimonthly']) ){
260
+ $schedules['bimonthly'] = array(
261
+ 'interval' => 15*24*2600, //15 days
262
+ 'display' => __('Twice a Month')
263
+ );
264
+ }
265
+
266
+ return $schedules;
267
+ }
268
+ add_filter('cron_schedules', 'blc_cron_schedules');
269
+
270
+ /***********************************************
271
+ Main functionality
272
+ ************************************************/
273
+
274
+ //Execute the installation/upgrade script when the plugin is activated.
275
+ function blc_activation_hook(){
276
+ require BLC_DIRECTORY . '/includes/activation.php';
277
+ }
278
+ register_activation_hook(BLC_PLUGIN_FILE, 'blc_activation_hook');
279
+
280
+ //Load the plugin if installed successfully
281
+ if ( $blc_config_manager->options['installation_complete'] ){
282
+ function blc_init(){
283
+ global $blc_module_manager, $blc_config_manager, $ws_link_checker;
284
+
285
+ static $init_done = false;
286
+ if ( $init_done ){
287
+ return;
288
+ }
289
+ $init_done = true;
290
+
291
+ //Ensure the database is up to date
292
+ if ($blc_config_manager->options['current_db_version'] != BLC_DATABASE_VERSION) {
293
+ require_once BLC_DIRECTORY . '/includes/admin/db-upgrade.php';
294
+ blcDatabaseUpgrader::upgrade_database(); //Also updates the DB ver. in options['current_db_version'].
295
+ }
296
+
297
+ //Load the base classes and utilities
298
+ require_once BLC_DIRECTORY . '/includes/links.php';
299
+ require_once BLC_DIRECTORY . '/includes/link-query.php';
300
+ require_once BLC_DIRECTORY . '/includes/instances.php';
301
+ require_once BLC_DIRECTORY . '/includes/utility-class.php';
302
+
303
+ //Load the module subsystem
304
+ require_once BLC_DIRECTORY . '/includes/modules.php';
305
+
306
+ //Load the modules that want to be executed in all contexts
307
+ $blc_module_manager->load_modules();
308
+
309
+ if ( is_admin() || defined('DOING_CRON') ){
310
+
311
+ //It's an admin-side or Cron request. Load the core.
312
+ require_once BLC_DIRECTORY . '/core/core.php';
313
+ $ws_link_checker = new wsBrokenLinkChecker( BLC_PLUGIN_FILE, $blc_config_manager );
314
+
315
+ } else {
316
+
317
+ //This is user-side request, so we don't need to load the core.
318
+ //We might need to inject the CSS for removed links, though.
319
+ if ( $blc_config_manager->options['mark_removed_links'] && !empty($blc_config_manager->options['removed_link_css']) ){
320
+ function blc_print_removed_link_css(){
321
+ global $blc_config_manager;
322
+ echo '<style type="text/css">',$blc_config_manager->options['removed_link_css'],'</style>';
323
+ }
324
+ add_action('wp_head', 'blc_print_removed_link_css');
325
+ }
326
+ }
327
+ }
328
+ add_action('init', 'blc_init', 2000);
329
+ } else {
330
+ //Display installation errors (if any) on the Dashboard.
331
+ function blc_print_installation_errors(){
332
+ global $blc_config_manager, $wpdb; /** @var wpdb $wpdb */
333
+ if ( $blc_config_manager->options['installation_complete'] ) {
334
+ return;
335
+ }
336
+
337
+ $messages = array(
338
+ '<strong>' . __('Broken Link Checker installation failed. Try deactivating and then reactivating the plugin.', 'broken-link-checker') . '</strong>',
339
+ );
340
+
341
+ if ( is_multisite() && is_plugin_active_for_network(plugin_basename(BLC_PLUGIN_FILE)) ) {
342
+ $messages[] = __('Please activate the plugin separately on each site. Network activation is not supported.', 'broken-link-checker');
343
+ $messages[] = '';
344
+ }
345
+
346
+ if ( ! $blc_config_manager->db_option_loaded ) {
347
+ $messages[] = sprintf(
348
+ '<strong>Failed to load plugin settings from the "%s" option.</strong>',
349
+ $blc_config_manager->option_name
350
+ );
351
+ $messages[] = '';
352
+
353
+ $serialized_config = $wpdb->get_var(
354
+ sprintf(
355
+ 'SELECT option_value FROM `%s` WHERE option_name = "%s"',
356
+ $wpdb->options,
357
+ $blc_config_manager->option_name
358
+ )
359
+ );
360
+
361
+ if ( $serialized_config === null ) {
362
+ $messages[] = "Option doesn't exist in the {$wpdb->options} table.";
363
+ } else {
364
+ $messages[] = "Option exists in the {$wpdb->options} table and has the following value:";
365
+ $messages[] = '';
366
+ $messages[] = '<textarea cols="120" rows="20">' . htmlentities($serialized_config) . '</textarea>';
367
+ }
368
+
369
+ } else {
370
+ $logger = new blcCachedOptionLogger('blc_installation_log');
371
+ $messages = array_merge(
372
+ $messages,
373
+ array(
374
+ 'installation_complete = ' . (isset($blc_config_manager->options['installation_complete']) ? intval($blc_config_manager->options['installation_complete']) : 'no value'),
375
+ 'installation_flag_cleared_on = ' . $blc_config_manager->options['installation_flag_cleared_on'],
376
+ 'installation_flag_set_on = ' . $blc_config_manager->options['installation_flag_set_on'],
377
+ '',
378
+ '<em>Installation log follows :</em>'
379
+ ),
380
+ $logger->get_messages()
381
+ );
382
+ }
383
+
384
+ echo "<div class='error'><p>", implode("<br>\n", $messages), "</p></div>";
385
+ }
386
+ add_action('admin_notices', 'blc_print_installation_errors');
387
+ }
388
+
389
  }
css/links-page.css CHANGED
@@ -1,499 +1,499 @@
1
- /* Link table layout */
2
-
3
- table#blc-links {
4
- width: 100%;
5
- table-layout: fixed;
6
- }
7
-
8
- tr.blc-link-details {
9
- display: none;
10
- }
11
-
12
- .blc-detail-container {
13
- display: block;
14
- width: 100%;
15
- }
16
-
17
- .blc-detail-block {
18
- width: 50%;
19
- }
20
-
21
- /* Column headers */
22
-
23
- th.column-new-url {
24
- width: 33%;
25
- }
26
-
27
- th.column-status {
28
- width: 12em;
29
- }
30
-
31
- th.column-new-link-text {
32
-
33
- }
34
-
35
- th.column-used-in {
36
-
37
- }
38
-
39
- th.column-source {
40
- width: 33%;
41
- }
42
-
43
- th.column-link-text {
44
- width: 24%;
45
- }
46
-
47
- th.column-url {
48
- width: 35%;
49
- }
50
-
51
- th.column-last-checked {
52
- width: 10em;
53
- }
54
-
55
- th.column-broken-for {
56
- width: 10em;
57
- }
58
-
59
- th.column-instance-count {
60
- width: 10em;
61
- }
62
-
63
- /* Cells */
64
-
65
- td.column-new-url,
66
- td.column-used-in
67
- {
68
- /*
69
- overflow: hidden;
70
- white-space: nowrap;
71
- text-overflow: ellipsis;
72
- -o-text-overflow: ellipsis;
73
- */
74
- }
75
-
76
- td.column-new-url .row-actions,
77
- td.column-url .row-actions
78
- {
79
- margin-left: 16px;
80
- }
81
-
82
- td.column-new-url .mini-status {
83
- margin-left: 16px;
84
- color: black;
85
- }
86
-
87
- table.mini-status {
88
- border-spacing: 0;
89
- }
90
-
91
- /* Styles for broken links, redirects and other link states or types */
92
-
93
- .blc-redirect .blc-link-url {
94
- background-image: url("../images/blue_dot.png");
95
- }
96
-
97
- .link-status-error .blc-link-url {
98
- background-image: url("../images/font-awesome/font-awesome-exclamation-sign.png");
99
- }
100
-
101
- .link-status-warning .blc-link-url {
102
- background-image: url("../images/font-awesome/font-awesome-warning-sign.png");
103
- }
104
-
105
- .blc-excluded-link {
106
- background-color: #E2E2E2;
107
- }
108
-
109
- .blc-permanently-broken {
110
-
111
- }
112
-
113
- .blc-permanently-broken-hl {
114
- background-image: url("../images/red_highlight.png");
115
- }
116
-
117
-
118
- /* The "Status" column */
119
- td.column-status {
120
- cursor: pointer;
121
- }
122
-
123
- .mini-status, .mini-status th, .mini-status td {
124
- border: 0;
125
- font-size: 1em;
126
- padding: 0;
127
- color: gray;
128
- }
129
-
130
- .mini-status div {
131
- display: inline-block;
132
- }
133
-
134
- .mini-status .status-text {
135
- color: black;
136
- }
137
-
138
- .link-status-unknown td.column-status .http-code {
139
- display: none;
140
- }
141
-
142
-
143
- /* Status colors */
144
- .color-code-link-status .mini-status .http-code,
145
- .color-code-link-status .mini-status .status-text
146
- {
147
- font-weight: bold;
148
- }
149
-
150
- .color-code-link-status .link-status-unknown td.column-status .status-text,
151
- .color-code-link-status .link-status-unknown td.column-status .http-code
152
- {
153
- color: gray;
154
- font-weight: normal;
155
- }
156
-
157
- .color-code-link-status .link-status-ok td.column-status .status-text,
158
- .color-code-link-status .link-status-ok td.column-status .http-code
159
- {
160
- color: green;
161
- }
162
-
163
- .color-code-link-status .link-status-info td.column-status .status-text,
164
- .color-code-link-status .link-status-info td.column-status .http-code
165
- {
166
- color: blue;
167
- }
168
-
169
- .color-code-link-status .link-status-warning td.column-status .status-text,
170
- .color-code-link-status .link-status-warning td.column-status .http-code
171
- {
172
- color: #FF8C00; /*#FFA500*/
173
- }
174
-
175
- .color-code-link-status .link-status-error td.column-status .status-text,
176
- .color-code-link-status .link-status-error td.column-status .http-code
177
- {
178
- color: red;
179
- }
180
-
181
-
182
- /* "Compact" view */
183
-
184
- .compact td.column-url,
185
- .compact td.column-status,
186
- .compact td.column-link-text,
187
- .compact td.column-used-in,
188
- .compact td.column-new-url,
189
- .compact td.column-source,
190
- .compact td.column-new-link-text,
191
- .compact td.column-redirect-url
192
- {
193
- overflow: hidden;
194
- -o-text-overflow: ellipsis;
195
- text-overflow: ellipsis;
196
- white-space: nowrap;
197
- word-wrap: normal;
198
- }
199
-
200
- .compact .link-last-checked,
201
- .compact .link-broken-for,
202
- .compact .link-text
203
- {
204
- display: none;
205
- }
206
-
207
-
208
- /* Only show the "Details" link in the compact view. */
209
- #blc-links .column-status .row-actions {
210
- display: none;
211
- }
212
- #blc-links.compact .column-status .row-actions {
213
- display: block;
214
- }
215
-
216
- /* Misc table styles */
217
-
218
- .blc-link-url {
219
- padding-left: 16px;
220
- overflow: hidden;
221
-
222
- background-image: none;
223
- background-position: left center;
224
- background-repeat: no-repeat;
225
- }
226
-
227
- td.column-new-url { /* The URL never wraps */
228
- word-wrap: normal;
229
- white-space: nowrap;
230
- text-overflow: ellipsis;
231
- -o-text-overflow: ellipsis;
232
- overflow-x: hidden;
233
- }
234
-
235
- td.column-new-url .row-actions { /* However, the action links below the URL can wrap. */
236
- word-wrap: normal;
237
- white-space: normal;
238
- }
239
-
240
- .blc-link-editor {
241
- font-size: 1em;
242
- width: 95%;
243
- margin-left: 12px;
244
- margin-top: -1px;
245
- }
246
-
247
- .blc-url-editor-buttons {
248
- margin-left: 12px;
249
- margin-top: 2px;
250
- display: none;
251
- width: 95%;
252
- }
253
-
254
- img.waiting {
255
- float:right;
256
- padding:4px 8px 0;
257
- vertical-align:top;
258
- }
259
-
260
- td.column-link-text, td.column-new-link-text {
261
- cursor: pointer;
262
- }
263
-
264
- .blc-small-image {
265
- vertical-align: middle;
266
- }
267
-
268
-
269
- /* Inline editor */
270
-
271
- .blc-inline-editor label {
272
- display: block;
273
- margin: 0.2em 0;
274
- }
275
-
276
- .blc-inline-editor .blc-input-text-wrap {
277
- display: block;
278
- margin-left: 5em;
279
- }
280
-
281
- .blc-inline-editor .blc-input-text-wrap input[type="text"] {
282
- width: 100%;
283
- }
284
-
285
- .blc-inline-editor .blc-input-text-wrap textarea {
286
- width: 100%;
287
- }
288
-
289
-
290
- .blc-inline-editor label span.title {
291
- display: block;
292
- float: left;
293
- width: 5em;
294
- }
295
-
296
- .blc-inline-editor span.title {
297
- line-height: 1.8em;
298
- }
299
-
300
- .blc-inline-editor-content > h4:first-child {
301
- margin-top: 0.4em;
302
- }
303
-
304
- /* Inline editor: suggestion list */
305
- ul.blc-suggestion-list {
306
- list-style: disc outside;
307
- }
308
-
309
- ul.blc-suggestion-list li {
310
- margin-left: 1.5em;
311
- }
312
-
313
- .blc-suggestion-details {
314
- display: inline-block;
315
- width: 465px;
316
-
317
- overflow: hidden;
318
- white-space: nowrap;
319
- text-overflow: ellipsis;
320
- word-wrap: normal;
321
-
322
- vertical-align: top;
323
- }
324
-
325
- .blc-suggestion-list input.blc-use-url-button {
326
- float: right;
327
- max-width: 110px;
328
- margin-top: 10px;
329
- }
330
-
331
- .blc-suggestion-list code.blc-suggestion-url {
332
- display: block;
333
-
334
- overflow: hidden;
335
- text-overflow: ellipsis;
336
-
337
- white-space: nowrap;
338
- word-wrap: normal;
339
- }
340
-
341
- .blc-suggestion-list .blc-suggestion-name {
342
- display: block;
343
- }
344
-
345
-
346
- .blc-inline-editor-buttons {
347
- clear: both;
348
- margin: 0.5em 0 0;
349
- padding: 0.5em;
350
- }
351
-
352
- .blc-inline-editor-content {
353
- max-width: 600px;
354
- }
355
-
356
-
357
- /* Bulk edit form */
358
- #bulk-edit {
359
- display: none;
360
- }
361
-
362
- #bulk-edit-wrap {
363
- max-width: 600px;
364
- padding-left: 0.3em;
365
- }
366
-
367
- #bulk-edit fieldset {
368
- font-size: 12px;
369
- }
370
-
371
- #bulk-edit h4 {
372
- margin: 0.4em 0 0.8em;
373
- /*display: none;*/
374
- }
375
-
376
- #bulk-edit label {
377
- display: block;
378
- margin-bottom: 0.2em;
379
- margin-left: 0;
380
- margin-right: 0;
381
- margin-top: 0.2em;
382
- }
383
-
384
- #bulk-edit .title {
385
- display: block;
386
- float: left;
387
- width: 15%;
388
- font-size: 12px;
389
- line-height: 1.8em;
390
- }
391
-
392
- #bulk-edit input.text {
393
- display: block;
394
- width: 84%;
395
- }
396
-
397
- #bulk-edit-options label {
398
- float: left;
399
- margin-right: 2em;
400
- }
401
-
402
- #bulk-edit p.submit {
403
- padding-top: 0.5em;
404
- }
405
-
406
- /* Search form */
407
-
408
- .blc-search-container {
409
- background : white !important;
410
- border: 3px solid #EEEEEE;
411
- padding: 12px;
412
-
413
- border-radius: 6px;
414
- -moz-border-radius: 6px;
415
- -webkit-border-radius: 6px;
416
- }
417
-
418
- .blc-search-container .ui-dialog-titlebar {
419
- display: none;
420
- margin: 0;
421
- }
422
-
423
- #search-links-dialog {
424
- display: none;
425
- }
426
-
427
- #search-links-dialog label, #search-links-dialog input.text, #search-links-dialog select { display:block; }
428
-
429
- #search-links-dialog input.text,
430
- #search-links-dialog select{
431
- margin-bottom: 12px;
432
- width: 290px;
433
- }
434
-
435
- #search-links-dialog select#s_filter {
436
- width: auto;
437
- }
438
-
439
- #search-links-dialog fieldset {
440
- padding:0;
441
- border:0;
442
- margin-top:25px;
443
- }
444
-
445
- #blc-search-button-row {
446
- text-align: center;
447
- }
448
-
449
- #blc-search-button-row input {
450
- margin-left: 8px;
451
- margin-right: 8px;
452
- margin-top: 8px;
453
- }
454
-
455
- .blc-inline-form {
456
- display: inline;
457
- }
458
-
459
- div.search-box{
460
- float: right;
461
- margin-top: -5px;
462
- margin-right: 0;
463
- margin-bottom: 0;
464
- margin-left: 0;
465
- }
466
-
467
- #s_link_type optgroup {
468
- font-style: normal;
469
- font-size: 13px;
470
- }
471
-
472
- #s_link_type optgroup option {
473
- margin-left: 1em;
474
- }
475
-
476
- /* Filter-related styles */
477
- .base-filter-all .blc-dismiss-action {
478
- display: none;
479
- }
480
-
481
- /* Miscellaneous styles */
482
-
483
- #blc-more-plugins-link-wrap,
484
- #blc-plugin-news-link-wrap {
485
- background: #00C31F none;
486
- }
487
-
488
- #screen-meta-links #blc-more-plugins-link,
489
- #screen-meta-links #blc-plugin-news-link {
490
- font-weight: bold;
491
- color: #DEFFD8;
492
- text-shadow: none;
493
- }
494
-
495
- #screen-meta-links #blc-more-plugins-link-wrap a.show-settings:hover,
496
- #screen-meta-links #blc-plugin-news-link-wrap a.show-settings:hover
497
- {
498
- color: white;
499
  }
1
+ /* Link table layout */
2
+
3
+ table#blc-links {
4
+ width: 100%;
5
+ table-layout: fixed;
6
+ }
7
+
8
+ tr.blc-link-details {
9
+ display: none;
10
+ }
11
+
12
+ .blc-detail-container {
13
+ display: block;
14
+ width: 100%;
15
+ }
16
+
17
+ .blc-detail-block {
18
+ width: 50%;
19
+ }
20
+
21
+ /* Column headers */
22
+
23
+ th.column-new-url {
24
+ width: 33%;
25
+ }
26
+
27
+ th.column-status {
28
+ width: 12em;
29
+ }
30
+
31
+ th.column-new-link-text {
32
+
33
+ }
34
+
35
+ th.column-used-in {
36
+
37
+ }
38
+
39
+ th.column-source {
40
+ width: 33%;
41
+ }
42
+
43
+ th.column-link-text {
44
+ width: 24%;
45
+ }
46
+
47
+ th.column-url {
48
+ width: 35%;
49
+ }
50
+
51
+ th.column-last-checked {
52
+ width: 10em;
53
+ }
54
+
55
+ th.column-broken-for {
56
+ width: 10em;
57
+ }
58
+
59
+ th.column-instance-count {
60
+ width: 10em;
61
+ }
62
+
63
+ /* Cells */
64
+
65
+ td.column-new-url,
66
+ td.column-used-in
67
+ {
68
+ /*
69
+ overflow: hidden;
70
+ white-space: nowrap;
71
+ text-overflow: ellipsis;
72
+ -o-text-overflow: ellipsis;
73
+ */
74
+ }
75
+
76
+ td.column-new-url .row-actions,
77
+ td.column-url .row-actions
78
+ {
79
+ margin-left: 16px;
80
+ }
81
+
82
+ td.column-new-url .mini-status {
83
+ margin-left: 16px;
84
+ color: black;
85
+ }
86
+
87
+ table.mini-status {
88
+ border-spacing: 0;
89
+ }
90
+
91
+ /* Styles for broken links, redirects and other link states or types */
92
+
93
+ .blc-redirect .blc-link-url {
94
+ background-image: url("../images/blue_dot.png");
95
+ }
96
+
97
+ .link-status-error .blc-link-url {
98
+ background-image: url("../images/font-awesome/font-awesome-exclamation-sign.png");
99
+ }
100
+
101
+ .link-status-warning .blc-link-url {
102
+ background-image: url("../images/font-awesome/font-awesome-warning-sign.png");
103
+ }
104
+
105
+ .blc-excluded-link {
106
+ background-color: #E2E2E2;
107
+ }
108
+
109
+ .blc-permanently-broken {
110
+
111
+ }
112
+
113
+ .blc-permanently-broken-hl {
114
+ background-image: url("../images/red_highlight.png");
115
+ }
116
+
117
+
118
+ /* The "Status" column */
119
+ td.column-status {
120
+ cursor: pointer;
121
+ }
122
+
123
+ .mini-status, .mini-status th, .mini-status td {
124
+ border: 0;
125
+ font-size: 1em;
126
+ padding: 0;
127
+ color: gray;
128
+ }
129
+
130
+ .mini-status div {
131
+ display: inline-block;
132
+ }
133
+
134
+ .mini-status .status-text {
135
+ color: black;
136
+ }
137
+
138
+ .link-status-unknown td.column-status .http-code {
139
+ display: none;
140
+ }
141
+
142
+
143
+ /* Status colors */
144
+ .color-code-link-status .mini-status .http-code,
145
+ .color-code-link-status .mini-status .status-text
146
+ {
147
+ font-weight: bold;
148
+ }
149
+
150
+ .color-code-link-status .link-status-unknown td.column-status .status-text,
151
+ .color-code-link-status .link-status-unknown td.column-status .http-code
152
+ {
153
+ color: gray;
154
+ font-weight: normal;
155
+ }
156
+
157
+ .color-code-link-status .link-status-ok td.column-status .status-text,
158
+ .color-code-link-status .link-status-ok td.column-status .http-code
159
+ {
160
+ color: green;
161
+ }
162
+
163
+ .color-code-link-status .link-status-info td.column-status .status-text,
164
+ .color-code-link-status .link-status-info td.column-status .http-code
165
+ {
166
+ color: blue;
167
+ }
168
+
169
+ .color-code-link-status .link-status-warning td.column-status .status-text,
170
+ .color-code-link-status .link-status-warning td.column-status .http-code
171
+ {
172
+ color: #FF8C00; /*#FFA500*/
173
+ }
174
+
175
+ .color-code-link-status .link-status-error td.column-status .status-text,
176
+ .color-code-link-status .link-status-error td.column-status .http-code
177
+ {
178
+ color: red;
179
+ }
180
+
181
+
182
+ /* "Compact" view */
183
+
184
+ .compact td.column-url,
185
+ .compact td.column-status,
186
+ .compact td.column-link-text,
187
+ .compact td.column-used-in,
188
+ .compact td.column-new-url,
189
+ .compact td.column-source,
190
+ .compact td.column-new-link-text,
191
+ .compact td.column-redirect-url
192
+ {
193
+ overflow: hidden;
194
+ -o-text-overflow: ellipsis;
195
+ text-overflow: ellipsis;
196
+ white-space: nowrap;
197
+ word-wrap: normal;
198
+ }
199
+
200
+ .compact .link-last-checked,
201
+ .compact .link-broken-for,
202
+ .compact .link-text
203
+ {
204
+ display: none;
205
+ }
206
+
207
+
208
+ /* Only show the "Details" link in the compact view. */
209
+ #blc-links .column-status .row-actions {
210
+ display: none;
211
+ }
212
+ #blc-links.compact .column-status .row-actions {
213
+ display: block;
214
+ }
215
+
216
+ /* Misc table styles */
217
+
218
+ .blc-link-url {
219
+ padding-left: 16px;
220
+ overflow: hidden;
221
+
222
+ background-image: none;
223
+ background-position: left center;
224
+ background-repeat: no-repeat;
225
+ }
226
+
227
+ td.column-new-url { /* The URL never wraps */
228
+ word-wrap: normal;
229
+ white-space: nowrap;
230
+ text-overflow: ellipsis;
231
+ -o-text-overflow: ellipsis;
232
+ overflow-x: hidden;
233
+ }
234
+
235
+ td.column-new-url .row-actions { /* However, the action links below the URL can wrap. */
236
+ word-wrap: normal;
237
+ white-space: normal;
238
+ }
239
+
240
+ .blc-link-editor {
241
+ font-size: 1em;
242
+ width: 95%;
243
+ margin-left: 12px;
244
+ margin-top: -1px;
245
+ }
246
+
247
+ .blc-url-editor-buttons {
248
+ margin-left: 12px;
249
+ margin-top: 2px;
250
+ display: none;
251
+ width: 95%;
252
+ }
253
+
254
+ img.waiting {
255
+ float:right;
256
+ padding:4px 8px 0;
257
+ vertical-align:top;
258
+ }
259
+
260
+ td.column-link-text, td.column-new-link-text {
261
+ cursor: pointer;
262
+ }
263
+
264
+ .blc-small-image {
265
+ vertical-align: middle;
266
+ }
267
+
268
+
269
+ /* Inline editor */
270
+
271
+ .blc-inline-editor label {
272
+ display: block;
273
+ margin: 0.2em 0;
274
+ }
275
+
276
+ .blc-inline-editor .blc-input-text-wrap {
277
+ display: block;
278
+ margin-left: 5em;
279
+ }
280
+
281
+ .blc-inline-editor .blc-input-text-wrap input[type="text"] {
282
+ width: 100%;
283
+ }
284
+
285
+ .blc-inline-editor .blc-input-text-wrap textarea {
286
+ width: 100%;
287
+ }
288
+
289
+
290
+ .blc-inline-editor label span.title {
291
+ display: block;
292
+ float: left;
293
+ width: 5em;
294
+ }
295
+
296
+ .blc-inline-editor span.title {
297
+ line-height: 1.8em;
298
+ }
299
+
300
+ .blc-inline-editor-content > h4:first-child {
301
+ margin-top: 0.4em;
302
+ }
303
+
304
+ /* Inline editor: suggestion list */
305
+ ul.blc-suggestion-list {
306
+ list-style: disc outside;
307
+ }
308
+
309
+ ul.blc-suggestion-list li {
310
+ margin-left: 1.5em;
311
+ }
312
+
313
+ .blc-suggestion-details {
314
+ display: inline-block;
315
+ width: 465px;
316
+
317
+ overflow: hidden;
318
+ white-space: nowrap;
319
+ text-overflow: ellipsis;
320
+ word-wrap: normal;
321
+
322
+ vertical-align: top;
323
+ }
324
+
325
+ .blc-suggestion-list input.blc-use-url-button {
326
+ float: right;
327
+ max-width: 110px;
328
+ margin-top: 10px;
329
+ }
330
+
331
+ .blc-suggestion-list code.blc-suggestion-url {
332
+ display: block;
333
+
334
+ overflow: hidden;
335
+ text-overflow: ellipsis;
336
+
337
+ white-space: nowrap;
338
+ word-wrap: normal;
339
+ }
340
+
341
+ .blc-suggestion-list .blc-suggestion-name {
342
+ display: block;
343
+ }
344
+
345
+
346
+ .blc-inline-editor-buttons {
347
+ clear: both;
348
+ margin: 0.5em 0 0;
349
+ padding: 0.5em;
350
+ }
351
+
352
+ .blc-inline-editor-content {
353
+ max-width: 600px;
354
+ }
355
+
356
+
357
+ /* Bulk edit form */
358
+ #bulk-edit {
359
+ display: none;
360
+ }
361
+
362
+ #bulk-edit-wrap {
363
+ max-width: 600px;
364
+ padding-left: 0.3em;
365
+ }
366
+
367
+ #bulk-edit fieldset {
368
+ font-size: 12px;
369
+ }
370
+
371
+ #bulk-edit h4 {
372
+ margin: 0.4em 0 0.8em;
373
+ /*display: none;*/
374
+ }
375
+
376
+ #bulk-edit label {
377
+ display: block;
378
+ margin-bottom: 0.2em;
379
+ margin-left: 0;
380
+ margin-right: 0;
381
+ margin-top: 0.2em;
382
+ }
383
+
384
+ #bulk-edit .title {
385
+ display: block;
386
+ float: left;
387
+ width: 15%;
388
+ font-size: 12px;
389
+ line-height: 1.8em;
390
+ }
391
+
392
+ #bulk-edit input.text {
393
+ display: block;
394
+ width: 84%;
395
+ }
396
+
397
+ #bulk-edit-options label {
398
+ float: left;
399
+ margin-right: 2em;
400
+ }
401
+
402
+ #bulk-edit p.submit {
403
+ padding-top: 0.5em;
404
+ }
405
+
406
+ /* Search form */
407
+
408
+ .blc-search-container {
409
+ background : white !important;
410
+ border: 3px solid #EEEEEE;
411
+ padding: 12px;
412
+
413
+ border-radius: 6px;
414
+ -moz-border-radius: 6px;
415
+ -webkit-border-radius: 6px;
416
+ }
417
+
418
+ .blc-search-container .ui-dialog-titlebar {
419
+ display: none;
420
+ margin: 0;
421
+ }
422
+
423
+ #search-links-dialog {
424
+ display: none;
425
+ }
426
+
427
+ #search-links-dialog label, #search-links-dialog input.text, #search-links-dialog select { display:block; }
428
+
429
+ #search-links-dialog input.text,
430
+ #search-links-dialog select{
431
+ margin-bottom: 12px;
432
+ width: 290px;
433
+ }
434
+
435
+ #search-links-dialog select#s_filter {
436
+ width: auto;
437
+ }
438
+
439
+ #search-links-dialog fieldset {
440
+ padding:0;
441
+ border:0;
442
+ margin-top:25px;
443
+ }
444
+
445
+ #blc-search-button-row {
446
+ text-align: center;
447
+ }
448
+
449
+ #blc-search-button-row input {
450
+ margin-left: 8px;
451
+ margin-right: 8px;
452
+ margin-top: 8px;
453
+ }
454
+
455
+ .blc-inline-form {
456
+ display: inline;
457
+ }
458
+
459
+ div.search-box{
460
+ float: right;
461
+ margin-top: -5px;
462
+ margin-right: 0;
463
+ margin-bottom: 0;
464
+ margin-left: 0;
465
+ }
466
+
467
+ #s_link_type optgroup {
468
+ font-style: normal;
469
+ font-size: 13px;
470
+ }
471
+
472
+ #s_link_type optgroup option {
473
+ margin-left: 1em;
474
+ }
475
+
476
+ /* Filter-related styles */
477
+ .base-filter-all .blc-dismiss-action {
478
+ display: none;
479
+ }
480
+
481
+ /* Miscellaneous styles */
482
+
483
+ #blc-more-plugins-link-wrap,
484
+ #blc-plugin-news-link-wrap {
485
+ background: #00C31F none;
486
+ }
487
+
488
+ #screen-meta-links #blc-more-plugins-link,
489
+ #screen-meta-links #blc-plugin-news-link {
490
+ font-weight: bold;
491
+ color: #DEFFD8;
492
+ text-shadow: none;
493
+ }
494
+
495
+ #screen-meta-links #blc-more-plugins-link-wrap a.show-settings:hover,
496
+ #screen-meta-links #blc-plugin-news-link-wrap a.show-settings:hover
497
+ {
498
+ color: white;
499
  }
css/options-page.css CHANGED
@@ -1,243 +1,243 @@
1
- /*
2
- Sidebar
3
- */
4
-
5
- #blc-sidebar {
6
- clear: right;
7
- float: right;
8
- position: relative;
9
- width: 255px;
10
- }
11
-
12
- #blc-admin-content {
13
- clear: left;
14
- float: left;
15
- margin-right: -275px;
16
- width: 100%;
17
- }
18
-
19
- #blc-admin-content > * {
20
- margin-right: 267px;
21
- }
22
-
23
- #advertising {
24
- border-color: green !important;
25
- border-width: 2px;
26
- }
27
-
28
- #advertising h3 {
29
- color: green;
30
- }
31
-
32
- #advertising .inside {
33
- text-align: left;
34
- }
35
-
36
- .ws-ame-ad-copy {
37
- font-size: 1.2em;
38
- font-style: italic;
39
- }
40
- .ws-ame-ad-link {
41
- font-size: 1.1em;
42
- font-weight: bold;
43
- }
44
-
45
- #themefuse-ad .inside,
46
- #managewp-ad .inside,
47
- #embedplus-ad .inside
48
- {
49
- padding: 2px 0 0 0;
50
- margin: 0;
51
- text-align: center;
52
- }
53
-
54
- #embedplus-ad .inside {
55
- padding-top: 0;
56
- }
57
-
58
- #embedplus-ad {
59
- border-style: none;
60
- background-color: transparent;
61
- box-shadow: none;
62
- }
63
-
64
-
65
- #link_checker_options .form-table th {
66
- width: 25%;
67
- min-width: 110px;
68
- max-width: 200px;
69
- }
70
-
71
- #blc-debug-info-toggle {
72
- font-size: smaller;
73
- }
74
-
75
- .blc-debug-item-ok {
76
- background-color: #d7ffa2;
77
- }
78
- .blc-debug-item-warning {
79
- background-color: #fcffa2;
80
- }
81
- .blc-debug-item-error {
82
- background-color: #ffc4c4;
83
- }
84
-
85
- #blc-debug-info {
86
- display: none;
87
-
88
- text-align: left;
89
-
90
- border-width: 1px;
91
- border-color: gray;
92
- border-style: solid;
93
-
94
- border-spacing: 0;
95
- border-collapse: collapse;
96
- }
97
-
98
- #blc-debug-info th, #blc-debug-info td {
99
- padding: 6px;
100
- font-weight: normal;
101
- text-shadow: none;
102
-
103
- border-width: 1px ;
104
- border-color: silver;
105
- border-style: solid;
106
-
107
- border-collapse: collapse;
108
- }
109
-
110
- .blc-toggle-link {
111
- /*padding-left: 20px; */
112
- }
113
-
114
- .module-container {
115
- margin: 1em 0;
116
- font-size: 12px;
117
- line-height: 140%;
118
- }
119
-
120
- .module-extra-settings {
121
- margin-top: 0.5em;
122
- }
123
-
124
- .form-table td p:first-child, .form-table td .module-container:first-child {
125
- margin-top: 0.2em;
126
- }
127
-
128
- .module-requires-pro {
129
- color: gray;
130
- }
131
-
132
- #blc-tabs {
133
- margin-top: 0.5em;
134
- }
135
-
136
- /* Tab navigation */
137
- ul.ui-tabs-nav {
138
- margin-bottom: -1px;
139
- height: 31px;
140
- padding-left: 1.5em;
141
-
142
- overflow: hidden;
143
- white-space: nowrap;
144
- padding-right: 3.5em;
145
- }
146
-
147
- ul.ui-tabs-nav li {
148
- display: inline-block;
149
- height: 29px;
150
- margin: 0 0.5em 0 0;
151
-
152
- border: 1px solid #DFDFDF;
153
-
154
- background: none repeat scroll 0 0 #F4F4F4;
155
-
156
- border-top-right-radius: 6px;
157
- border-top-left-radius: 6px;
158
-
159
- -moz-border-radius-topright: 6px;
160
- -moz-border-radius-topleft: 6px;
161
-
162
- -webkit-border-top-right-radius: 6px;
163
- -webkit-border-top-left-radius: 6px;
164
-
165
- font-weight: bold;
166
- text-shadow: 0 1px 0 #FFFFFF;
167
-
168
- max-width: 20%;
169
- overflow: hidden;
170
- text-align: center;
171
- }
172
-
173
- #tab-button-which {
174
- max-width: 25%;
175
- }
176
-
177
- ul.ui-tabs-nav li.ui-tabs-selected,
178
- ul.ui-tabs-nav li.ui-tabs-active
179
- {
180
- background: white;
181
- color: black;
182
- border-bottom-color: white;
183
- }
184
-
185
- ul.ui-tabs-nav li a {
186
- display: inline-block;
187
- height: 17px;
188
- text-decoration: none;
189
- color: #C1C1C1;
190
- padding: 0.5em 1em 0.6em 1em;
191
- }
192
-
193
- ul.ui-tabs-nav li a:focus {
194
- outline: none;
195
- }
196
-
197
- ul.ui-tabs-nav li.ui-tabs-selected a,
198
- ul.ui-tabs-nav li.ui-tabs-active a {
199
- color: black;
200
- outline: none;
201
- }
202
-
203
- /* Tab panels */
204
-
205
- .ui-tabs .blc-section {
206
- border: 1px solid #DFDFDF;
207
- border-top-width: 1px;
208
-
209
- padding-bottom: 1em;
210
-
211
- background: white;
212
-
213
- border-radius: 6px;
214
- -moz-border-radius: 6px;
215
- -webkit-border-radius: 6px;
216
-
217
- border-top-right-radius: 6px;
218
- border-bottom-right-radius: 6px;
219
- border-bottom-left-radius: 6px;
220
-
221
- -moz-border-radius-topright: 6px;
222
- -moz-border-radius-bottomright: 6px;
223
- -moz-border-radius-bottomleft: 6px;
224
-
225
- -webkit-border-top-right-radius: 6px;
226
- -webkit-border-bottom-right-radius: 6px;
227
- -webkit-border-bottom-left-radius: 6px;
228
- }
229
-
230
- .blc-section table.form-table th {
231
- padding-left: 10px;
232
- }
233
-
234
- /* Right sidebar */
235
-
236
- #blc-settings-wrap .hndle {
237
- cursor: default;
238
- }
239
-
240
- /* Miscellaneous */
241
- td:target, label:target {
242
- background: #ffffaa;
243
  }
1
+ /*
2
+ Sidebar
3
+ */
4
+
5
+ #blc-sidebar {
6
+ clear: right;
7
+ float: right;
8
+ position: relative;
9
+ width: 255px;
10
+ }
11
+
12
+ #blc-admin-content {
13
+ clear: left;
14
+ float: left;
15
+ margin-right: -275px;
16
+ width: 100%;
17
+ }
18
+
19
+ #blc-admin-content > * {
20
+ margin-right: 267px;
21
+ }
22
+
23
+ #advertising {
24
+ border-color: green !important;
25
+ border-width: 2px;
26
+ }
27
+
28
+ #advertising h3 {
29
+ color: green;
30
+ }
31
+
32
+ #advertising .inside {
33
+ text-align: left;
34
+ }
35
+
36
+ .ws-ame-ad-copy {
37
+ font-size: 1.2em;
38
+ font-style: italic;
39
+ }
40
+ .ws-ame-ad-link {
41
+ font-size: 1.1em;
42
+ font-weight: bold;
43
+ }
44
+
45
+ #themefuse-ad .inside,
46
+ #managewp-ad .inside,
47
+ #embedplus-ad .inside
48
+ {
49
+ padding: 2px 0 0 0;
50
+ margin: 0;
51
+ text-align: center;
52
+ }
53
+
54
+ #embedplus-ad .inside {
55
+ padding-top: 0;
56
+ }
57
+
58
+ #embedplus-ad {
59
+ border-style: none;
60
+ background-color: transparent;
61
+ box-shadow: none;
62
+ }
63
+
64
+
65
+ #link_checker_options .form-table th {
66
+ width: 25%;
67
+ min-width: 110px;
68
+ max-width: 200px;
69
+ }
70
+
71
+ #blc-debug-info-toggle {
72
+ font-size: smaller;
73
+ }
74
+
75
+ .blc-debug-item-ok {
76
+ background-color: #d7ffa2;
77
+ }
78
+ .blc-debug-item-warning {
79
+ background-color: #fcffa2;
80
+ }
81
+ .blc-debug-item-error {
82
+ background-color: #ffc4c4;
83
+ }
84
+
85
+ #blc-debug-info {
86
+ display: none;
87
+
88
+ text-align: left;
89
+
90
+ border-width: 1px;
91
+ border-color: gray;
92
+ border-style: solid;
93
+
94
+ border-spacing: 0;
95
+ border-collapse: collapse;
96
+ }
97
+
98
+ #blc-debug-info th, #blc-debug-info td {
99
+ padding: 6px;
100
+ font-weight: normal;
101
+ text-shadow: none;
102
+
103
+ border-width: 1px ;
104
+ border-color: silver;
105
+ border-style: solid;
106
+
107
+ border-collapse: collapse;
108
+ }
109
+
110
+ .blc-toggle-link {
111
+ /*padding-left: 20px; */
112
+ }
113
+
114
+ .module-container {
115
+ margin: 1em 0;
116
+ font-size: 12px;
117
+ line-height: 140%;
118
+ }
119
+
120
+ .module-extra-settings {
121
+ margin-top: 0.5em;
122
+ }
123
+
124
+ .form-table td p:first-child, .form-table td .module-container:first-child {
125
+ margin-top: 0.2em;
126
+ }
127
+
128
+ .module-requires-pro {
129
+ color: gray;
130
+ }
131
+
132
+ #blc-tabs {
133
+ margin-top: 0.5em;
134
+ }
135
+
136
+ /* Tab navigation */
137
+ ul.ui-tabs-nav {
138
+ margin-bottom: -1px;
139
+ height: 31px;
140
+ padding-left: 1.5em;
141
+
142
+ overflow: hidden;
143
+ white-space: nowrap;
144
+ padding-right: 3.5em;
145
+ }
146
+
147
+ ul.ui-tabs-nav li {
148
+ display: inline-block;
149
+ height: 29px;
150
+ margin: 0 0.5em 0 0;
151
+
152
+ border: 1px solid #DFDFDF;
153
+
154
+ background: none repeat scroll 0 0 #F4F4F4;
155
+
156
+ border-top-right-radius: 6px;
157
+ border-top-left-radius: 6px;
158
+
159
+ -moz-border-radius-topright: 6px;
160
+ -moz-border-radius-topleft: 6px;
161
+
162
+ -webkit-border-top-right-radius: 6px;
163
+ -webkit-border-top-left-radius: 6px;
164
+
165
+ font-weight: bold;
166
+ text-shadow: 0 1px 0 #FFFFFF;
167
+
168
+ max-width: 20%;
169
+ overflow: hidden;
170
+ text-align: center;
171
+ }
172
+
173
+ #tab-button-which {
174
+ max-width: 25%;
175
+ }
176
+
177
+ ul.ui-tabs-nav li.ui-tabs-selected,
178
+ ul.ui-tabs-nav li.ui-tabs-active
179
+ {
180
+ background: white;
181
+ color: black;
182
+ border-bottom-color: white;
183
+ }
184
+
185
+ ul.ui-tabs-nav li a {
186
+ display: inline-block;
187
+ height: 17px;
188
+ text-decoration: none;
189
+ color: #C1C1C1;
190
+ padding: 0.5em 1em 0.6em 1em;
191
+ }
192
+
193
+ ul.ui-tabs-nav li a:focus {
194
+ outline: none;
195
+ }
196
+
197
+ ul.ui-tabs-nav li.ui-tabs-selected a,
198
+ ul.ui-tabs-nav li.ui-tabs-active a {
199
+ color: black;
200
+ outline: none;
201
+ }
202
+
203
+ /* Tab panels */
204
+
205
+ .ui-tabs .blc-section {
206
+ border: 1px solid #DFDFDF;
207
+ border-top-width: 1px;
208
+
209
+ padding-bottom: 1em;
210
+
211
+ background: white;
212
+
213
+ border-radius: 6px;
214
+ -moz-border-radius: 6px;
215
+ -webkit-border-radius: 6px;
216
+
217
+ border-top-right-radius: 6px;
218
+ border-bottom-right-radius: 6px;
219
+ border-bottom-left-radius: 6px;
220
+
221
+ -moz-border-radius-topright: 6px;
222
+ -moz-border-radius-bottomright: 6px;
223
+ -moz-border-radius-bottomleft: 6px;
224
+
225
+ -webkit-border-top-right-radius: 6px;
226
+ -webkit-border-bottom-right-radius: 6px;
227
+ -webkit-border-bottom-left-radius: 6px;
228
+ }
229
+
230
+ .blc-section table.form-table th {
231
+ padding-left: 10px;
232
+ }
233
+
234
+ /* Right sidebar */
235
+
236
+ #blc-settings-wrap .hndle {
237
+ cursor: default;
238
+ }
239
+
240
+ /* Miscellaneous */
241
+ td:target, label:target {
242
+ background: #ffffaa;
243
  }
idn/LICENCE CHANGED
File without changes
idn/ReadMe.txt CHANGED
File without changes
idn/idna_convert.class.php CHANGED
File without changes
idn/transcode_wrapper.php CHANGED
File without changes
idn/uctc.php CHANGED
File without changes
images/blue_dot.png CHANGED
File without changes
images/dailymotion-embed.png CHANGED
File without changes
images/font-awesome/font-awesome-code.png CHANGED
File without changes
images/font-awesome/font-awesome-comment-alt.png CHANGED
File without changes
images/font-awesome/font-awesome-exclamation-sign.png CHANGED
File without changes
images/font-awesome/font-awesome-link.png CHANGED
File without changes
images/font-awesome/font-awesome-picture.png CHANGED
File without changes
images/font-awesome/font-awesome-user.png CHANGED
File without changes
images/font-awesome/font-awesome-warning-sign.png CHANGED
File without changes
images/font-awesome/readme.txt CHANGED
@@ -1,10 +1,10 @@
1
- This directory contains icons from the Font Awesome 3.2.1 icon font:
2
- http://fortawesome.github.io/Font-Awesome/
3
-
4
- The Font Awesome font is licensed under SIL OFL 1.1:
5
- http://scripts.sil.org/OFL
6
-
7
- ------------------------------------------------------------------------------
8
-
9
- The icons have been converted to PNG and in some cases minor adjustments have
10
- been made, such as adding a background colour or a drop shadow.
1
+ This directory contains icons from the Font Awesome 3.2.1 icon font:
2
+ http://fortawesome.github.io/Font-Awesome/
3
+
4
+ The Font Awesome font is licensed under SIL OFL 1.1:
5
+ http://scripts.sil.org/OFL
6
+
7
+ ------------------------------------------------------------------------------
8
+
9
+ The icons have been converted to PNG and in some cases minor adjustments have
10
+ been made, such as adding a background colour or a drop shadow.
images/googlevideo-embed.png CHANGED
File without changes
images/red_highlight.png CHANGED
File without changes
images/vimeo-embed.png CHANGED
File without changes
images/youtube-embed.png CHANGED
File without changes
images/youtube-iframe.png CHANGED
File without changes
images/youtube-playlist-embed.png CHANGED
File without changes
includes/activation.php CHANGED
@@ -1,114 +1,114 @@
1
- <?php
2
-
3
- global $blclog, $blc_config_manager, $wpdb;
4
- $queryCnt = $wpdb->num_queries;
5
-
6
- //Completing the installation/upgrade is required for the plugin to work, so make sure
7
- //the script doesn't get aborted by (for example) the browser timing out.
8
- set_time_limit(300); //5 minutes should be plenty, anything more would probably indicate an infinite loop or a deadlock
9
- ignore_user_abort(true);
10
-
11
- //Log installation progress to a DB option
12
- $blclog = new blcCachedOptionLogger('blc_installation_log');
13
- register_shutdown_function(array(&$blclog, 'save')); //Make sure the log is saved even if the plugin crashes
14
-
15
- $blclog->clear();
16
- $blclog->info( sprintf('Plugin activated at %s.', date_i18n('Y-m-d H:i:s')) );
17
- $activation_start = microtime(true);
18
-
19
- //Reset the "installation_complete" flag
20
- $blc_config_manager->options['installation_complete'] = false;
21
- $blc_config_manager->options['installation_flag_cleared_on'] = date('c') . ' (' . microtime(true) . ')';
22
- //Note the time of the first installation (not very accurate, but still useful)
23
- if ( empty($blc_config_manager->options['first_installation_timestamp']) ){
24
- $blc_config_manager->options['first_installation_timestamp'] = time();
25
- }
26
- $blc_config_manager->save_options();
27
- $blclog->info('Installation/update begins.');
28
-
29
- //Load the base classes and utilities
30
- require_once BLC_DIRECTORY . '/includes/links.php';
31
- require_once BLC_DIRECTORY . '/includes/link-query.php';
32
- require_once BLC_DIRECTORY . '/includes/instances.php';
33
- require_once BLC_DIRECTORY . '/includes/utility-class.php';
34
-
35
- //Load the module subsystem
36
- require_once BLC_DIRECTORY . '/includes/modules.php';
37
- $moduleManager = blcModuleManager::getInstance();
38
-
39
- //If upgrading, activate/deactivate custom field and comment containers based on old ver. settings
40
- if ( isset($blc_config_manager->options['check_comment_links']) ){
41
- if ( !$blc_config_manager->options['check_comment_links'] ){
42
- $moduleManager->deactivate('comment');
43
- }
44
- unset($blc_config_manager->options['check_comment_links']);
45
- }
46
- if ( empty($blc_config_manager->options['custom_fields']) ){
47
- $moduleManager->deactivate('custom_field');
48
- }
49
- if ( empty($blc_config_manager->options['acf_fields']) ){
50
- $moduleManager->deactivate('acf_field');
51
- }
52
-
53
- //Prepare the database.
54
- $blclog->info('Upgrading the database...');
55
- $upgrade_start = microtime(true);
56
- require_once BLC_DIRECTORY . '/includes/admin/db-upgrade.php';
57
- blcDatabaseUpgrader::upgrade_database();
58
- $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $upgrade_start));
59
-
60
- //Remove invalid DB entries
61
- $blclog->info('Cleaning up the database...');
62
- $cleanup_start = microtime(true);
63
- blc_cleanup_database();
64
- $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $cleanup_start));
65
-
66
- //Notify modules that the plugin has been activated. This will cause container
67
- //modules to create and update synch. records for all new/modified posts and other items.
68
- $blclog->info('Notifying modules...');
69
- $notification_start = microtime(true);
70
- $moduleManager->plugin_activated();
71
- blc_got_unsynched_items();
72
- $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $notification_start));
73
-
74
- //Turn off load limiting if it's not available on this server.
75
- $blclog->info('Updating server load limit settings...');
76
- $load = blcUtility::get_server_load();
77
- if ( empty($load) ){
78
- $blc_config_manager->options['enable_load_limit'] = false;
79
- $blclog->info('Disable load limit. Cannot retrieve current load average.');
80
- } elseif ( $blc_config_manager->options['enable_load_limit'] && !isset($blc_config_manager->options['server_load_limit']) ) {
81
- $fifteen_minutes = floatval(end($load));
82
- $default_load_limit = round(max(min($fifteen_minutes * 2, $fifteen_minutes + 2), 4));
83
- $blc_config_manager->options['server_load_limit'] = $default_load_limit;
84
-
85
- $blclog->info(sprintf(
86
- 'Set server load limit to %.2f. Current load average is %.2f',
87
- $default_load_limit,
88
- $fifteen_minutes
89
- ));
90
- }
91
-
92
- //And optimize my DB tables, too (for good measure)
93
- $blclog->info('Optimizing the database...');
94
- $optimize_start = microtime(true);
95
- blcUtility::optimize_database();
96
- $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $optimize_start));
97
-
98
- $blclog->info('Completing installation...');
99
- $blc_config_manager->options['installation_complete'] = true;
100
- $blc_config_manager->options['installation_flag_set_on'] = date('c') . ' (' . microtime(true) . ')';
101
- if ( $blc_config_manager->save_options() ){
102
- $blclog->info('Configuration saved.');
103
- } else {
104
- $blclog->error('Error saving plugin configuration!');
105
- };
106
-
107
- $blclog->info(sprintf(
108
- 'Installation/update completed at %s with %d queries executed.',
109
- date_i18n('Y-m-d H:i:s'),
110
- $wpdb->num_queries - $queryCnt
111
- ));
112
- $blclog->info(sprintf('Total time: %.3f seconds', microtime(true) - $activation_start));
113
- $blclog->save();
114
-
1
+ <?php
2
+
3
+ global $blclog, $blc_config_manager, $wpdb;
4
+ $queryCnt = $wpdb->num_queries;
5
+
6
+ //Completing the installation/upgrade is required for the plugin to work, so make sure
7
+ //the script doesn't get aborted by (for example) the browser timing out.
8
+ set_time_limit(300); //5 minutes should be plenty, anything more would probably indicate an infinite loop or a deadlock
9
+ ignore_user_abort(true);
10
+
11
+ //Log installation progress to a DB option
12
+ $blclog = new blcCachedOptionLogger('blc_installation_log');
13
+ register_shutdown_function(array(&$blclog, 'save')); //Make sure the log is saved even if the plugin crashes
14
+
15
+ $blclog->clear();
16
+ $blclog->info( sprintf('Plugin activated at %s.', date_i18n('Y-m-d H:i:s')) );
17
+ $activation_start = microtime(true);
18
+
19
+ //Reset the "installation_complete" flag
20
+ $blc_config_manager->options['installation_complete'] = false;
21
+ $blc_config_manager->options['installation_flag_cleared_on'] = date('c') . ' (' . microtime(true) . ')';
22
+ //Note the time of the first installation (not very accurate, but still useful)
23
+ if ( empty($blc_config_manager->options['first_installation_timestamp']) ){
24
+ $blc_config_manager->options['first_installation_timestamp'] = time();
25
+ }
26
+ $blc_config_manager->save_options();
27
+ $blclog->info('Installation/update begins.');
28
+
29
+ //Load the base classes and utilities
30
+ require_once BLC_DIRECTORY . '/includes/links.php';
31
+ require_once BLC_DIRECTORY . '/includes/link-query.php';
32
+ require_once BLC_DIRECTORY . '/includes/instances.php';
33
+ require_once BLC_DIRECTORY . '/includes/utility-class.php';
34
+
35
+ //Load the module subsystem
36
+ require_once BLC_DIRECTORY . '/includes/modules.php';
37
+ $moduleManager = blcModuleManager::getInstance();
38
+
39
+ //If upgrading, activate/deactivate custom field and comment containers based on old ver. settings
40
+ if ( isset($blc_config_manager->options['check_comment_links']) ){
41
+ if ( !$blc_config_manager->options['check_comment_links'] ){
42
+ $moduleManager->deactivate('comment');
43
+ }
44
+ unset($blc_config_manager->options['check_comment_links']);
45
+ }
46
+ if ( empty($blc_config_manager->options['custom_fields']) ){
47
+ $moduleManager->deactivate('custom_field');
48
+ }
49
+ if ( empty($blc_config_manager->options['acf_fields']) ){
50
+ $moduleManager->deactivate('acf_field');
51
+ }
52
+
53
+ //Prepare the database.
54
+ $blclog->info('Upgrading the database...');
55
+ $upgrade_start = microtime(true);
56
+ require_once BLC_DIRECTORY . '/includes/admin/db-upgrade.php';
57
+ blcDatabaseUpgrader::upgrade_database();
58
+ $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $upgrade_start));
59
+
60
+ //Remove invalid DB entries
61
+ $blclog->info('Cleaning up the database...');
62
+ $cleanup_start = microtime(true);
63
+ blc_cleanup_database();
64
+ $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $cleanup_start));
65
+
66
+ //Notify modules that the plugin has been activated. This will cause container
67
+ //modules to create and update synch. records for all new/modified posts and other items.
68
+ $blclog->info('Notifying modules...');
69
+ $notification_start = microtime(true);
70
+ $moduleManager->plugin_activated();
71
+ blc_got_unsynched_items();
72
+ $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $notification_start));
73
+
74
+ //Turn off load limiting if it's not available on this server.
75
+ $blclog->info('Updating server load limit settings...');
76
+ $load = blcUtility::get_server_load();
77
+ if ( empty($load) ){
78
+ $blc_config_manager->options['enable_load_limit'] = false;
79
+ $blclog->info('Disable load limit. Cannot retrieve current load average.');
80
+ } elseif ( $blc_config_manager->options['enable_load_limit'] && !isset($blc_config_manager->options['server_load_limit']) ) {
81
+ $fifteen_minutes = floatval(end($load));
82
+ $default_load_limit = round(max(min($fifteen_minutes * 2, $fifteen_minutes + 2), 4));
83
+ $blc_config_manager->options['server_load_limit'] = $default_load_limit;
84
+
85
+ $blclog->info(sprintf(
86
+ 'Set server load limit to %.2f. Current load average is %.2f',
87
+ $default_load_limit,
88
+ $fifteen_minutes
89
+ ));
90
+ }
91
+
92
+ //And optimize my DB tables, too (for good measure)
93
+ $blclog->info('Optimizing the database...');
94
+ $optimize_start = microtime(true);
95
+ blcUtility::optimize_database();
96
+ $blclog->info(sprintf('--- Total: %.3f seconds', microtime(true) - $optimize_start));
97
+
98
+ $blclog->info('Completing installation...');
99
+ $blc_config_manager->options['installation_complete'] = true;
100
+ $blc_config_manager->options['installation_flag_set_on'] = date('c') . ' (' . microtime(true) . ')';
101
+ if ( $blc_config_manager->save_options() ){
102
+ $blclog->info('Configuration saved.');
103
+ } else {
104
+ $blclog->error('Error saving plugin configuration!');
105
+ };
106
+
107
+ $blclog->info(sprintf(
108
+ 'Installation/update completed at %s with %d queries executed.',
109
+ date_i18n('Y-m-d H:i:s'),
110
+ $wpdb->num_queries - $queryCnt
111
+ ));
112
+ $blclog->info(sprintf('Total time: %.3f seconds', microtime(true) - $activation_start));
113
+ $blclog->save();
114
+
includes/admin/db-schema.php CHANGED
@@ -1,96 +1,96 @@
1
- <?php
2
-
3
- if( !function_exists('blc_get_db_schema') ){
4
-
5
- function blc_get_db_schema(){
6
- global $wpdb;
7
-
8
- //Use the character set and collation that's configured for WP tables
9
- $charset_collate = '';
10
- if ( !empty($wpdb->charset) ){
11
- //Some German installs use "utf-8" (invalid) instead of "utf8" (valid). None of
12
- //the charset ids supported by MySQL contain dashes, so we can safely strip them.
13
- //See http://dev.mysql.com/doc/refman/5.0/en/charset-charsets.html
14
- $charset = str_replace('-', '', $wpdb->charset);
15
-
16
- $charset_collate = "DEFAULT CHARACTER SET {$charset}";
17
- }
18
- if ( !empty($wpdb->collate) ){
19
- $charset_collate .= " COLLATE {$wpdb->collate}";
20
- }
21
-
22
- $blc_db_schema = <<<EOM
23
-
24
- CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_filters` (
25
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
26
- `name` varchar(100) NOT NULL,
27
- `params` text NOT NULL,
28
-
29
- PRIMARY KEY (`id`)
30
- ) {$charset_collate};
31
-
32
- CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_instances` (
33
- `instance_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
34
- `link_id` int(10) unsigned NOT NULL,
35
- `container_id` int(10) unsigned NOT NULL,
36
- `container_type` varchar(40) NOT NULL DEFAULT 'post',
37
- `link_text` text NOT NULL DEFAULT '',
38
- `parser_type` varchar(40) NOT NULL DEFAULT 'link',
39
- `container_field` varchar(250) NOT NULL DEFAULT '',
40
- `link_context` varchar(250) NOT NULL DEFAULT '',
41
- `raw_url` text NOT NULL,
42
-
43
- PRIMARY KEY (`instance_id`),
44
- KEY `link_id` (`link_id`),
45
- KEY `source_id` (`container_type`, `container_id`),
46
- KEY `parser_type` (`parser_type`)
47
- ) {$charset_collate};
48
-
49
- CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_links` (
50
- `link_id` int(20) unsigned NOT NULL AUTO_INCREMENT,
51
- `url` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
52
- `first_failure` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
53
- `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
54
- `last_success` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
55
- `last_check_attempt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
56
- `check_count` int(4) unsigned NOT NULL DEFAULT '0',
57
- `final_url` text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
58
- `redirect_count` smallint(5) unsigned NOT NULL DEFAULT '0',
59
- `log` text NOT NULL,
60
- `http_code` smallint(6) NOT NULL DEFAULT '0',
61
- `status_code` varchar(100) DEFAULT '',
62
- `status_text` varchar(250) DEFAULT '',
63
- `request_duration` float NOT NULL DEFAULT '0',
64
- `timeout` tinyint(1) unsigned NOT NULL DEFAULT '0',
65
- `broken` tinyint(1) unsigned NOT NULL DEFAULT '0',
66
- `warning` tinyint(1) unsigned NOT NULL DEFAULT '0',
67
- `may_recheck` tinyint(1) NOT NULL DEFAULT '1',
68
- `being_checked` tinyint(1) NOT NULL DEFAULT '0',
69
-
70
- `result_hash` varchar(200) NOT NULL DEFAULT '',
71
- `false_positive` tinyint(1) NOT NULL DEFAULT '0',
72
- `dismissed` tinyint(1) NOT NULL DEFAULT '0',
73
-
74
- PRIMARY KEY (`link_id`),
75
- KEY `url` (`url`(150)),
76
- KEY `final_url` (`final_url`(150)),
77
- KEY `http_code` (`http_code`),
78
- KEY `broken` (`broken`)
79
- ) {$charset_collate};
80
-
81
- CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_synch` (
82
- `container_id` int(20) unsigned NOT NULL,
83
- `container_type` varchar(40) NOT NULL,
84
- `synched` tinyint(2) unsigned NOT NULL,
85
- `last_synch` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
86
-
87
- PRIMARY KEY (`container_type`,`container_id`),
88
- KEY `synched` (`synched`)
89
- ) {$charset_collate};
90
-
91
- EOM;
92
-
93
- return $blc_db_schema;
94
- }
95
-
96
- }
1
+ <?php
2
+
3
+ if( !function_exists('blc_get_db_schema') ){
4
+
5
+ function blc_get_db_schema(){
6
+ global $wpdb;
7
+
8
+ //Use the character set and collation that's configured for WP tables
9
+ $charset_collate = '';
10
+ if ( !empty($wpdb->charset) ){
11
+ //Some German installs use "utf-8" (invalid) instead of "utf8" (valid). None of
12
+ //the charset ids supported by MySQL contain dashes, so we can safely strip them.
13
+ //See http://dev.mysql.com/doc/refman/5.0/en/charset-charsets.html
14
+ $charset = str_replace('-', '', $wpdb->charset);
15
+
16
+ $charset_collate = "DEFAULT CHARACTER SET {$charset}";
17
+ }
18
+ if ( !empty($wpdb->collate) ){
19
+ $charset_collate .= " COLLATE {$wpdb->collate}";
20
+ }
21
+
22
+ $blc_db_schema = <<<EOM
23
+
24
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_filters` (
25
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
26
+ `name` varchar(100) NOT NULL,
27
+ `params` text NOT NULL,
28
+
29
+ PRIMARY KEY (`id`)
30
+ ) {$charset_collate};
31
+
32
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_instances` (
33
+ `instance_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
34
+ `link_id` int(10) unsigned NOT NULL,
35
+ `container_id` int(10) unsigned NOT NULL,
36
+ `container_type` varchar(40) NOT NULL DEFAULT 'post',
37
+ `link_text` text NOT NULL DEFAULT '',
38
+ `parser_type` varchar(40) NOT NULL DEFAULT 'link',
39
+ `container_field` varchar(250) NOT NULL DEFAULT '',
40
+ `link_context` varchar(250) NOT NULL DEFAULT '',
41
+ `raw_url` text NOT NULL,
42
+
43
+ PRIMARY KEY (`instance_id`),
44
+ KEY `link_id` (`link_id`),
45
+ KEY `source_id` (`container_type`, `container_id`),
46
+ KEY `parser_type` (`parser_type`)
47
+ ) {$charset_collate};
48
+
49
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_links` (
50
+ `link_id` int(20) unsigned NOT NULL AUTO_INCREMENT,
51
+ `url` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
52
+ `first_failure` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
53
+ `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
54
+ `last_success` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
55
+ `last_check_attempt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
56
+ `check_count` int(4) unsigned NOT NULL DEFAULT '0',
57
+ `final_url` text CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
58
+ `redirect_count` smallint(5) unsigned NOT NULL DEFAULT '0',
59
+ `log` text NOT NULL,
60
+ `http_code` smallint(6) NOT NULL DEFAULT '0',
61
+ `status_code` varchar(100) DEFAULT '',
62
+ `status_text` varchar(250) DEFAULT '',
63
+ `request_duration` float NOT NULL DEFAULT '0',
64
+ `timeout` tinyint(1) unsigned NOT NULL DEFAULT '0',
65
+ `broken` tinyint(1) unsigned NOT NULL DEFAULT '0',
66
+ `warning` tinyint(1) unsigned NOT NULL DEFAULT '0',
67
+ `may_recheck` tinyint(1) NOT NULL DEFAULT '1',
68
+ `being_checked` tinyint(1) NOT NULL DEFAULT '0',
69
+
70
+ `result_hash` varchar(200) NOT NULL DEFAULT '',
71
+ `false_positive` tinyint(1) NOT NULL DEFAULT '0',
72
+ `dismissed` tinyint(1) NOT NULL DEFAULT '0',
73
+
74
+ PRIMARY KEY (`link_id`),
75
+ KEY `url` (`url`(150)),
76
+ KEY `final_url` (`final_url`(150)),
77
+ KEY `http_code` (`http_code`),
78
+ KEY `broken` (`broken`)
79
+ ) {$charset_collate};
80
+
81
+ CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}blc_synch` (
82
+ `container_id` int(20) unsigned NOT NULL,
83
+ `container_type` varchar(40) NOT NULL,
84
+ `synched` tinyint(2) unsigned NOT NULL,
85
+ `last_synch` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
86
+
87
+ PRIMARY KEY (`container_type`,`container_id`),
88
+ KEY `synched` (`synched`)
89
+ ) {$charset_collate};
90
+
91
+ EOM;
92
+
93
+ return $blc_db_schema;
94
+ }
95
+
96
+ }
includes/admin/db-upgrade.php CHANGED
@@ -1,585 +1,585 @@
1
- <?php
2
-
3
- class blcDatabaseUpgrader {
4
-
5
- /**
6
- * Create and/or upgrade the plugin's database tables.
7
- *
8
- * @return bool
9
- */
10
- public static function upgrade_database(){
11
- global $blclog;
12
-
13
- $conf = blc_get_configuration();
14
- $current = $conf->options['current_db_version'];
15
-
16
- if ( ($current != 0) && ( $current < 4 ) ){
17
- //The 4th DB version makes a lot of backwards-incompatible changes to the main
18
- //BLC tables, so instead of upgrading we just throw them away and recreate.
19
- if ( !blcDatabaseUpgrader::drop_tables() ){
20
- return false;
21
- };
22
- $current = 0;
23
- }
24
-
25
- //Create/update the plugin's tables
26
- if ( !blcDatabaseUpgrader::make_schema_current() ) {
27
- return false;
28
- }
29
-
30
- if ( $current != 0 ){
31
-
32
- if ( $current < 5 ){
33
- blcDatabaseUpgrader::upgrade_095();
34
- }
35
-
36
- }
37
-
38
- $conf->options['current_db_version'] = BLC_DATABASE_VERSION;
39
- $conf->save_options();
40
- $blclog->info('Database successfully upgraded.');
41
-
42
- return true;
43
- }
44
-
45
- /**
46
- * Create or update the plugin's DB tables.
47
- *
48
- * @return bool
49
- */
50
- static function make_schema_current(){
51
- global $blclog;
52
-
53
- $start = microtime(true);
54
- if ( !function_exists('blc_get_db_schema') ){
55
- require 'db-schema.php';
56
- }
57
- list($dummy, $query_log) = blcTableDelta::delta(blc_get_db_schema());
58
-
59
- $have_errors = false;
60
- foreach($query_log as $item){
61
- if ( $item['success'] ){
62
- $blclog->info(' [OK] ' . $item['query'] . sprintf(' (%.3f seconds)', $item['query_time']));
63
- } else {
64
- $blclog->error(' [ ] ' . $item['query']);
65
- $blclog->error(' Database error : ' . $item['error_message']);
66
- $have_errors = true;
67
- }
68
- }
69
- $blclog->info(sprintf('Schema update took %.3f seconds', microtime(true) - $start));
70
-
71
- $blclog->info('Database schema updated.');
72
- return !$have_errors;
73
- }
74
-
75
- /**
76
- * Drop the plugin's tables.
77
- *
78
- * @return bool
79
- */
80
- static function drop_tables(){
81
- global $wpdb, $blclog; /** @var wpdb $wpdb */
82
-
83
- $blclog->info('Deleting the plugin\'s database tables');
84
- $tables = array(
85
- $wpdb->prefix . 'blc_linkdata',
86
- $wpdb->prefix . 'blc_postdata',
87
- $wpdb->prefix . 'blc_instances',
88
- $wpdb->prefix . 'blc_synch',
89
- $wpdb->prefix . 'blc_links',
90
- );
91
-
92
- $q = "DROP TABLE IF EXISTS " . implode(', ', $tables);
93
- $rez = $wpdb->query( $q );
94
-
95
- if ( $rez === false ){
96
- $error = sprintf(
97
- __("Failed to delete old DB tables. Database error : %s", 'broken-link-checker'),
98
- $wpdb->last_error
99
- );
100
-
101
- $blclog->error($error);
102
- /*
103
- //FIXME: In very rare cases, DROP TABLE IF EXISTS throws an error when the table(s) don't exist.
104
- return false;
105
- //*/
106
- }
107
- $blclog->info('Done.');
108
-
109
- return true;
110
- }
111
-
112
- static function upgrade_095($trigger_errors = false){
113
- global $wpdb; /** @var wpdb $wpdb */
114
-
115
- //Prior to 0.9.5 all supported post types were internally represented using
116
- //a common 'post' container type. The current version creates a unique container
117
- //type to each post type.
118
-
119
- //Update synch records and instances to reflect this change
120
- $q = "
121
- UPDATE
122
- {$wpdb->prefix}blc_synch AS synch
123
- LEFT JOIN {$wpdb->posts} AS posts ON (posts.ID = synch.container_id)
124
- SET
125
- synch.container_type = posts.post_type
126
- WHERE
127
- synch.container_type = 'post' AND posts.post_type IS NOT NULL";
128
- $wpdb->query($q);
129
-
130
- $q = "
131
- UPDATE
132
- {$wpdb->prefix}blc_instances AS instances
133
- LEFT JOIN {$wpdb->posts} AS posts ON (posts.ID = instances.container_id)
134
- SET
135
- instances.container_type = posts.post_type
136
- WHERE
137
- instances.container_type = 'post' AND posts.post_type IS NOT NULL";
138
- $wpdb->query($q);
139
- }
140
-
141
- }
142
-
143
- class blcTableDelta {
144
-
145
- /**
146
- * Parse one or more CREATE TABLE queries and generate a list of SQL queries that need
147
- * to be executed to make the current database schema match those queries. Will also
148
- * execute those queries by default.
149
- *
150
- * This function returns an array with two items. The first is a list of human-readable
151
- * messages explaining what database changes were/would be made. The second array item
152
- * is an array of the generated SQL queries and (if $execute was True) their results.
153
- *
154
- * Each item of this second array is itself an associative array with these keys :
155
- * 'query' - the generated query.
156
- * 'success' - True if the query was executed successfully, False if it caused an error.
157
- * 'error_message' - the MySQL error message (only meaningful when 'success' = false).
158
- *
159
- * The 'success' and 'error_message' keys will only be present if $execute was set to True.
160
- *
161
- * @param string $queries One or more CREATE TABLE queries separated by a semicolon.
162
- * @param bool $execute Whether to apply the schema changes. Defaults to true.
163
- * @param bool $drop_columns Whether to drop columns not present in the input. Defaults to true.
164
- * @param bool $drop_indexes Whether to drop indexes not present in the input. Defaults to true.
165
- * @return array
166
- */
167
- static function delta($queries, $execute = true, $drop_columns = true, $drop_indexes = true){
168
- global $wpdb, $blclog; /** @var wpdb $wpdb */
169
-
170
- // Separate individual queries into an array
171
- if ( !is_array($queries) ) {
172
- $queries = explode( ';', $queries );
173
- if ('' == $queries[count($queries) - 1]) array_pop($queries);
174
- }
175
-
176
- $cqueries = array(); // Creation Queries
177
- $for_update = array();
178
-
179
- // Create a tablename index for an array ($cqueries) of queries
180
- foreach($queries as $qry) {
181
- if (preg_match("|CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([^\s(]+)|i", $qry, $matches)) {
182
- $table = trim( $matches[1], '`' );
183
- $cqueries[$table] = $qry;
184
- $for_update[$table] = 'Create table `'.$table.'`';
185
- }
186
- }
187
-
188
- // Check to see which tables and fields exist
189
- $start_show_tables = microtime(true);
190
- if ($tables = $wpdb->get_col('SHOW TABLES;')) {
191
- $blclog->info(sprintf('... SHOW TABLES (%.3f seconds)', microtime(true) - $start_show_tables));
192
-
193
- // For every table in the database
194
- foreach ($tables as $table) {
195
-
196
- // If a table query exists for the database table...
197
- if ( array_key_exists($table, $cqueries) ) {
198
-
199
- // Clear the field and index arrays
200
- $cfields = $indices = array();
201
-
202
- // Get all of the field names in the query from between the parens
203
- preg_match("|\((.*)\)|ms", $cqueries[$table], $match2);
204
- $qryline = trim($match2[1]);
205
-
206
- // Separate field lines into an array
207
- $flds = preg_split('@[\r\n]+@', $qryline);
208
-
209
- //echo "<hr/><pre>\n".print_r(strtolower($table), true).":\n".print_r($flds, true)."</pre><hr/>";
210
-
211
- // For every field line specified in the query
212
- foreach ($flds as $fld) {
213
- $definition = blcTableDelta::parse_create_definition($fld);
214
-
215
- if ( $definition ){
216
- if ( $definition['index'] ){
217
- $indices[ $definition['index_definition'] ] = $definition; //Index
218
- } else {
219
- $cfields[ $definition['name'] ] = $definition; //Column
220
- }
221
- }
222
- }
223
-
224
- //echo "Detected fields : <br>"; print_r($cfields);
225
-
226
- // Fetch the table column structure from the database
227
- $start = microtime(true);
228
- $tablefields = $wpdb->get_results("SHOW FULL COLUMNS FROM {$table};");
229
- $blclog->info(sprintf('... SHOW FULL COLUMNS FROM %s %.3f seconds', $table, microtime(true) - $start));
230
-
231
- // For every field in the table
232
- foreach ($tablefields as $tablefield) {
233
- $field_name = strtolower($tablefield->Field); //Field names are case-insensitive in MySQL
234
-
235
- // If the table field exists in the field array...
236
- if (array_key_exists($field_name, $cfields)) {
237
- $definition = $cfields[$field_name];
238
-
239
- // Is actual field definition different from that in the query?
240
- $different =
241
- ( $tablefield->Type != $definition['data_type'] ) ||
242
- ( $definition['collation'] && ($tablefield->Collation != $definition['collation']) ) ||
243
- ( $definition['null_allowed'] && ($tablefield->Null == 'NO') ) ||
244
- ( !$definition['null_allowed'] && ($tablefield->Null == 'YES') ) ||
245
- ( $tablefield->Default !== $definition['default'] );
246
-
247
- // Add a query to change the column type
248
- if ( $different ) {
249
- $cqueries[] = "ALTER TABLE `{$table}` MODIFY COLUMN `{$field_name}` {$definition['column_definition']}";
250
- $for_update[$table.'.'.$field_name] = "Changed type of {$table}.{$field_name} from {$tablefield->Type} to {$definition['column_definition']}";
251
- }
252
-
253
- // Remove the field from the array (so it's not added)
254
- unset($cfields[$field_name]);
255
- } else {
256
- // This field exists in the table, but not in the creation queries? Drop it.
257
- if ( $drop_columns ){
258
- $cqueries[] = "ALTER TABLE `{$table}` DROP COLUMN `$field_name`";
259
- $for_update[$table.'.'.$field_name] = 'Removed column '.$table.'.'.$field_name;
260
- }
261
- }
262
- }
263
-
264
- // For every remaining field specified for the table
265
- foreach ($cfields as $field_name => $definition) {
266
- // Push a query line into $cqueries that adds the field to that table
267
- $cqueries[] = "ALTER TABLE `{$table}` ADD COLUMN `$field_name` {$definition['column_definition']}";
268
- $for_update[$table.'.'.$field_name] = 'Added column '.$table.'.'.$field_name;
269
- }
270
-
271
- // Index stuff goes here
272
- //echo 'Detected indexes : <br>'; print_r($indices);
273
-
274
- // Fetch the table index structure from the database
275
- $start = microtime(true);
276
- $tableindices = $wpdb->get_results("SHOW INDEX FROM `{$table}`;");
277
- $blclog->info(sprintf('... SHOW INDEX FROM %s %.3f seconds', $table, microtime(true) - $start));
278
-
279
- if ($tableindices) {
280
- // Clear the index array
281
- $index_ary = array();
282
-
283
- // For every index in the table
284
- foreach ($tableindices as $tableindex) {
285
- // Add the index to the index data array
286
- $keyname = strtolower($tableindex->Key_name);
287
- $index_ary[$keyname]['name'] = $keyname;
288
-
289
- $index_ary[$keyname]['columns'][] = array(
290
- 'column_name' => strtolower($tableindex->Column_name),
291
- 'length' => $tableindex->Sub_part
292
- );
293
-
294
- if ( !isset($index_ary[$keyname]['index_modifier']) ){
295
- if ( $keyname == 'primary' ){
296
- $index_ary[$keyname]['index_modifier'] = 'primary';
297
- } else if ( $tableindex->Non_unique == 0 ){
298
- $index_ary[$keyname]['index_modifier'] = 'unique';
299
- }
300
- }
301
- }
302
-
303
- // For each actual index in the index array
304
- foreach ($index_ary as $index_name => $index_data) {
305
- // Build a create string to compare to the query
306
- $index_string = blcTableDelta::generate_index_string($index_data);
307
- if ( array_key_exists($index_string, $indices) ){
308
- //echo "Found index $index_string<br>";
309
- unset($indices[$index_string]);
310
- } else {
311
- //echo "Didn't find index $index_string<br>";
312
- if ( $drop_indexes ){
313
- if ( $index_name == 'primary' ){
314
- $cqueries[] = "ALTER TABLE `{$table}` DROP PRIMARY KEY";
315
- } else {
316
- $cqueries[] = "ALTER TABLE `{$table}` DROP KEY `$index_name`";
317
- }
318
- $for_update[$table.'.'.$index_name] = 'Removed index '.$table.'.'.$index_name;
319
- }
320
- }
321
- }
322
- }
323
-
324
- // For every remaining index specified for the table
325
- foreach ( $indices as $index ) {
326
- // Push a query line into $cqueries that adds the index to that table
327
- $cqueries[] = "ALTER TABLE `{$table}` ADD {$index['index_definition']}";
328
- $for_update[$table.'.'.$index['name']] = 'Added index '.$table.' '.$index['index_definition'];
329
- }
330
-
331
- // Remove the original table creation query from processing
332
- unset($cqueries[$table]);
333
- unset($for_update[$table]);
334
- } else {
335
- // This table exists in the database, but not in the creation queries?
336
- }
337
- }
338
- }
339
-
340
- //echo "Execute queries : <br>"; print_r($cqueries);
341
- $query_log = array();
342
- foreach ($cqueries as $query) {
343
- $log_item = array('query' => $query,);
344
- if ( $execute ) {
345
- $start = microtime(true);
346
- $log_item['success'] = ($wpdb->query($query) !== false);
347
- $log_item['error_message'] = $wpdb->last_error;
348
- $log_item['query_time'] = microtime(true) - $start;
349
- }
350
- $query_log[] = $log_item;
351
- }
352
-
353
- return array($for_update, $query_log);
354
- }
355
-
356
- /**
357
- * Parse a a single column or index definition.
358
- *
359
- * This function can parse many (but not all) types of syntax used to define columns
360
- * and indexes in a "CREATE TABLE" query.
361
- *
362
- * @param string $line
363
- * @return array
364
- */
365
- static function parse_create_definition($line){
366
- $line = preg_replace('@[,\r\n\s]+$@', '', $line); //Strip the ", " line separator
367
-
368
- $pieces = preg_split('@\s+|(?=\()@', $line, -1, PREG_SPLIT_NO_EMPTY);
369
- if ( empty($pieces) ){
370
- return null;
371
- }
372
-
373
- $token = strtolower(array_shift($pieces));
374
-
375
- $index_modifier = '';
376
- $index = false;
377
-
378
- //Determine if this line defines an index
379
- if ( in_array($token, array('primary', 'unique', 'fulltext')) ){
380
- $index_modifier = $token;
381
- $index = true;
382
- $token = strtolower(array_shift($pieces));
383
- }
384
-
385
- if ( in_array($token, array('index', 'key')) ){
386
- $index = true;
387
- $token = strtolower(array_shift($pieces));
388
- }
389
-
390
- //Determine column/index name
391
- $name = '';
392
- if ( $index ){
393
- //Names are optional for indexes; the INDEX/etc keyword can be immediately
394
- //followed by a column list (or index_type, but we're ignoring that possibility).
395
- if ( strpos($token, '(') === false ){
396
- $name = $token;
397
- } else {
398
- if ( $index_modifier == 'primary' ){
399
- $name = 'primary';
400
- }
401
- array_unshift($pieces, $token);
402
- }
403
- } else {
404
- $name = $token;
405
- }
406
- $name = strtolower(trim($name, '`'));
407
-
408
- $definition = compact('name', 'index', 'index_modifier');
409
-
410
- //Parse the rest of the line
411
- $remainder = implode(' ', $pieces);
412
- if ( $index ){
413
- $definition['columns'] = blcTableDelta::parse_index_column_list($remainder);
414
-
415
- //If the index doesn't have a name, use the name of the first column
416
- //(this is what MySQL does, but only when there isn't already an index with that name).
417
- if ( empty($definition['name']) ){
418
- $definition['name'] = $definition['columns'][0]['column_name'];
419
- }
420
- //Rebuild the index def. in a normalized form
421
- $definition['index_definition'] = blcTableDelta::generate_index_string($definition);
422
-
423
- } else {
424
- $column_def = blcTableDelta::parse_column_definition($remainder);
425
- $definition = array_merge($definition, $column_def);
426
- }
427
-
428
- return $definition;
429
- }
430
-
431
- /**
432
- * Parse the list of columns included in an index.
433
- *
434
- * This function returns a list of column descriptors. Each descriptor is
435
- * an associative array with the keys 'column_name', 'length' and 'order'.
436
- *
437
- * @param string $line
438
- * @return array Array of index columns
439
- */
440
- static function parse_index_column_list($line){
441
- $line = preg_replace('@^\s*\(|\)\s*$@', '', $line); //Strip the braces that surround the column list
442
- $pieces = preg_split('@\s*,\s*@', $line);
443
-
444
- $columns = array();
445
- foreach($pieces as $piece){
446
- if ( preg_match('@`?(?P<column_name>[^\s`]+)`?(?:\s*\(\s*(?P<length>\d+)\s*\))?(?:\s+(?P<order>ASC|DESC))?@i', $piece, $matches) ){
447
-
448
- $column = array(
449
- 'column_name' => strtolower($matches['column_name']),
450
- 'length' => null,
451
- 'order' => null //unused; included for completeness
452
- );
453
-
454
- if ( isset($matches['length']) && is_numeric($matches['length']) ){
455
- $column['length'] = intval($matches['length']);
456
- }
457
- if ( isset($matches['order']) && !empty($matches['order']) ){
458
- $column['order'] = strtolower($matches['order']);
459
- }
460
-
461
- $columns[] = $column;
462
- };
463
- }
464
-
465
- return $columns;
466
- }
467
-
468
- /**
469
- * Parse column datatype and flags.
470
- *
471
- *
472
- * @param string $line
473
- * @return array
474
- */
475
- static function parse_column_definition($line){
476
- $line = trim($line);
477
-
478
- //Extract datatype. This regexp is not entirely reliable - for example, it won't work
479
- //with enum fields where one of values contains brackets "()".
480
- $data_type = '';
481
- $regexp = '
482
- @
483
- (?P<type_name>^\w+)
484
-
485
- # followed by an optional length or a list of enum values
486
- (?:\s*
487
- \(
488
- \s* (?P<length>[^()]+) \s*
489
- \)
490
- )?
491
-
492
- # various type modifiers/keywords
493
- (?P<keywords>
494
- (?:\s+
495
- (?: BINARY | UNSIGNED | ZEROFILL )
496
- )*
497
- )?
498
- @xi';
499
-
500
- if ( preg_match($regexp, $line, $matches) ){
501
- $data_type = strtolower($matches['type_name']);
502
- if ( !empty($matches['length']) ){
503
- $data_type .= '(' . trim($matches['length']) . ')';
504
- }
505
- if ( !empty($matches['keywords']) ){
506
- $data_type .= preg_replace('@\s+@', ' ', $matches['keywords']); //Collapse spaces
507
- }
508
- $line = substr($line, strlen($data_type));
509
- }
510
-
511
- //Extract flags
512
- $null_allowed = !preg_match('@\sNOT\s+NULL\b@i', $line);
513
- $auto_increment = preg_match('@\sAUTO_INCREMENT\b@i', $line);
514
-
515
- //Got a default value?
516
- $default = null;
517
- if ( preg_match("@\sDEFAULT\s+('[^']*'|\"[^\"]*\"|\d+)@i", $line, $matches) ){
518
- $default = trim($matches[1], '"\'');
519
- }
520
-
521
- //Custom character set and/or collation?
522
- $charset = $collation = null;
523
- if ( preg_match('@ (?:\s CHARACTER \s+ SET \s+ (?P<charset>[^\s()]+) )? (?:\s COLLATE \s+ (?P<collation>[^\s()]+) )? @xi', $line, $matches) ){
524
- if ( isset($matches['charset']) ){
525
- $charset = $matches['charset'];
526
- }
527
- if ( isset($matches['collation']) ){
528
- $collation = $matches['collation'];
529
- }
530
- }
531
-
532
- //Generate the normalized column definition
533
- $column_definition = $data_type;
534
- if ( !empty($charset) ){
535
- $column_definition .= " CHARACTER SET {$charset}";
536
- }
537
- if ( !empty($collation) ){
538
- $column_definition .= " COLLATE {$collation}";
539
- }
540
- if ( !$null_allowed ){
541
- $column_definition .= " NOT NULL";
542
- }
543
- if ( !is_null($default) ){
544
- $column_definition .= " DEFAULT '{$default}'";
545
- }
546
- if ( $auto_increment ){
547
- $column_definition .= " AUTO_INCREMENT";
548
- }
549
-
550
- return compact('data_type', 'null_allowed', 'auto_increment', 'default', 'charset', 'collation', 'column_definition');
551
- }
552
-
553
- /**
554
- * Generate an index's definition string from its parsed representation.
555
- *
556
- * @param array $definition The return value of blcTableDelta::parse_create_definition()
557
- * @return string
558
- */
559
- static function generate_index_string($definition){
560
-
561
- //Rebuild the index def. in a normalized form
562
- $index_definition = '';
563
- if ( !empty($definition['index_modifier']) ){
564
- $index_definition .= strtoupper($definition['index_modifier']) . ' ';
565
- }
566
- $index_definition .= 'KEY';
567
- if ( empty($definition['index_modifier']) || ($definition['index_modifier'] != 'primary') ){
568
- $index_definition .= ' `' . $definition['name'].'`';
569
- }
570
-
571
- $column_strings = array();
572
- foreach($definition['columns'] as $column){
573
- $c = '`' . $column['column_name'] . '`';
574
- if ( $column['length'] ){
575
- $c .= '('.$column['length'].')';
576
- }
577
- $column_strings[] = $c;
578
- }
579
-
580
- $index_definition .= ' (' . implode(', ', $column_strings) . ')';
581
- return $index_definition;
582
- }
583
-
584
- }
585
-
1
+ <?php
2
+
3
+ class blcDatabaseUpgrader {
4
+
5
+ /**
6
+ * Create and/or upgrade the plugin's database tables.
7
+ *
8
+ * @return bool
9
+ */
10
+ public static function upgrade_database(){
11
+ global $blclog;
12
+
13
+ $conf = blc_get_configuration();
14
+ $current = $conf->options['current_db_version'];
15
+
16
+ if ( ($current != 0) && ( $current < 4 ) ){
17
+ //The 4th DB version makes a lot of backwards-incompatible changes to the main
18
+ //BLC tables, so instead of upgrading we just throw them away and recreate.
19
+ if ( !blcDatabaseUpgrader::drop_tables() ){
20
+ return false;
21
+ };
22
+ $current = 0;
23
+ }
24
+
25
+ //Create/update the plugin's tables
26
+ if ( !blcDatabaseUpgrader::make_schema_current() ) {
27
+ return false;
28
+ }
29
+
30
+ if ( $current != 0 ){
31
+
32
+ if ( $current < 5 ){
33
+ blcDatabaseUpgrader::upgrade_095();
34
+ }
35
+
36
+ }
37
+
38
+ $conf->options['current_db_version'] = BLC_DATABASE_VERSION;
39
+ $conf->save_options();
40
+ $blclog->info('Database successfully upgraded.');
41
+
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * Create or update the plugin's DB tables.
47
+ *
48
+ * @return bool
49
+ */
50
+ static function make_schema_current(){
51
+ global $blclog;
52
+
53
+ $start = microtime(true);
54
+ if ( !function_exists('blc_get_db_schema') ){
55
+ require 'db-schema.php';
56
+ }
57
+ list($dummy, $query_log) = blcTableDelta::delta(blc_get_db_schema());
58
+
59
+ $have_errors = false;
60
+ foreach($query_log as $item){
61
+ if ( $item['success'] ){
62
+ $blclog->info(' [OK] ' . $item['query'] . sprintf(' (%.3f seconds)', $item['query_time']));
63
+ } else {
64
+ $blclog->error(' [ ] ' . $item['query']);
65
+ $blclog->error(' Database error : ' . $item['error_message']);
66
+ $have_errors = true;
67
+ }
68
+ }
69
+ $blclog->info(sprintf('Schema update took %.3f seconds', microtime(true) - $start));
70
+
71
+ $blclog->info('Database schema updated.');
72
+ return !$have_errors;
73
+ }
74
+
75
+ /**
76
+ * Drop the plugin's tables.
77
+ *
78
+ * @return bool
79
+ */
80
+ static function drop_tables(){
81
+ global $wpdb, $blclog; /** @var wpdb $wpdb */
82
+
83
+ $blclog->info('Deleting the plugin\'s database tables');
84
+ $tables = array(
85
+ $wpdb->prefix . 'blc_linkdata',
86
+ $wpdb->prefix . 'blc_postdata',
87
+ $wpdb->prefix . 'blc_instances',
88
+ $wpdb->prefix . 'blc_synch',
89
+ $wpdb->prefix . 'blc_links',
90
+ );
91
+
92
+ $q = "DROP TABLE IF EXISTS " . implode(', ', $tables);
93
+ $rez = $wpdb->query( $q );
94
+
95
+ if ( $rez === false ){
96
+ $error = sprintf(
97
+ __("Failed to delete old DB tables. Database error : %s", 'broken-link-checker'),
98
+ $wpdb->last_error
99
+ );
100
+
101
+ $blclog->error($error);
102
+ /*
103
+ //FIXME: In very rare cases, DROP TABLE IF EXISTS throws an error when the table(s) don't exist.
104
+ return false;
105
+ //*/
106
+ }
107
+ $blclog->info('Done.');
108
+
109
+ return true;
110
+ }
111
+
112
+ static function upgrade_095($trigger_errors = false){
113
+ global $wpdb; /** @var wpdb $wpdb */
114
+
115
+ //Prior to 0.9.5 all supported post types were internally represented using
116
+ //a common 'post' container type. The current version creates a unique container
117
+ //type to each post type.
118
+
119
+ //Update synch records and instances to reflect this change
120
+ $q = "
121
+ UPDATE
122
+ {$wpdb->prefix}blc_synch AS synch
123
+ LEFT JOIN {$wpdb->posts} AS posts ON (posts.ID = synch.container_id)
124
+ SET
125
+ synch.container_type = posts.post_type
126
+ WHERE
127
+ synch.container_type = 'post' AND posts.post_type IS NOT NULL";
128
+ $wpdb->query($q);
129
+
130
+ $q = "
131
+ UPDATE
132
+ {$wpdb->prefix}blc_instances AS instances
133
+ LEFT JOIN {$wpdb->posts} AS posts ON (posts.ID = instances.container_id)
134
+ SET
135
+ instances.container_type = posts.post_type
136
+ WHERE
137
+ instances.container_type = 'post' AND posts.post_type IS NOT NULL";
138
+ $wpdb->query($q);
139
+ }
140
+
141
+ }
142
+
143
+ class blcTableDelta {
144
+
145
+ /**
146
+ * Parse one or more CREATE TABLE queries and generate a list of SQL queries that need
147
+ * to be executed to make the current database schema match those queries. Will also
148
+ * execute those queries by default.
149
+ *
150
+ * This function returns an array with two items. The first is a list of human-readable
151
+ * messages explaining what database changes were/would be made. The second array item
152
+ * is an array of the generated SQL queries and (if $execute was True) their results.
153
+ *
154
+ * Each item of this second array is itself an associative array with these keys :
155
+ * 'query' - the generated query.
156
+ * 'success' - True if the query was executed successfully, False if it caused an error.
157
+ * 'error_message' - the MySQL error message (only meaningful when 'success' = false).
158
+ *
159
+ * The 'success' and 'error_message' keys will only be present if $execute was set to True.
160
+ *
161
+ * @param string $queries One or more CREATE TABLE queries separated by a semicolon.
162
+ * @param bool $execute Whether to apply the schema changes. Defaults to true.
163
+ * @param bool $drop_columns Whether to drop columns not present in the input. Defaults to true.
164
+ * @param bool $drop_indexes Whether to drop indexes not present in the input. Defaults to true.
165
+ * @return array
166
+ */
167
+ static function delta($queries, $execute = true, $drop_columns = true, $drop_indexes = true){
168
+ global $wpdb, $blclog; /** @var wpdb $wpdb */
169
+
170
+ // Separate individual queries into an array
171
+ if ( !is_array($queries) ) {
172
+ $queries = explode( ';', $queries );
173
+ if ('' == $queries[count($queries) - 1]) array_pop($queries);
174
+ }
175
+
176
+ $cqueries = array(); // Creation Queries
177
+ $for_update = array();
178
+
179
+ // Create a tablename index for an array ($cqueries) of queries
180
+ foreach($queries as $qry) {
181
+ if (preg_match("|CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([^\s(]+)|i", $qry, $matches)) {
182
+ $table = trim( $matches[1], '`' );
183
+ $cqueries[$table] = $qry;
184
+ $for_update[$table] = 'Create table `'.$table.'`';
185
+ }
186
+ }
187
+
188
+ // Check to see which tables and fields exist
189
+ $start_show_tables = microtime(true);
190
+ if ($tables = $wpdb->get_col('SHOW TABLES;')) {
191
+ $blclog->info(sprintf('... SHOW TABLES (%.3f seconds)', microtime(true) - $start_show_tables));
192
+
193
+ // For every table in the database
194
+ foreach ($tables as $table) {
195
+
196
+ // If a table query exists for the database table...
197
+ if ( array_key_exists($table, $cqueries) ) {
198
+
199
+ // Clear the field and index arrays
200
+ $cfields = $indices = array();
201
+
202
+ // Get all of the field names in the query from between the parens
203
+ preg_match("|\((.*)\)|ms", $cqueries[$table], $match2);
204
+ $qryline = trim($match2[1]);
205
+
206
+ // Separate field lines into an array
207
+ $flds = preg_split('@[\r\n]+@', $qryline);
208
+
209
+ //echo "<hr/><pre>\n".print_r(strtolower($table), true).":\n".print_r($flds, true)."</pre><hr/>";
210
+
211
+ // For every field line specified in the query
212
+ foreach ($flds as $fld) {
213
+ $definition = blcTableDelta::parse_create_definition($fld);
214
+
215
+ if ( $definition ){
216
+ if ( $definition['index'] ){
217
+ $indices[ $definition['index_definition'] ] = $definition; //Index
218
+ } else {
219
+ $cfields[ $definition['name'] ] = $definition; //Column
220
+ }
221
+ }
222
+ }
223
+
224
+ //echo "Detected fields : <br>"; print_r($cfields);
225
+
226
+ // Fetch the table column structure from the database
227
+ $start = microtime(true);
228
+ $tablefields = $wpdb->get_results("SHOW FULL COLUMNS FROM {$table};");
229
+ $blclog->info(sprintf('... SHOW FULL COLUMNS FROM %s %.3f seconds', $table, microtime(true) - $start));
230
+
231
+ // For every field in the table
232
+ foreach ($tablefields as $tablefield) {
233
+ $field_name = strtolower($tablefield->Field); //Field names are case-insensitive in MySQL
234
+
235
+ // If the table field exists in the field array...
236
+ if (array_key_exists($field_name, $cfields)) {
237
+ $definition = $cfields[$field_name];
238
+
239
+ // Is actual field definition different from that in the query?
240
+ $different =
241
+ ( $tablefield->Type != $definition['data_type'] ) ||
242
+ ( $definition['collation'] && ($tablefield->Collation != $definition['collation']) ) ||
243
+ ( $definition['null_allowed'] && ($tablefield->Null == 'NO') ) ||
244
+ ( !$definition['null_allowed'] && ($tablefield->Null == 'YES') ) ||
245
+ ( $tablefield->Default !== $definition['default'] );
246
+
247
+ // Add a query to change the column type
248
+ if ( $different ) {
249
+ $cqueries[] = "ALTER TABLE `{$table}` MODIFY COLUMN `{$field_name}` {$definition['column_definition']}";
250
+ $for_update[$table.'.'.$field_name] = "Changed type of {$table}.{$field_name} from {$tablefield->Type} to {$definition['column_definition']}";
251
+ }
252
+
253
+ // Remove the field from the array (so it's not added)
254
+ unset($cfields[$field_name]);
255
+ } else {
256
+ // This field exists in the table, but not in the creation queries? Drop it.
257
+ if ( $drop_columns ){
258
+ $cqueries[] = "ALTER TABLE `{$table}` DROP COLUMN `$field_name`";
259
+ $for_update[$table.'.'.$field_name] = 'Removed column '.$table.'.'.$field_name;
260
+ }
261
+ }
262
+ }
263
+
264
+ // For every remaining field specified for the table
265
+ foreach ($cfields as $field_name => $definition) {
266
+ // Push a query line into $cqueries that adds the field to that table
267
+ $cqueries[] = "ALTER TABLE `{$table}` ADD COLUMN `$field_name` {$definition['column_definition']}";
268
+ $for_update[$table.'.'.$field_name] = 'Added column '.$table.'.'.$field_name;
269
+ }
270
+
271
+ // Index stuff goes here
272
+ //echo 'Detected indexes : <br>'; print_r($indices);
273
+
274
+ // Fetch the table index structure from the database
275
+ $start = microtime(true);
276
+ $tableindices = $wpdb->get_results("SHOW INDEX FROM `{$table}`;");
277
+ $blclog->info(sprintf('... SHOW INDEX FROM %s %.3f seconds', $table, microtime(true) - $start));
278
+
279
+ if ($tableindices) {
280
+ // Clear the index array
281
+ $index_ary = array();
282
+
283
+ // For every index in the table
284
+ foreach ($tableindices as $tableindex) {
285
+ // Add the index to the index data array
286
+ $keyname = strtolower($tableindex->Key_name);
287
+ $index_ary[$keyname]['name'] = $keyname;
288
+
289
+ $index_ary[$keyname]['columns'][] = array(
290
+ 'column_name' => strtolower($tableindex->Column_name),
291
+ 'length' => $tableindex->Sub_part
292
+ );
293
+
294
+ if ( !isset($index_ary[$keyname]['index_modifier']) ){
295
+ if ( $keyname == 'primary' ){
296
+ $index_ary[$keyname]['index_modifier'] = 'primary';
297
+ } else if ( $tableindex->Non_unique == 0 ){
298
+ $index_ary[$keyname]['index_modifier'] = 'unique';
299
+ }
300
+ }
301
+ }
302
+
303
+ // For each actual index in the index array
304
+ foreach ($index_ary as $index_name => $index_data) {
305
+ // Build a create string to compare to the query
306
+ $index_string = blcTableDelta::generate_index_string($index_data);
307
+ if ( array_key_exists($index_string, $indices) ){
308
+ //echo "Found index $index_string<br>";
309
+ unset($indices[$index_string]);
310
+ } else {
311
+ //echo "Didn't find index $index_string<br>";
312
+ if ( $drop_indexes ){
313
+ if ( $index_name == 'primary' ){
314
+ $cqueries[] = "ALTER TABLE `{$table}` DROP PRIMARY KEY";
315
+ } else {
316
+ $cqueries[] = "ALTER TABLE `{$table}` DROP KEY `$index_name`";
317
+ }
318
+ $for_update[$table.'.'.$index_name] = 'Removed index '.$table.'.'.$index_name;
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ // For every remaining index specified for the table
325
+ foreach ( $indices as $index ) {
326
+ // Push a query line into $cqueries that adds the index to that table
327
+ $cqueries[] = "ALTER TABLE `{$table}` ADD {$index['index_definition']}";
328
+ $for_update[$table.'.'.$index['name']] = 'Added index '.$table.' '.$index['index_definition'];
329
+ }
330
+
331
+ // Remove the original table creation query from processing
332
+ unset($cqueries[$table]);
333
+ unset($for_update[$table]);
334
+ } else {
335
+ // This table exists in the database, but not in the creation queries?
336
+ }
337
+ }
338
+ }
339
+
340
+ //echo "Execute queries : <br>"; print_r($cqueries);
341
+ $query_log = array();
342
+ foreach ($cqueries as $query) {
343
+ $log_item = array('query' => $query,);
344
+ if ( $execute ) {
345
+ $start = microtime(true);
346
+ $log_item['success'] = ($wpdb->query($query) !== false);
347
+ $log_item['error_message'] = $wpdb->last_error;
348
+ $log_item['query_time'] = microtime(true) - $start;
349
+ }
350
+ $query_log[] = $log_item;
351
+ }
352
+
353
+ return array($for_update, $query_log);
354
+ }
355
+
356
+ /**
357
+ * Parse a a single column or index definition.
358
+ *
359
+ * This function can parse many (but not all) types of syntax used to define columns
360
+ * and indexes in a "CREATE TABLE" query.
361
+ *
362
+ * @param string $line
363
+ * @return array
364
+ */
365
+ static function parse_create_definition($line){
366
+ $line = preg_replace('@[,\r\n\s]+$@', '', $line); //Strip the ", " line separator
367
+
368
+ $pieces = preg_split('@\s+|(?=\()@', $line, -1, PREG_SPLIT_NO_EMPTY);
369
+ if ( empty($pieces) ){
370
+ return null;
371
+ }
372
+
373
+ $token = strtolower(array_shift($pieces));
374
+
375
+ $index_modifier = '';
376
+ $index = false;
377
+
378
+ //Determine if this line defines an index
379
+ if ( in_array($token, array('primary', 'unique', 'fulltext')) ){
380
+ $index_modifier = $token;
381
+ $index = true;
382
+ $token = strtolower(array_shift($pieces));
383
+ }
384
+
385
+ if ( in_array($token, array('index', 'key')) ){
386
+ $index = true;
387
+ $token = strtolower(array_shift($pieces));
388
+ }
389
+
390
+ //Determine column/index name
391
+ $name = '';
392
+ if ( $index ){
393
+ //Names are optional for indexes; the INDEX/etc keyword can be immediately
394
+ //followed by a column list (or index_type, but we're ignoring that possibility).
395
+ if ( strpos($token, '(') === false ){
396
+ $name = $token;
397
+ } else {
398
+ if ( $index_modifier == 'primary' ){
399
+ $name = 'primary';
400
+ }
401
+ array_unshift($pieces, $token);
402
+ }
403
+ } else {
404
+ $name = $token;
405
+ }
406
+ $name = strtolower(trim($name, '`'));
407
+
408
+ $definition = compact('name', 'index', 'index_modifier');
409
+
410
+ //Parse the rest of the line
411
+ $remainder = implode(' ', $pieces);
412
+ if ( $index ){
413
+ $definition['columns'] = blcTableDelta::parse_index_column_list($remainder);
414
+
415
+ //If the index doesn't have a name, use the name of the first column
416
+ //(this is what MySQL does, but only when there isn't already an index with that name).
417
+ if ( empty($definition['name']) ){
418
+ $definition['name'] = $definition['columns'][0]['column_name'];
419
+ }
420
+ //Rebuild the index def. in a normalized form
421
+ $definition['index_definition'] = blcTableDelta::generate_index_string($definition);
422
+
423
+ } else {
424
+ $column_def = blcTableDelta::parse_column_definition($remainder);
425
+ $definition = array_merge($definition, $column_def);
426
+ }
427
+
428
+ return $definition;
429
+ }
430
+
431
+ /**
432
+ * Parse the list of columns included in an index.
433
+ *
434
+ * This function returns a list of column descriptors. Each descriptor is
435
+ * an associative array with the keys 'column_name', 'length' and 'order'.
436
+ *
437
+ * @param string $line
438
+ * @return array Array of index columns
439
+ */
440
+ static function parse_index_column_list($line){
441
+ $line = preg_replace('@^\s*\(|\)\s*$@', '', $line); //Strip the braces that surround the column list
442
+ $pieces = preg_split('@\s*,\s*@', $line);
443
+
444
+ $columns = array();
445
+ foreach($pieces as $piece){
446
+ if ( preg_match('@`?(?P<column_name>[^\s`]+)`?(?:\s*\(\s*(?P<length>\d+)\s*\))?(?:\s+(?P<order>ASC|DESC))?@i', $piece, $matches) ){
447
+
448
+ $column = array(
449
+ 'column_name' => strtolower($matches['column_name']),
450
+ 'length' => null,
451
+ 'order' => null //unused; included for completeness
452
+ );
453
+
454
+ if ( isset($matches['length']) && is_numeric($matches['length']) ){
455
+ $column['length'] = intval($matches['length']);
456
+ }
457
+ if ( isset($matches['order']) && !empty($matches['order']) ){
458
+ $column['order'] = strtolower($matches['order']);
459
+ }
460
+
461
+ $columns[] = $column;
462
+ };
463
+ }
464
+
465
+ return $columns;
466
+ }
467
+
468
+ /**
469
+ * Parse column datatype and flags.
470
+ *
471
+ *
472
+ * @param string $line
473
+ * @return array
474
+ */
475
+ static function parse_column_definition($line){
476
+ $line = trim($line);
477
+
478
+ //Extract datatype. This regexp is not entirely reliable - for example, it won't work
479
+ //with enum fields where one of values contains brackets "()".
480
+ $data_type = '';
481
+ $regexp = '
482
+ @
483
+ (?P<type_name>^\w+)
484
+
485
+ # followed by an optional length or a list of enum values
486
+ (?:\s*
487
+ \(
488
+ \s* (?P<length>[^()]+) \s*
489
+ \)
490
+ )?
491
+
492
+ # various type modifiers/keywords
493
+ (?P<keywords>
494
+ (?:\s+
495
+ (?: BINARY | UNSIGNED | ZEROFILL )
496
+ )*
497
+ )?
498
+ @xi';
499
+
500
+ if ( preg_match($regexp, $line, $matches) ){
501
+ $data_type = strtolower($matches['type_name']);
502
+ if ( !empty($matches['length']) ){
503
+ $data_type .= '(' . trim($matches['length']) . ')';
504
+ }
505
+ if ( !empty($matches['keywords']) ){
506
+ $data_type .= preg_replace('@\s+@', ' ', $matches['keywords']); //Collapse spaces
507
+ }
508
+ $line = substr($line, strlen($data_type));
509
+ }
510
+
511
+ //Extract flags
512
+ $null_allowed = !preg_match('@\sNOT\s+NULL\b@i', $line);
513
+ $auto_increment = preg_match('@\sAUTO_INCREMENT\b@i', $line);
514
+
515
+ //Got a default value?
516
+ $default = null;
517
+ if ( preg_match("@\sDEFAULT\s+('[^']*'|\"[^\"]*\"|\d+)@i", $line, $matches) ){
518
+ $default = trim($matches[1], '"\'');
519
+ }
520
+
521
+ //Custom character set and/or collation?
522
+ $charset = $collation = null;
523
+ if ( preg_match('@ (?:\s CHARACTER \s+ SET \s+ (?P<charset>[^\s()]+) )? (?:\s COLLATE \s+ (?P<collation>[^\s()]+) )? @xi', $line, $matches) ){
524
+ if ( isset($matches['charset']) ){
525
+ $charset = $matches['charset'];
526
+ }
527
+ if ( isset($matches['collation']) ){
528
+ $collation = $matches['collation'];
529
+ }
530
+ }
531
+
532
+ //Generate the normalized column definition
533
+ $column_definition = $data_type;
534
+ if ( !empty($charset) ){
535
+ $column_definition .= " CHARACTER SET {$charset}";
536
+ }
537
+ if ( !empty($collation) ){
538
+ $column_definition .= " COLLATE {$collation}";
539
+ }
540
+ if ( !$null_allowed ){
541
+ $column_definition .= " NOT NULL";
542
+ }
543
+ if ( !is_null($default) ){
544
+ $column_definition .= " DEFAULT '{$default}'";
545
+ }
546
+ if ( $auto_increment ){
547
+ $column_definition .= " AUTO_INCREMENT";
548
+ }
549
+
550
+ return compact('data_type', 'null_allowed', 'auto_increment', 'default', 'charset', 'collation', 'column_definition');
551
+ }
552
+
553
+ /**
554
+ * Generate an index's definition string from its parsed representation.
555
+ *
556
+ * @param array $definition The return value of blcTableDelta::parse_create_definition()
557
+ * @return string
558
+ */
559
+ static function generate_index_string($definition){
560
+
561
+ //Rebuild the index def. in a normalized form
562
+ $index_definition = '';
563
+ if ( !empty($definition['index_modifier']) ){
564
+ $index_definition .= strtoupper($definition['index_modifier']) . ' ';
565
+ }
566
+ $index_definition .= 'KEY';
567
+ if ( empty($definition['index_modifier']) || ($definition['index_modifier'] != 'primary') ){
568
+ $index_definition .= ' `' . $definition['name'].'`';
569
+ }
570
+
571
+ $column_strings = array();
572
+ foreach($definition['columns'] as $column){
573
+ $c = '`' . $column['column_name'] . '`';
574
+ if ( $column['length'] ){
575
+ $c .= '('.$column['length'].')';
576
+ }
577
+ $column_strings[] = $c;
578
+ }
579
+
580
+ $index_definition .= ' (' . implode(', ', $column_strings) . ')';
581
+ return $index_definition;
582
+ }
583
+
584
+ }
585
+
includes/admin/links-page-js.php CHANGED
@@ -1,946 +1,946 @@
1
- <script type='text/javascript'>
2
-
3
- function alterLinkCounter(factor, filterId){
4
- var counter;
5
- if (filterId) {
6
- counter = jQuery('.filter-' + filterId + '-link-count');
7
- } else {
8
- counter = jQuery('.current-link-count');
9
- }
10
-
11
- var cnt = parseInt(counter.eq(0).html(), 10);
12
- cnt = cnt + factor;
13
- counter.html(cnt);
14
-
15
- if ( blc_is_broken_filter ){
16
- //Update the broken link count displayed beside the "Broken Links" menu
17
- var menuBubble = jQuery('span.blc-menu-bubble');
18
- if ( menuBubble.length > 0 ){
19
- cnt = parseInt(menuBubble.eq(0).html());
20
- cnt = cnt + factor;
21
- if ( cnt > 0 ){
22
- menuBubble.html(cnt);
23
- } else {
24
- menuBubble.parent().hide();
25
- }
26
- }
27
- }
28
- }
29
-
30
- function replaceLinkId(old_id, new_id){
31
- var master = jQuery('#blc-row-'+old_id);
32
-
33
- master.attr('id', 'blc-row-'+new_id);
34
- master.find('.blc-link-id').html(new_id);
35
- master.find('th.check-column input[type="checkbox"]').val(new_id);
36
-
37
- var details_row = jQuery('#link-details-'+old_id);
38
- details_row.attr('id', 'link-details-'+new_id);
39
- }
40
-
41
- function reloadDetailsRow(link_id){
42
- var details_row = jQuery('#link-details-'+link_id);
43
-
44
- //Load up the new link info (so sue me)
45
- details_row.find('td').html('<center><?php echo esc_js(__('Loading...' , 'broken-link-checker')); ?></center>').load(
46
- "<?php echo admin_url('admin-ajax.php'); ?>",
47
- {
48
- 'action' : 'blc_link_details',
49
- 'link_id' : link_id
50
- }
51
- );
52
- }
53
-
54
- jQuery(function($){
55
-
56
- //The details button - display/hide detailed info about a link
57
- $(".blc-details-button, td.column-link-text, td.column-status, td.column-new-link-text").click(function () {
58
- var master = $(this).parents('.blc-row');
59
- var link_id = master.attr('id').split('-')[2];
60
- $('#link-details-'+link_id).toggle();
61
- return false;
62
- });
63
-
64
- var ajaxInProgressHtml = '<?php echo esc_js(__('Wait...', 'broken-link-checker')); ?>';
65
-
66
- //The "Not broken" button - manually mark the link as valid. The link will be checked again later.
67
- $(".blc-discard-button").click(function () {
68
- var me = $(this);
69
- me.html(ajaxInProgressHtml);
70
-
71
- var master = me.parents('.blc-row');
72
- var link_id = master.attr('id').split('-')[2];
73
-
74
- $.post(
75
- "<?php echo admin_url('admin-ajax.php'); ?>",
76
- {
77
- 'action' : 'blc_discard',
78
- 'link_id' : link_id,
79
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_discard')); ?>'
80
- },
81
- function (data, textStatus){
82
- if (data == 'OK'){
83
- var details = $('#link-details-'+link_id);
84
-
85
- //Remove the "Not broken" action
86
- me.parent().remove();
87
-
88
- //Set the displayed link status to OK
89
- var classNames = master.attr('class');
90
- classNames = classNames.replace(/(^|\s)link-status-[^\s]+(\s|$)/, ' ') + ' link-status-ok';
91
- master.attr('class', classNames);
92
-
93
- //Flash the main row green to indicate success, then remove it if the current view
94
- //is supposed to show only broken links or warnings.
95
- var should_hide_link = blc_is_broken_filter || (blc_current_base_filter == 'warnings');
96
-
97
- flashElementGreen(master, function(){
98
- if ( should_hide_link ){
99
- details.remove();
100
- master.remove();
101
- } else {
102
- reloadDetailsRow(link_id);
103
- }
104
- });
105
-
106
- //Update the elements displaying the number of results for the current filter.
107
- if( should_hide_link ){
108
- alterLinkCounter(-1);
109
- }
110
- } else {
111
- me.html('<?php echo esc_js(__('Not broken' , 'broken-link-checker')); ?>');
112
- alert(data);
113
- }
114
- }
115
- );
116
-
117
- return false;
118
- });
119
-
120
- //The "Dismiss" button - hide the link from the "Broken" and "Redirects" filters, but still apply link tweaks and so on.
121
- $(".blc-dismiss-button").click(function () {
122
- var me = $(this);
123
- var oldButtonHtml = me.html();
124
- me.html(ajaxInProgressHtml);
125
-
126
- var master = me.closest('.blc-row');
127
- var link_id = master.attr('id').split('-')[2];
128
- var should_hide_link = $.inArray(blc_current_base_filter, ['broken', 'redirects', 'warnings']) > -1;
129
-
130
- $.post(
131
- "<?php echo admin_url('admin-ajax.php'); ?>",
132
- {
133
- 'action' : 'blc_dismiss',
134
- 'link_id' : link_id,
135
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_dismiss')); ?>'
136
- },
137
- function (data, textStatus){
138
- if (data == 'OK'){
139
- var details = $('#link-details-'+link_id);
140
-
141
- //Remove the "Dismiss" action
142
- me.parent().hide();
143
-
144
- //Flash the main row green to indicate success, then remove it if necessary.
145
- flashElementGreen(master, function(){
146
- if ( should_hide_link ){
147
- details.remove();
148
- master.remove();
149
- }
150
- });
151
-
152
- //Update the elements displaying the number of results for the current filter.
153
- if( should_hide_link ){
154
- alterLinkCounter(-1);
155
- alterLinkCounter(1, 'dismissed');
156
- }
157
- } else {
158
- me.html(oldButtonHtml);
159
- alert(data);
160
- }
161
- }
162
- );
163
-
164
- return false;
165
- });
166
-
167
- //The "Undismiss" button.
168
- $(".blc-undismiss-button").click(function () {
169
- var me = $(this);
170
- var oldButtonHtml = me.html();
171
- me.html(ajaxInProgressHtml);
172
-
173
- var master = me.closest('.blc-row');
174
- var link_id = master.attr('id').split('-')[2];
175
- var should_hide_link = (blc_current_base_filter == 'dismissed');
176
-
177
- $.post(
178
- "<?php echo admin_url('admin-ajax.php'); ?>",
179
- {
180
- 'action' : 'blc_undismiss',
181
- 'link_id' : link_id,
182
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_undismiss')); ?>'
183
- },
184
- function (data, textStatus){
185
- if (data == 'OK'){
186
- var details = $('#link-details-'+link_id);
187
-
188
- //Remove the action.
189
- me.parent().hide();
190
-
191
- //Flash the main row green to indicate success, then remove it if necessary.
192
- flashElementGreen(master, function(){
193
- if ( should_hide_link ){
194
- details.remove();
195
- master.remove();
196
- }
197
- });
198
-
199
- //Update the elements displaying the number of results for the current filter.
200
- if( should_hide_link ){
201
- alterLinkCounter(-1);
202
- }
203
- } else {
204
- me.html(oldButtonHtml);
205
- alert(data);
206
- }
207
- }
208
- );
209
-
210
- return false;
211
- });
212
-
213
- //The "Recheck" button.
214
- $(".blc-recheck-button").click(function () {
215
- var me = $(this);
216
- var oldButtonHtml = me.html();
217
- me.html(ajaxInProgressHtml);
218
-
219
- var master = me.closest('.blc-row');
220
- var link_id = master.attr('id').split('-')[2];
221
-
222
- $.post(
223
- "<?php echo admin_url('admin-ajax.php'); ?>",
224
- {
225
- 'action' : 'blc_recheck',
226
- 'link_id' : link_id,
227
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_recheck')); ?>'
228
- },
229
- function (response){
230
- me.html(oldButtonHtml);
231
-
232
- if (response && (typeof(response['error']) != 'undefined')){
233
- //An internal error occurred before the plugin could check the link (e.g. database error).
234
- alert(response.error);
235
- } else {
236
- //Display the new status in the link row.
237
- displayLinkStatus(master, response);
238
- reloadDetailsRow(link_id);
239
-
240
- //Flash the row green to indicate success
241
- flashElementGreen(master);
242
- }
243
- },
244
- 'json'
245
- );
246
-
247
- return false;
248
- });
249
-
250
- //The "Fix redirect" action.
251
- $('.blc-deredirect-button').click(function() {
252
- //This action can only be used once. If it succeeds, it will no longer be applicable to the current link.
253
- //If it fails, something is broken and trying again probably won't help.
254
- var me = $(this);
255
- me.text(ajaxInProgressHtml);
256
-
257
- var master = me.closest('.blc-row');
258
- var linkId = master.attr('id').split('-')[2];
259
- var shouldHideLink = blc_current_base_filter == 'redirects';
260
- var details = $('#link-details-' + linkId);
261
-
262
- $.post(
263
- "<?php echo admin_url('admin-ajax.php'); ?>",
264
- {
265
- 'action' : 'blc_deredirect',
266
- 'link_id' : linkId,
267
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_deredirect')); ?>'
268
- },
269
- function (response){
270
- me.closest('span').hide();
271
-
272
- if (handleEditResponse(response, master, linkId, null)) {
273
- if (shouldHideLink) {
274
- details.remove();
275
- master.remove();
276
- }
277
- }
278
- },
279
- 'json'
280
- );
281
-
282
- return false;
283
- });
284
-
285
- function flashElementGreen(element, callback) {
286
- var oldColor = element.css('background-color');
287
- element.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, callback);
288
- }
289
-
290
-
291
- /**
292
- * Update status indicators for a link. This includes the contents of the "Status" column, CSS classes and so on.
293
- *
294
- * @param {Object} row Table row as a jQuery object.
295
- * @param {Object} status
296
- */
297
- function displayLinkStatus(row, status) {
298
- //Update the status code and class.
299
- var statusColumn = row.find('td.column-status');
300
- if (status.status_text) {
301
- statusColumn.find('.status-text').text(status.status_text);
302
- }
303
- statusColumn.find('.http-code').text(status.http_code ? status.http_code : '');
304
-
305
- var oldStatusClass = row.attr('class').match(/(?:^|\s)(link-status-[^\s]+)(?:\s|$)/);
306
- oldStatusClass = oldStatusClass ? oldStatusClass[1] : '';
307
- var newStatusClass = 'link-status-' + status.status_code;
308
-
309
- statusColumn.find('.link-status-row').removeClass(oldStatusClass).addClass(newStatusClass);
310
- row.removeClass(oldStatusClass).addClass(newStatusClass);
311
-
312
- //Last check time and failure duration are complicated to update, so we'll just hide them.
313
- //The user can refresh the page to get the new values.
314
- statusColumn.find('.link-last-checked td').html('&nbsp;');
315
- statusColumn.find('.link-broken-for td').html('&nbsp;');
316
-
317
- //The link may or may not be a redirect now.
318
- row.toggleClass('blc-redirect', status.redirect_count > 0);
319
-
320
- if (typeof status['redirect_count'] !== 'undefined') {
321
- var redirectColumn = row.find('td.column-redirect-url').empty();
322
-
323
- if (status.redirect_count > 0 && status.final_url) {
324
- redirectColumn.append(
325
- $(
326
- '<a></a>',
327
- {
328
- href: status.final_url,
329
- text: status.final_url,
330
- title: status.final_url,
331
- 'class': 'blc-redirect-url',
332
- target: '_blank'
333
- }
334
- )
335
- );
336
- }
337
- }
338
- }
339
-
340
-
341
- /**
342
- * Display the inline link editor.
343
- *
344
- * @param {Number} link_id Link ID. The link must be visible in the current view.
345
- */
346
- function showLinkEditor(link_id) {
347
- var master = $('#blc-row-' + link_id),
348
- editorId = 'blc-edit-row-' + link_id,
349
- editRow;
350
-
351
- //Get rid of all existing inline editors.
352
- master.closest('table').find('tr.blc-inline-editor').each(function() {
353
- hideLinkEditor($(this));
354
- });
355
-
356
- //Create an inline editor for this link.
357
- editRow = $('#blc-inline-edit-row').clone(true).attr('id', editorId);
358
- editRow.toggleClass('alternate', master.hasClass('alternate'));
359
- master.after(editRow);
360
-
361
- //Populate editor fields.
362
- var urlElement = master.find('a.blc-link-url');
363
- var linkUrl = urlElement.data('editable-url') || urlElement.attr('href');
364
- var urlInput = editRow.find('.blc-link-url-field').val(linkUrl);
365
-
366
- var titleInput = editRow.find('.blc-link-text-field');
367
- var linkText = master.data('link-text'),
368
- canEditText = master.data('can-edit-text') == 1, //jQuery will convert a '1' to 1 (number) when reading a data attribute.
369
- canEditUrl = master.data('can-edit-url') == 1,
370
- noneText = '<?php echo esc_js(_x('(None)', 'link text', 'broken-link-checker')); ?>',
371
- multipleLinksText = '<?php echo esc_js(_x('(Multiple links)', 'link text', 'broken-link-checker')); ?>';
372
-
373
- titleInput.prop('readonly', !canEditText);
374
- urlInput.prop('readonly', !canEditUrl);
375
-
376
- if ( (typeof linkText !== 'undefined') && (linkText !== null) ) {
377
- if (linkText === '') {
378
- titleInput.val(canEditText ? linkText : noneText);
379
- } else {
380
- titleInput.val(linkText)
381
- }
382
- titleInput.prop('placeholder', noneText);
383
- } else {
384
- if (canEditText) {
385
- titleInput.val('').prop('placeholder', multipleLinksText);
386
- } else {
387
- titleInput.val(multipleLinksText)
388
- }
389
- }
390
-
391
- //Populate the list of URL replacement suggestions.
392
- if (canEditUrl && blc_suggestions_enabled && (master.hasClass('link-status-error') || master.hasClass('link-status-warning'))) {
393
- editRow.find('.blc-url-replacement-suggestions').show();
394
- var suggestionList = editRow.find('.blc-suggestion-list');
395
- findReplacementSuggestions(linkUrl, suggestionList);
396
- }
397
-
398
- editRow.find('.blc-update-link-button').prop('disabled', !(canEditUrl || canEditText));
399
-
400
- //Make the editor span the entire width of the table.
401
- editRow.find('td.blc-colspan-change').attr('colspan', master.closest('table').find('thead th:visible').length);
402
-
403
- master.hide();
404
- editRow.show();
405
- urlInput.focus();
406
- if (canEditUrl) {
407
- urlInput.select();
408
- }
409
- }
410
-
411
- /**
412
- * Hide the inline editor for a particular link.
413
- *
414
- * @param link_id Either a numeric link ID or a jQuery object that represents the editor row.
415
- */
416
- function hideLinkEditor(link_id) {
417
- var editRow = isNaN(link_id) ? link_id : $('#blc-edit-row-' + link_id);
418
- editRow.prev('tr.blc-row').show();
419
- editRow.remove();
420
- }
421
-
422
- /**
423
- * Find possible replacements for a broken link and display them in a list.
424
- *
425
- * @param {String} url The current link URL.
426
- * @param suggestionList jQuery object that represents a list element.
427
- */
428
- function findReplacementSuggestions(url, suggestionList) {
429
- var searchingText = '<?php echo esc_js(_x('Searching...', 'link suggestions', 'broken-link-checker')) ?>';
430
- var noSuggestionsText = '<?php echo esc_js(_x('No suggestions available.', 'link suggestions', 'broken-link-checker')) ?>';
431
- var iaSuggestionName = '<?php echo esc_js(_x('Archived page from %s (via the Wayback Machine)', 'link suggestions', 'broken-link-checker')); ?>';
432
-
433
- suggestionList.empty().append('<li>' + searchingText + '</li>');
434
-
435
- var suggestionTemplate = $('#blc-suggestion-template').find('li').first();
436
-
437
- //Check the Wayback Machine for an archived version of the page.
438
- $.getJSON(
439
- 'https://archive.org/wayback/available?callback=?',
440
- { url: url },
441
-
442
- function(data) {
443
- suggestionList.empty();
444
-
445
- //Check if there are any results.
446
- if (!data || !data.archived_snapshots || !data.archived_snapshots.closest || !data.archived_snapshots.closest.available ) {
447
- suggestionList.append('<li>' + noSuggestionsText + '</li>');
448
- return;
449
- }
450
-
451
- var snapshot = data.archived_snapshots.closest;
452
-
453
- //Convert the timestamp from YYYYMMDDHHMMSS to ISO 8601 date format.
454
- var readableTimestamp = snapshot.timestamp.substr(0, 4) +
455
- '-' + snapshot.timestamp.substr(4, 2) +
456
- '-' + snapshot.timestamp.substr(6, 2);
457
- var name = sprintf(iaSuggestionName, readableTimestamp);
458
-
459
- //Enforce HTTPS by default
460
- snapshot.url = (snapshot.url).replace( new RegExp("^http:", "m"), "https:");
461
-
462
- //Display the suggestion.
463
- var item = suggestionTemplate.clone();
464
- item.find('.blc-suggestion-name a').text(name).attr('href', snapshot.url);
465
- item.find('.blc-suggestion-url').text(snapshot.url);
466
- suggestionList.append(item);
467
- }
468
- );
469
- }
470
-
471
- /**
472
- * Call our PHP backend and tell it to edit all occurrences of particular link.
473
- * Updates UI with the new link info and displays any error messages that might be generated.
474
- *
475
- * @param linkId Either a numeric link ID or a jQuery object representing the link row.
476
- * @param {String} newUrl The new link URL.
477
- * @param {String} newText The new link text. Optional. Set to null to leave it unchanged.
478
- */
479
- function updateLink(linkId, newUrl, newText) {
480
- var master, editRow;
481
- if ( isNaN(linkId) ){
482
- master = linkId;
483
- linkId = master.attr('id').split("-")[2]; //id="blc-row-$linkid"
484
- } else {
485
- master = $('#blc-row-' + linkId);
486
- }
487
- editRow = $('#blc-edit-row-' + linkId);
488
-
489
- var urlElement = master.find('a.blc-link-url');
490
- var progressIndicator = editRow.find('.waiting'),
491
- updateButton = editRow.find('.blc-update-link-button');
492
- progressIndicator.show();
493
- updateButton.prop('disabled', true);
494
-
495
- $.post(
496
- '<?php echo admin_url('admin-ajax.php'); ?>',
497
- {
498
- 'action' : 'blc_edit',
499
- 'link_id' : linkId,
500
- 'new_url' : newUrl,
501
- 'new_text' : newText,
502
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_edit')); ?>'
503
- },
504
- function(response) {
505
- progressIndicator.hide();
506
- updateButton.prop('disabled', false);
507
-
508
- handleEditResponse(response, master, linkId, newText);
509
-
510
- hideLinkEditor(editRow);
511
- },
512
- 'json'
513
- );
514
-
515
- }
516
-
517
- function handleEditResponse(response, master, linkId, newText) {
518
- if (response && (typeof(response['error']) != 'undefined')){
519
- //An internal error occurred before the link could be edited.
520
- alert(response.error);
521
- return false;
522
- } else if (response.errors.length > 0) {
523
- //Build and display an error message.
524
- var msg = '';
525
-
526
- if ( response.cnt_okay > 0 ){
527
- var fragment = sprintf(
528
- '<?php echo esc_js(__('%d instances of the link were successfully modified.', 'broken-link-checker')); ?>',
529
- response.cnt_okay
530
- );
531
- msg = msg + fragment + '\n';
532
- if ( response.cnt_error > 0 ){
533
- fragment = sprintf(
534
- '<?php echo esc_js(__("However, %d instances couldn't be edited and still point to the old URL.", 'broken-link-checker')); ?>',
535
- response.cnt_error
536
- );
537
- msg = msg + fragment + "\n";
538
- }
539
- } else {
540
- msg = msg + '<?php echo esc_js(__('The link could not be modified.', 'broken-link-checker')); ?>\n';
541
- }
542
-
543
- msg = msg + '\n<?php echo esc_js(__("The following error(s) occurred :", 'broken-link-checker')); ?>\n* ';
544
- msg = msg + response.errors.join('\n* ');
545
-
546
- alert(msg);
547
- return false;
548
- } else {
549
- //Everything went well. Update the link row with the new values.
550
-
551
- //Replace the displayed link URL with the new one.
552
- var urlElement = master.find('a.blc-link-url');
553
- urlElement
554
- .attr('href', response.url)
555
- .text(response.url)
556
- .data('editable-url', response.url)
557
- .prop('title', response.url);
558
- if ( typeof response['escaped_url'] != 'undefined' ) {
559
- urlElement.attr('href', response.escaped_url)
560
- }
561
-
562
- //Save the new ID
563
- replaceLinkId(linkId, response.new_link_id);
564
- //Load up the new link info
565
- reloadDetailsRow(response.new_link_id);
566
-
567
- //Update the link text if it was edited.
568
- if ((newText !== null) && (response.link_text !== null)) {
569
- master.data('link-text', response.link_text);
570
- if (response.ui_link_text !== null) {
571
- master.find('.column-new-link-text').html(response.ui_link_text);
572
- }
573
- }
574
-
575
- //Update the status code and class.
576
- displayLinkStatus(master, response);
577
-
578
- //Flash the row green to indicate success
579
- flashElementGreen(master);
580
-
581
- return true;
582
- }
583
- }
584
-
585
- //The "Edit URL" button - displays the inline editor
586
- $(".blc-edit-button").click(function () {
587
- var master = $(this).closest('.blc-row');
588
- var link_id = master.attr('id').split('-')[2];
589
- showLinkEditor(link_id);
590
- });
591
-
592
- //Let the user use Enter and Esc as shortcuts for "Update" and "Cancel"
593
- $('.blc-inline-editor input[type="text"]').keypress(function (e) {
594
- var editRow = $(this).closest('.blc-inline-editor');
595
- if (e.which == 13) {
596
- editRow.find('.blc-update-link-button').click();
597
- return false;
598
- } else if (e.which == 27) {
599
- editRow.find('.blc-cancel-button').click();
600
- return false;
601
- }
602
- return true;
603
- });
604
-
605
-
606
- //The "Update" button in the inline editor.
607
- $('.blc-update-link-button').click(function() {
608
- var editRow = $(this).closest('tr'),
609
- master = editRow.prev('.blc-row');
610
-
611
- //Ensure the new URL is not empty.
612
- var urlField = editRow.find('.blc-link-url-field');
613
- var newUrl = urlField.val();
614
- if ($.trim(newUrl) == '') {
615
- alert('<?php echo esc_js(__('Error: Link URL must not be empty.', 'broken-link-checker')); ?>');
616
- urlField.focus();
617
- return;
618
- }
619
-
620
- var newLinkText = null,
621
- linkTextField = editRow.find('.blc-link-text-field'),
622
- oldLinkText = master.data('link-text');
623
- if (!linkTextField.prop('readonly')) {
624
- newLinkText = linkTextField.val();
625
- //Empty or identical to the original = leave the text unchanged.
626
- if ((newLinkText === '') || (newLinkText === oldLinkText)) {
627
- newLinkText = null;
628
- }
629
- }
630
-
631
- updateLink(master, newUrl, newLinkText);
632
- });
633
-
634
- //The "Cancel" in the inline editor.
635
- $(".blc-cancel-button").click(function () {
636
- var editRow = $(this).closest('tr');
637
- hideLinkEditor(editRow);
638
- });
639
-
640
- //The "Use this URL" button in the inline editor replaces the link URL
641
- //with the selected suggestion URL.
642
- $('#blc-links').on('click', '.blc-use-url-button', function() {
643
- var button = $(this);
644
- var suggestionUrl = button.closest('tr').find('.blc-suggestion-name a').attr('href');
645
- button.closest('.blc-inline-editor').find('.blc-link-url-field').val(suggestionUrl);
646
- });
647
-
648
-
649
- //The "Unlink" button - remove the link/image from all posts, custom fields, etc.
650
- $(".blc-unlink-button").click(function () {
651
- var me = this;
652
- var master = $(me).parents('.blc-row');
653
- $(me).html('<?php echo esc_js(__('Wait...' , 'broken-link-checker')); ?>');
654
-
655
- //Find the link ID
656
- var link_id = master.attr('id').split('-')[2];
657
-
658
- $.post(
659
- "<?php echo admin_url('admin-ajax.php'); ?>",
660
- {
661
- 'action' : 'blc_unlink',
662
- 'link_id' : link_id,
663
- '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_unlink')); ?>'
664
- },
665
- function (data, textStatus){
666
- eval('data = ' + data);
667
-
668
- if ( data && (typeof(data['error']) != 'undefined') ){
669
- //An internal error occurred before the link could be edited.
670
- //data.error is an error message.
671
- alert(data.error);
672
- } else {
673
- if ( data.errors.length == 0 ){
674
- //The link was successfully removed. Hide its details.
675
- $('#link-details-'+link_id).hide();
676
- //Flash the main row green to indicate success, then hide it.
677
- var oldColor = master.css('background-color');
678
- master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
679
- master.hide();
680
- });
681
-
682
- alterLinkCounter(-1);
683
-
684
- return;
685
- } else {
686
- //Build and display an error message.
687
- var msg = '';
688
-
689
- if ( data.cnt_okay > 0 ){
690
- msg = msg + sprintf(
691
- '<?php echo esc_js(__("%d instances of the link were successfully unlinked.", 'broken-link-checker')); ?>\n',
692
- data.cnt_okay
693
- );
694
-
695
- if ( data.cnt_error > 0 ){
696
- msg = msg + sprintf(
697
- '<?php echo esc_js(__("However, %d instances couldn't be removed.", 'broken-link-checker')); ?>\n',
698
- data.cnt_error
699
- );
700
- }
701
- } else {
702
- msg = msg + '<?php echo esc_js(__("The plugin failed to remove the link.", 'broken-link-checker')); ?>\n';
703
- }
704
-
705
- msg = msg + '\n<?php echo esc_js(__("The following error(s) occured :", 'broken-link-checker')); ?>\n* ';
706
- msg = msg + data.errors.join('\n* ');
707
-
708
- //Show the error message
709
- alert(msg);
710
- }
711
- }
712
-
713
- $(me).html('<?php echo esc_js(__('Unlink' , 'broken-link-checker')); ?>');
714
- }
715
- );
716
- });
717
-
718
- //--------------------------------------------
719
- //The search box(es)
720
- //--------------------------------------------
721
-
722
- var searchForm = $('#search-links-dialog');
723
-
724
- searchForm.dialog({
725
- autoOpen : false,
726
- dialogClass : 'blc-search-container',
727
- resizable: false
728
- });
729
-
730
- $('#blc-open-search-box').click(function(){
731
- if ( searchForm.dialog('isOpen') ){
732
- searchForm.dialog('close');
733
- } else {
734
- searchForm
735
- .dialog('open')
736
- .dialog('widget')
737
- .position({
738
- my: 'right top',
739
- at: 'right bottom',
740
- of: $('#blc-open-search-box')
741
- });
742
- }
743
- });
744
-
745
- $('#blc-cancel-search').click(function(){
746
- searchForm.dialog('close');
747
- });
748
-
749
- //The "Save This Search Query" button creates a new custom filter based on the current search
750
- $('#blc-create-filter').click(function(){
751
- var filter_name = prompt("<?php echo esc_js(__("Enter a name for the new custom filter", 'broken-link-checker')); ?>", "");
752
- if ( filter_name ){
753
- $('#blc-custom-filter-name').val(filter_name);
754
- $('#custom-filter-form').submit();
755
- }
756
- });
757
-
758
- //Display a confirmation dialog when the user clicks the "Delete This Filter" button
759
- $('#blc-delete-filter').click(function(){
760
- var message = '<?php
761
- echo esc_js(
762
- html_entity_decode(
763
- __("You are about to delete the current filter.\n'Cancel' to stop, 'OK' to delete", 'broken-link-checker'),
764
- ENT_QUOTES,
765
- get_bloginfo('charset')
766
- )
767
- );
768
- ?>';
769
- return confirm(message);
770
- });
771
-
772
- //--------------------------------------------
773
- // Bulk actions
774
- //--------------------------------------------
775
-
776
- $('#blc-bulk-action-form').submit(function(){
777
- var action = $('#blc-bulk-action').val(), message;
778
- if ( action == '-1' ){
779
- action = $('#blc-bulk-action2').val();
780
- }
781
-
782
- if ( action == 'bulk-delete-sources' ){
783
- //Convey the gravitas of deleting link sources.
784
- message = '<?php
785
- echo esc_js(
786
- html_entity_decode(
787
- __("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'),
788
- ENT_QUOTES,
789
- get_bloginfo('charset')
790
- )
791
- );
792
- ?>';
793
- if ( !confirm(message) ){
794
- return false;
795
- }
796
- } else if ( action == 'bulk-unlink' ){
797
- //Likewise for unlinking.
798
- message = '<?php
799
- echo esc_js(
800
- html_entity_decode(
801
- __("Are you sure you want to remove the selected links? This action can't be undone.\n'Cancel' to stop, 'OK' to remove", 'broken-link-checker'),
802
- ENT_QUOTES,
803
- get_bloginfo('charset')
804
- )
805
- );
806
- ?>';
807
- if ( !confirm(message) ){
808
- return false;
809
- }
810
- }
811
- });
812
-
813
- //Automatically disable bulk actions that don't apply to the currently selected links.
814
- $('#blc-bulk-action').focus(function() {
815
- var redirectsSelected = false, brokenLinksSelected = false;
816
- $('tr th.check-column input:checked', '#blc-links').each(function() {
817
- var row = $(this).closest('tr');
818
- if (row.hasClass('blc-redirect')) {
819
- redirectsSelected = true
820
- }
821
- if (row.hasClass('link-status-error') || row.hasClass('link-status-warning')) {
822
- brokenLinksSelected = true;
823
- }
824
- });
825
-
826
- var bulkAction = $(this);
827
- bulkAction.find('option[value="bulk-deredirect"]').prop('disabled', !redirectsSelected);
828
- bulkAction.find('option[value="bulk-not-broken"]').prop('disabled', !brokenLinksSelected);
829
- });
830
-
831
- //------------------------------------------------------------
832
- // Manipulate highlight settings for permanently broken links
833
- //------------------------------------------------------------
834
- var highlight_permanent_failures_checkbox = $('#highlight_permanent_failures');
835
- var failure_duration_threshold_input = $('#failure_duration_threshold');
836
-
837
- //Apply/remove highlights when the checkbox is (un)checked
838
- highlight_permanent_failures_checkbox.change(function(){
839
- //save_highlight_settings();
840
-
841
- if ( this.checked ){
842
- $('#blc-links tr.blc-permanently-broken').addClass('blc-permanently-broken-hl');
843
- } else {
844
- $('#blc-links tr.blc-permanently-broken').removeClass('blc-permanently-broken-hl');
845
- }
846
- });
847
-
848
- //Apply/remove highlights when the duration threshold is changed.
849
- failure_duration_threshold_input.change(function(){
850
- var new_threshold = parseInt($(this).val());
851
- //save_highlight_settings();
852
- if (isNaN(new_threshold) || (new_threshold < 1)) {
853
- return;
854
- }
855
-
856
- highlight_permanent_failures = highlight_permanent_failures_checkbox.is(':checked');
857
-
858
- $('#blc-links tr.blc-row').each(function(index){
859
- var days_broken = $(this).attr('data-days-broken');
860
- if ( days_broken >= new_threshold ){
861
- $(this).addClass('blc-permanently-broken');
862
- if ( highlight_permanent_failures ){
863
- $(this).addClass('blc-permanently-broken-hl');
864
- }
865
- } else {
866
- $(this).removeClass('blc-permanently-broken').removeClass('blc-permanently-broken-hl');
867
- }
868
- });
869
- });
870
-
871
- //Show/hide table columns dynamically
872
- $('#blc-column-selector input[type="checkbox"]').change(function(){
873
- var checkbox = $(this);
874
- var column_id = checkbox.attr('name').split(/\[|\]/)[1];
875
- if (checkbox.is(':checked')){
876
- $('td.column-'+column_id+', th.column-'+column_id, '#blc-links').removeClass('hidden');
877
- } else {
878
- $('td.column-'+column_id+', th.column-'+column_id, '#blc-links').addClass('hidden');
879
- }
880
-
881
- //Recalculate colspan's for detail rows to take into account the changed number of
882
- //visible columns. Otherwise you can get some ugly layout glitches.
883
- $('#blc-links tr.blc-link-details td').attr(
884
- 'colspan',
885
- $('#blc-column-selector input[type="checkbox"]:checked').length+1
886
- );
887
- });
888
-
889
- //Unlike other fields in "Screen Options", the links-per-page setting
890
- //is handled using straight form submission (POST), not AJAX.
891
- $('#blc-per-page-apply-button').click(function(){
892
- $('#adv-settings').submit();
893
- });
894
-
895
- $('#blc_links_per_page').keypress(function(e){
896
- if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
897
- $('#adv-settings').submit();
898
- }
899
- });
900
-
901
- //Toggle status code colors when the corresponding checkbox is toggled
902
- $('#table_color_code_status').click(function(){
903
- if ( $(this).is(':checked') ){
904
- $('#blc-links').addClass('color-code-link-status');
905
- } else {
906
- $('#blc-links').removeClass('color-code-link-status');
907
- }
908
- });
909
-
910
- //Show the bulk edit/find & replace form when the user applies the appropriate bulk action
911
- $('#doaction, #doaction2').click(function(e){
912
- var n = $(this).attr('id').substr(2);
913
- if ( $('select[name="'+n+'"]').val() == 'bulk-edit' ) {
914
- e.preventDefault();
915
- //Any links selected?
916
- if ($('tbody th.check-column input:checked').length > 0){
917
- $('#bulk-edit').show();
918
- }
919
- }
920
- });
921
-
922
- //Hide the bulk edit/find & replace form when "Cancel" is clicked
923
- $('#bulk-edit .cancel').click(function(){
924
- $('#bulk-edit').hide();
925
- return false;
926
- });
927
-
928
- //Minimal input validation for the bulk edit form
929
- $('#bulk-edit input[type="submit"]').click(function(e){
930
- if( $('#bulk-edit input[name="search"]').val() == '' ){
931
- alert('<?php echo esc_js(__('Enter a search string first.', 'broken-link-checker')); ?>');
932
- $('#bulk-edit input[name="search"]').focus();
933
- e.preventDefault();
934
- return;
935
- }
936
-
937
- if ($('tbody th.check-column input:checked').length == 0){
938
- alert('<?php echo esc_js(__('Select one or more links to edit.', 'broken-link-checker')); ?>');
939
- e.preventDefault();
940
- }
941
- });
942
-
943
-
944
- });
945
-
946
  </script>
1
+ <script type='text/javascript'>
2
+
3
+ function alterLinkCounter(factor, filterId){
4
+ var counter;
5
+ if (filterId) {
6
+ counter = jQuery('.filter-' + filterId + '-link-count');
7
+ } else {
8
+ counter = jQuery('.current-link-count');
9
+ }
10
+
11
+ var cnt = parseInt(counter.eq(0).html(), 10);
12
+ cnt = cnt + factor;
13
+ counter.html(cnt);
14
+
15
+ if ( blc_is_broken_filter ){
16
+ //Update the broken link count displayed beside the "Broken Links" menu
17
+ var menuBubble = jQuery('span.blc-menu-bubble');
18
+ if ( menuBubble.length > 0 ){
19
+ cnt = parseInt(menuBubble.eq(0).html());
20
+ cnt = cnt + factor;
21
+ if ( cnt > 0 ){
22
+ menuBubble.html(cnt);
23
+ } else {
24
+ menuBubble.parent().hide();
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ function replaceLinkId(old_id, new_id){
31
+ var master = jQuery('#blc-row-'+old_id);
32
+
33
+ master.attr('id', 'blc-row-'+new_id);
34
+ master.find('.blc-link-id').html(new_id);
35
+ master.find('th.check-column input[type="checkbox"]').val(new_id);
36
+
37
+ var details_row = jQuery('#link-details-'+old_id);
38
+ details_row.attr('id', 'link-details-'+new_id);
39
+ }
40
+
41
+ function reloadDetailsRow(link_id){
42
+ var details_row = jQuery('#link-details-'+link_id);
43
+
44
+ //Load up the new link info (so sue me)
45
+ details_row.find('td').html('<center><?php echo esc_js(__('Loading...' , 'broken-link-checker')); ?></center>').load(
46
+ "<?php echo admin_url('admin-ajax.php'); ?>",
47
+ {
48
+ 'action' : 'blc_link_details',
49
+ 'link_id' : link_id
50
+ }
51
+ );
52
+ }
53
+
54
+ jQuery(function($){
55
+
56
+ //The details button - display/hide detailed info about a link
57
+ $(".blc-details-button, td.column-link-text, td.column-status, td.column-new-link-text").click(function () {
58
+ var master = $(this).parents('.blc-row');
59
+ var link_id = master.attr('id').split('-')[2];
60
+ $('#link-details-'+link_id).toggle();
61
+ return false;
62
+ });
63
+
64
+ var ajaxInProgressHtml = '<?php echo esc_js(__('Wait...', 'broken-link-checker')); ?>';
65
+
66
+ //The "Not broken" button - manually mark the link as valid. The link will be checked again later.
67
+ $(".blc-discard-button").click(function () {
68
+ var me = $(this);
69
+ me.html(ajaxInProgressHtml);
70
+
71
+ var master = me.parents('.blc-row');
72
+ var link_id = master.attr('id').split('-')[2];
73
+
74
+ $.post(
75
+ "<?php echo admin_url('admin-ajax.php'); ?>",
76
+ {
77
+ 'action' : 'blc_discard',
78
+ 'link_id' : link_id,
79
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_discard')); ?>'
80
+ },
81
+ function (data, textStatus){
82
+ if (data == 'OK'){
83
+ var details = $('#link-details-'+link_id);
84
+
85
+ //Remove the "Not broken" action
86
+ me.parent().remove();
87
+
88
+ //Set the displayed link status to OK
89
+ var classNames = master.attr('class');
90
+ classNames = classNames.replace(/(^|\s)link-status-[^\s]+(\s|$)/, ' ') + ' link-status-ok';
91
+ master.attr('class', classNames);
92
+
93
+ //Flash the main row green to indicate success, then remove it if the current view
94
+ //is supposed to show only broken links or warnings.
95
+ var should_hide_link = blc_is_broken_filter || (blc_current_base_filter == 'warnings');
96
+
97
+ flashElementGreen(master, function(){
98
+ if ( should_hide_link ){
99
+ details.remove();
100
+ master.remove();
101
+ } else {
102
+ reloadDetailsRow(link_id);
103
+ }
104
+ });
105
+
106
+ //Update the elements displaying the number of results for the current filter.
107
+ if( should_hide_link ){
108
+ alterLinkCounter(-1);
109
+ }
110
+ } else {
111
+ me.html('<?php echo esc_js(__('Not broken' , 'broken-link-checker')); ?>');
112
+ alert(data);
113
+ }
114
+ }
115
+ );
116
+
117
+ return false;
118
+ });
119
+
120
+ //The "Dismiss" button - hide the link from the "Broken" and "Redirects" filters, but still apply link tweaks and so on.
121
+ $(".blc-dismiss-button").click(function () {
122
+ var me = $(this);
123
+ var oldButtonHtml = me.html();
124
+ me.html(ajaxInProgressHtml);
125
+
126
+ var master = me.closest('.blc-row');
127
+ var link_id = master.attr('id').split('-')[2];
128
+ var should_hide_link = $.inArray(blc_current_base_filter, ['broken', 'redirects', 'warnings']) > -1;
129
+
130
+ $.post(
131
+ "<?php echo admin_url('admin-ajax.php'); ?>",
132
+ {
133
+ 'action' : 'blc_dismiss',
134
+ 'link_id' : link_id,
135
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_dismiss')); ?>'
136
+ },
137
+ function (data, textStatus){
138
+ if (data == 'OK'){
139
+ var details = $('#link-details-'+link_id);
140
+
141
+ //Remove the "Dismiss" action
142
+ me.parent().hide();
143
+
144
+ //Flash the main row green to indicate success, then remove it if necessary.
145
+ flashElementGreen(master, function(){
146
+ if ( should_hide_link ){
147
+ details.remove();
148
+ master.remove();
149
+ }
150
+ });
151
+
152
+ //Update the elements displaying the number of results for the current filter.
153
+ if( should_hide_link ){
154
+ alterLinkCounter(-1);
155
+ alterLinkCounter(1, 'dismissed');
156
+ }
157
+ } else {
158
+ me.html(oldButtonHtml);
159
+ alert(data);
160
+ }
161
+ }
162
+ );
163
+
164
+ return false;
165
+ });
166
+
167
+ //The "Undismiss" button.
168
+ $(".blc-undismiss-button").click(function () {
169
+ var me = $(this);
170
+ var oldButtonHtml = me.html();
171
+ me.html(ajaxInProgressHtml);
172
+
173
+ var master = me.closest('.blc-row');
174
+ var link_id = master.attr('id').split('-')[2];
175
+ var should_hide_link = (blc_current_base_filter == 'dismissed');
176
+
177
+ $.post(
178
+ "<?php echo admin_url('admin-ajax.php'); ?>",
179
+ {
180
+ 'action' : 'blc_undismiss',
181
+ 'link_id' : link_id,
182
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_undismiss')); ?>'
183
+ },
184
+ function (data, textStatus){
185
+ if (data == 'OK'){
186
+ var details = $('#link-details-'+link_id);
187
+
188
+ //Remove the action.
189
+ me.parent().hide();
190
+
191
+ //Flash the main row green to indicate success, then remove it if necessary.
192
+ flashElementGreen(master, function(){
193
+ if ( should_hide_link ){
194
+ details.remove();
195
+ master.remove();
196
+ }
197
+ });
198
+
199
+ //Update the elements displaying the number of results for the current filter.
200
+ if( should_hide_link ){
201
+ alterLinkCounter(-1);
202
+ }
203
+ } else {
204
+ me.html(oldButtonHtml);
205
+ alert(data);
206
+ }
207
+ }
208
+ );
209
+
210
+ return false;
211
+ });
212
+
213
+ //The "Recheck" button.
214
+ $(".blc-recheck-button").click(function () {
215
+ var me = $(this);
216
+ var oldButtonHtml = me.html();
217
+ me.html(ajaxInProgressHtml);
218
+
219
+ var master = me.closest('.blc-row');
220
+ var link_id = master.attr('id').split('-')[2];
221
+
222
+ $.post(
223
+ "<?php echo admin_url('admin-ajax.php'); ?>",
224
+ {
225
+ 'action' : 'blc_recheck',
226
+ 'link_id' : link_id,
227
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_recheck')); ?>'
228
+ },
229
+ function (response){
230
+ me.html(oldButtonHtml);
231
+
232
+ if (response && (typeof(response['error']) != 'undefined')){
233
+ //An internal error occurred before the plugin could check the link (e.g. database error).
234
+ alert(response.error);
235
+ } else {
236
+ //Display the new status in the link row.
237
+ displayLinkStatus(master, response);
238
+ reloadDetailsRow(link_id);
239
+
240
+ //Flash the row green to indicate success
241
+ flashElementGreen(master);
242
+ }
243
+ },
244
+ 'json'
245
+ );
246
+
247
+ return false;
248
+ });
249
+
250
+ //The "Fix redirect" action.
251
+ $('.blc-deredirect-button').click(function() {
252
+ //This action can only be used once. If it succeeds, it will no longer be applicable to the current link.
253
+ //If it fails, something is broken and trying again probably won't help.
254
+ var me = $(this);
255
+ me.text(ajaxInProgressHtml);
256
+
257
+ var master = me.closest('.blc-row');
258
+ var linkId = master.attr('id').split('-')[2];
259
+ var shouldHideLink = blc_current_base_filter == 'redirects';
260
+ var details = $('#link-details-' + linkId);
261
+
262
+ $.post(
263
+ "<?php echo admin_url('admin-ajax.php'); ?>",
264
+ {
265
+ 'action' : 'blc_deredirect',
266
+ 'link_id' : linkId,
267
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_deredirect')); ?>'
268
+ },
269
+ function (response){
270
+ me.closest('span').hide();
271
+
272
+ if (handleEditResponse(response, master, linkId, null)) {
273
+ if (shouldHideLink) {
274
+ details.remove();
275
+ master.remove();
276
+ }
277
+ }
278
+ },
279
+ 'json'
280
+ );
281
+
282
+ return false;
283
+ });
284
+
285
+ function flashElementGreen(element, callback) {
286
+ var oldColor = element.css('background-color');
287
+ element.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, callback);
288
+ }
289
+
290
+
291
+ /**
292
+ * Update status indicators for a link. This includes the contents of the "Status" column, CSS classes and so on.
293
+ *
294
+ * @param {Object} row Table row as a jQuery object.
295
+ * @param {Object} status
296
+ */
297
+ function displayLinkStatus(row, status) {
298
+ //Update the status code and class.
299
+ var statusColumn = row.find('td.column-status');
300
+ if (status.status_text) {
301
+ statusColumn.find('.status-text').text(status.status_text);
302
+ }
303
+ statusColumn.find('.http-code').text(status.http_code ? status.http_code : '');
304
+
305
+ var oldStatusClass = row.attr('class').match(/(?:^|\s)(link-status-[^\s]+)(?:\s|$)/);
306
+ oldStatusClass = oldStatusClass ? oldStatusClass[1] : '';
307
+ var newStatusClass = 'link-status-' + status.status_code;
308
+
309
+ statusColumn.find('.link-status-row').removeClass(oldStatusClass).addClass(newStatusClass);
310
+ row.removeClass(oldStatusClass).addClass(newStatusClass);
311
+
312
+ //Last check time and failure duration are complicated to update, so we'll just hide them.
313
+ //The user can refresh the page to get the new values.
314
+ statusColumn.find('.link-last-checked td').html('&nbsp;');
315
+ statusColumn.find('.link-broken-for td').html('&nbsp;');
316
+
317
+ //The link may or may not be a redirect now.
318
+ row.toggleClass('blc-redirect', status.redirect_count > 0);
319
+
320
+ if (typeof status['redirect_count'] !== 'undefined') {
321
+ var redirectColumn = row.find('td.column-redirect-url').empty();
322
+
323
+ if (status.redirect_count > 0 && status.final_url) {
324
+ redirectColumn.append(
325
+ $(
326
+ '<a></a>',
327
+ {
328
+ href: status.final_url,
329
+ text: status.final_url,
330
+ title: status.final_url,
331
+ 'class': 'blc-redirect-url',
332
+ target: '_blank'
333
+ }
334
+ )
335
+ );
336
+ }
337
+ }
338
+ }
339
+
340
+
341
+ /**
342
+ * Display the inline link editor.
343
+ *
344
+ * @param {Number} link_id Link ID. The link must be visible in the current view.
345
+ */
346
+ function showLinkEditor(link_id) {
347
+ var master = $('#blc-row-' + link_id),
348
+ editorId = 'blc-edit-row-' + link_id,
349
+ editRow;
350
+
351
+ //Get rid of all existing inline editors.
352
+ master.closest('table').find('tr.blc-inline-editor').each(function() {
353
+ hideLinkEditor($(this));
354
+ });
355
+
356
+ //Create an inline editor for this link.
357
+ editRow = $('#blc-inline-edit-row').clone(true).attr('id', editorId);
358
+ editRow.toggleClass('alternate', master.hasClass('alternate'));
359
+ master.after(editRow);
360
+
361
+ //Populate editor fields.
362
+ var urlElement = master.find('a.blc-link-url');
363
+ var linkUrl = urlElement.data('editable-url') || urlElement.attr('href');
364
+ var urlInput = editRow.find('.blc-link-url-field').val(linkUrl);
365
+
366
+ var titleInput = editRow.find('.blc-link-text-field');
367
+ var linkText = master.data('link-text'),
368
+ canEditText = master.data('can-edit-text') == 1, //jQuery will convert a '1' to 1 (number) when reading a data attribute.
369
+ canEditUrl = master.data('can-edit-url') == 1,
370
+ noneText = '<?php echo esc_js(_x('(None)', 'link text', 'broken-link-checker')); ?>',
371
+ multipleLinksText = '<?php echo esc_js(_x('(Multiple links)', 'link text', 'broken-link-checker')); ?>';
372
+
373
+ titleInput.prop('readonly', !canEditText);
374
+ urlInput.prop('readonly', !canEditUrl);
375
+
376
+ if ( (typeof linkText !== 'undefined') && (linkText !== null) ) {
377
+ if (linkText === '') {
378
+ titleInput.val(canEditText ? linkText : noneText);
379
+ } else {
380
+ titleInput.val(linkText)
381
+ }
382
+ titleInput.prop('placeholder', noneText);
383
+ } else {
384
+ if (canEditText) {
385
+ titleInput.val('').prop('placeholder', multipleLinksText);
386
+ } else {
387
+ titleInput.val(multipleLinksText)
388
+ }
389
+ }
390
+
391
+ //Populate the list of URL replacement suggestions.
392
+ if (canEditUrl && blc_suggestions_enabled && (master.hasClass('link-status-error') || master.hasClass('link-status-warning'))) {
393
+ editRow.find('.blc-url-replacement-suggestions').show();
394
+ var suggestionList = editRow.find('.blc-suggestion-list');
395
+ findReplacementSuggestions(linkUrl, suggestionList);
396
+ }
397
+
398
+ editRow.find('.blc-update-link-button').prop('disabled', !(canEditUrl || canEditText));
399
+
400
+ //Make the editor span the entire width of the table.
401
+ editRow.find('td.blc-colspan-change').attr('colspan', master.closest('table').find('thead th:visible').length);
402
+
403
+ master.hide();
404
+ editRow.show();
405
+ urlInput.focus();
406
+ if (canEditUrl) {
407
+ urlInput.select();
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Hide the inline editor for a particular link.
413
+ *
414
+ * @param link_id Either a numeric link ID or a jQuery object that represents the editor row.
415
+ */
416
+ function hideLinkEditor(link_id) {
417
+ var editRow = isNaN(link_id) ? link_id : $('#blc-edit-row-' + link_id);
418
+ editRow.prev('tr.blc-row').show();
419
+ editRow.remove();
420
+ }
421
+
422
+ /**
423
+ * Find possible replacements for a broken link and display them in a list.
424
+ *
425
+ * @param {String} url The current link URL.
426
+ * @param suggestionList jQuery object that represents a list element.
427
+ */
428
+ function findReplacementSuggestions(url, suggestionList) {
429
+ var searchingText = '<?php echo esc_js(_x('Searching...', 'link suggestions', 'broken-link-checker')) ?>';
430
+ var noSuggestionsText = '<?php echo esc_js(_x('No suggestions available.', 'link suggestions', 'broken-link-checker')) ?>';
431
+ var iaSuggestionName = '<?php echo esc_js(_x('Archived page from %s (via the Wayback Machine)', 'link suggestions', 'broken-link-checker')); ?>';
432
+
433
+ suggestionList.empty().append('<li>' + searchingText + '</li>');
434
+
435
+ var suggestionTemplate = $('#blc-suggestion-template').find('li').first();
436
+
437
+ //Check the Wayback Machine for an archived version of the page.
438
+ $.getJSON(
439
+ 'https://archive.org/wayback/available?callback=?',
440
+ { url: url },
441
+
442
+ function(data) {
443
+ suggestionList.empty();
444
+
445
+ //Check if there are any results.
446
+ if (!data || !data.archived_snapshots || !data.archived_snapshots.closest || !data.archived_snapshots.closest.available ) {
447
+ suggestionList.append('<li>' + noSuggestionsText + '</li>');
448
+ return;
449
+ }
450
+
451
+ var snapshot = data.archived_snapshots.closest;
452
+
453
+ //Convert the timestamp from YYYYMMDDHHMMSS to ISO 8601 date format.
454
+ var readableTimestamp = snapshot.timestamp.substr(0, 4) +
455
+ '-' + snapshot.timestamp.substr(4, 2) +
456
+ '-' + snapshot.timestamp.substr(6, 2);
457
+ var name = sprintf(iaSuggestionName, readableTimestamp);
458
+
459
+ //Enforce HTTPS by default
460
+ snapshot.url = (snapshot.url).replace( new RegExp("^http:", "m"), "https:");
461
+
462
+ //Display the suggestion.
463
+ var item = suggestionTemplate.clone();
464
+ item.find('.blc-suggestion-name a').text(name).attr('href', snapshot.url);
465
+ item.find('.blc-suggestion-url').text(snapshot.url);
466
+ suggestionList.append(item);
467
+ }
468
+ );
469
+ }
470
+
471
+ /**
472
+ * Call our PHP backend and tell it to edit all occurrences of particular link.
473
+ * Updates UI with the new link info and displays any error messages that might be generated.
474
+ *
475
+ * @param linkId Either a numeric link ID or a jQuery object representing the link row.
476
+ * @param {String} newUrl The new link URL.
477
+ * @param {String} newText The new link text. Optional. Set to null to leave it unchanged.
478
+ */
479
+ function updateLink(linkId, newUrl, newText) {
480
+ var master, editRow;
481
+ if ( isNaN(linkId) ){
482
+ master = linkId;
483
+ linkId = master.attr('id').split("-")[2]; //id="blc-row-$linkid"
484
+ } else {
485
+ master = $('#blc-row-' + linkId);
486
+ }
487
+ editRow = $('#blc-edit-row-' + linkId);
488
+
489
+ var urlElement = master.find('a.blc-link-url');
490
+ var progressIndicator = editRow.find('.waiting'),
491
+ updateButton = editRow.find('.blc-update-link-button');
492
+ progressIndicator.show();
493
+ updateButton.prop('disabled', true);
494
+
495
+ $.post(
496
+ '<?php echo admin_url('admin-ajax.php'); ?>',
497
+ {
498
+ 'action' : 'blc_edit',
499
+ 'link_id' : linkId,
500
+ 'new_url' : newUrl,
501
+ 'new_text' : newText,
502
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_edit')); ?>'
503
+ },
504
+ function(response) {
505
+ progressIndicator.hide();
506
+ updateButton.prop('disabled', false);
507
+
508
+ handleEditResponse(response, master, linkId, newText);
509
+
510
+ hideLinkEditor(editRow);
511
+ },
512
+ 'json'
513
+ );
514
+
515
+ }
516
+
517
+ function handleEditResponse(response, master, linkId, newText) {
518
+ if (response && (typeof(response['error']) != 'undefined')){
519
+ //An internal error occurred before the link could be edited.
520
+ alert(response.error);
521
+ return false;
522
+ } else if (response.errors.length > 0) {
523
+ //Build and display an error message.
524
+ var msg = '';
525
+
526
+ if ( response.cnt_okay > 0 ){
527
+ var fragment = sprintf(
528
+ '<?php echo esc_js(__('%d instances of the link were successfully modified.', 'broken-link-checker')); ?>',
529
+ response.cnt_okay
530
+ );
531
+ msg = msg + fragment + '\n';
532
+ if ( response.cnt_error > 0 ){
533
+ fragment = sprintf(
534
+ '<?php echo esc_js(__("However, %d instances couldn't be edited and still point to the old URL.", 'broken-link-checker')); ?>',
535
+ response.cnt_error
536
+ );
537
+ msg = msg + fragment + "\n";
538
+ }
539
+ } else {
540
+ msg = msg + '<?php echo esc_js(__('The link could not be modified.', 'broken-link-checker')); ?>\n';
541
+ }
542
+
543
+ msg = msg + '\n<?php echo esc_js(__("The following error(s) occurred :", 'broken-link-checker')); ?>\n* ';
544
+ msg = msg + response.errors.join('\n* ');
545
+
546
+ alert(msg);
547
+ return false;
548
+ } else {
549
+ //Everything went well. Update the link row with the new values.
550
+
551
+ //Replace the displayed link URL with the new one.
552
+ var urlElement = master.find('a.blc-link-url');
553
+ urlElement
554
+ .attr('href', response.url)
555
+ .text(response.url)
556
+ .data('editable-url', response.url)
557
+ .prop('title', response.url);
558
+ if ( typeof response['escaped_url'] != 'undefined' ) {
559
+ urlElement.attr('href', response.escaped_url)
560
+ }
561
+
562
+ //Save the new ID
563
+ replaceLinkId(linkId, response.new_link_id);
564
+ //Load up the new link info
565
+ reloadDetailsRow(response.new_link_id);
566
+
567
+ //Update the link text if it was edited.
568
+ if ((newText !== null) && (response.link_text !== null)) {
569
+ master.data('link-text', response.link_text);
570
+ if (response.ui_link_text !== null) {
571
+ master.find('.column-new-link-text').html(response.ui_link_text);
572
+ }
573
+ }
574
+
575
+ //Update the status code and class.
576
+ displayLinkStatus(master, response);
577
+
578
+ //Flash the row green to indicate success
579
+ flashElementGreen(master);
580
+
581
+ return true;
582
+ }
583
+ }
584
+
585
+ //The "Edit URL" button - displays the inline editor
586
+ $(".blc-edit-button").click(function () {
587
+ var master = $(this).closest('.blc-row');
588
+ var link_id = master.attr('id').split('-')[2];
589
+ showLinkEditor(link_id);
590
+ });
591
+
592
+ //Let the user use Enter and Esc as shortcuts for "Update" and "Cancel"
593
+ $('.blc-inline-editor input[type="text"]').keypress(function (e) {
594
+ var editRow = $(this).closest('.blc-inline-editor');
595
+ if (e.which == 13) {
596
+ editRow.find('.blc-update-link-button').click();
597
+ return false;
598
+ } else if (e.which == 27) {
599
+ editRow.find('.blc-cancel-button').click();
600
+ return false;
601
+ }
602
+ return true;
603
+ });
604
+
605
+
606
+ //The "Update" button in the inline editor.
607
+ $('.blc-update-link-button').click(function() {
608
+ var editRow = $(this).closest('tr'),
609
+ master = editRow.prev('.blc-row');
610
+
611
+ //Ensure the new URL is not empty.
612
+ var urlField = editRow.find('.blc-link-url-field');
613
+ var newUrl = urlField.val();
614
+ if ($.trim(newUrl) == '') {
615
+ alert('<?php echo esc_js(__('Error: Link URL must not be empty.', 'broken-link-checker')); ?>');
616
+ urlField.focus();
617
+ return;
618
+ }
619
+
620
+ var newLinkText = null,
621
+ linkTextField = editRow.find('.blc-link-text-field'),
622
+ oldLinkText = master.data('link-text');
623
+ if (!linkTextField.prop('readonly')) {
624
+ newLinkText = linkTextField.val();
625
+ //Empty or identical to the original = leave the text unchanged.
626
+ if ((newLinkText === '') || (newLinkText === oldLinkText)) {
627
+ newLinkText = null;
628
+ }
629
+ }
630
+
631
+ updateLink(master, newUrl, newLinkText);
632
+ });
633
+
634
+ //The "Cancel" in the inline editor.
635
+ $(".blc-cancel-button").click(function () {
636
+ var editRow = $(this).closest('tr');
637
+ hideLinkEditor(editRow);
638
+ });
639
+
640
+ //The "Use this URL" button in the inline editor replaces the link URL
641
+ //with the selected suggestion URL.
642
+ $('#blc-links').on('click', '.blc-use-url-button', function() {
643
+ var button = $(this);
644
+ var suggestionUrl = button.closest('tr').find('.blc-suggestion-name a').attr('href');
645
+ button.closest('.blc-inline-editor').find('.blc-link-url-field').val(suggestionUrl);
646
+ });
647
+
648
+
649
+ //The "Unlink" button - remove the link/image from all posts, custom fields, etc.
650
+ $(".blc-unlink-button").click(function () {
651
+ var me = this;
652
+ var master = $(me).parents('.blc-row');
653
+ $(me).html('<?php echo esc_js(__('Wait...' , 'broken-link-checker')); ?>');
654
+
655
+ //Find the link ID
656
+ var link_id = master.attr('id').split('-')[2];
657
+
658
+ $.post(
659
+ "<?php echo admin_url('admin-ajax.php'); ?>",
660
+ {
661
+ 'action' : 'blc_unlink',
662
+ 'link_id' : link_id,
663
+ '_ajax_nonce' : '<?php echo esc_js(wp_create_nonce('blc_unlink')); ?>'
664
+ },
665
+ function (data, textStatus){
666
+ eval('data = ' + data);
667
+
668
+ if ( data && (typeof(data['error']) != 'undefined') ){
669
+ //An internal error occurred before the link could be edited.
670
+ //data.error is an error message.
671
+ alert(data.error);
672
+ } else {
673
+ if ( data.errors.length == 0 ){
674
+ //The link was successfully removed. Hide its details.
675
+ $('#link-details-'+link_id).hide();
676
+ //Flash the main row green to indicate success, then hide it.
677
+ var oldColor = master.css('background-color');
678
+ master.animate({ backgroundColor: "#E0FFB3" }, 200).animate({ backgroundColor: oldColor }, 300, function(){
679
+ master.hide();
680
+ });
681
+
682
+ alterLinkCounter(-1);
683
+
684
+ return;
685
+ } else {
686
+ //Build and display an error message.
687
+ var msg = '';
688
+
689
+ if ( data.cnt_okay > 0 ){
690
+ msg = msg + sprintf(
691
+ '<?php echo esc_js(__("%d instances of the link were successfully unlinked.", 'broken-link-checker')); ?>\n',
692
+ data.cnt_okay
693
+ );
694
+
695
+ if ( data.cnt_error > 0 ){
696
+ msg = msg + sprintf(
697
+ '<?php echo esc_js(__("However, %d instances couldn't be removed.", 'broken-link-checker')); ?>\n',
698
+ data.cnt_error
699
+ );
700
+ }
701
+ } else {
702
+ msg = msg + '<?php echo esc_js(__("The plugin failed to remove the link.", 'broken-link-checker')); ?>\n';
703
+ }
704
+
705
+ msg = msg + '\n<?php echo esc_js(__("The following error(s) occured :", 'broken-link-checker')); ?>\n* ';
706
+ msg = msg + data.errors.join('\n* ');
707
+
708
+ //Show the error message
709
+ alert(msg);
710
+ }
711
+ }
712
+
713
+ $(me).html('<?php echo esc_js(__('Unlink' , 'broken-link-checker')); ?>');
714
+ }
715
+ );
716
+ });
717
+
718
+ //--------------------------------------------
719
+ //The search box(es)
720
+ //--------------------------------------------
721
+
722
+ var searchForm = $('#search-links-dialog');
723
+
724
+ searchForm.dialog({
725
+ autoOpen : false,
726
+ dialogClass : 'blc-search-container',
727
+ resizable: false
728
+ });
729
+
730
+ $('#blc-open-search-box').click(function(){
731
+ if ( searchForm.dialog('isOpen') ){
732
+ searchForm.dialog('close');
733
+ } else {
734
+ searchForm
735
+ .dialog('open')
736
+ .dialog('widget')
737
+ .position({
738
+ my: 'right top',
739
+ at: 'right bottom',
740
+ of: $('#blc-open-search-box')
741
+ });
742
+ }
743
+ });
744
+
745
+ $('#blc-cancel-search').click(function(){
746
+ searchForm.dialog('close');
747
+ });
748
+
749
+ //The "Save This Search Query" button creates a new custom filter based on the current search
750
+ $('#blc-create-filter').click(function(){
751
+ var filter_name = prompt("<?php echo esc_js(__("Enter a name for the new custom filter", 'broken-link-checker')); ?>", "");
752
+ if ( filter_name ){
753
+ $('#blc-custom-filter-name').val(filter_name);
754
+ $('#custom-filter-form').submit();
755
+ }
756
+ });
757
+
758
+ //Display a confirmation dialog when the user clicks the "Delete This Filter" button
759
+ $('#blc-delete-filter').click(function(){
760
+ var message = '<?php
761
+ echo esc_js(
762
+ html_entity_decode(
763
+ __("You are about to delete the current filter.\n'Cancel' to stop, 'OK' to delete", 'broken-link-checker'),
764
+ ENT_QUOTES,
765
+ get_bloginfo('charset')
766
+ )
767
+ );
768
+ ?>';
769
+ return confirm(message);
770
+ });
771
+
772
+ //--------------------------------------------
773
+ // Bulk actions
774
+ //--------------------------------------------
775
+
776
+ $('#blc-bulk-action-form').submit(function(){
777
+ var action = $('#blc-bulk-action').val(), message;
778
+ if ( action == '-1' ){
779
+ action = $('#blc-bulk-action2').val();
780
+ }
781
+
782
+ if ( action == 'bulk-delete-sources' ){
783
+ //Convey the gravitas of deleting link sources.
784
+ message = '<?php
785
+ echo esc_js(
786
+ html_entity_decode(
787
+ __("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'),
788
+ ENT_QUOTES,
789
+ get_bloginfo('charset')
790
+ )
791
+ );
792
+ ?>';
793
+ if ( !confirm(message) ){
794
+ return false;
795
+ }
796
+ } else if ( action == 'bulk-unlink' ){
797
+ //Likewise for unlinking.
798
+ message = '<?php
799
+ echo esc_js(
800
+ html_entity_decode(
801
+ __("Are you sure you want to remove the selected links? This action can't be undone.\n'Cancel' to stop, 'OK' to remove", 'broken-link-checker'),
802
+ ENT_QUOTES,
803
+ get_bloginfo('charset')
804
+ )
805
+ );
806
+ ?>';
807
+ if ( !confirm(message) ){
808
+ return false;
809
+ }
810
+ }
811
+ });
812
+
813
+ //Automatically disable bulk actions that don't apply to the currently selected links.
814
+ $('#blc-bulk-action').focus(function() {
815
+ var redirectsSelected = false, brokenLinksSelected = false;
816
+ $('tr th.check-column input:checked', '#blc-links').each(function() {
817
+ var row = $(this).closest('tr');
818
+ if (row.hasClass('blc-redirect')) {
819
+ redirectsSelected = true
820
+ }
821
+ if (row.hasClass('link-status-error') || row.hasClass('link-status-warning')) {
822
+ brokenLinksSelected = true;
823
+ }
824
+ });
825
+
826
+ var bulkAction = $(this);
827
+ bulkAction.find('option[value="bulk-deredirect"]').prop('disabled', !redirectsSelected);
828
+ bulkAction.find('option[value="bulk-not-broken"]').prop('disabled', !brokenLinksSelected);
829
+ });
830
+
831
+ //------------------------------------------------------------
832
+ // Manipulate highlight settings for permanently broken links
833
+ //------------------------------------------------------------
834
+ var highlight_permanent_failures_checkbox = $('#highlight_permanent_failures');
835
+ var failure_duration_threshold_input = $('#failure_duration_threshold');
836
+
837
+ //Apply/remove highlights when the checkbox is (un)checked
838
+ highlight_permanent_failures_checkbox.change(function(){
839
+ //save_highlight_settings();
840
+
841
+ if ( this.checked ){
842
+ $('#blc-links tr.blc-permanently-broken').addClass('blc-permanently-broken-hl');
843
+ } else {
844
+ $('#blc-links tr.blc-permanently-broken').removeClass('blc-permanently-broken-hl');
845
+ }
846
+ });
847
+
848
+ //Apply/remove highlights when the duration threshold is changed.
849
+ failure_duration_threshold_input.change(function(){
850
+ var new_threshold = parseInt($(this).val());
851
+ //save_highlight_settings();
852
+ if (isNaN(new_threshold) || (new_threshold < 1)) {
853
+ return;
854
+ }
855
+
856
+ highlight_permanent_failures = highlight_permanent_failures_checkbox.is(':checked');
857
+
858
+ $('#blc-links tr.blc-row').each(function(index){
859
+ var days_broken = $(this).attr('data-days-broken');
860
+ if ( days_broken >= new_threshold ){
861
+ $(this).addClass('blc-permanently-broken');
862
+ if ( highlight_permanent_failures ){
863
+ $(this).addClass('blc-permanently-broken-hl');
864
+ }
865
+ } else {
866
+ $(this).removeClass('blc-permanently-broken').removeClass('blc-permanently-broken-hl');
867
+ }
868
+ });
869
+ });
870
+
871
+ //Show/hide table columns dynamically
872
+ $('#blc-column-selector input[type="checkbox"]').change(function(){
873
+ var checkbox = $(this);
874
+ var column_id = checkbox.attr('name').split(/\[|\]/)[1];
875
+ if (checkbox.is(':checked')){
876
+ $('td.column-'+column_id+', th.column-'+column_id, '#blc-links').removeClass('hidden');
877
+ } else {
878
+ $('td.column-'+column_id+', th.column-'+column_id, '#blc-links').addClass('hidden');
879
+ }
880
+
881
+ //Recalculate colspan's for detail rows to take into account the changed number of
882
+ //visible columns. Otherwise you can get some ugly layout glitches.
883
+ $('#blc-links tr.blc-link-details td').attr(
884
+ 'colspan',
885
+ $('#blc-column-selector input[type="checkbox"]:checked').length+1
886
+ );
887
+ });
888
+
889
+ //Unlike other fields in "Screen Options", the links-per-page setting
890
+ //is handled using straight form submission (POST), not AJAX.
891
+ $('#blc-per-page-apply-button').click(function(){
892
+ $('#adv-settings').submit();
893
+ });
894
+
895
+ $('#blc_links_per_page').keypress(function(e){
896
+ if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
897
+ $('#adv-settings').submit();
898
+ }
899
+ });
900
+
901
+ //Toggle status code colors when the corresponding checkbox is toggled
902
+ $('#table_color_code_status').click(function(){
903
+ if ( $(this).is(':checked') ){
904
+ $('#blc-links').addClass('color-code-link-status');
905
+ } else {
906
+ $('#blc-links').removeClass('color-code-link-status');
907
+ }
908
+ });
909
+
910
+ //Show the bulk edit/find & replace form when the user applies the appropriate bulk action
911
+ $('#doaction, #doaction2').click(function(e){
912
+ var n = $(this).attr('id').substr(2);
913
+ if ( $('select[name="'+n+'"]').val() == 'bulk-edit' ) {
914
+ e.preventDefault();
915
+ //Any links selected?
916
+ if ($('tbody th.check-column input:checked').length > 0){
917
+ $('#bulk-edit').show();
918
+ }
919
+ }
920
+ });
921
+
922
+ //Hide the bulk edit/find & replace form when "Cancel" is clicked
923
+ $('#bulk-edit .cancel').click(function(){
924
+ $('#bulk-edit').hide();
925
+ return false;
926
+ });
927
+
928
+ //Minimal input validation for the bulk edit form
929
+ $('#bulk-edit input[type="submit"]').click(function(e){
930
+ if( $('#bulk-edit input[name="search"]').val() == '' ){
931
+ alert('<?php echo esc_js(__('Enter a search string first.', 'broken-link-checker')); ?>');
932
+ $('#bulk-edit input[name="search"]').focus();
933
+ e.preventDefault();
934
+ return;
935
+ }
936
+
937
+ if ($('tbody th.check-column input:checked').length == 0){
938
+ alert('<?php echo esc_js(__('Select one or more links to edit.', 'broken-link-checker')); ?>');
939
+ e.preventDefault();
940
+ }
941
+ });
942
+
943
+
944
+ });
945
+
946
  </script>
includes/admin/options-page-js.php CHANGED
@@ -1,147 +1,147 @@
1
- <script type="text/javascript">
2
-
3
- jQuery(function($){
4
- $('#blc-tabs').tabs();
5
-
6
- //Refresh the "Status" box every 10 seconds
7
- function blcUpdateStatus(){
8
- $.getJSON(
9
- "<?php echo admin_url('admin-ajax.php'); ?>",
10
- {
11
- 'action' : 'blc_full_status',
12
- 'random' : Math.random()
13
- },
14
- function (data, textStatus){
15
- if ( data && ( typeof(data['text']) != 'undefined' ) ){
16
- $('#wsblc_full_status').html(data.text);
17
- } else {
18
- $('#wsblc_full_status').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
19
- }
20
-
21
- setTimeout(blcUpdateStatus, 10000);
22
- }
23
- );
24
- }
25
- blcUpdateStatus();
26
-
27
- //Refresh the avg. load display every 10 seconds
28
- function blcUpdateLoad(){
29
- $.get(
30
- "<?php echo admin_url('admin-ajax.php'); ?>",
31
- {
32
- 'action' : 'blc_current_load'
33
- },
34
- function (data, textStatus){
35
- $('#wsblc_current_load').html(data);
36
-
37
- setTimeout(blcUpdateLoad, 10000); //...update every 10 seconds
38
- }
39
- );
40
- }
41
- //Only do it if load limiting is available on this server, though.
42
- if ( $('#wsblc_current_load').length > 0 ){
43
- blcUpdateLoad();
44
- }
45
-
46
-
47
- var toggleButton = $('#blc-debug-info-toggle');
48
-
49
- toggleButton.click(function(){
50
-
51
- var box = $('#blc-debug-info');
52
- box.toggle();
53
- if( box.is(':visible') ){
54
- toggleButton.text('<?php _e('Hide debug info', 'broken-link-checker'); ?>');
55
- } else {
56
- toggleButton.text('<?php _e('Show debug info', 'broken-link-checker'); ?>');
57
- }
58
-
59
- });
60
-
61
- $('#toggle-broken-link-css-editor').click(function(){
62
- var box = $('#broken-link-css-wrap').toggleClass('hidden');
63
-
64
- $.cookie(
65
- box.attr('id'),
66
- box.hasClass('hidden')?'0':'1',
67
- {
68
- expires : 365
69
- }
70
- );
71
-
72
- return false;
73
- });
74
-
75
- $('#toggle-removed-link-css-editor').click(function(){
76
- var box = $('#removed-link-css-wrap').toggleClass('hidden');
77
-
78
- $.cookie(
79
- box.attr('id'),
80
- box.hasClass('hidden')?'0':'1',
81
- {
82
- expires : 365
83
- }
84
- );
85
-
86
- return false;
87
- });
88
-
89
- //Show/hide per-module settings
90
- $('.toggle-module-settings').click(function(){
91
- var settingsBox = $(this).parent().find('.module-extra-settings');
92
- if ( settingsBox.length > 0 ){
93
- settingsBox.toggleClass('hidden');
94
- $.cookie(
95
- settingsBox.attr('id'),
96
- settingsBox.hasClass('hidden')?'0':'1',
97
- {
98
- expires : 365
99
- }
100
- );
101
- }
102
- return false;
103
- });
104
-
105
- //When the user ticks the "Custom fields" box, display the field list input
106
- //so that they notice that they need to enter the field names.
107
- $('#module-checkbox-custom_field').click(function(){
108
- var box = $(this);
109
- var fieldList = $('#blc_custom_fields');
110
- if ( box.is(':checked') && ( $.trim(fieldList.val()) == '' ) ){
111
- $('#module-extra-settings-custom_field').removeClass('hidden');
112
- }
113
- });
114
-
115
- //When the user ticks the "Custom fields" box, display the field list input
116
- //so that they notice that they need to enter the field names.
117
- $('#module-checkbox-acf_field').click(function(){
118
- var box = $(this);
119
- var fieldList = $('#blc_acf_fields');
120
- if ( box.is(':checked') && ( $.trim(fieldList.val()) == '' ) ){
121
- $('#module-extra-settings-acf_field').removeClass('hidden');
122
- }
123
- });
124
-
125
- //Handle the "Recheck" button
126
- $('#start-recheck').click(function(){
127
- $('#recheck').val('1'); //populate the hidden field
128
- $('#link_checker_options input[name="submit"]').click(); //.submit() didn't work for some reason
129
- });
130
-
131
- //Enable/disable log-related options depending on whether "Enable logging" is on.
132
- function blcToggleLogOptions() {
133
- $('#blc-logging-options')
134
- .find('input')
135
- .prop('disabled', ! $('#logging_enabled').is(':checked'));
136
- }
137
-
138
- blcToggleLogOptions();
139
- $('#logging_enabled').change(blcToggleLogOptions);
140
-
141
- //
142
- $('#target_resource_usage').change(function() {
143
- $('#target_resource_usage_percent').text($(this).val() + '%')
144
- });
145
- });
146
-
147
  </script>
1
+ <script type="text/javascript">
2
+
3
+ jQuery(function($){
4
+ $('#blc-tabs').tabs();
5
+
6
+ //Refresh the "Status" box every 10 seconds
7
+ function blcUpdateStatus(){
8
+ $.getJSON(
9
+ "<?php echo admin_url('admin-ajax.php'); ?>",
10
+ {
11
+ 'action' : 'blc_full_status',
12
+ 'random' : Math.random()
13
+ },
14
+ function (data, textStatus){
15
+ if ( data && ( typeof(data['text']) != 'undefined' ) ){
16
+ $('#wsblc_full_status').html(data.text);
17
+ } else {
18
+ $('#wsblc_full_status').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
19
+ }
20
+
21
+ setTimeout(blcUpdateStatus, 10000);
22
+ }
23
+ );
24
+ }
25
+ blcUpdateStatus();
26
+
27
+ //Refresh the avg. load display every 10 seconds
28
+ function blcUpdateLoad(){
29
+ $.get(
30
+ "<?php echo admin_url('admin-ajax.php'); ?>",
31
+ {
32
+ 'action' : 'blc_current_load'
33
+ },
34
+ function (data, textStatus){
35
+ $('#wsblc_current_load').html(data);
36
+
37
+ setTimeout(blcUpdateLoad, 10000); //...update every 10 seconds
38
+ }
39
+ );
40
+ }
41
+ //Only do it if load limiting is available on this server, though.
42
+ if ( $('#wsblc_current_load').length > 0 ){
43
+ blcUpdateLoad();
44
+ }
45
+
46
+
47
+ var toggleButton = $('#blc-debug-info-toggle');
48
+
49
+ toggleButton.click(function(){
50
+
51
+ var box = $('#blc-debug-info');
52
+ box.toggle();
53
+ if( box.is(':visible') ){
54
+ toggleButton.text('<?php _e('Hide debug info', 'broken-link-checker'); ?>');
55
+ } else {
56
+ toggleButton.text('<?php _e('Show debug info', 'broken-link-checker'); ?>');
57
+ }
58
+
59
+ });
60
+
61
+ $('#toggle-broken-link-css-editor').click(function(){
62
+ var box = $('#broken-link-css-wrap').toggleClass('hidden');
63
+
64
+ $.cookie(
65
+ box.attr('id'),
66
+ box.hasClass('hidden')?'0':'1',
67
+ {
68
+ expires : 365
69
+ }
70
+ );
71
+
72
+ return false;
73
+ });
74
+
75
+ $('#toggle-removed-link-css-editor').click(function(){
76
+ var box = $('#removed-link-css-wrap').toggleClass('hidden');
77
+
78
+ $.cookie(
79
+ box.attr('id'),
80
+ box.hasClass('hidden')?'0':'1',
81
+ {
82
+ expires : 365
83
+ }
84
+ );
85
+
86
+ return false;
87
+ });
88
+
89
+ //Show/hide per-module settings
90
+ $('.toggle-module-settings').click(function(){
91
+ var settingsBox = $(this).parent().find('.module-extra-settings');
92
+ if ( settingsBox.length > 0 ){
93
+ settingsBox.toggleClass('hidden');
94
+ $.cookie(
95
+ settingsBox.attr('id'),
96
+ settingsBox.hasClass('hidden')?'0':'1',
97
+ {
98
+ expires : 365
99
+ }
100
+ );
101
+ }
102
+ return false;
103
+ });
104
+
105
+ //When the user ticks the "Custom fields" box, display the field list input
106
+ //so that they notice that they need to enter the field names.
107
+ $('#module-checkbox-custom_field').click(function(){
108
+ var box = $(this);
109
+ var fieldList = $('#blc_custom_fields');
110
+ if ( box.is(':checked') && ( $.trim(fieldList.val()) == '' ) ){
111
+ $('#module-extra-settings-custom_field').removeClass('hidden');
112
+ }
113
+ });
114
+
115
+ //When the user ticks the "Custom fields" box, display the field list input
116
+ //so that they notice that they need to enter the field names.
117
+ $('#module-checkbox-acf_field').click(function(){
118
+ var box = $(this);
119
+ var fieldList = $('#blc_acf_fields');
120
+ if ( box.is(':checked') && ( $.trim(fieldList.val()) == '' ) ){
121
+ $('#module-extra-settings-acf_field').removeClass('hidden');
122
+ }
123
+ });
124
+
125
+ //Handle the "Recheck" button
126
+ $('#start-recheck').click(function(){
127
+ $('#recheck').val('1'); //populate the hidden field
128
+ $('#link_checker_options input[name="submit"]').click(); //.submit() didn't work for some reason
129
+ });
130
+
131
+ //Enable/disable log-related options depending on whether "Enable logging" is on.
132
+ function blcToggleLogOptions() {
133
+ $('#blc-logging-options')
134
+ .find('input')
135
+ .prop('disabled', ! $('#logging_enabled').is(':checked'));
136
+ }
137
+
138
+ blcToggleLogOptions();
139
+ $('#logging_enabled').change(blcToggleLogOptions);
140
+
141
+ //
142
+ $('#target_resource_usage').change(function() {
143
+ $('#target_resource_usage_percent').text($(this).val() + '%')
144
+ });
145
+ });
146
+
147
  </script>
includes/admin/search-form.php CHANGED
@@ -1,117 +1,117 @@
1
- <?php
2
- $search_params = $current_filter['search_params'];
3
- ?>
4
- <div class="search-box">
5
-
6
- <?php
7
- //If we're currently displaying search results offer the user the option to
8
- //save the search query as a custom filter.
9
- if ( $filter_id == 'search' ){
10
- ?>
11
- <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">
12
- <?php wp_nonce_field('create-custom-filter'); ?>
13
- <input type="hidden" name="name" id="blc-custom-filter-name" value="" />
14
- <input type="hidden" name="params" id="blc-custom-filter-params" value="<?php echo http_build_query($search_params, null, '&'); ?>" />
15
- <input type="hidden" name="action" value="create-custom-filter" />
16
- <input type="button" value="<?php esc_attr_e( 'Save This Search As a Filter', 'broken-link-checker' ); ?>" id="blc-create-filter" class="button" />
17
- </form>
18
- <?php
19
- } elseif ( !empty($current_filter['custom']) ){
20
- //If we're displaying a custom filter give an option to delete it.
21
- ?>
22
- <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">
23
- <?php wp_nonce_field('delete-custom-filter'); ?>
24
- <input type="hidden" name="filter_id" id="blc-custom-filter-id" value="<?php echo $filter_id; ?>" />
25
- <input type="hidden" name="action" value="delete-custom-filter" />
26
- <input type="submit" value="<?php esc_attr_e( 'Delete This Filter', 'broken-link-checker' ); ?>" id="blc-delete-filter" class="button" />
27
- </form>
28
- <?php
29
- }
30
- ?>
31
-
32
- <input type="button" value="<?php esc_attr_e( 'Search', 'broken-link-checker' ); ?> &raquo;" id="blc-open-search-box" class="button" />
33
- </div>
34
-
35
- <!-- The search dialog -->
36
- <div id='search-links-dialog' title='Search'>
37
- <form class="search-form" action="<?php echo admin_url('tools.php?page=view-broken-links'); ?>" method="get">
38
- <input type="hidden" name="page" value="view-broken-links" />
39
- <input type="hidden" name="filter_id" value="search" />
40
- <fieldset>
41
-
42
- <label for="s_link_text"><?php _e('Link text', 'broken-link-checker'); ?></label>
43
- <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" />
44
-
45
- <label for="s_link_url"><?php _e('URL', 'broken-link-checker'); ?></label>
46
- <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" />
47
-
48
- <label for="s_http_code"><?php _e('HTTP code', 'broken-link-checker'); ?></label>
49
- <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" />
50
-
51
- <label for="s_filter"><?php _e('Link status', 'broken-link-checker'); ?></label>
52
- <select name="s_filter" id="s_filter">
53
- <?php
54
- if ( !empty($search_params['s_filter']) ){
55
- $search_subfilter = $search_params['s_filter'];
56
- } else {
57
- $search_subfilter = 'all';
58
- }
59
-
60
- $linkQuery = blcLinkQuery::getInstance();
61
- foreach ($linkQuery->native_filters as $filter => $data){
62
- $selected = ($search_subfilter == $filter)?' selected="selected"':'';
63
- printf('<option value="%s"%s>%s</option>', $filter, $selected, $data['name']);
64
- }
65
- ?>
66
- </select>
67
-
68
- <label for="s_link_type"><?php _e('Link type', 'broken-link-checker'); ?></label>
69
- <select name="s_link_type" id="s_link_type">
70
- <option value=""><?php _e('Any', 'broken-link-checker'); ?></option>
71
- <?php
72
- $moduleManager = blcModuleManager::getInstance();
73
-
74
- printf('<optgroup label="%s">', esc_attr(__('Links used in', 'broken-link-checker')));
75
- $containers = $moduleManager->get_modules_by_category('container', false, true);
76
- foreach($containers as $container_type => $module_data){
77
- if ( !empty($module_data['ModuleHidden']) || !$moduleManager->is_active($container_type) ){
78
- continue;
79
- }
80
- $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $container_type )?' selected="selected"':'';
81
- printf('<option value="%s"%s>%s</option>', $container_type, $selected, $module_data['Name']);
82
- }
83
- echo '</optgroup>';
84
- //TODO: Better group labels
85
- printf('<optgroup label="%s">', esc_attr(__('Link type', 'broken-link-checker')));
86
- $parsers = $moduleManager->get_modules_by_category('parser', false, true);
87
- foreach($parsers as $parser_type => $module_data){
88
- if ( !empty($module_data['ModuleHidden']) || !$moduleManager->is_active($parser_type) ){
89
- continue;
90
- }
91
- $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $parser_type )?' selected="selected"':'';
92
- printf('<option value="%s"%s>%s</option>', $parser_type, $selected, $module_data['Name']);
93
- }
94
- echo '</optgroup>';
95
-
96
- /*
97
- $link_types = array(
98
- __('Any', 'broken-link-checker') => '',
99
- __('Normal link', 'broken-link-checker') => 'link',
100
- __('Image', 'broken-link-checker') => 'image',
101
- __('Custom field', 'broken-link-checker') => 'custom_field',
102
- __('Bookmark', 'broken-link-checker') => 'blogroll',
103
- __('Comment', 'broken-link-checker') => 'comment',
104
- );
105
- */
106
- ?>
107
- </select>
108
-
109
- </fieldset>
110
-
111
- <div id="blc-search-button-row">
112
- <input type="submit" value="<?php esc_attr_e( 'Search Links', 'broken-link-checker' ); ?>" id="blc-search-button" name="search_button" class="button-primary" />
113
- <input type="button" value="<?php esc_attr_e( 'Cancel', 'broken-link-checker' ); ?>" id="blc-cancel-search" class="button" />
114
- </div>
115
-
116
- </form>
117
  </div>
1
+ <?php
2
+ $search_params = $current_filter['search_params'];
3
+ ?>
4
+ <div class="search-box">
5
+
6
+ <?php
7
+ //If we're currently displaying search results offer the user the option to
8
+ //save the search query as a custom filter.
9
+ if ( $filter_id == 'search' ){
10
+ ?>
11
+ <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">
12
+ <?php wp_nonce_field('create-custom-filter'); ?>
13
+ <input type="hidden" name="name" id="blc-custom-filter-name" value="" />
14
+ <input type="hidden" name="params" id="blc-custom-filter-params" value="<?php echo http_build_query($search_params, null, '&'); ?>" />
15
+ <input type="hidden" name="action" value="create-custom-filter" />
16
+ <input type="button" value="<?php esc_attr_e( 'Save This Search As a Filter', 'broken-link-checker' ); ?>" id="blc-create-filter" class="button" />
17
+ </form>
18
+ <?php
19
+ } elseif ( !empty($current_filter['custom']) ){
20
+ //If we're displaying a custom filter give an option to delete it.
21
+ ?>
22
+ <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">
23
+ <?php wp_nonce_field('delete-custom-filter'); ?>
24
+ <input type="hidden" name="filter_id" id="blc-custom-filter-id" value="<?php echo $filter_id; ?>" />
25
+ <input type="hidden" name="action" value="delete-custom-filter" />
26
+ <input type="submit" value="<?php esc_attr_e( 'Delete This Filter', 'broken-link-checker' ); ?>" id="blc-delete-filter" class="button" />
27
+ </form>
28
+ <?php
29
+ }
30
+ ?>
31
+
32
+ <input type="button" value="<?php esc_attr_e( 'Search', 'broken-link-checker' ); ?> &raquo;" id="blc-open-search-box" class="button" />
33
+ </div>
34
+
35
+ <!-- The search dialog -->
36
+ <div id='search-links-dialog' title='Search'>
37
+ <form class="search-form" action="<?php echo admin_url('tools.php?page=view-broken-links'); ?>" method="get">
38
+ <input type="hidden" name="page" value="view-broken-links" />
39
+ <input type="hidden" name="filter_id" value="search" />
40
+ <fieldset>
41
+
42
+ <label for="s_link_text"><?php _e('Link text', 'broken-link-checker'); ?></label>
43
+ <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" />
44
+
45
+ <label for="s_link_url"><?php _e('URL', 'broken-link-checker'); ?></label>
46
+ <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" />
47
+
48
+ <label for="s_http_code"><?php _e('HTTP code', 'broken-link-checker'); ?></label>
49
+ <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" />
50
+
51
+ <label for="s_filter"><?php _e('Link status', 'broken-link-checker'); ?></label>
52
+ <select name="s_filter" id="s_filter">
53
+ <?php
54
+ if ( !empty($search_params['s_filter']) ){
55
+ $search_subfilter = $search_params['s_filter'];
56
+ } else {
57
+ $search_subfilter = 'all';
58
+ }
59
+
60
+ $linkQuery = blcLinkQuery::getInstance();
61
+ foreach ($linkQuery->native_filters as $filter => $data){
62
+ $selected = ($search_subfilter == $filter)?' selected="selected"':'';
63
+ printf('<option value="%s"%s>%s</option>', $filter, $selected, $data['name']);
64
+ }
65
+ ?>
66
+ </select>
67
+
68
+ <label for="s_link_type"><?php _e('Link type', 'broken-link-checker'); ?></label>
69
+ <select name="s_link_type" id="s_link_type">
70
+ <option value=""><?php _e('Any', 'broken-link-checker'); ?></option>
71
+ <?php
72
+ $moduleManager = blcModuleManager::getInstance();
73
+
74
+ printf('<optgroup label="%s">', esc_attr(__('Links used in', 'broken-link-checker')));
75
+ $containers = $moduleManager->get_modules_by_category('container', false, true);
76
+ foreach($containers as $container_type => $module_data){
77
+ if ( !empty($module_data['ModuleHidden']) || !$moduleManager->is_active($container_type) ){
78
+ continue;
79
+ }
80
+ $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $container_type )?' selected="selected"':'';
81
+ printf('<option value="%s"%s>%s</option>', $container_type, $selected, $module_data['Name']);
82
+ }
83
+ echo '</optgroup>';
84
+ //TODO: Better group labels
85
+ printf('<optgroup label="%s">', esc_attr(__('Link type', 'broken-link-checker')));
86
+ $parsers = $moduleManager->get_modules_by_category('parser', false, true);
87
+ foreach($parsers as $parser_type => $module_data){
88
+ if ( !empty($module_data['ModuleHidden']) || !$moduleManager->is_active($parser_type) ){
89
+ continue;
90
+ }
91
+ $selected = ( isset($search_params['s_link_type']) && $search_params['s_link_type'] == $parser_type )?' selected="selected"':'';
92
+ printf('<option value="%s"%s>%s</option>', $parser_type, $selected, $module_data['Name']);
93
+ }
94
+ echo '</optgroup>';
95
+
96
+ /*
97
+ $link_types = array(
98
+ __('Any', 'broken-link-checker') => '',
99
+ __('Normal link', 'broken-link-checker') => 'link',
100
+ __('Image', 'broken-link-checker') => 'image',
101
+ __('Custom field', 'broken-link-checker') => 'custom_field',
102
+ __('Bookmark', 'broken-link-checker') => 'blogroll',
103
+ __('Comment', 'broken-link-checker') => 'comment',
104
+ );
105
+ */
106
+ ?>
107
+ </select>
108
+
109
+ </fieldset>
110
+
111
+ <div id="blc-search-button-row">
112
+ <input type="submit" value="<?php esc_attr_e( 'Search Links', 'broken-link-checker' ); ?>" id="blc-search-button" name="search_button" class="button-primary" />
113
+ <input type="button" value="<?php esc_attr_e( 'Cancel', 'broken-link-checker' ); ?>" id="blc-cancel-search" class="button" />
114
+ </div>
115
+
116
+ </form>
117
  </div>
includes/admin/sidebar.php CHANGED
@@ -1,14 +1,14 @@
1
- <!-- Advertising -->
2
- <?php
3
- $configuration = blc_get_configuration();
4
- if ( !$configuration->get('user_has_donated') ):
5
- ?>
6
- <div id="managewp-ad" class="postbox">
7
- <div class="inside">
8
- <a href="http://managewp.com/?utm_source=broken_link_checker&utm_medium=Banner&utm_content=mwp250_2&utm_campaign=Plugins" title="ManageWP">
9
- <img src="<?php echo plugins_url('images/mwp250_2.png', BLC_PLUGIN_FILE) ?>" width="250" height="250" alt="ManageWP">
10
- </a>
11
- </div>
12
- </div>
13
- <?php
14
  endif; ?>
1
+ <!-- Advertising -->
2
+ <?php
3
+ $configuration = blc_get_configuration();
4
+ if ( !$configuration->get('user_has_donated') ):
5
+ ?>
6
+ <div id="managewp-ad" class="postbox">
7
+ <div class="inside">
8
+ <a href="http://managewp.com/?utm_source=broken_link_checker&utm_medium=Banner&utm_content=mwp250_2&utm_campaign=Plugins" title="ManageWP">
9
+ <img src="<?php echo plugins_url('images/mwp250_2.png', BLC_PLUGIN_FILE) ?>" width="250" height="250" alt="ManageWP">
10
+ </a>
11
+ </div>
12
+ </div>
13
+ <?php
14
  endif; ?>
includes/admin/table-printer.php CHANGED
File without changes
includes/any-post.php CHANGED
@@ -1,774 +1,804 @@
1
- <?php
2
-
3
- /**
4
- * The manager to rule all (post) managers.
5
- *
6
- * This class dynamically registers container modules for the available post types
7
- * (including custom post types) and does stuff that pertain to all of them, such
8
- * as handling save/delete hooks and (re)creating synch records.
9
- *
10
- * @package Broken Link Checker
11
- * @author Janis Elsts
12
- * @access private
13
- */
14
- class blcPostTypeOverlord {
15
- public $enabled_post_types = array(); //Post types currently selected for link checking
16
- public $enabled_post_statuses = array('publish'); //Only posts that have one of these statuses shall be checked
17
-
18
- var $plugin_conf;
19
- var $resynch_already_done = false;
20
-
21
- /**
22
- * Class "constructor". Can't use an actual constructor due to how PHP4 handles object references.
23
- *
24
- * Specifically, this class is a singleton. The function needs to pass $this to several other
25
- * functions (to set up hooks), which will store the reference for later use. However, it appears
26
- * that in PHP4 the actual value of $this is thrown away right after the constructor finishes, and
27
- * `new` returns a *copy* of $this. The result is that getInstance() won't be returning a ref.
28
- * to the same object as is used for hook callbacks. And that's horrible.
29
- *
30
- * Sets up hooks that monitor added/modified/deleted posts and registers
31
- * virtual modules for all post types.
32
- *
33
- * @return void
34
- */
35
- function init(){
36
- $this->plugin_conf = blc_get_configuration();
37
-
38
- if ( isset($this->plugin_conf->options['enabled_post_statuses']) ){
39
- $this->enabled_post_statuses = $this->plugin_conf->options['enabled_post_statuses'];
40
- }
41
-
42
- //Register a virtual container module for each enabled post type
43
- $module_manager = blcModuleManager::getInstance();
44
-
45
- $post_types = get_post_types(array(), 'objects');
46
- $exceptions = array('revision', 'nav_menu_item', 'attachment');
47
-
48
- foreach($post_types as $data){
49
- $post_type = $data->name;
50
-
51
- if ( in_array($post_type, $exceptions) ){
52
- continue;
53
- }
54
-
55
- $module_manager->register_virtual_module(
56
- $post_type,
57
- array(
58
- 'Name' => $data->labels->name,
59
- 'ModuleCategory' => 'container',
60
- 'ModuleContext' => 'all',
61
- 'ModuleClassName' => 'blcAnyPostContainerManager',
62
- )
63
- );
64
- }
65
-
66
- //These hooks update the synch & instance records when posts are added, deleted or modified.
67
- add_action('delete_post', array(&$this,'post_deleted'));
68
- add_action('save_post', array(&$this,'post_saved'));
69
- //We also treat post trashing/untrashing as delete/save.
70
- add_action('trash_post', array(&$this,'post_deleted'));
71
- add_action('untrash_post', array(&$this,'post_saved'));
72
-
73
- //Highlight and nofollow broken links in posts & pages
74
- if ( $this->plugin_conf->options['mark_broken_links'] || $this->plugin_conf->options['nofollow_broken_links'] ){
75
- add_filter( 'the_content', array(&$this, 'hook_the_content') );
76
- if ( $this->plugin_conf->options['mark_broken_links'] && !empty( $this->plugin_conf->options['broken_link_css'] ) ){
77
- add_action( 'wp_head', array(&$this,'hook_wp_head') );
78
- }
79
- }
80
- }
81
-
82
- /**
83
- * Retrieve an instance of the overlord class.
84
- *
85
- * @return blcPostTypeOverlord
86
- */
87
- static function getInstance(){
88
- static $instance = null;
89
- if ( is_null($instance) ){
90
- $instance = new blcPostTypeOverlord;
91
- $instance->init();
92
- }
93
- return $instance;
94
- }
95
-
96
- /**
97
- * Notify the overlord that a post type is active.
98
- *
99
- * Called by individual instances of blcAnyPostContainerManager to let
100
- * the overlord know that they've been created. Since a module instance
101
- * is only created if the module is active, this event indicates that
102
- * the user has enabled the corresponding post type for link checking.
103
- *
104
- * @param string $post_type
105
- * @return void
106
- */
107
- function post_type_enabled($post_type){
108
- if ( !in_array($post_type, $this->enabled_post_types) ){
109
- $this->enabled_post_types[] = $post_type;
110
- }
111
- }
112
-
113
- /**
114
- * Remove the synch. record and link instances associated with a post when it's deleted
115
- *
116
- * @param int $post_id
117
- * @return void
118
- */
119
- function post_deleted($post_id){
120
- //Get the container type matching the type of the deleted post
121
- $post = get_post($post_id);
122
- if ( !$post ){
123
- return;
124
- }
125
- //Get the associated container object
126
- $post_container = blcContainerHelper::get_container( array($post->post_type, intval($post_id)) );
127
-
128
- if ( $post_container ){
129
- //Delete it
130
- $post_container->delete();
131
- //Clean up any dangling links
132
- blc_cleanup_links();
133
- }
134
- }
135
-
136
- /**
137
- * When a post is saved or modified, mark it as unparsed.
138
- *
139
- * @param int $post_id
140
- * @return void
141
- */
142
- function post_saved($post_id){
143
- //Get the container type matching the type of the deleted post
144
- $post = get_post($post_id);
145
- if ( !$post ){
146
- return;
147
- }
148
-
149
- //Only check links in currently enabled post types
150
- if ( !in_array($post->post_type, $this->enabled_post_types) ) return;
151
-
152
- //Only check posts that have one of the allowed statuses
153
- if ( !in_array($post->post_status, $this->enabled_post_statuses) ) return;
154
-
155
- //Get the container & mark it as unparsed
156
- $args = array($post->post_type, intval($post_id));
157
- $post_container = blcContainerHelper::get_container( $args );
158
-
159
- $post_container->mark_as_unsynched();
160
- }
161
-
162
-
163
- /**
164
- * Create or update synchronization records for all posts.
165
- *
166
- * @param string $container_type
167
- * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
168
- * @return void
169
- */
170
- function resynch($container_type = '', $forced = false){
171
- global $wpdb; /** @var wpdb $wpdb */
172
- global $blclog;
173
-
174
- //Resynch is expensive in terms of DB performance. Thus we only do it once, processing
175
- //all post types in one go and ignoring any further resynch requests during this pageload.
176
- //BUG: This might be a problem if there ever is an actual need to run resynch twice or
177
- //more per pageload.
178
- if ( $this->resynch_already_done ){
179
- $blclog->log(sprintf('...... Skipping "%s" resyncyh since all post types were already synched.', $container_type));
180
- return;
181
- }
182
-
183
- if ( empty($this->enabled_post_types) ){
184
- $blclog->warn(sprintf('...... Skipping "%s" resyncyh since no post types are enabled.', $container_type));
185
- return;
186
- }
187
-
188
- $escaped_post_types = array_map('esc_sql', $this->enabled_post_types);
189
- $escaped_post_statuses = array_map('esc_sql', $this->enabled_post_statuses);
190
-
191
- if ( $forced ){
192
- //Create new synchronization records for all posts.
193
- $blclog->log('...... Creating synch records for these post types: '.implode(', ', $escaped_post_types) . ' that have one of these statuses: ' . implode(', ', $escaped_post_statuses));
194
- $start = microtime(true);
195
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
196
- SELECT posts.id, posts.post_type, 0
197
- FROM {$wpdb->posts} AS posts
198
- WHERE
199
- posts.post_status IN (%s)
200
- AND posts.post_type IN (%s)";
201
- $q = sprintf(
202
- $q,
203
- "'" . implode("', '", $escaped_post_statuses) . "'",
204
- "'" . implode("', '", $escaped_post_types) . "'"
205
- );
206
- $wpdb->query( $q );
207
- $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
208
- } else {
209
- //Delete synch records corresponding to posts that no longer exist.
210
- $blclog->log('...... Deleting synch records for removed posts');
211
- $start = microtime(true);
212
- $q = "DELETE synch.*
213
- FROM
214
- {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->posts} AS posts
215
- ON posts.ID = synch.container_id
216
- WHERE
217
- synch.container_type IN (%s) AND posts.ID IS NULL";
218
- $q = sprintf(
219
- $q,
220
- "'" . implode("', '", $escaped_post_types) . "'"
221
- );
222
- $wpdb->query( $q );
223
- $elapsed = microtime(true) - $start;
224
- $blclog->debug($q);
225
- $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
226
-
227
- //Delete records where the post status is not one of the enabled statuses.
228
- $blclog->log('...... Deleting synch records for posts that have a disallowed status');
229
- $start = microtime(true);
230
- $q = "DELETE synch.*
231
- FROM
232
- {$wpdb->prefix}blc_synch AS synch
233
- LEFT JOIN {$wpdb->posts} AS posts
234
- ON (synch.container_id = posts.ID and synch.container_type = posts.post_type)
235
- WHERE
236
- posts.post_status NOT IN (%s)";
237
- $q = sprintf(
238
- $q,
239
- "'" . implode("', '", $escaped_post_statuses) . "'"
240
- );
241
- $wpdb->query( $q );
242
- $elapsed = microtime(true) - $start;
243
- $blclog->debug($q);
244
- $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
245
-
246
- //Remove the 'synched' flag from all posts that have been updated
247
- //since the last time they were parsed/synchronized.
248
- $blclog->log('...... Marking changed posts as unsynched');
249
- $start = microtime(true);
250
- $q = "UPDATE
251
- {$wpdb->prefix}blc_synch AS synch
252
- JOIN {$wpdb->posts} AS posts ON (synch.container_id = posts.ID and synch.container_type=posts.post_type)
253
- SET
254
- synched = 0
255
- WHERE
256
- synch.last_synch < posts.post_modified";
257
- $wpdb->query( $q );
258
- $elapsed = microtime(true) - $start;
259
- $blclog->debug($q);
260
- $blclog->log(sprintf('...... %d rows updated in %.3f seconds', $wpdb->rows_affected, $elapsed));
261
-
262
- //Create synch. records for posts that don't have them.
263
- $blclog->log('...... Creating synch records for new posts');
264
- $start = microtime(true);
265
- $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
266
- SELECT posts.id, posts.post_type, 0
267
- FROM
268
- {$wpdb->posts} AS posts LEFT JOIN {$wpdb->prefix}blc_synch AS synch
269
- ON (synch.container_id = posts.ID and synch.container_type=posts.post_type)
270
- WHERE
271
- posts.post_status IN (%s)
272
- AND posts.post_type IN (%s)
273
- AND synch.container_id IS NULL";
274
- $q = sprintf(
275
- $q,
276
- "'" . implode("', '", $escaped_post_statuses) . "'",
277
- "'" . implode("', '", $escaped_post_types) . "'"
278
- );
279
- $wpdb->query($q);
280
- $elapsed = microtime(true) - $start;
281
- $blclog->debug($q);
282
- $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, $elapsed));
283
- }
284
-
285
- $this->resynch_already_done = true;
286
- }
287
-
288
- /**
289
- * Hook for the 'the_content' filter. Scans the current post and adds the 'broken_link'
290
- * CSS class to all links that are known to be broken. Currently works only on standard
291
- * HTML links (i.e. the '<a href=...' kind).
292
- *
293
- * @param string $content Post content
294
- * @return string Modified post content.
295
- */
296
- function hook_the_content($content){
297
- global $post, $wpdb; /** @var wpdb $wpdb */
298
- if ( empty($post) || !in_array($post->post_type, $this->enabled_post_types)) {
299
- return $content;
300
- }
301
-
302
- //Retrieve info about all occurrences of broken links in the current post
303
- $q = "
304
- SELECT instances.raw_url
305
- FROM {$wpdb->prefix}blc_instances AS instances JOIN {$wpdb->prefix}blc_links AS links
306
- ON instances.link_id = links.link_id
307
- WHERE
308
- instances.container_type = %s
309
- AND instances.container_id = %d
310
- AND links.broken = 1
311
- AND parser_type = 'link'
312
- ";
313
- $q = $wpdb->prepare($q, $post->post_type, $post->ID);
314
- $links = $wpdb->get_results($q, ARRAY_A);
315
-
316
- //Return the content unmodified if there are no broken links in this post.
317
- if ( empty($links) || !is_array($links) ){
318
- return $content;
319
- }
320
-
321
- //Put the broken link URLs in an array
322
- $broken_link_urls = array();
323
- foreach($links as $link){
324
- $broken_link_urls[] = $link['raw_url'];
325
- }
326
-
327
- //Iterate over all HTML links and modify the broken ones
328
- if ( $parser = blcParserHelper::get_parser('link') ){
329
- $content = $parser->multi_edit($content, array(&$this, 'highlight_broken_link'), $broken_link_urls);
330
- }
331
-
332
- return $content;
333
- }
334
-
335
- /**
336
- * Analyse a link and add 'broken_link' CSS class if the link is broken.
337
- *
338
- * @see blcHtmlLink::multi_edit()
339
- *
340
- * @param array $link Associative array of link data.
341
- * @param array $broken_link_urls List of broken link URLs present in the current post.
342
- * @return array|string The modified link
343
- */
344
- function highlight_broken_link($link, $broken_link_urls){
345
- if ( !in_array($link['href'], $broken_link_urls) ){
346
- //Link not broken = return the original link tag
347
- return $link['#raw'];
348
- }
349
-
350
- //Add 'broken_link' to the 'class' attribute (unless already present).
351
- if ( $this->plugin_conf->options['mark_broken_links'] ){
352
- if ( isset($link['class']) ){
353
- $classes = explode(' ', $link['class']);
354
- if ( !in_array('broken_link', $classes) ){
355
- $classes[] = 'broken_link';
356
- $link['class'] = implode(' ', $classes);
357
- }
358
- } else {
359
- $link['class'] = 'broken_link';
360
- }
361
- }
362
-
363
- //Nofollow the link (unless it's already nofollow'ed)
364
- if ( $this->plugin_conf->options['nofollow_broken_links'] ){
365
- if ( isset($link['rel']) ){
366
- $relations = explode(' ', $link['rel']);
367
- if ( !in_array('nofollow', $relations) ){
368
- $relations[] = 'nofollow';
369
- $link['rel'] = implode(' ', $relations);
370
- }
371
- } else {
372
- $link['rel'] = 'nofollow';
373
- }
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
- echo '<style type="text/css">',$this->plugin_conf->options['broken_link_css'],'</style>';
386
- }
387
- }
388
-
389
- //Start up the post overlord
390
- blcPostTypeOverlord::getInstance();
391
-
392
-
393
- /**
394
- * Universal container item class used for all post types.
395
- *
396
- * @package Broken Link Checker
397
- * @author Janis Elsts
398
- * @access public
399
- */
400
- class blcAnyPostContainer extends blcContainer {
401
- var $default_field = 'post_content';
402
-
403
- /**
404
- * Get action links for this post.
405
- *
406
- * @param string $container_field Ignored.
407
- * @return array of action link HTML.
408
- */
409
- function ui_get_action_links($container_field = ''){
410
- $actions = array();
411
-
412
- //Fetch the post (it should be cached already)
413
- $post = $this->get_wrapped_object();
414
- if ( !$post ){
415
- return $actions;
416
- }
417
-
418
- $post_type_object = get_post_type_object($post->post_type);
419
-
420
- //Each post type can have its own cap requirements
421
- if ( current_user_can( $post_type_object->cap->edit_post, $this->container_id ) ){
422
- $actions['edit'] = sprintf(
423
- '<span class="edit"><a href="%s" title="%s">%s</a>',
424
- $this->get_edit_url(),
425
- $post_type_object->labels->edit_item,
426
- __('Edit')
427
- );
428
-
429
- //Trash/Delete link
430
- if ( current_user_can( $post_type_object->cap->delete_post, $this->container_id ) ){
431
- if ( $this->can_be_trashed() ) {
432
- $actions['trash'] = sprintf(
433
- "<span class='trash'><a class='submitdelete' title='%s' href='%s'>%s</a>",
434
- esc_attr(__('Move this item to the Trash')),
435
- esc_attr(get_delete_post_link($this->container_id, '', false)),
436
- __('Trash')
437
- );
438
- } else {
439
- $actions['delete'] = sprintf(
440
- "<span><a class='submitdelete' title='%s' href='%s'>%s</a>",
441
- esc_attr(__('Delete this item permanently')),
442
- esc_attr(get_delete_post_link($this->container_id, '', true)),
443
- __('Delete')
444
- );
445
- }
446
- }
447
- }
448
-
449
- //View/Preview link
450
- $title = get_the_title($this->container_id);
451
- if ( in_array($post->post_status, array('pending', 'draft')) ) {
452
- if ( current_user_can($post_type_object->cap->edit_post, $this->container_id) ){
453
- $actions['view'] = sprintf(
454
- '<span class="view"><a href="%s" title="%s" rel="permalink">%s</a>',
455
- esc_url( add_query_arg( 'preview', 'true', get_permalink($this->container_id) ) ),
456
- esc_attr(sprintf(__('Preview &#8220;%s&#8221;'), $title)),
457
- __('Preview')
458
- );
459
- }
460
- } elseif ( 'trash' != $post->post_status ) {
461
- $actions['view'] = sprintf(
462
- '<span class="view"><a href="%s" title="%s" rel="permalink">%s</a>',
463
- esc_url( get_permalink($this->container_id) ),
464
- esc_attr(sprintf(__('View &#8220;%s&#8221;'), $title)),
465
- __('View')
466
- );
467
- }
468
-
469
- return $actions;
470
- }
471
-
472
- /**
473
- * Get the HTML for displaying the post title in the "Source" column.
474
- *
475
- * @param string $container_field Ignored.
476
- * @param string $context How to filter the output. Optional, defaults to 'display'.
477
- * @return string HTML
478
- */
479
- function ui_get_source($container_field = '', $context = 'display'){
480
- $source = '<a class="row-title" href="%s" title="%s">%s</a>';
481
- $source = sprintf(
482
- $source,
483
- $this->get_edit_url(),
484
- esc_attr(__('Edit this item')),
485
- get_the_title($this->container_id)
486
- );
487
-
488
- return $source;
489
- }
490
-
491
- /**
492
- * Get edit URL for this container. Returns the URL of the Dashboard page where the item
493
- * associated with this container can be edited.
494
- *
495
- * @access protected
496
- *
497
- * @return string
498
- */
499
- function get_edit_url(){
500
- /*
501
- The below is a near-exact copy of the get_post_edit_link() function.
502
- Unfortunately we can't just call that function because it has a hardcoded
503
- caps-check which fails when called from the email notification script
504
- executed by Cron.
505
- */
506
-
507
- if ( !$post = $this->get_wrapped_object() ){
508
- return '';
509
- }
510
-
511
- $context = 'display';
512
- $action = '&amp;action=edit';
513
-
514
- $post_type_object = get_post_type_object( $post->post_type );
515
- if ( !$post_type_object ){
516
- return '';
517
- }
518
-
519
- return apply_filters( 'get_edit_post_link', admin_url( sprintf($post_type_object->_edit_link . $action, $post->ID) ), $post->ID, $context );
520
- }
521
-
522
- /**
523
- * Retrieve the post associated with this container.
524
- *
525
- * @access protected
526
- *
527
- * @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).
528
- * @return object Post data.
529
- */
530
- function get_wrapped_object($ensure_consistency = false){
531
- if( $ensure_consistency || is_null($this->wrapped_object) ){
532
- $this->wrapped_object = get_post($this->container_id);
533
- }
534
- return $this->wrapped_object;
535
- }
536
-
537
- /**
538
- * Update the post associated with this container.
539
- *
540
- * @access protected
541
- *
542
- * @return bool|WP_Error True on success, an error if something went wrong.
543
- */
544
- function update_wrapped_object(){
545
- if ( is_null($this->wrapped_object) ){
546
- return new WP_Error(
547
- 'no_wrapped_object',
548
- __('Nothing to update', 'broken-link-checker')
549
- );
550
- }
551
-
552
- $post_id = wp_update_post($this->wrapped_object, true);
553
- if ( is_wp_error($post_id) ) {
554
- return $post_id;
555
- } else if ( $post_id == 0 ){
556
- return new WP_Error(
557
- 'update_failed',
558
- sprintf(__('Updating post %d failed', 'broken-link-checker'), $this->container_id)
559
- );
560
- } else {
561
- return true;
562
- }
563
- }
564
-
565
- /**
566
- * Get the base URL of the container. For posts, the post permalink is used
567
- * as the base URL when normalizing relative links.
568
- *
569
- * @return string
570
- */
571
- function base_url(){
572
- return get_permalink($this->container_id);
573
- }
574
-
575
- /**
576
- * Delete or trash the post corresponding to this container.
577
- * Will always move to trash instead of deleting if trash is enabled.
578
- *
579
- * @return bool|WP_error
580
- */
581
- function delete_wrapped_object(){
582
- //Note that we don't need to delete the synch record and instances here -
583
- //wp_delete_post()/wp_trash_post() will run the post_delete/trash hook,
584
- //which will be caught by blcPostContainerManager, which will in turn
585
- //delete anything that needs to be deleted.
586
- if ( EMPTY_TRASH_DAYS ){
587
- return $this->trash_wrapped_object();
588
- } else {
589
- if ( wp_delete_post($this->container_id, true) ){
590
- return true;
591
- } else {
592
- return new WP_Error(
593
- 'delete_failed',
594
- sprintf(
595
- __('Failed to delete post "%s" (%d)', 'broken-link-checker'),
596
- get_the_title($this->container_id),
597
- $this->container_id
598
- )
599
- );
600
- }
601
- }
602
- }
603
-
604
- /**
605
- * Move the post corresponding to this container to the Trash.
606
- *
607
- * @return bool|WP_Error
608
- */
609
- function trash_wrapped_object(){
610
- if ( !EMPTY_TRASH_DAYS ){
611
- return new WP_Error(
612
- 'trash_disabled',
613
- sprintf(
614
- __('Can\'t move post "%s" (%d) to the trash because the trash feature is disabled', 'broken-link-checker'),
615
- get_the_title($this->container_id),
616
- $this->container_id
617
- )
618
- );
619
- }
620
-
621
- $post = get_post($this->container_id);
622
- if ( $post->post_status == 'trash' ){
623
- //Prevent conflicts between post and custom field containers trying to trash the same post.
624
- //BUG: Post and custom field containers shouldn't wrap the same object
625
- return true;
626
- }
627
-
628
- if ( wp_trash_post($this->container_id) ){
629
- return true;
630
- } else {
631
- return new WP_Error(
632
- 'trash_failed',
633
- sprintf(
634
- __('Failed to move post "%s" (%d) to the trash', 'broken-link-checker'),
635
- get_the_title($this->container_id),
636
- $this->container_id
637
- )
638
- );
639
- }
640
- }
641
-
642
- /**
643
- * Check if the current user can delete/trash this post.
644
- *
645
- * @return bool
646
- */
647
- function current_user_can_delete(){
648
- $post = $this->get_wrapped_object();
649
- $post_type_object = get_post_type_object($post->post_type);
650
- return current_user_can( $post_type_object->cap->delete_post, $this->container_id );
651
- }
652
-
653
- function can_be_trashed(){
654
- return defined('EMPTY_TRASH_DAYS') && EMPTY_TRASH_DAYS;
655
- }
656
- }
657
-
658
-
659
-
660
- /**
661
- * Universal manager usable for most post types.
662
- *
663
- * @package Broken Link Checker
664
- * @access public
665
- */
666
- class blcAnyPostContainerManager extends blcContainerManager {
667
- var $container_class_name = 'blcAnyPostContainer';
668
- var $fields = array('post_content' => 'html');
669
-
670
- function init(){
671
- parent::init();
672
-
673
- //Notify the overlord that the post/container type that this instance is
674
- //responsible for is enabled.
675
- $overlord = blcPostTypeOverlord::getInstance();
676
- $overlord->post_type_enabled($this->container_type);
677
- }
678
-
679
- /**
680
- * Instantiate multiple containers of the container type managed by this class.
681
- *
682
- * @param array $containers Array of assoc. arrays containing container data.
683
- * @param string $purpose An optional code indicating how the retrieved containers will be used.
684
- * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
685
- *
686
- * @return array of blcPostContainer indexed by "container_type|container_id"
687
- */
688
- function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
689
- $containers = $this->make_containers($containers);
690
-
691
- //Preload post data if it is likely to be useful later
692
- $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_PARSING));
693
- if ( $preload ){
694
- $post_ids = array();
695
- foreach($containers as $container){
696
- $post_ids[] = $container->container_id;
697
- }
698
-
699
- $args = array('include' => implode(',', $post_ids));
700
- $posts = get_posts($args);
701
-
702
- foreach($posts as $post){
703
- $key = $this->container_type . '|' . $post->ID;
704
- if ( isset($containers[$key]) ){
705
- $containers[$key]->wrapped_object = $post;
706
- }
707
- }
708
- }
709
-
710
- return $containers;
711
- }
712
-
713
- /**
714
- * Create or update synchronization records for all posts.
715
- *
716
- * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
717
- * @return void
718
- */
719
- function resynch($forced = false){
720
- $overlord = blcPostTypeOverlord::getInstance();
721
- $overlord->resynch($this->container_type, $forced);
722
- }
723
-
724
- /**
725
- * Get the message to display after $n posts have been deleted.
726
- *
727
- * @param int $n Number of deleted posts.
728
- * @return string A delete confirmation message, e.g. "5 posts were moved deleted"
729
- */
730
- function ui_bulk_delete_message($n){
731
- //Since the "Trash" feature has been introduced, calling wp_delete_post
732
- //doesn't actually delete the post (unless you set force_delete to True),
733
- //just moves it to the trash. So we pick the message accordingly.
734
- //(If possible, BLC *always* moves to trash instead of deleting permanently.)
735
- if ( function_exists('wp_trash_post') && EMPTY_TRASH_DAYS ){
736
- return blcAnyPostContainerManager::ui_bulk_trash_message($n);
737
- } else {
738
- $post_type_object = get_post_type_object($this->container_type);
739
- $type_name = '';
740
-
741
- if ( $this->container_type == 'post' || is_null($post_type_object) ){
742
- $delete_msg = _n("%d post deleted.", "%d posts deleted.", $n, 'broken-link-checker');
743
- } elseif ( $this->container_type == 'page' ){
744
- $delete_msg = _n("%d page deleted.", "%d pages deleted.", $n, 'broken-link-checker');
745
- } else {
746
- $delete_msg = _n('%d "%s" deleted.', '%d "%s" deleted.', $n, 'broken-link-checker');
747
- $type_name = ($n == 1 ? $post_type_object->labels->singular_name : $post_type_object->labels->name);
748
- }
749
- return sprintf($delete_msg, $n, $type_name);
750
- }
751
- }
752
-
753
-
754
- /**
755
- * Get the message to display after $n posts have been trashed.
756
- *
757
- * @param int $n Number of deleted posts.
758
- * @return string A confirmation message, e.g. "5 posts were moved to trash"
759
- */
760
- function ui_bulk_trash_message($n){
761
- $post_type_object = get_post_type_object($this->container_type);
762
- $type_name = '';
763
-
764
- if ( $this->container_type == 'post' || is_null($post_type_object) ){
765
- $delete_msg = _n("%d post moved to the Trash.", "%d posts moved to the Trash.", $n, 'broken-link-checker');
766
- } elseif ( $this->container_type == 'page' ){
767
- $delete_msg = _n("%d page moved to the Trash.", "%d pages moved to the Trash.", $n, 'broken-link-checker');
768
- } else {
769
- $delete_msg = _n('%d "%s" moved to the Trash.', '%d "%s" moved to the Trash.', $n, 'broken-link-checker');
770
- $type_name = ($n == 1 ? $post_type_object->labels->singular_name : $post_type_object->labels->name);
771
- }
772
- return sprintf($delete_msg, $n, $type_name);
773
- }
774
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * The manager to rule all (post) managers.
5
+ *
6
+ * This class dynamically registers container modules for the available post types
7
+ * (including custom post types) and does stuff that pertain to all of them, such
8
+ * as handling save/delete hooks and (re)creating synch records.
9
+ *
10
+ * @package Broken Link Checker
11
+ * @author Janis Elsts
12
+ * @access private
13
+ */
14
+ class blcPostTypeOverlord {
15
+ public $enabled_post_types = array(); //Post types currently selected for link checking
16
+ public $enabled_post_statuses = array('publish'); //Only posts that have one of these statuses shall be checked
17
+
18
+ var $plugin_conf;
19
+ var $resynch_already_done = false;
20
+
21
+ /**
22
+ * Class "constructor". Can't use an actual constructor due to how PHP4 handles object references.
23
+ *
24
+ * Specifically, this class is a singleton. The function needs to pass $this to several other
25
+ * functions (to set up hooks), which will store the reference for later use. However, it appears
26
+ * that in PHP4 the actual value of $this is thrown away right after the constructor finishes, and
27
+ * `new` returns a *copy* of $this. The result is that getInstance() won't be returning a ref.
28
+ * to the same object as is used for hook callbacks. And that's horrible.
29
+ *
30
+ * Sets up hooks that monitor added/modified/deleted posts and registers
31
+ * virtual modules for all post types.
32
+ *
33
+ * @return void
34
+ */
35
+ function init(){
36
+ $this->plugin_conf = blc_get_configuration();
37
+
38
+ if ( isset($this->plugin_conf->options['enabled_post_statuses']) ){
39
+ $this->enabled_post_statuses = $this->plugin_conf->options['enabled_post_statuses'];
40
+ }
41
+
42
+ //Register a virtual container module for each enabled post type
43
+ $module_manager = blcModuleManager::getInstance();
44
+
45
+ $post_types = get_post_types(array(), 'objects');
46
+ $exceptions = array('revision', 'nav_menu_item', 'attachment');
47
+
48
+ foreach($post_types as $data){
49
+ $post_type = $data->name;
50
+
51
+ if ( in_array($post_type, $exceptions) ){
52
+ continue;
53
+ }
54
+
55
+ $module_manager->register_virtual_module(
56
+ $post_type,
57
+ array(
58
+ 'Name' => $data->labels->name,
59
+ 'ModuleCategory' => 'container',
60
+ 'ModuleContext' => 'all',
61
+ 'ModuleClassName' => 'blcAnyPostContainerManager',
62
+ )
63
+ );
64
+ }
65
+
66
+ //These hooks update the synch & instance records when posts are added, deleted or modified.
67
+ add_action('delete_post', array(&$this,'post_deleted'));
68
+ add_action('save_post', array(&$this,'post_saved'));
69
+ //We also treat post trashing/untrashing as delete/save.
70
+ add_action('trash_post', array(&$this,'post_deleted'));
71
+ add_action('untrash_post', array(&$this,'post_saved'));
72
+
73
+ //Highlight and nofollow broken links in posts & pages
74
+ if ( $this->plugin_conf->options['mark_broken_links'] || $this->plugin_conf->options['nofollow_broken_links'] ){
75
+ add_filter( 'the_content', array(&$this, 'hook_the_content') );
76
+ if ( $this->plugin_conf->options['mark_broken_links'] && !empty( $this->plugin_conf->options['broken_link_css'] ) ){
77
+ add_action( 'wp_head', array(&$this,'hook_wp_head') );
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Retrieve an instance of the overlord class.
84
+ *
85
+ * @return blcPostTypeOverlord
86
+ */
87
+ static function getInstance(){
88
+ static $instance = null;
89
+ if ( is_null($instance) ){
90
+ $instance = new blcPostTypeOverlord;
91
+ $instance->init();
92
+ }
93
+ return $instance;
94
+ }
95
+
96
+ /**
97
+ * Notify the overlord that a post type is active.
98
+ *
99
+ * Called by individual instances of blcAnyPostContainerManager to let
100
+ * the overlord know that they've been created. Since a module instance
101
+ * is only created if the module is active, this event indicates that
102
+ * the user has enabled the corresponding post type for link checking.
103
+ *
104
+ * @param string $post_type
105
+ * @return void
106
+ */
107
+ function post_type_enabled($post_type){
108
+ if ( !in_array($post_type, $this->enabled_post_types) ){
109
+ $this->enabled_post_types[] = $post_type;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Remove the synch. record and link instances associated with a post when it's deleted
115
+ *
116
+ * @param int $post_id
117
+ * @return void
118
+ */
119
+ function post_deleted($post_id){
120
+ global $wpdb;
121
+
122
+ $post_id = intval( $post_id );
123
+ //Get the container type matching the type of the deleted post
124
+ $post = get_post( $post_id );
125
+ if ( !$post ){
126
+ return;
127
+ }
128
+ //Get the associated container object
129
+ $post_type = get_post_type( $post );
130
+ $post_container = blcContainerHelper::get_container( array( $post_type, $post_id ) );
131
+
132
+ if ( $post_container ){
133
+ //Delete the container
134
+ $post_container->delete();
135
+
136
+ // Firstly: See if we have any current instances
137
+ $q_current_instance_ids = $wpdb->prepare(
138
+ 'SELECT instance_id FROM `'.$wpdb->prefix.'_blc_instances` WHERE container_id = %d AND container_type = %s',
139
+ $post_id,
140
+ $post_type );
141
+
142
+ $current_instance_ids_results = $wpdb->get_results( $q_current_instance_ids, ARRAY_A );
143
+
144
+ if( $wpdb->num_rows == 0 ) {
145
+ // No current instances present, skip cleanup at once
146
+ return;
147
+ }
148
+
149
+ $current_instance_ids = wp_list_pluck( $current_instance_ids_results, 'instance_id' );
150
+
151
+ // Secondly: Get all link_ids used in our current instances
152
+ $q_current_link_ids = 'SELECT DISTINCT link_id FROM `'.$wpdb->prefix.'_blc_instances` WHERE instance_id IN (\''.implode("', '", $current_instance_ids).'\')';
153
+
154
+ $q_current_link_ids_results = $wpdb->get_results( $q_current_link_ids, ARRAY_A );
155
+
156
+ $current_link_ids = wp_list_pluck( $q_current_link_ids_results, 'link_id' );
157
+
158
+ // Go ahead and remove blc_instances for this container, blc_cleanup_links( $current_link_ids ) will find and remove any dangling links in the blc_links table
159
+ $wpdb->query( 'DELETE FROM `'.$wpdb->prefix.'_blc_instances` WHERE instance_id IN (\''.implode("', '", $current_instance_ids).'\')' );
160
+
161
+ //Clean up any dangling links
162
+ blc_cleanup_links( $current_link_ids );
163
+ }
164
+ }
165
+
166
+ /**
167
+ * When a post is saved or modified, mark it as unparsed.
168
+ *
169
+ * @param int $post_id
170
+ * @return void
171
+ */
172
+ function post_saved($post_id){
173
+ //Get the container type matching the type of the deleted post
174
+ $post = get_post($post_id);
175
+ if ( !$post ){
176
+ return;
177
+ }
178
+
179
+ //Only check links in currently enabled post types
180
+ if ( !in_array($post->post_type, $this->enabled_post_types) ) return;
181
+
182
+ //Only check posts that have one of the allowed statuses
183
+ if ( !in_array($post->post_status, $this->enabled_post_statuses) ) return;
184
+
185
+ //Get the container & mark it as unparsed
186
+ $args = array($post->post_type, intval($post_id));
187
+ $post_container = blcContainerHelper::get_container( $args );
188
+
189
+ $post_container->mark_as_unsynched();
190
+ }
191
+
192
+
193
+ /**
194
+ * Create or update synchronization records for all posts.
195
+ *
196
+ * @param string $container_type
197
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
198
+ * @return void
199
+ */
200
+ function resynch($container_type = '', $forced = false){
201
+ global $wpdb; /** @var wpdb $wpdb */
202
+ global $blclog;
203
+
204
+ //Resynch is expensive in terms of DB performance. Thus we only do it once, processing
205
+ //all post types in one go and ignoring any further resynch requests during this pageload.
206
+ //BUG: This might be a problem if there ever is an actual need to run resynch twice or
207
+ //more per pageload.
208
+ if ( $this->resynch_already_done ){
209
+ $blclog->log(sprintf('...... Skipping "%s" resyncyh since all post types were already synched.', $container_type));
210
+ return;
211
+ }
212
+
213
+ if ( empty($this->enabled_post_types) ){
214
+ $blclog->warn(sprintf('...... Skipping "%s" resyncyh since no post types are enabled.', $container_type));
215
+ return;
216
+ }
217
+
218
+ $escaped_post_types = array_map('esc_sql', $this->enabled_post_types);
219
+ $escaped_post_statuses = array_map('esc_sql', $this->enabled_post_statuses);
220
+
221
+ if ( $forced ){
222
+ //Create new synchronization records for all posts.
223
+ $blclog->log('...... Creating synch records for these post types: '.implode(', ', $escaped_post_types) . ' that have one of these statuses: ' . implode(', ', $escaped_post_statuses));
224
+ $start = microtime(true);
225
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
226
+ SELECT posts.id, posts.post_type, 0
227
+ FROM {$wpdb->posts} AS posts
228
+ WHERE
229
+ posts.post_status IN (%s)
230
+ AND posts.post_type IN (%s)";
231
+ $q = sprintf(
232
+ $q,
233
+ "'" . implode("', '", $escaped_post_statuses) . "'",
234
+ "'" . implode("', '", $escaped_post_types) . "'"
235
+ );
236
+ $wpdb->query( $q );
237
+ $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, microtime(true) - $start));
238
+ } else {
239
+ //Delete synch records corresponding to posts that no longer exist.
240
+ $blclog->log('...... Deleting synch records for removed posts');
241
+ $start = microtime(true);
242
+ $q = "DELETE synch.*
243
+ FROM
244
+ {$wpdb->prefix}blc_synch AS synch LEFT JOIN {$wpdb->posts} AS posts
245
+ ON posts.ID = synch.container_id
246
+ WHERE
247
+ synch.container_type IN (%s) AND posts.ID IS NULL";
248
+ $q = sprintf(
249
+ $q,
250
+ "'" . implode("', '", $escaped_post_types) . "'"
251
+ );
252
+ $wpdb->query( $q );
253
+ $elapsed = microtime(true) - $start;
254
+ $blclog->debug($q);
255
+ $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
256
+
257
+ //Delete records where the post status is not one of the enabled statuses.
258
+ $blclog->log('...... Deleting synch records for posts that have a disallowed status');
259
+ $start = microtime(true);
260
+ $q = "DELETE synch.*
261
+ FROM
262
+ {$wpdb->prefix}blc_synch AS synch
263
+ LEFT JOIN {$wpdb->posts} AS posts
264
+ ON (synch.container_id = posts.ID and synch.container_type = posts.post_type)
265
+ WHERE
266
+ posts.post_status NOT IN (%s)";
267
+ $q = sprintf(
268
+ $q,
269
+ "'" . implode("', '", $escaped_post_statuses) . "'"
270
+ );
271
+ $wpdb->query( $q );
272
+ $elapsed = microtime(true) - $start;
273
+ $blclog->debug($q);
274
+ $blclog->log(sprintf('...... %d rows deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
275
+
276
+ //Remove the 'synched' flag from all posts that have been updated
277
+ //since the last time they were parsed/synchronized.
278
+ $blclog->log('...... Marking changed posts as unsynched');
279
+ $start = microtime(true);
280
+ $q = "UPDATE
281
+ {$wpdb->prefix}blc_synch AS synch
282
+ JOIN {$wpdb->posts} AS posts ON (synch.container_id = posts.ID and synch.container_type=posts.post_type)
283
+ SET
284
+ synched = 0
285
+ WHERE
286
+ synch.last_synch < posts.post_modified";
287
+ $wpdb->query( $q );
288
+ $elapsed = microtime(true) - $start;
289
+ $blclog->debug($q);
290
+ $blclog->log(sprintf('...... %d rows updated in %.3f seconds', $wpdb->rows_affected, $elapsed));
291
+
292
+ //Create synch. records for posts that don't have them.
293
+ $blclog->log('...... Creating synch records for new posts');
294
+ $start = microtime(true);
295
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch(container_id, container_type, synched)
296
+ SELECT posts.id, posts.post_type, 0
297
+ FROM
298
+ {$wpdb->posts} AS posts LEFT JOIN {$wpdb->prefix}blc_synch AS synch
299
+ ON (synch.container_id = posts.ID and synch.container_type=posts.post_type)
300
+ WHERE
301
+ posts.post_status IN (%s)
302
+ AND posts.post_type IN (%s)
303
+ AND synch.container_id IS NULL";
304
+ $q = sprintf(
305
+ $q,
306
+ "'" . implode("', '", $escaped_post_statuses) . "'",
307
+ "'" . implode("', '", $escaped_post_types) . "'"
308
+ );
309
+ $wpdb->query($q);
310
+ $elapsed = microtime(true) - $start;
311
+ $blclog->debug($q);
312
+ $blclog->log(sprintf('...... %d rows inserted in %.3f seconds', $wpdb->rows_affected, $elapsed));
313
+ }
314
+
315
+ $this->resynch_already_done = true;
316
+ }
317
+
318
+ /**
319
+ * Hook for the 'the_content' filter. Scans the current post and adds the 'broken_link'
320
+ * CSS class to all links that are known to be broken. Currently works only on standard
321
+ * HTML links (i.e. the '<a href=...' kind).
322
+ *
323
+ * @param string $content Post content
324
+ * @return string Modified post content.
325
+ */
326
+ function hook_the_content($content){
327
+ global $post, $wpdb; /** @var wpdb $wpdb */
328
+ if ( empty($post) || !in_array($post->post_type, $this->enabled_post_types)) {
329
+ return $content;
330
+ }
331
+
332
+ //Retrieve info about all occurrences of broken links in the current post
333
+ $q = "
334
+ SELECT instances.raw_url
335
+ FROM {$wpdb->prefix}blc_instances AS instances JOIN {$wpdb->prefix}blc_links AS links
336
+ ON instances.link_id = links.link_id
337
+ WHERE
338
+ instances.container_type = %s
339
+ AND instances.container_id = %d
340
+ AND links.broken = 1
341
+ AND parser_type = 'link'
342
+ ";
343
+ $q = $wpdb->prepare($q, $post->post_type, $post->ID);
344
+ $links = $wpdb->get_results($q, ARRAY_A);
345
+
346
+ //Return the content unmodified if there are no broken links in this post.
347
+ if ( empty($links) || !is_array($links) ){
348
+ return $content;
349
+ }
350
+
351
+ //Put the broken link URLs in an array
352
+ $broken_link_urls = array();
353
+ foreach($links as $link){
354
+ $broken_link_urls[] = $link['raw_url'];
355
+ }
356
+
357
+ //Iterate over all HTML links and modify the broken ones
358
+ if ( $parser = blcParserHelper::get_parser('link') ){
359
+ $content = $parser->multi_edit($content, array(&$this, 'highlight_broken_link'), $broken_link_urls);
360
+ }
361
+
362
+ return $content;
363
+ }
364
+
365
+ /**
366
+ * Analyse a link and add 'broken_link' CSS class if the link is broken.
367
+ *
368
+ * @see blcHtmlLink::multi_edit()
369
+ *
370
+ * @param array $link Associative array of link data.
371
+ * @param array $broken_link_urls List of broken link URLs present in the current post.
372
+ * @return array|string The modified link
373
+ */
374
+ function highlight_broken_link($link, $broken_link_urls){
375
+ if ( !in_array($link['href'], $broken_link_urls) ){
376
+ //Link not broken = return the original link tag
377
+ return $link['#raw'];
378
+ }
379
+
380
+ //Add 'broken_link' to the 'class' attribute (unless already present).
381
+ if ( $this->plugin_conf->options['mark_broken_links'] ){
382
+ if ( isset($link['class']) ){
383
+ $classes = explode(' ', $link['class']);
384
+ if ( !in_array('broken_link', $classes) ){
385
+ $classes[] = 'broken_link';
386
+ $link['class'] = implode(' ', $classes);
387
+ }
388
+ } else {
389
+ $link['class'] = 'broken_link';
390
+ }
391
+ }
392
+
393
+ //Nofollow the link (unless it's already nofollow'ed)
394
+ if ( $this->plugin_conf->options['nofollow_broken_links'] ){
395
+ if ( isset($link['rel']) ){
396
+ $relations = explode(' ', $link['rel']);
397
+ if ( !in_array('nofollow', $relations) ){
398
+ $relations[] = 'nofollow';
399
+ $link['rel'] = implode(' ', $relations);
400
+ }
401
+ } else {
402
+ $link['rel'] = 'nofollow';
403
+ }
404
+ }
405
+
406
+ return $link;
407
+ }
408
+
409
+ /**
410
+ * A hook for the 'wp_head' action. Outputs the user-defined broken link CSS.
411
+ *
412
+ * @return void
413
+ */
414
+ function hook_wp_head(){
415
+ echo '<style type="text/css">',$this->plugin_conf->options['broken_link_css'],'</style>';
416
+ }
417
+ }
418
+
419
+ //Start up the post overlord
420
+ blcPostTypeOverlord::getInstance();
421
+
422
+
423
+ /**
424
+ * Universal container item class used for all post types.
425
+ *
426
+ * @package Broken Link Checker
427
+ * @author Janis Elsts
428
+ * @access public
429
+ */
430
+ class blcAnyPostContainer extends blcContainer {
431
+ var $default_field = 'post_content';
432
+
433
+ /**
434
+ * Get action links for this post.
435
+ *
436
+ * @param string $container_field Ignored.
437
+ * @return array of action link HTML.
438
+ */
439
+ function ui_get_action_links($container_field = ''){
440
+ $actions = array();
441
+
442
+ //Fetch the post (it should be cached already)
443
+ $post = $this->get_wrapped_object();
444
+ if ( !$post ){
445
+ return $actions;
446
+ }
447
+
448
+ $post_type_object = get_post_type_object($post->post_type);
449
+
450
+ //Each post type can have its own cap requirements
451
+ if ( current_user_can( $post_type_object->cap->edit_post, $this->container_id ) ){
452
+ $actions['edit'] = sprintf(
453
+ '<span class="edit"><a href="%s" title="%s">%s</a>',
454
+ $this->get_edit_url(),
455
+ $post_type_object->labels->edit_item,
456
+ __('Edit')
457
+ );
458
+
459
+ //Trash/Delete link
460
+ if ( current_user_can( $post_type_object->cap->delete_post, $this->container_id ) ){
461
+ if ( $this->can_be_trashed() ) {
462
+ $actions['trash'] = sprintf(
463
+ "<span class='trash'><a class='submitdelete' title='%s' href='%s'>%s</a>",
464
+ esc_attr(__('Move this item to the Trash')),
465
+ esc_attr(get_delete_post_link($this->container_id, '', false)),
466
+ __('Trash')
467
+ );
468
+ } else {
469
+ $actions['delete'] = sprintf(
470
+ "<span><a class='submitdelete' title='%s' href='%s'>%s</a>",
471
+ esc_attr(__('Delete this item permanently')),
472
+ esc_attr(get_delete_post_link($this->container_id, '', true)),
473
+ __('Delete')
474
+ );
475
+ }
476
+ }
477
+ }
478
+
479
+ //View/Preview link
480
+ $title = get_the_title($this->container_id);
481
+ if ( in_array($post->post_status, array('pending', 'draft')) ) {
482
+ if ( current_user_can($post_type_object->cap->edit_post, $this->container_id) ){
483
+ $actions['view'] = sprintf(
484
+ '<span class="view"><a href="%s" title="%s" rel="permalink">%s</a>',
485
+ esc_url( add_query_arg( 'preview', 'true', get_permalink($this->container_id) ) ),
486
+ esc_attr(sprintf(__('Preview &#8220;%s&#8221;'), $title)),
487
+ __('Preview')
488
+ );
489
+ }
490
+ } elseif ( 'trash' != $post->post_status ) {
491
+ $actions['view'] = sprintf(
492
+ '<span class="view"><a href="%s" title="%s" rel="permalink">%s</a>',
493
+ esc_url( get_permalink($this->container_id) ),
494
+ esc_attr(sprintf(__('View &#8220;%s&#8221;'), $title)),
495
+ __('View')
496
+ );
497
+ }
498
+
499
+ return $actions;
500
+ }
501
+
502
+ /**
503
+ * Get the HTML for displaying the post title in the "Source" column.
504
+ *
505
+ * @param string $container_field Ignored.
506
+ * @param string $context How to filter the output. Optional, defaults to 'display'.
507
+ * @return string HTML
508
+ */
509
+ function ui_get_source($container_field = '', $context = 'display'){
510
+ $source = '<a class="row-title" href="%s" title="%s">%s</a>';
511
+ $source = sprintf(
512
+ $source,
513
+ $this->get_edit_url(),
514
+ esc_attr(__('Edit this item')),
515
+ get_the_title($this->container_id)
516
+ );
517
+
518
+ return $source;
519
+ }
520
+
521
+ /**
522
+ * Get edit URL for this container. Returns the URL of the Dashboard page where the item
523
+ * associated with this container can be edited.
524
+ *
525
+ * @access protected
526
+ *
527
+ * @return string
528
+ */
529
+ function get_edit_url(){
530
+ /*
531
+ The below is a near-exact copy of the get_post_edit_link() function.
532
+ Unfortunately we can't just call that function because it has a hardcoded
533
+ caps-check which fails when called from the email notification script
534
+ executed by Cron.
535
+ */
536
+
537
+ if ( !$post = $this->get_wrapped_object() ){
538
+ return '';
539
+ }
540
+
541
+ $context = 'display';
542
+ $action = '&amp;action=edit';
543
+
544
+ $post_type_object = get_post_type_object( $post->post_type );
545
+ if ( !$post_type_object ){
546
+ return '';
547
+ }
548
+
549
+ return apply_filters( 'get_edit_post_link', admin_url( sprintf($post_type_object->_edit_link . $action, $post->ID) ), $post->ID, $context );
550
+ }
551
+
552
+ /**
553
+ * Retrieve the post associated with this container.
554
+ *
555
+ * @access protected
556
+ *
557
+ * @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).
558
+ * @return object Post data.
559
+ */
560
+ function get_wrapped_object($ensure_consistency = false){
561
+ if( $ensure_consistency || is_null($this->wrapped_object) ){
562
+ $this->wrapped_object = get_post($this->container_id);
563
+ }
564
+ return $this->wrapped_object;
565
+ }
566
+
567
+ /**
568
+ * Update the post associated with this container.
569
+ *
570
+ * @access protected
571
+ *
572
+ * @return bool|WP_Error True on success, an error if something went wrong.
573
+ */
574
+ function update_wrapped_object(){
575
+ if ( is_null($this->wrapped_object) ){
576
+ return new WP_Error(
577
+ 'no_wrapped_object',
578
+ __('Nothing to update', 'broken-link-checker')
579
+ );
580
+ }
581
+
582
+ $post_id = wp_update_post($this->wrapped_object, true);
583
+ if ( is_wp_error($post_id) ) {
584
+ return $post_id;
585
+ } else if ( $post_id == 0 ){
586
+ return new WP_Error(
587
+ 'update_failed',
588
+ sprintf(__('Updating post %d failed', 'broken-link-checker'), $this->container_id)
589
+ );
590
+ } else {
591
+ return true;
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Get the base URL of the container. For posts, the post permalink is used
597
+ * as the base URL when normalizing relative links.
598
+ *
599
+ * @return string
600
+ */
601
+ function base_url(){
602
+ return get_permalink($this->container_id);
603
+ }
604
+
605
+ /**
606
+ * Delete or trash the post corresponding to this container.
607
+ * Will always move to trash instead of deleting if trash is enabled.
608
+ *
609
+ * @return bool|WP_error
610
+ */
611
+ function delete_wrapped_object(){
612
+ //Note that we don't need to delete the synch record and instances here -
613
+ //wp_delete_post()/wp_trash_post() will run the post_delete/trash hook,
614
+ //which will be caught by blcPostContainerManager, which will in turn
615
+ //delete anything that needs to be deleted.
616
+ if ( EMPTY_TRASH_DAYS ){
617
+ return $this->trash_wrapped_object();
618
+ } else {
619
+ if ( wp_delete_post($this->container_id, true) ){
620
+ return true;
621
+ } else {
622
+ return new WP_Error(
623
+ 'delete_failed',
624
+ sprintf(
625
+ __('Failed to delete post "%s" (%d)', 'broken-link-checker'),
626
+ get_the_title($this->container_id),
627
+ $this->container_id
628
+ )
629
+ );
630
+ }
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Move the post corresponding to this container to the Trash.
636
+ *
637
+ * @return bool|WP_Error
638
+ */
639
+ function trash_wrapped_object(){
640
+ if ( !EMPTY_TRASH_DAYS ){
641
+ return new WP_Error(
642
+ 'trash_disabled',
643
+ sprintf(
644
+ __('Can\'t move post "%s" (%d) to the trash because the trash feature is disabled', 'broken-link-checker'),
645
+ get_the_title($this->container_id),
646
+ $this->container_id
647
+ )
648
+ );
649
+ }
650
+
651
+ $post = get_post($this->container_id);
652
+ if ( $post->post_status == 'trash' ){
653
+ //Prevent conflicts between post and custom field containers trying to trash the same post.
654
+ //BUG: Post and custom field containers shouldn't wrap the same object
655
+ return true;
656
+ }
657
+
658
+ if ( wp_trash_post($this->container_id) ){
659
+ return true;
660
+ } else {
661
+ return new WP_Error(
662
+ 'trash_failed',
663
+ sprintf(
664
+ __('Failed to move post "%s" (%d) to the trash', 'broken-link-checker'),
665
+ get_the_title($this->container_id),
666
+ $this->container_id
667
+ )
668
+ );
669
+ }
670
+ }
671
+
672
+ /**
673
+ * Check if the current user can delete/trash this post.
674
+ *
675
+ * @return bool
676
+ */
677
+ function current_user_can_delete(){
678
+ $post = $this->get_wrapped_object();
679
+ $post_type_object = get_post_type_object($post->post_type);
680
+ return current_user_can( $post_type_object->cap->delete_post, $this->container_id );
681
+ }
682
+
683
+ function can_be_trashed(){
684
+ return defined('EMPTY_TRASH_DAYS') && EMPTY_TRASH_DAYS;
685
+ }
686
+ }
687
+
688
+
689
+
690
+ /**
691
+ * Universal manager usable for most post types.
692
+ *
693
+ * @package Broken Link Checker
694
+ * @access public
695
+ */
696
+ class blcAnyPostContainerManager extends blcContainerManager {
697
+ var $container_class_name = 'blcAnyPostContainer';
698
+ var $fields = array('post_content' => 'html');
699
+
700
+ function init(){
701
+ parent::init();
702
+
703
+ //Notify the overlord that the post/container type that this instance is
704
+ //responsible for is enabled.
705
+ $overlord = blcPostTypeOverlord::getInstance();
706
+ $overlord->post_type_enabled($this->container_type);
707
+ }
708
+
709
+ /**
710
+ * Instantiate multiple containers of the container type managed by this class.
711
+ *
712
+ * @param array $containers Array of assoc. arrays containing container data.
713
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
714
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
715
+ *
716
+ * @return array of blcPostContainer indexed by "container_type|container_id"
717
+ */
718
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
719
+ $containers = $this->make_containers($containers);
720
+
721
+ //Preload post data if it is likely to be useful later
722
+ $preload = $load_wrapped_objects || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_PARSING));
723
+ if ( $preload ){
724
+ $post_ids = array();
725
+ foreach($containers as $container){
726
+ $post_ids[] = $container->container_id;
727
+ }
728
+
729
+ $args = array('include' => implode(',', $post_ids));
730
+ $posts = get_posts($args);
731
+
732
+ foreach($posts as $post){
733
+ $key = $this->container_type . '|' . $post->ID;
734
+ if ( isset($containers[$key]) ){
735
+ $containers[$key]->wrapped_object = $post;
736
+ }
737
+ }
738
+ }
739
+
740
+ return $containers;
741
+ }
742
+
743
+ /**
744
+ * Create or update synchronization records for all posts.
745
+ *
746
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
747
+ * @return void
748
+ */
749
+ function resynch($forced = false){
750
+ $overlord = blcPostTypeOverlord::getInstance();
751
+ $overlord->resynch($this->container_type, $forced);
752
+ }
753
+
754
+ /**
755
+ * Get the message to display after $n posts have been deleted.
756
+ *
757
+ * @param int $n Number of deleted posts.
758
+ * @return string A delete confirmation message, e.g. "5 posts were moved deleted"
759
+ */
760
+ function ui_bulk_delete_message($n){
761
+ //Since the "Trash" feature has been introduced, calling wp_delete_post
762
+ //doesn't actually delete the post (unless you set force_delete to True),
763
+ //just moves it to the trash. So we pick the message accordingly.
764
+ //(If possible, BLC *always* moves to trash instead of deleting permanently.)
765
+ if ( function_exists('wp_trash_post') && EMPTY_TRASH_DAYS ){
766
+ return blcAnyPostContainerManager::ui_bulk_trash_message($n);
767
+ } else {
768
+ $post_type_object = get_post_type_object($this->container_type);
769
+ $type_name = '';
770
+
771
+ if ( $this->container_type == 'post' || is_null($post_type_object) ){
772
+ $delete_msg = _n("%d post deleted.", "%d posts deleted.", $n, 'broken-link-checker');
773
+ } elseif ( $this->container_type == 'page' ){
774
+ $delete_msg = _n("%d page deleted.", "%d pages deleted.", $n, 'broken-link-checker');
775
+ } else {
776
+ $delete_msg = _n('%d "%s" deleted.', '%d "%s" deleted.', $n, 'broken-link-checker');
777
+ $type_name = ($n == 1 ? $post_type_object->labels->singular_name : $post_type_object->labels->name);
778
+ }
779
+ return sprintf($delete_msg, $n, $type_name);
780
+ }
781
+ }
782
+
783
+
784
+ /**
785
+ * Get the message to display after $n posts have been trashed.
786
+ *
787
+ * @param int $n Number of deleted posts.
788
+ * @return string A confirmation message, e.g. "5 posts were moved to trash"
789
+ */
790
+ function ui_bulk_trash_message($n){
791
+ $post_type_object = get_post_type_object($this->container_type);
792
+ $type_name = '';
793
+
794
+ if ( $this->container_type == 'post' || is_null($post_type_object) ){
795
+ $delete_msg = _n("%d post moved to the Trash.", "%d posts moved to the Trash.", $n, 'broken-link-checker');
796
+ } elseif ( $this->container_type == 'page' ){
797
+ $delete_msg = _n("%d page moved to the Trash.", "%d pages moved to the Trash.", $n, 'broken-link-checker');
798
+ } else {
799
+ $delete_msg = _n('%d "%s" moved to the Trash.', '%d "%s" moved to the Trash.', $n, 'broken-link-checker');
800
+ $type_name = ($n == 1 ? $post_type_object->labels->singular_name : $post_type_object->labels->name);
801
+ }
802
+ return sprintf($delete_msg, $n, $type_name);
803
+ }
804
+ }
includes/checkers.php CHANGED
@@ -1,120 +1,120 @@
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 extends blcModule {
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|bool $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 blcCheckerHelper {
67
-
68
- /**
69
- * Get a reference to a specific checker.
70
- *
71
- * @uses blcModuleManager::get_module()
72
- *
73
- * @param string $checker_id
74
- * @return blcChecker
75
- */
76
- static function get_checker($checker_id){
77
- $manager = blcModuleManager::getInstance();
78
- return $manager->get_module($checker_id, true, 'checker');
79
- }
80
-
81
- /**
82
- * Get a checker object that can check the specified URL.
83
- *
84
- * @param string $url
85
- * @return blcChecker|null
86
- */
87
- static function get_checker_for($url){
88
- $parsed = @parse_url($url);
89
-
90
- $manager = blcModuleManager::getInstance();
91
- $active_checkers = $manager->get_active_by_category('checker');
92
-
93
- foreach($active_checkers as $module_id => $module_data){
94
- //Try the URL pattern in the header first. If it doesn't match,
95
- //we can avoid loading the module altogether.
96
- if ( !empty($module_data['ModuleCheckerUrlPattern']) ){
97
- if ( !preg_match($module_data['ModuleCheckerUrlPattern'], $url) ){
98
- continue;
99
- }
100
- }
101
-
102
- $checker = $manager->get_module($module_id);
103
-
104
- if ( !$checker ){
105
- continue;
106
- }
107
-
108
- //The can_check() method can perform more sophisticated filtering,
109
- //or just return true if the checker thinks matching the URL regex
110
- //is sufficient.
111
- if ( $checker->can_check($url, $parsed) ){
112
- return $checker;
113
- }
114
- }
115
-
116
- $checker = null;
117
- return $checker;
118
- }
119
- }
120
-
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 extends blcModule {
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|bool $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 blcCheckerHelper {
67
+
68
+ /**
69
+ * Get a reference to a specific checker.
70
+ *
71
+ * @uses blcModuleManager::get_module()
72
+ *
73
+ * @param string $checker_id
74
+ * @return blcChecker
75
+ */
76
+ static function get_checker($checker_id){
77
+ $manager = blcModuleManager::getInstance();
78
+ return $manager->get_module($checker_id, true, 'checker');
79
+ }
80
+
81
+ /**
82
+ * Get a checker object that can check the specified URL.
83
+ *
84
+ * @param string $url
85
+ * @return blcChecker|null
86
+ */
87
+ static function get_checker_for($url){
88
+ $parsed = @parse_url($url);
89
+
90
+ $manager = blcModuleManager::getInstance();
91
+ $active_checkers = $manager->get_active_by_category('checker');
92
+
93
+ foreach($active_checkers as $module_id => $module_data){
94
+ //Try the URL pattern in the header first. If it doesn't match,
95
+ //we can avoid loading the module altogether.
96
+ if ( !empty($module_data['ModuleCheckerUrlPattern']) ){
97
+ if ( !preg_match($module_data['ModuleCheckerUrlPattern'], $url) ){
98
+ continue;
99
+ }
100
+ }
101
+
102
+ $checker = $manager->get_module($module_id);
103
+
104
+ if ( !$checker ){
105
+ continue;
106
+ }
107
+
108
+ //The can_check() method can perform more sophisticated filtering,
109
+ //or just return true if the checker thinks matching the URL regex
110
+ //is sufficient.
111
+ if ( $checker->can_check($url, $parsed) ){
112
+ return $checker;
113
+ }
114
+ }
115
+
116
+ $checker = null;
117
+ return $checker;
118
+ }
119
+ }
120
+
includes/config-manager.php CHANGED
@@ -1,127 +1,127 @@
1
- <?php
2
-
3
- /**
4
- * @author W-Shadow
5
- * @copyright 2009
6
- */
7
-
8
- if ( !class_exists('blcConfigurationManager') ){
9
-
10
- class blcConfigurationManager {
11
-
12
- var $option_name;
13
-
14
- var $options;
15
- var $defaults;
16
- var $loaded_values;
17
-
18
- /**
19
- * @var bool Whether options have been successfully loaded from the database.
20
- */
21
- public $db_option_loaded = false;
22
-
23
- function __construct( $option_name = '', $default_settings = null ){
24
- $this->option_name = $option_name;
25
-
26
- if ( is_array($default_settings) ){
27
- $this->defaults = $default_settings;
28
- } else {
29
- $this->defaults = array();
30
- }
31
- $this->loaded_values = array();
32
-
33
- $this->options = $this->defaults;
34
-
35
- if ( !empty( $this->option_name ) ) {
36
- $this->load_options();
37
- }
38
- }
39
-
40
- function set_defaults( $default_settings = null ){
41
- if ( is_array($default_settings) ){
42
- $this->defaults = array();
43
- } else {
44
- $this->defaults = $default_settings;
45
- }
46
- $this->options = array_merge($this->defaults, $this->loaded_values);
47
- }
48
-
49
- /**
50
- * blcOptionManager::load_options()
51
- * Load plugin options from the database. The current $options values are not affected
52
- * if this function fails.
53
- *
54
- * @param string $option_name
55
- * @return bool True if options were loaded, false otherwise.
56
- */
57
- function load_options( $option_name = '' ){
58
- $this->db_option_loaded = false;
59
-
60
- if ( !empty($option_name) ){
61
- $this->option_name = $option_name;
62
- }
63
-
64
- if ( empty($this->option_name) ) return false;
65
-
66
- $new_options = get_option($this->option_name);
67
-
68
- //Decode JSON (if applicable).
69
- if ( is_string($new_options) && !empty($new_options) ) {
70
- $new_options = json_decode($new_options, true);
71
- }
72
-
73
- if( !is_array( $new_options ) ){
74
- return false;
75
- } else {
76
- $this->loaded_values = $new_options;
77
- $this->options = array_merge( $this->defaults, $this->loaded_values );
78
- $this->db_option_loaded = true;
79
- return true;
80
- }
81
- }
82
-
83
- /**
84
- * blcOptionManager::save_options()
85
- * Save plugin options to the database.
86
- *
87
- * @param string $option_name (Optional) Save the options under this name
88
- * @return bool True if settings were saved, false if settings haven't been changed or if there was an error.
89
- */
90
- function save_options( $option_name = '' ){
91
- if ( !empty($option_name) ){
92
- $this->option_name = $option_name;
93
- }
94
-
95
- if ( empty($this->option_name) ) return false;
96
-
97
- return update_option( $this->option_name, json_encode($this->options) );
98
- }
99
-
100
- /**
101
- * Retrieve a specific setting.
102
- *
103
- * @param string $key
104
- * @param mixed $default
105
- * @return mixed
106
- */
107
- function get($key, $default = null){
108
- if ( array_key_exists($key, $this->options) ){
109
- return $this->options[$key];
110
- } else {
111
- return $default;
112
- }
113
- }
114
-
115
- /**
116
- * Update or add a setting.
117
- *
118
- * @param string $key
119
- * @param mixed $value
120
- * @return void
121
- */
122
- function set($key, $value){
123
- $this->options[$key] = $value;
124
- }
125
- }
126
-
127
- }
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2009
6
+ */
7
+
8
+ if ( !class_exists('blcConfigurationManager') ){
9
+
10
+ class blcConfigurationManager {
11
+
12
+ var $option_name;
13
+
14
+ var $options;
15
+ var $defaults;
16
+ var $loaded_values;
17
+
18
+ /**
19
+ * @var bool Whether options have been successfully loaded from the database.
20
+ */
21
+ public $db_option_loaded = false;
22
+
23
+ function __construct( $option_name = '', $default_settings = null ){
24
+ $this->option_name = $option_name;
25
+
26
+ if ( is_array($default_settings) ){
27
+ $this->defaults = $default_settings;
28
+ } else {
29
+ $this->defaults = array();
30
+ }
31
+ $this->loaded_values = array();
32
+
33
+ $this->options = $this->defaults;
34
+
35
+ if ( !empty( $this->option_name ) ) {
36
+ $this->load_options();
37
+ }
38
+ }
39
+
40
+ function set_defaults( $default_settings = null ){
41
+ if ( is_array($default_settings) ){
42
+ $this->defaults = array();
43
+ } else {
44
+ $this->defaults = $default_settings;
45
+ }
46
+ $this->options = array_merge($this->defaults, $this->loaded_values);
47
+ }
48
+
49
+ /**
50
+ * blcOptionManager::load_options()
51
+ * Load plugin options from the database. The current $options values are not affected
52
+ * if this function fails.
53
+ *
54
+ * @param string $option_name
55
+ * @return bool True if options were loaded, false otherwise.
56
+ */
57
+ function load_options( $option_name = '' ){
58
+ $this->db_option_loaded = false;
59
+
60
+ if ( !empty($option_name) ){
61
+ $this->option_name = $option_name;
62
+ }
63
+
64
+ if ( empty($this->option_name) ) return false;
65
+
66
+ $new_options = get_option($this->option_name);
67
+
68
+ //Decode JSON (if applicable).
69
+ if ( is_string($new_options) && !empty($new_options) ) {
70
+ $new_options = json_decode($new_options, true);
71
+ }
72
+
73
+ if( !is_array( $new_options ) ){
74
+ return false;
75
+ } else {
76
+ $this->loaded_values = $new_options;
77
+ $this->options = array_merge( $this->defaults, $this->loaded_values );
78
+ $this->db_option_loaded = true;
79
+ return true;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * blcOptionManager::save_options()
85
+ * Save plugin options to the database.
86
+ *
87
+ * @param string $option_name (Optional) Save the options under this name
88
+ * @return bool True if settings were saved, false if settings haven't been changed or if there was an error.
89
+ */
90
+ function save_options( $option_name = '' ){
91
+ if ( !empty($option_name) ){
92
+ $this->option_name = $option_name;
93
+ }
94
+
95
+ if ( empty($this->option_name) ) return false;
96
+
97
+ return update_option( $this->option_name, json_encode($this->options) );
98
+ }
99
+
100
+ /**
101
+ * Retrieve a specific setting.
102
+ *
103
+ * @param string $key
104
+ * @param mixed $default
105
+ * @return mixed
106
+ */
107
+ function get($key, $default = null){
108
+ if ( array_key_exists($key, $this->options) ){
109
+ return $this->options[$key];
110
+ } else {
111
+ return $default;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Update or add a setting.
117
+ *
118
+ * @param string $key
119
+ * @param mixed $value
120
+ * @return void
121
+ */
122
+ function set($key, $value){
123
+ $this->options[$key] = $value;
124
+ }
125
+ }
126
+
127
+ }
includes/containers.php CHANGED
@@ -1,911 +1,911 @@
1
- <?php
2
-
3
- /**
4
- * The base class for link container managers.
5
- *
6
- * Sub-classes should override at least the get_containers() and resynch() methods.
7
- *
8
- * @package Broken Link Checker
9
- * @access public
10
- */
11
- class blcContainerManager extends blcModule {
12
-
13
- var $container_type = '';
14
- var $fields = array();
15
- var $container_class_name = 'blcContainer';
16
-
17
- /**
18
- * Do whatever setup necessary that wasn't already done in the constructor.
19
- *
20
- * This method was added so that sub-classes would have something "safe" to
21
- * over-ride without having to deal with PHP4/5 constructors.
22
- *
23
- * @return void
24
- */
25
- function init(){
26
- parent::init();
27
- $this->container_type = $this->module_id;
28
- //Sub-classes might also use it to set up hooks, etc.
29
- }
30
-
31
- /**
32
- * Instantiate a link container.
33
- *
34
- * @param array $container An associative array of container data.
35
- * @return blcContainer
36
- */
37
- function get_container($container){
38
- $container['fields'] = $this->get_parseable_fields();
39
- $container_obj = new $this->container_class_name($container);
40
- return $container_obj;
41
- }
42
-
43
- /**
44
- * Instantiate multiple containers of the container type managed by this class and optionally
45
- * pre-load container data used for display/parsing.
46
- *
47
- * Sub-classes should, if possible, use the $purpose argument to pre-load any extra data required for
48
- * the specified task right away, instead of making multiple DB roundtrips later. For example, if
49
- * $purpose is set to the BLC_FOR_DISPLAY constant, you might want to preload any DB data that the
50
- * container will need in blcContainer::ui_get_source().
51
- *
52
- * @see blcContainer::make_containers()
53
- * @see blcContainer::ui_get_source()
54
- * @see blcContainer::ui_get_action_links()
55
- *
56
- * @param array $containers Array of assoc. arrays containing container data.
57
- * @param string $purpose An optional code indicating how the retrieved containers will be used.
58
- * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
59
- *
60
- * @return array of blcContainer indexed by "container_type|container_id"
61
- */
62
- function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
63
- return $this->make_containers($containers);
64
- }
65
-
66
- /**
67
- * Instantiate multiple containers of the container type managed by this class
68
- *
69
- * @param array $containers Array of assoc. arrays containing container data.
70
- * @return array of blcContainer indexed by "container_type|container_id"
71
- */
72
- function make_containers($containers){
73
- $results = array();
74
- foreach($containers as $container){
75
- $key = $container['container_type'] . '|' . $container['container_id'];
76
- $results[ $key ] = $this->get_container($container);
77
- }
78
- return $results;
79
- }
80
-
81
- /**
82
- * Create or update synchronization records for all containers managed by this class.
83
- *
84
- * Must be over-ridden in subclasses.
85
- *
86
- * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
87
- * @return void
88
- */
89
- function resynch($forced = false){
90
- trigger_error('Function blcContainerManager::resynch() must be over-ridden in a sub-class', E_USER_ERROR);
91
- }
92
-
93
- /**
94
- * Resynch when activated.
95
- *
96
- * @uses blcContainerManager::resynch()
97
- *
98
- * @return void
99
- */
100
- function activated(){
101
- $this->resynch();
102
- blc_got_unsynched_items();
103
- }
104
-
105
- /**
106
- * Get a list of the parseable fields and their formats common to all containers of this type.
107
- *
108
- * @return array Associative array of formats indexed by field name.
109
- */
110
- function get_parseable_fields(){
111
- return $this->fields;
112
- }
113
-
114
- /**
115
- * Get the message to display after $n containers have been deleted.
116
- *
117
- * @param int $n Number of deleted containers.
118
- * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
119
- */
120
- function ui_bulk_delete_message($n){
121
- return sprintf(
122
- _n(
123
- "%d '%s' has been deleted",
124
- "%d '%s' have been deleted",
125
- $n,
126
- 'broken-link-checker'
127
- ),
128
- $n,
129
- $this->container_type
130
- );
131
- }
132
-
133
- /**
134
- * Get the message to display after $n containers have been moved to the trash.
135
- *
136
- * @param int $n Number of trashed containers.
137
- * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
138
- */
139
- function ui_bulk_trash_message($n){
140
- return $this->ui_bulk_delete_message($n);
141
- }
142
- }
143
-
144
- /**
145
- * The base class for link containers. All containers should extend this class.
146
- *
147
- * @package Broken Link Checker
148
- * @access public
149
- */
150
- class blcContainer {
151
-
152
- var $fields = array();
153
- var $default_field = '';
154
-
155
- var $container_type;
156
- var $container_id = 0;
157
-
158
- var $synched = false;
159
- var $last_synch = '0000-00-00 00:00:00';
160
-
161
- var $wrapped_object = null;
162
-
163
- /**
164
- * Constructor
165
- *
166
- * @param array $data
167
- * @param object $wrapped_object
168
- * @return void
169
- */
170
- function __construct( $data = null, $wrapped_object = null ){
171
- $this->wrapped_object = $wrapped_object;
172
- if ( !empty($data) && is_array($data) ){
173
- foreach($data as $name => $value){
174
- $this->$name = $value;
175
- }
176
- }
177
- }
178
-
179
- /**
180
- * Get the value of the specified field of the object wrapped by this container.
181
- *
182
- * @access protected
183
- *
184
- * @param string $field Field name. If omitted, the value of the default field will be returned.
185
- * @return string
186
- */
187
- function get_field($field = ''){
188
- if ( empty($field) ){
189
- $field = $this->default_field;
190
- }
191
-
192
- $w = $this->get_wrapped_object();
193
- return $w->$field;
194
- }
195
-
196
- /**
197
- * Update the value of the specified field in the wrapped object.
198
- * This method will also immediately save the changed value by calling update_wrapped_object().
199
- *
200
- * @access protected
201
- *
202
- * @param string $field Field name.
203
- * @param string $new_value Set the field to this value.
204
- * @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).
205
- * @return bool|WP_Error True on success, an error object if something went wrong.
206
- */
207
- function update_field($field, $new_value, $old_value = ''){
208
- $w = $this->get_wrapped_object();
209
- $w->$field = $new_value;
210
- return $this->update_wrapped_object();
211
- }
212
-
213
- /**
214
- * Retrieve the entity wrapped by this container.
215
- * The fetched object will also be cached in the $wrapped_object variable.
216
- *
217
- * @access protected
218
- *
219
- * @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).
220
- * @return object The wrapped object.
221
- */
222
- function get_wrapped_object($ensure_consistency = false){
223
- trigger_error('Function blcContainer::get_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
224
- }
225
-
226
- /**
227
- * Update the entity wrapped by the container with values currently in the $wrapped_object.
228
- *
229
- * @access protected
230
- *
231
- * @return bool|WP_Error True on success, an error if something went wrong.
232
- */
233
- function update_wrapped_object(){
234
- trigger_error('Function blcContainer::update_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
235
- }
236
-
237
- /**
238
- * Parse the container for links and save the results to the DB.
239
- *
240
- * @return void
241
- */
242
- function synch(){
243
- //FB::log("Parsing {$this->container_type}[{$this->container_id}]");
244
-
245
- //Remove any existing link instance records associated with the container
246
- $this->delete_instances();
247
-
248
- //Load the wrapped object, if not done already
249
- $this->get_wrapped_object();
250
-
251
- //FB::log($this->fields, "Parseable fields :");
252
-
253
- //Iterate over all parse-able fields
254
- foreach($this->fields as $name => $format){
255
- //Get the field value
256
- $value = $this->get_field($name);
257
- if ( empty($value) ){
258
- //FB::log($name, "Skipping empty field");
259
- continue;
260
- }
261
- //FB::log($name, "Parsing field");
262
-
263
- //Get all parsers applicable to this field
264
- $parsers = blcParserHelper::get_parsers( $format, $this->container_type );
265
- //FB::log($parsers, "Applicable parsers");
266
-
267
- if ( empty($parsers) ) continue;
268
-
269
- $base_url = $this->base_url();
270
- $default_link_text = $this->default_link_text($name);
271
-
272
- //Parse the field with each parser
273
- foreach($parsers as $parser){
274
- //FB::log("Parsing $name with '{$parser->parser_type}' parser");
275
- $found_instances = $parser->parse( $value, $base_url, $default_link_text );
276
- //FB::log($found_instances, "Found instances");
277
-
278
- $transactionManager = TransactionManager::getInstance();
279
- $transactionManager->start();
280
-
281
- //Complete the link instances by adding container info, then save them to the DB.
282
- foreach($found_instances as $instance){
283
- $instance->set_container($this, $name);
284
- $instance->save();
285
- }
286
-
287
- $transactionManager->commit();
288
-
289
- }
290
- }
291
-
292
- $this->mark_as_synched();
293
- }
294
-
295
- /**
296
- * Mark the container as successfully synchronized (parsed for links).
297
- *
298
- * @return bool
299
- */
300
- function mark_as_synched(){
301
- global $wpdb; /* @var wpdb $wpdb */
302
-
303
- $this->last_synch = time();
304
-
305
- $q = "INSERT INTO {$wpdb->prefix}blc_synch( container_id, container_type, synched, last_synch)
306
- VALUES( %d, %s, %d, NOW() )
307
- ON DUPLICATE KEY UPDATE synched = VALUES(synched), last_synch = VALUES(last_synch)";
308
- $rez = $wpdb->query( $wpdb->prepare( $q, $this->container_id, $this->container_type, 1 ) );
309
-
310
- return ($rez !== false);
311
- }
312
-
313
- /**
314
- * blcContainer::mark_as_unsynched()
315
- * Mark the container as not synchronized (not parsed, or modified since the last parse).
316
- * The plugin will attempt to (re)parse the container at the earliest opportunity.
317
- *
318
- * @return bool
319
- */
320
- function mark_as_unsynched(){
321
- global $wpdb; /* @var wpdb $wpdb */
322
-
323
- $q = "INSERT INTO {$wpdb->prefix}blc_synch( container_id, container_type, synched, last_synch)
324
- VALUES( %d, %s, %d, '0000-00-00 00:00:00' )
325
- ON DUPLICATE KEY UPDATE synched = VALUES(synched)";
326
- $rez = $wpdb->query( $wpdb->prepare( $q, $this->container_id, $this->container_type, 0 ) );
327
-
328
- blc_got_unsynched_items();
329
-
330
- return ($rez !== false);
331
- }
332
-
333
- /**
334
- * Get the base URL of the container. Used to normalize relative URLs found
335
- * in the container. For example, for posts this would be the post permalink.
336
- *
337
- * @return string
338
- */
339
- function base_url(){
340
- return home_url();
341
- }
342
-
343
- /**
344
- * Get the default link text to use for links found in a specific container field.
345
- *
346
- * This is generally only meaningful for non-HTML container fields.
347
- * For example, if the container is post metadata, the default
348
- * link text might be equal to the name of the custom field.
349
- *
350
- * @param string $field
351
- * @return string
352
- */
353
- function default_link_text($field = ''){
354
- return '';
355
- }
356
-
357
-
358
-
359
- /**
360
- * Delete the DB record of this container.
361
- * Also deletes the DB records of all link instances associated with it.
362
- * Calling this method will not affect the WP entity (e.g. a post) corresponding to this container.
363
- *
364
- * @return bool
365
- */
366
- function delete(){
367
- global $wpdb; /* @var wpdb $wpdb */
368
-
369
- //Delete instances first.
370
- $rez = $this->delete_instances();
371
- if ( !$rez ){
372
- return false;
373
- }
374
-
375
- //Now delete the container record.
376
- $q = "DELETE FROM {$wpdb->prefix}blc_synch
377
- WHERE container_id = %d AND container_type = %s";
378
- $q = $wpdb->prepare($q, $this->container_id, $this->container_type);
379
-
380
- if ( $wpdb->query( $q ) === false ){
381
- return false;
382
- } else {
383
- return true;
384
- }
385
- }
386
-
387
- /**
388
- * Delete all link instance records associated with this container.
389
- * NB: Calling this method will not affect the WP entity (e.g. a post) corresponding to this container.
390
- *
391
- * @return bool
392
- */
393
- function delete_instances(){
394
- global $wpdb; /* @var wpdb $wpdb */
395
-
396
- //Remove instances associated with this container
397
- $q = "DELETE FROM {$wpdb->prefix}blc_instances
398
- WHERE container_id = %d AND container_type = %s";
399
- $q = $wpdb->prepare($q, $this->container_id, $this->container_type);
400
-
401
- if ( $wpdb->query( $q ) === false ){
402
- return false;
403
- } else {
404
- return true;
405
- }
406
- }
407
-
408
- /**
409
- * Delete or trash the WP entity corresponding to this container. Should prefer moving to trash, if possible.
410
- * Also remove the synch. record of the container and all associated instances.
411
- *
412
- * Must be over-ridden in a sub-class.
413
- *
414
- * @return bool|WP_Error
415
- */
416
- function delete_wrapped_object(){
417
- trigger_error('Function blcContainer::delete_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
418
- }
419
-
420
- /**
421
- * Move the WP entity corresponding to this container to the Trash.
422
- *
423
- * Must be over-riden in a subclass.
424
- *
425
- * @return bool|WP_Error
426
- */
427
- function trash_wrapped_object(){
428
- trigger_error('Function blcContainer::trash_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
429
- }
430
-
431
- /**
432
- * Check if the current user can delete/trash this container.
433
- *
434
- * Should be over-ridden in a subclass.
435
- *
436
- * @return bool
437
- */
438
- function current_user_can_delete(){
439
- return false;
440
- }
441
-
442
- /**
443
- * Determine if this container can be moved to the trash.
444
- *
445
- * Should be over-ridden in a subclass.
446
- *
447
- * @return bool
448
- */
449
- function can_be_trashed(){
450
- return false;
451
- }
452
-
453
-
454
- /**
455
- * Change all links with the specified URL to a new URL.
456
- *
457
- * @param string $field_name
458
- * @param blcParser $parser
459
- * @param string $new_url
460
- * @param string $old_url
461
- * @param string $old_raw_url
462
- * @param string $new_text Optional.
463
- *
464
- * @return array|WP_Error The new value of raw_url on success, or an error object if something went wrong.
465
- */
466
- function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = '', $new_text = null){
467
- //Ensure we're operating on a consistent copy of the wrapped object.
468
- /*
469
- Explanation
470
-
471
- Consider this scenario where the container object wraps a blog post :
472
- 1) The container object gets created and loads the post data.
473
- 2) Someone modifies the DB data corresponding to the post.
474
- 3) The container tries to edit a link present in the post. However, the post
475
- has changed since the time it was first cached, so when the container updates
476
- the post with it's changes, it will overwrite whatever modifications were made
477
- in step 2.
478
-
479
- This would not be a problem if WP entities like posts and comments were
480
- actually real objects, not just bags of key=>value pairs, but oh well.
481
-
482
- Therefore, it is necessary to re-load the wrapped object before editing it.
483
- */
484
- $this->get_wrapped_object(true);
485
-
486
- //Get the current value of the field that needs to be edited.
487
- $old_value = $this->get_field($field_name);
488
-
489
- //Have the parser modify the specified link. If successful, the parser will
490
- //return an associative array with two keys - 'content' and 'raw_url'.
491
- //Otherwise we'll get an instance of WP_Error.
492
- if ( $parser->is_link_text_editable() ) {
493
- $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url, $new_text);
494
- } else {
495
- $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
496
- }
497
- if ( is_wp_error($edit_result) ){
498
- return $edit_result;
499
- }
500
-
501
- //Update the field with the new value returned by the parser.
502
- $update_result = $this->update_field( $field_name, $edit_result['content'], $old_value );
503
- if ( is_wp_error($update_result) ){
504
- return $update_result;
505
- }
506
-
507
- //Return the new values to the instance.
508
- unset($edit_result['content']); //(Except content, which it doesn't need.)
509
- return $edit_result;
510
- }
511
-
512
- /**
513
- * Remove all links with the specified URL, leaving their anchor text intact.
514
- *
515
- * @param string $field_name
516
- * @param blcParser $parser
517
- * @param string $url
518
- * @param string $raw_url
519
- * @return bool|WP_Error True on success, or an error object if something went wrong.
520
- */
521
- function unlink($field_name, $parser, $url, $raw_url =''){
522
- //Ensure we're operating on a consistent copy of the wrapped object.
523
- $this->get_wrapped_object(true);
524
-
525
- $old_value = $this->get_field($field_name);
526
-
527
- $new_value = $parser->unlink($old_value, $url, $raw_url);
528
- if ( is_wp_error($new_value) ){
529
- return $new_value;
530
- }
531
-
532
- return $this->update_field( $field_name, $new_value, $old_value );
533
- }
534
-
535
- /**
536
- * Retrieve a list of links found in this container.
537
- *
538
- * @access public
539
- *
540
- * @return array of blcLink
541
- */
542
- function get_links(){
543
- $params = array(
544
- 's_container_type' => $this->container_type,
545
- 's_container_id' => $this->container_id,
546
- );
547
- return blc_get_links($params);
548
- }
549
-
550
-
551
- /**
552
- * Get action links to display in the "Source" column of the Tools -> Broken Links link table.
553
- *
554
- * @param string $container_field
555
- * @return array
556
- */
557
- function ui_get_action_links($container_field){
558
- return array();
559
- }
560
-
561
- /**
562
- * Get the container name to display in the "Source" column of the Tools -> Broken Links link table.
563
- *
564
- * @param string $container_field
565
- * @param string $context
566
- * @return string
567
- */
568
- function ui_get_source($container_field, $context = 'display'){
569
- return sprintf('%s[%d] : %s', $this->container_type, $this->container_id, $container_field);
570
- }
571
-
572
- /**
573
- * Get edit URL. Returns the URL of the Dashboard page where the item associated with this
574
- * container can be edited.
575
- *
576
- * HTML entities like '&' will be properly escaped for display.
577
- *
578
- * @access protected
579
- *
580
- * @return string
581
- */
582
- function get_edit_url(){
583
- //Should be over-ridden in a sub-class.
584
- return '';
585
- }
586
-
587
- }
588
-
589
-
590
- /**
591
- * An utility class for working with link container types.
592
- * All methods of this class should be called statically.
593
- *
594
- * @package Broken Link Checker
595
- */
596
- class blcContainerHelper {
597
-
598
- /**
599
- * Get the manager associated with a container type.
600
- *
601
- * @param string $container_type
602
- * @param string $fallback If there is no manager associated with $container_type, return the manager of this container type instead.
603
- * @return blcContainerManager|null
604
- */
605
- static function get_manager( $container_type, $fallback = '' ){
606
- $module_manager = blcModuleManager::getInstance();
607
- $container_manager = null;
608
-
609
- if ( $container_manager = $module_manager->get_module($container_type, true, 'container') ){
610
- return $container_manager;
611
- } elseif ( !empty($fallback) && ( $container_manager = $module_manager->get_module($fallback, true, 'container') ) ) {
612
- return $container_manager;
613
- } else {
614
- return null;
615
- }
616
- }
617
-
618
- /**
619
- * Retrieve or instantiate a container object.
620
- *
621
- * Pass an array containing the container type (string) and ID (int) to retrieve the container
622
- * from the database. Alternatively, pass an associative array to create a new container object
623
- * from the data in the array.
624
- *
625
- * @param array $container Either [container_type, container_id], or an assoc. array of container data.
626
- * @return blcContainer|null
627
- */
628
- static function get_container( $container ){
629
- global $wpdb; /* @var wpdb $wpdb */
630
-
631
- if ( !is_array($container) || ( count($container) < 2 ) ){
632
- return null;
633
- }
634
-
635
- if ( is_string($container[0]) && is_numeric($container[1]) ){
636
- //The argument is in the [container_type, id] format
637
- //Fetch the container's synch record.
638
- $q = "SELECT * FROM {$wpdb->prefix}blc_synch WHERE container_type = %s AND container_id = %d";
639
- $q = $wpdb->prepare($q, $container[0], $container[1]);
640
- $rez = $wpdb->get_row($q, ARRAY_A);
641
-
642
- if ( empty($rez) ){
643
- //The container wasn't found, so we'll create a new one.
644
- $container = array(
645
- 'container_type' => $container[0],
646
- 'container_id' => $container[1],
647
- );
648
- } else {
649
- $container = $rez;
650
- }
651
- }
652
-
653
- if ( !($manager = blcContainerHelper::get_manager($container['container_type'])) ){
654
- return null;
655
- }
656
-
657
- return $manager->get_container($container);
658
- }
659
-
660
- /**
661
- * Retrieve or instantiate multiple link containers.
662
- *
663
- * Takes an array of container specifications and returns an array of container objects.
664
- * Each input array entry should be an array and consist either of a container type (string)
665
- * and container ID (int), or name => value pairs describing a container object.
666
- *
667
- * @see blcContainerHelper::get_container()
668
- *
669
- * @param array $containers
670
- * @param string $purpose Optional code indicating how the retrieved containers will be used.
671
- * @param string $fallback The fallback container type to use for unrecognized containers.
672
- * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
673
- * @return blcContainer[] Array of blcContainer indexed by "container_type|container_id"
674
- */
675
- static function get_containers( $containers, $purpose = '', $fallback = '', $load_wrapped_objects = false ){
676
- global $wpdb; /* @var wpdb $wpdb */
677
-
678
- //If the input is invalid or empty, return an empty array.
679
- if ( !is_array($containers) || (count($containers) < 1) ) {
680
- return array();
681
- }
682
-
683
- $first = reset($containers);
684
- if ( !is_array($first) ){
685
- return array();
686
- }
687
-
688
- if ( isset($first[0]) && is_string($first[0]) && is_numeric($first[1]) ){
689
- //The argument is an array of [container_type, id].
690
- //Divide the container IDs by container type.
691
- $by_type = array();
692
-
693
- foreach($containers as $container){
694
- if ( isset($by_type[$container[0]]) ){
695
- array_push($by_type[$container[0]], intval($container[1]));
696
- } else {
697
- $by_type[$container[0]] = array( intval($container[1]) );
698
- }
699
- }
700
-
701
- //Build the SQL to fetch all the specified containers
702
- $q = "SELECT *
703
- FROM {$wpdb->prefix}blc_synch
704
- WHERE";
705
-
706
- $pieces = array();
707
- foreach($by_type as $container_type => $container_ids){
708
- $pieces[] = '( container_type = "'. esc_sql($container_type) .'" AND container_id IN ('. implode(', ', $container_ids) .') )';
709
- }
710
-
711
- $q .= implode("\n\t OR ", $pieces);
712
-
713
- //Fetch the container synch. records from the DB.
714
- $containers = $wpdb->get_results($q, ARRAY_A);
715
- }
716
-
717
- /*
718
- Divide the inputs into separate arrays by container type (again), then invoke
719
- the appropriate manager for each type to instantiate the container objects.
720
- */
721
-
722
- //At this point, $containers is an array of assoc. arrays comprising container data.
723
- $by_type = array();
724
- foreach($containers as $container){
725
- if ( isset($by_type[$container['container_type']]) ){
726
- array_push($by_type[$container['container_type']], $container);
727
- } else {
728
- $by_type[$container['container_type']] = array($container);
729
- }
730
- }
731
-
732
- $results = array();
733
- foreach($by_type as $container_type => $entries){
734
- $manager = blcContainerHelper::get_manager($container_type, $fallback);
735
- if ( !is_null($manager) ){
736
- $partial_results = $manager->get_containers($entries, $purpose, $load_wrapped_objects);
737
- $results = array_merge($results, $partial_results);
738
- }
739
- }
740
-
741
- return $results;
742
- }
743
-
744
- /**
745
- * Retrieve link containers that need to be synchronized (parsed).
746
- *
747
- * @param integer $max_results The maximum number of containers to return. Defaults to returning all unsynched containers.
748
- * @return blcContainer[]
749
- */
750
- static function get_unsynched_containers($max_results = 0){
751
- global $wpdb; /* @var wpdb $wpdb */
752
-
753
- $q = "SELECT * FROM {$wpdb->prefix}blc_synch WHERE synched = 0";
754
- if ( $max_results > 0 ){
755
- $q .= " LIMIT $max_results";
756
- }
757
-
758
- $container_data = $wpdb->get_results($q, ARRAY_A);
759
- //FB::log($container_data, "Unsynched containers");
760
- if( empty($container_data) ){
761
- return array();
762
- }
763
-
764
- $containers = blcContainerHelper::get_containers($container_data, BLC_FOR_PARSING, 'dummy');
765
- return $containers;
766
- }
767
-
768
- /**
769
- * (Re)create and update synchronization records for all supported containers.
770
- * Calls the resynch() method of all registered managers.
771
- *
772
- * @param bool $forced If true, assume that no synch. records exist and build all of them from scratch.
773
- * @return void
774
- */
775
- static function resynch($forced = false){
776
- global $wpdb;
777
-
778
- $module_manager = blcModuleManager::getInstance();
779
- $active_managers = $module_manager->get_active_by_category('container');
780
- foreach($active_managers as $module_id => $module_data){
781
- $manager = $module_manager->get_module($module_id);
782
- if ( $manager ){
783
- $manager->resynch($forced);
784
- }
785
- }
786
- }
787
-
788
- /**
789
- * Mark as unparsed all containers that match one of the the specified formats or
790
- * container types and that were last parsed after a specific timestamp.
791
- *
792
- * Used by newly activated parsers to force the containers they're interested in
793
- * to resynchronize and thus let the parser process them.
794
- *
795
- * @param array $formats Associative array of timestamps, indexed by format IDs.
796
- * @param array $container_types Associative array of timestamps, indexed by container types.
797
- * @return bool
798
- */
799
- static function mark_as_unsynched_where($formats, $container_types){
800
- global $wpdb; /* @var wpdb $wpdb */
801
- global $blclog;
802
-
803
- //Find containers that match any of the specified formats and add them to
804
- //the list of container types that need to be marked as unsynched.
805
- $module_manager = blcModuleManager::getInstance();
806
- $containers = $module_manager->get_active_by_category('container');
807
-
808
- foreach($containers as $module_id => $module_data){
809
- if ( $container_manager = $module_manager->get_module($module_id) ){
810
- $fields = $container_manager->get_parseable_fields();
811
- $container_type = $container_manager->container_type;
812
- foreach($formats as $format => $timestamp){
813
- if ( in_array($format, $fields) ){
814
- //Choose the earliest timestamp
815
- if ( isset($container_types[$container_type]) ){
816
- $container_types[$container_type] = min($timestamp, $container_types[$container_type]);
817
- } else {
818
- $container_types[$container_type] = $timestamp;
819
- }
820
- }
821
- }
822
- };
823
- }
824
-
825
- if ( empty($container_types) ){
826
- return true;
827
- }
828
-
829
- //Build the query to update all synch. records that match one of the specified
830
- //container types and have been parsed after the specified time.
831
- $q = "UPDATE {$wpdb->prefix}blc_synch SET synched = 0 WHERE ";
832
-
833
- $pieces = array();
834
- foreach($container_types as $container_type => $timestamp){
835
- $pieces[] = $wpdb->prepare(
836
- '(container_type = %s AND last_synch >= %s)',
837
- $container_type,
838
- date('Y-m-d H:i:s', $timestamp)
839
- );
840
- }
841
-
842
- $q .= implode(' OR ', $pieces);
843
- $blclog->log('...... Executing query: ' . $q);
844
-
845
- $start_time = microtime(true);
846
- $rez = ($wpdb->query($q) !== false);
847
- $blclog->log(sprintf('...... %d rows affected, %.3f seconds', $wpdb->rows_affected, microtime(true) - $start_time));
848
-
849
- blc_got_unsynched_items();
850
-
851
- return $rez;
852
- }
853
-
854
- /**
855
- * Remove synch. records that reference container types not currently loaded
856
- *
857
- * @return bool
858
- */
859
- static function cleanup_containers(){
860
- global $wpdb; /* @var wpdb $wpdb */
861
- global $blclog;
862
-
863
- $module_manager = blcModuleManager::getInstance();
864
-
865
- $start = microtime(true);
866
- $active_containers = $module_manager->get_escaped_ids('container');
867
- $q = "DELETE synch.*
868
- FROM {$wpdb->prefix}blc_synch AS synch
869
- WHERE
870
- synch.container_type NOT IN ({$active_containers})";
871
- $rez = $wpdb->query($q);
872
- $elapsed = microtime(true) - $start;
873
- $blclog->log(sprintf('... %d synch records deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
874
-
875
- return $rez !== false;
876
- }
877
-
878
- /**
879
- * Get the message to display after $n containers of a specific type have been deleted.
880
- *
881
- * @param string $container_type
882
- * @param int $n Number of deleted containers.
883
- * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
884
- */
885
- static function ui_bulk_delete_message($container_type, $n){
886
- $manager = blcContainerHelper::get_manager($container_type);
887
- if ( is_null($manager) ){
888
- return sprintf(__("Container type '%s' not recognized", 'broken-link-checker'), $container_type);
889
- } else {
890
- return $manager->ui_bulk_delete_message($n);
891
- }
892
- }
893
-
894
- /**
895
- * Get the message to display after $n containers of a specific type have been moved to the trash.
896
- *
897
- * @see blcContainerHelper::ui_bulk_delete_message()
898
- *
899
- * @param string $container_type
900
- * @param int $n
901
- * @return string
902
- */
903
- static function ui_bulk_trash_message($container_type, $n){
904
- $manager = blcContainerHelper::get_manager($container_type);
905
- if ( is_null($manager) ){
906
- return sprintf(__("Container type '%s' not recognized", 'broken-link-checker'), $container_type);
907
- } else {
908
- return $manager->ui_bulk_trash_message($n);
909
- }
910
- }
911
- }
1
+ <?php
2
+
3
+ /**
4
+ * The base class for link container managers.
5
+ *
6
+ * Sub-classes should override at least the get_containers() and resynch() methods.
7
+ *
8
+ * @package Broken Link Checker
9
+ * @access public
10
+ */
11
+ class blcContainerManager extends blcModule {
12
+
13
+ var $container_type = '';
14
+ var $fields = array();
15
+ var $container_class_name = 'blcContainer';
16
+
17
+ /**
18
+ * Do whatever setup necessary that wasn't already done in the constructor.
19
+ *
20
+ * This method was added so that sub-classes would have something "safe" to
21
+ * over-ride without having to deal with PHP4/5 constructors.
22
+ *
23
+ * @return void
24
+ */
25
+ function init(){
26
+ parent::init();
27
+ $this->container_type = $this->module_id;
28
+ //Sub-classes might also use it to set up hooks, etc.
29
+ }
30
+
31
+ /**
32
+ * Instantiate a link container.
33
+ *
34
+ * @param array $container An associative array of container data.
35
+ * @return blcContainer
36
+ */
37
+ function get_container($container){
38
+ $container['fields'] = $this->get_parseable_fields();
39
+ $container_obj = new $this->container_class_name($container);
40
+ return $container_obj;
41
+ }
42
+
43
+ /**
44
+ * Instantiate multiple containers of the container type managed by this class and optionally
45
+ * pre-load container data used for display/parsing.
46
+ *
47
+ * Sub-classes should, if possible, use the $purpose argument to pre-load any extra data required for
48
+ * the specified task right away, instead of making multiple DB roundtrips later. For example, if
49
+ * $purpose is set to the BLC_FOR_DISPLAY constant, you might want to preload any DB data that the
50
+ * container will need in blcContainer::ui_get_source().
51
+ *
52
+ * @see blcContainer::make_containers()
53
+ * @see blcContainer::ui_get_source()
54
+ * @see blcContainer::ui_get_action_links()
55
+ *
56
+ * @param array $containers Array of assoc. arrays containing container data.
57
+ * @param string $purpose An optional code indicating how the retrieved containers will be used.
58
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
59
+ *
60
+ * @return array of blcContainer indexed by "container_type|container_id"
61
+ */
62
+ function get_containers($containers, $purpose = '', $load_wrapped_objects = false){
63
+ return $this->make_containers($containers);
64
+ }
65
+
66
+ /**
67
+ * Instantiate multiple containers of the container type managed by this class
68
+ *
69
+ * @param array $containers Array of assoc. arrays containing container data.
70
+ * @return array of blcContainer indexed by "container_type|container_id"
71
+ */
72
+ function make_containers($containers){
73
+ $results = array();
74
+ foreach($containers as $container){
75
+ $key = $container['container_type'] . '|' . $container['container_id'];
76
+ $results[ $key ] = $this->get_container($container);
77
+ }
78
+ return $results;
79
+ }
80
+
81
+ /**
82
+ * Create or update synchronization records for all containers managed by this class.
83
+ *
84
+ * Must be over-ridden in subclasses.
85
+ *
86
+ * @param bool $forced If true, assume that all synch. records are gone and will need to be recreated from scratch.
87
+ * @return void
88
+ */
89
+ function resynch($forced = false){
90
+ trigger_error('Function blcContainerManager::resynch() must be over-ridden in a sub-class', E_USER_ERROR);
91
+ }
92
+
93
+ /**
94
+ * Resynch when activated.
95
+ *
96
+ * @uses blcContainerManager::resynch()
97
+ *
98
+ * @return void
99
+ */
100
+ function activated(){
101
+ $this->resynch();
102
+ blc_got_unsynched_items();
103
+ }
104
+
105
+ /**
106
+ * Get a list of the parseable fields and their formats common to all containers of this type.
107
+ *
108
+ * @return array Associative array of formats indexed by field name.
109
+ */
110
+ function get_parseable_fields(){
111
+ return $this->fields;
112
+ }
113
+
114
+ /**
115
+ * Get the message to display after $n containers have been deleted.
116
+ *
117
+ * @param int $n Number of deleted containers.
118
+ * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
119
+ */
120
+ function ui_bulk_delete_message($n){
121
+ return sprintf(
122
+ _n(
123
+ "%d '%s' has been deleted",
124
+ "%d '%s' have been deleted",
125
+ $n,
126
+ 'broken-link-checker'
127
+ ),
128
+ $n,
129
+ $this->container_type
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Get the message to display after $n containers have been moved to the trash.
135
+ *
136
+ * @param int $n Number of trashed containers.
137
+ * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
138
+ */
139
+ function ui_bulk_trash_message($n){
140
+ return $this->ui_bulk_delete_message($n);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * The base class for link containers. All containers should extend this class.
146
+ *
147
+ * @package Broken Link Checker
148
+ * @access public
149
+ */
150
+ class blcContainer {
151
+
152
+ var $fields = array();
153
+ var $default_field = '';
154
+
155
+ var $container_type;
156
+ var $container_id = 0;
157
+
158
+ var $synched = false;
159
+ var $last_synch = '0000-00-00 00:00:00';
160
+
161
+ var $wrapped_object = null;
162
+
163
+ /**
164
+ * Constructor
165
+ *
166
+ * @param array $data
167
+ * @param object $wrapped_object
168
+ * @return void
169
+ */
170
+ function __construct( $data = null, $wrapped_object = null ){
171
+ $this->wrapped_object = $wrapped_object;
172
+ if ( !empty($data) && is_array($data) ){
173
+ foreach($data as $name => $value){
174
+ $this->$name = $value;
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Get the value of the specified field of the object wrapped by this container.
181
+ *
182
+ * @access protected
183
+ *
184
+ * @param string $field Field name. If omitted, the value of the default field will be returned.
185
+ * @return string
186
+ */
187
+ function get_field($field = ''){
188
+ if ( empty($field) ){
189
+ $field = $this->default_field;
190
+ }
191
+
192
+ $w = $this->get_wrapped_object();
193
+ return $w->$field;
194
+ }
195
+
196
+ /**
197
+ * Update the value of the specified field in the wrapped object.
198
+ * This method will also immediately save the changed value by calling update_wrapped_object().
199
+ *
200
+ * @access protected
201
+ *
202
+ * @param string $field Field name.
203
+ * @param string $new_value Set the field to this value.
204
+ * @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).
205
+ * @return bool|WP_Error True on success, an error object if something went wrong.
206
+ */
207
+ function update_field($field, $new_value, $old_value = ''){
208
+ $w = $this->get_wrapped_object();
209
+ $w->$field = $new_value;
210
+ return $this->update_wrapped_object();
211
+ }
212
+
213
+ /**
214
+ * Retrieve the entity wrapped by this container.
215
+ * The fetched object will also be cached in the $wrapped_object variable.
216
+ *
217
+ * @access protected
218
+ *
219
+ * @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).
220
+ * @return object The wrapped object.
221
+ */
222
+ function get_wrapped_object($ensure_consistency = false){
223
+ trigger_error('Function blcContainer::get_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
224
+ }
225
+
226
+ /**
227
+ * Update the entity wrapped by the container with values currently in the $wrapped_object.
228
+ *
229
+ * @access protected
230
+ *
231
+ * @return bool|WP_Error True on success, an error if something went wrong.
232
+ */
233
+ function update_wrapped_object(){
234
+ trigger_error('Function blcContainer::update_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
235
+ }
236
+
237
+ /**
238
+ * Parse the container for links and save the results to the DB.
239
+ *
240
+ * @return void
241
+ */
242
+ function synch(){
243
+ //FB::log("Parsing {$this->container_type}[{$this->container_id}]");
244
+
245
+ //Remove any existing link instance records associated with the container
246
+ $this->delete_instances();
247
+
248
+ //Load the wrapped object, if not done already
249
+ $this->get_wrapped_object();
250
+
251
+ //FB::log($this->fields, "Parseable fields :");
252
+
253
+ //Iterate over all parse-able fields
254
+ foreach($this->fields as $name => $format){
255
+ //Get the field value
256
+ $value = $this->get_field($name);
257
+ if ( empty($value) ){
258
+ //FB::log($name, "Skipping empty field");
259
+ continue;
260
+ }
261
+ //FB::log($name, "Parsing field");
262
+
263
+ //Get all parsers applicable to this field
264
+ $parsers = blcParserHelper::get_parsers( $format, $this->container_type );
265
+ //FB::log($parsers, "Applicable parsers");
266
+
267
+ if ( empty($parsers) ) continue;
268
+
269
+ $base_url = $this->base_url();
270
+ $default_link_text = $this->default_link_text($name);
271
+
272
+ //Parse the field with each parser
273
+ foreach($parsers as $parser){
274
+ //FB::log("Parsing $name with '{$parser->parser_type}' parser");
275
+ $found_instances = $parser->parse( $value, $base_url, $default_link_text );
276
+ //FB::log($found_instances, "Found instances");
277
+
278
+ $transactionManager = TransactionManager::getInstance();
279
+ $transactionManager->start();
280
+
281
+ //Complete the link instances by adding container info, then save them to the DB.
282
+ foreach($found_instances as $instance){
283
+ $instance->set_container($this, $name);
284
+ $instance->save();
285
+ }
286
+
287
+ $transactionManager->commit();
288
+
289
+ }
290
+ }
291
+
292
+ $this->mark_as_synched();
293
+ }
294
+
295
+ /**
296
+ * Mark the container as successfully synchronized (parsed for links).
297
+ *
298
+ * @return bool
299
+ */
300
+ function mark_as_synched(){
301
+ global $wpdb; /* @var wpdb $wpdb */
302
+
303
+ $this->last_synch = time();
304
+
305
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch( container_id, container_type, synched, last_synch)
306
+ VALUES( %d, %s, %d, NOW() )
307
+ ON DUPLICATE KEY UPDATE synched = VALUES(synched), last_synch = VALUES(last_synch)";
308
+ $rez = $wpdb->query( $wpdb->prepare( $q, $this->container_id, $this->container_type, 1 ) );
309
+
310
+ return ($rez !== false);
311
+ }
312
+
313
+ /**
314
+ * blcContainer::mark_as_unsynched()
315
+ * Mark the container as not synchronized (not parsed, or modified since the last parse).
316
+ * The plugin will attempt to (re)parse the container at the earliest opportunity.
317
+ *
318
+ * @return bool
319
+ */
320
+ function mark_as_unsynched(){
321
+ global $wpdb; /* @var wpdb $wpdb */
322
+
323
+ $q = "INSERT INTO {$wpdb->prefix}blc_synch( container_id, container_type, synched, last_synch)
324
+ VALUES( %d, %s, %d, '0000-00-00 00:00:00' )
325
+ ON DUPLICATE KEY UPDATE synched = VALUES(synched)";
326
+ $rez = $wpdb->query( $wpdb->prepare( $q, $this->container_id, $this->container_type, 0 ) );
327
+
328
+ blc_got_unsynched_items();
329
+
330
+ return ($rez !== false);
331
+ }
332
+
333
+ /**
334
+ * Get the base URL of the container. Used to normalize relative URLs found
335
+ * in the container. For example, for posts this would be the post permalink.
336
+ *
337
+ * @return string
338
+ */
339
+ function base_url(){
340
+ return home_url();
341
+ }
342
+
343
+ /**
344
+ * Get the default link text to use for links found in a specific container field.
345
+ *
346
+ * This is generally only meaningful for non-HTML container fields.
347
+ * For example, if the container is post metadata, the default
348
+ * link text might be equal to the name of the custom field.
349
+ *
350
+ * @param string $field
351
+ * @return string
352
+ */
353
+ function default_link_text($field = ''){
354
+ return '';
355
+ }
356
+
357
+
358
+
359
+ /**
360
+ * Delete the DB record of this container.
361
+ * Also deletes the DB records of all link instances associated with it.
362
+ * Calling this method will not affect the WP entity (e.g. a post) corresponding to this container.
363
+ *
364
+ * @return bool
365
+ */
366
+ function delete(){
367
+ global $wpdb; /* @var wpdb $wpdb */
368
+
369
+ //Delete instances first.
370
+ $rez = $this->delete_instances();
371
+ if ( !$rez ){
372
+ return false;
373
+ }
374
+
375
+ //Now delete the container record.
376
+ $q = "DELETE FROM {$wpdb->prefix}blc_synch
377
+ WHERE container_id = %d AND container_type = %s";
378
+ $q = $wpdb->prepare($q, $this->container_id, $this->container_type);
379
+
380
+ if ( $wpdb->query( $q ) === false ){
381
+ return false;
382
+ } else {
383
+ return true;
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Delete all link instance records associated with this container.
389
+ * NB: Calling this method will not affect the WP entity (e.g. a post) corresponding to this container.
390
+ *
391
+ * @return bool
392
+ */
393
+ function delete_instances(){
394
+ global $wpdb; /* @var wpdb $wpdb */
395
+
396
+ //Remove instances associated with this container
397
+ $q = "DELETE FROM {$wpdb->prefix}blc_instances
398
+ WHERE container_id = %d AND container_type = %s";
399
+ $q = $wpdb->prepare($q, $this->container_id, $this->container_type);
400
+
401
+ if ( $wpdb->query( $q ) === false ){
402
+ return false;
403
+ } else {
404
+ return true;
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Delete or trash the WP entity corresponding to this container. Should prefer moving to trash, if possible.
410
+ * Also remove the synch. record of the container and all associated instances.
411
+ *
412
+ * Must be over-ridden in a sub-class.
413
+ *
414
+ * @return bool|WP_Error
415
+ */
416
+ function delete_wrapped_object(){
417
+ trigger_error('Function blcContainer::delete_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
418
+ }
419
+
420
+ /**
421
+ * Move the WP entity corresponding to this container to the Trash.
422
+ *
423
+ * Must be over-riden in a subclass.
424
+ *
425
+ * @return bool|WP_Error
426
+ */
427
+ function trash_wrapped_object(){
428
+ trigger_error('Function blcContainer::trash_wrapped_object() must be over-ridden in a sub-class', E_USER_ERROR);
429
+ }
430
+
431
+ /**
432
+ * Check if the current user can delete/trash this container.
433
+ *
434
+ * Should be over-ridden in a subclass.
435
+ *
436
+ * @return bool
437
+ */
438
+ function current_user_can_delete(){
439
+ return false;
440
+ }
441
+
442
+ /**
443
+ * Determine if this container can be moved to the trash.
444
+ *
445
+ * Should be over-ridden in a subclass.
446
+ *
447
+ * @return bool
448
+ */
449
+ function can_be_trashed(){
450
+ return false;
451
+ }
452
+
453
+
454
+ /**
455
+ * Change all links with the specified URL to a new URL.
456
+ *
457
+ * @param string $field_name
458
+ * @param blcParser $parser
459
+ * @param string $new_url
460
+ * @param string $old_url
461
+ * @param string $old_raw_url
462
+ * @param string $new_text Optional.
463
+ *
464
+ * @return array|WP_Error The new value of raw_url on success, or an error object if something went wrong.
465
+ */
466
+ function edit_link($field_name, $parser, $new_url, $old_url = '', $old_raw_url = '', $new_text = null){
467
+ //Ensure we're operating on a consistent copy of the wrapped object.
468
+ /*
469
+ Explanation
470
+
471
+ Consider this scenario where the container object wraps a blog post :
472
+ 1) The container object gets created and loads the post data.
473
+ 2) Someone modifies the DB data corresponding to the post.
474
+ 3) The container tries to edit a link present in the post. However, the post
475
+ has changed since the time it was first cached, so when the container updates
476
+ the post with it's changes, it will overwrite whatever modifications were made
477
+ in step 2.
478
+
479
+ This would not be a problem if WP entities like posts and comments were
480
+ actually real objects, not just bags of key=>value pairs, but oh well.
481
+
482
+ Therefore, it is necessary to re-load the wrapped object before editing it.
483
+ */
484
+ $this->get_wrapped_object(true);
485
+
486
+ //Get the current value of the field that needs to be edited.
487
+ $old_value = $this->get_field($field_name);
488
+
489
+ //Have the parser modify the specified link. If successful, the parser will
490
+ //return an associative array with two keys - 'content' and 'raw_url'.
491
+ //Otherwise we'll get an instance of WP_Error.
492
+ if ( $parser->is_link_text_editable() ) {
493
+ $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url, $new_text);
494
+ } else {
495
+ $edit_result = $parser->edit($old_value, $new_url, $old_url, $old_raw_url);
496
+ }
497
+ if ( is_wp_error($edit_result) ){
498
+ return $edit_result;
499
+ }
500
+
501
+ //Update the field with the new value returned by the parser.
502
+ $update_result = $this->update_field( $field_name, $edit_result['content'], $old_value );
503
+ if ( is_wp_error($update_result) ){
504
+ return $update_result;
505
+ }
506
+
507
+ //Return the new values to the instance.
508
+ unset($edit_result['content']); //(Except content, which it doesn't need.)
509
+ return $edit_result;
510
+ }
511
+
512
+ /**
513
+ * Remove all links with the specified URL, leaving their anchor text intact.
514
+ *
515
+ * @param string $field_name
516
+ * @param blcParser $parser
517
+ * @param string $url
518
+ * @param string $raw_url
519
+ * @return bool|WP_Error True on success, or an error object if something went wrong.
520
+ */
521
+ function unlink($field_name, $parser, $url, $raw_url =''){
522
+ //Ensure we're operating on a consistent copy of the wrapped object.
523
+ $this->get_wrapped_object(true);
524
+
525
+ $old_value = $this->get_field($field_name);
526
+
527
+ $new_value = $parser->unlink($old_value, $url, $raw_url);
528
+ if ( is_wp_error($new_value) ){
529
+ return $new_value;
530
+ }
531
+
532
+ return $this->update_field( $field_name, $new_value, $old_value );
533
+ }
534
+
535
+ /**
536
+ * Retrieve a list of links found in this container.
537
+ *
538
+ * @access public
539
+ *
540
+ * @return array of blcLink
541
+ */
542
+ function get_links(){
543
+ $params = array(
544
+ 's_container_type' => $this->container_type,
545
+ 's_container_id' => $this->container_id,
546
+ );
547
+ return blc_get_links($params);
548
+ }
549
+
550
+
551
+ /**
552
+ * Get action links to display in the "Source" column of the Tools -> Broken Links link table.
553
+ *
554
+ * @param string $container_field
555
+ * @return array
556
+ */
557
+ function ui_get_action_links($container_field){
558
+ return array();
559
+ }
560
+
561
+ /**
562
+ * Get the container name to display in the "Source" column of the Tools -> Broken Links link table.
563
+ *
564
+ * @param string $container_field
565
+ * @param string $context
566
+ * @return string
567
+ */
568
+ function ui_get_source($container_field, $context = 'display'){
569
+ return sprintf('%s[%d] : %s', $this->container_type, $this->container_id, $container_field);
570
+ }
571
+
572
+ /**
573
+ * Get edit URL. Returns the URL of the Dashboard page where the item associated with this
574
+ * container can be edited.
575
+ *
576
+ * HTML entities like '&' will be properly escaped for display.
577
+ *
578
+ * @access protected
579
+ *
580
+ * @return string
581
+ */
582
+ function get_edit_url(){
583
+ //Should be over-ridden in a sub-class.
584
+ return '';
585
+ }
586
+
587
+ }
588
+
589
+
590
+ /**
591
+ * An utility class for working with link container types.
592
+ * All methods of this class should be called statically.
593
+ *
594
+ * @package Broken Link Checker
595
+ */
596
+ class blcContainerHelper {
597
+
598
+ /**
599
+ * Get the manager associated with a container type.
600
+ *
601
+ * @param string $container_type
602
+ * @param string $fallback If there is no manager associated with $container_type, return the manager of this container type instead.
603
+ * @return blcContainerManager|null
604
+ */
605
+ static function get_manager( $container_type, $fallback = '' ){
606
+ $module_manager = blcModuleManager::getInstance();
607
+ $container_manager = null;
608
+
609
+ if ( $container_manager = $module_manager->get_module($container_type, true, 'container') ){
610
+ return $container_manager;
611
+ } elseif ( !empty($fallback) && ( $container_manager = $module_manager->get_module($fallback, true, 'container') ) ) {
612
+ return $container_manager;
613
+ } else {
614
+ return null;
615
+ }
616
+ }
617
+
618
+ /**
619
+ * Retrieve or instantiate a container object.
620
+ *
621
+ * Pass an array containing the container type (string) and ID (int) to retrieve the container
622
+ * from the database. Alternatively, pass an associative array to create a new container object
623
+ * from the data in the array.
624
+ *
625
+ * @param array $container Either [container_type, container_id], or an assoc. array of container data.
626
+ * @return blcContainer|null
627
+ */
628
+ static function get_container( $container ){
629
+ global $wpdb; /* @var wpdb $wpdb */
630
+
631
+ if ( !is_array($container) || ( count($container) < 2 ) ){
632
+ return null;
633
+ }
634
+
635
+ if ( is_string($container[0]) && is_numeric($container[1]) ){
636
+ //The argument is in the [container_type, id] format
637
+ //Fetch the container's synch record.
638
+ $q = "SELECT * FROM {$wpdb->prefix}blc_synch WHERE container_type = %s AND container_id = %d";
639
+ $q = $wpdb->prepare($q, $container[0], $container[1]);
640
+ $rez = $wpdb->get_row($q, ARRAY_A);
641
+
642
+ if ( empty($rez) ){
643
+ //The container wasn't found, so we'll create a new one.
644
+ $container = array(
645
+ 'container_type' => $container[0],
646
+ 'container_id' => $container[1],
647
+ );
648
+ } else {
649
+ $container = $rez;
650
+ }
651
+ }
652
+
653
+ if ( !($manager = blcContainerHelper::get_manager($container['container_type'])) ){
654
+ return null;
655
+ }
656
+
657
+ return $manager->get_container($container);
658
+ }
659
+
660
+ /**
661
+ * Retrieve or instantiate multiple link containers.
662
+ *
663
+ * Takes an array of container specifications and returns an array of container objects.
664
+ * Each input array entry should be an array and consist either of a container type (string)
665
+ * and container ID (int), or name => value pairs describing a container object.
666
+ *
667
+ * @see blcContainerHelper::get_container()
668
+ *
669
+ * @param array $containers
670
+ * @param string $purpose Optional code indicating how the retrieved containers will be used.
671
+ * @param string $fallback The fallback container type to use for unrecognized containers.
672
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose.
673
+ * @return blcContainer[] Array of blcContainer indexed by "container_type|container_id"
674
+ */
675
+ static function get_containers( $containers, $purpose = '', $fallback = '', $load_wrapped_objects = false ){
676
+ global $wpdb; /* @var wpdb $wpdb */
677
+
678
+ //If the input is invalid or empty, return an empty array.
679
+ if ( !is_array($containers) || (count($containers) < 1) ) {
680
+ return array();
681
+ }
682
+
683
+ $first = reset($containers);
684
+ if ( !is_array($first) ){
685
+ return array();
686
+ }
687
+
688
+ if ( isset($first[0]) && is_string($first[0]) && is_numeric($first[1]) ){
689
+ //The argument is an array of [container_type, id].
690
+ //Divide the container IDs by container type.
691
+ $by_type = array();
692
+
693
+ foreach($containers as $container){
694
+ if ( isset($by_type[$container[0]]) ){
695
+ array_push($by_type[$container[0]], intval($container[1]));
696
+ } else {
697
+ $by_type[$container[0]] = array( intval($container[1]) );
698
+ }
699
+ }
700
+
701
+ //Build the SQL to fetch all the specified containers
702
+ $q = "SELECT *
703
+ FROM {$wpdb->prefix}blc_synch
704
+ WHERE";
705
+
706
+ $pieces = array();
707
+ foreach($by_type as $container_type => $container_ids){
708
+ $pieces[] = '( container_type = "'. esc_sql($container_type) .'" AND container_id IN ('. implode(', ', $container_ids) .') )';
709
+ }
710
+
711
+ $q .= implode("\n\t OR ", $pieces);
712
+
713
+ //Fetch the container synch. records from the DB.
714
+ $containers = $wpdb->get_results($q, ARRAY_A);
715
+ }
716
+
717
+ /*
718
+ Divide the inputs into separate arrays by container type (again), then invoke
719
+ the appropriate manager for each type to instantiate the container objects.
720
+ */
721
+
722
+ //At this point, $containers is an array of assoc. arrays comprising container data.
723
+ $by_type = array();
724
+ foreach($containers as $container){
725
+ if ( isset($by_type[$container['container_type']]) ){
726
+ array_push($by_type[$container['container_type']], $container);
727
+ } else {
728
+ $by_type[$container['container_type']] = array($container);
729
+ }
730
+ }
731
+
732
+ $results = array();
733
+ foreach($by_type as $container_type => $entries){
734
+ $manager = blcContainerHelper::get_manager($container_type, $fallback);
735
+ if ( !is_null($manager) ){
736
+ $partial_results = $manager->get_containers($entries, $purpose, $load_wrapped_objects);
737
+ $results = array_merge($results, $partial_results);
738
+ }
739
+ }
740
+
741
+ return $results;
742
+ }
743
+
744
+ /**
745
+ * Retrieve link containers that need to be synchronized (parsed).
746
+ *
747
+ * @param integer $max_results The maximum number of containers to return. Defaults to returning all unsynched containers.
748
+ * @return blcContainer[]
749
+ */
750
+ static function get_unsynched_containers($max_results = 0){
751
+ global $wpdb; /* @var wpdb $wpdb */
752
+
753
+ $q = "SELECT * FROM {$wpdb->prefix}blc_synch WHERE synched = 0";
754
+ if ( $max_results > 0 ){
755
+ $q .= " LIMIT $max_results";
756
+ }
757
+
758
+ $container_data = $wpdb->get_results($q, ARRAY_A);
759
+ //FB::log($container_data, "Unsynched containers");
760
+ if( empty($container_data) ){
761
+ return array();
762
+ }
763
+
764
+ $containers = blcContainerHelper::get_containers($container_data, BLC_FOR_PARSING, 'dummy');
765
+ return $containers;
766
+ }
767
+
768
+ /**
769
+ * (Re)create and update synchronization records for all supported containers.
770
+ * Calls the resynch() method of all registered managers.
771
+ *
772
+ * @param bool $forced If true, assume that no synch. records exist and build all of them from scratch.
773
+ * @return void
774
+ */
775
+ static function resynch($forced = false){
776
+ global $wpdb;
777
+
778
+ $module_manager = blcModuleManager::getInstance();
779
+ $active_managers = $module_manager->get_active_by_category('container');
780
+ foreach($active_managers as $module_id => $module_data){
781
+ $manager = $module_manager->get_module($module_id);
782
+ if ( $manager ){
783
+ $manager->resynch($forced);
784
+ }
785
+ }
786
+ }
787
+
788
+ /**
789
+ * Mark as unparsed all containers that match one of the the specified formats or
790
+ * container types and that were last parsed after a specific timestamp.
791
+ *
792
+ * Used by newly activated parsers to force the containers they're interested in
793
+ * to resynchronize and thus let the parser process them.
794
+ *
795
+ * @param array $formats Associative array of timestamps, indexed by format IDs.
796
+ * @param array $container_types Associative array of timestamps, indexed by container types.
797
+ * @return bool
798
+ */
799
+ static function mark_as_unsynched_where($formats, $container_types){
800
+ global $wpdb; /* @var wpdb $wpdb */
801
+ global $blclog;
802
+
803
+ //Find containers that match any of the specified formats and add them to
804
+ //the list of container types that need to be marked as unsynched.
805
+ $module_manager = blcModuleManager::getInstance();
806
+ $containers = $module_manager->get_active_by_category('container');
807
+
808
+ foreach($containers as $module_id => $module_data){
809
+ if ( $container_manager = $module_manager->get_module($module_id) ){
810
+ $fields = $container_manager->get_parseable_fields();
811
+ $container_type = $container_manager->container_type;
812
+ foreach($formats as $format => $timestamp){
813
+ if ( in_array($format, $fields) ){
814
+ //Choose the earliest timestamp
815
+ if ( isset($container_types[$container_type]) ){
816
+ $container_types[$container_type] = min($timestamp, $container_types[$container_type]);
817
+ } else {
818
+ $container_types[$container_type] = $timestamp;
819
+ }
820
+ }
821
+ }
822
+ };
823
+ }
824
+
825
+ if ( empty($container_types) ){
826
+ return true;
827
+ }
828
+
829
+ //Build the query to update all synch. records that match one of the specified
830
+ //container types and have been parsed after the specified time.
831
+ $q = "UPDATE {$wpdb->prefix}blc_synch SET synched = 0 WHERE ";
832
+
833
+ $pieces = array();
834
+ foreach($container_types as $container_type => $timestamp){
835
+ $pieces[] = $wpdb->prepare(
836
+ '(container_type = %s AND last_synch >= %s)',
837
+ $container_type,
838
+ date('Y-m-d H:i:s', $timestamp)
839
+ );
840
+ }
841
+
842
+ $q .= implode(' OR ', $pieces);
843
+ $blclog->log('...... Executing query: ' . $q);
844
+
845
+ $start_time = microtime(true);
846
+ $rez = ($wpdb->query($q) !== false);
847
+ $blclog->log(sprintf('...... %d rows affected, %.3f seconds', $wpdb->rows_affected, microtime(true) - $start_time));
848
+
849
+ blc_got_unsynched_items();
850
+
851
+ return $rez;
852
+ }
853
+
854
+ /**
855
+ * Remove synch. records that reference container types not currently loaded
856
+ *
857
+ * @return bool
858
+ */
859
+ static function cleanup_containers(){
860
+ global $wpdb; /* @var wpdb $wpdb */
861
+ global $blclog;
862
+
863
+ $module_manager = blcModuleManager::getInstance();
864
+
865
+ $start = microtime(true);
866
+ $active_containers = $module_manager->get_escaped_ids('container');
867
+ $q = "DELETE synch.*
868
+ FROM {$wpdb->prefix}blc_synch AS synch
869
+ WHERE
870
+ synch.container_type NOT IN ({$active_containers})";
871
+ $rez = $wpdb->query($q);
872
+ $elapsed = microtime(true) - $start;
873
+ $blclog->log(sprintf('... %d synch records deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
874
+
875
+ return $rez !== false;
876
+ }
877
+
878
+ /**
879
+ * Get the message to display after $n containers of a specific type have been deleted.
880
+ *
881
+ * @param string $container_type
882
+ * @param int $n Number of deleted containers.
883
+ * @return string A delete confirmation message, e.g. "5 posts were moved to trash"
884
+ */
885
+ static function ui_bulk_delete_message($container_type, $n){
886
+ $manager = blcContainerHelper::get_manager($container_type);
887
+ if ( is_null($manager) ){
888
+ return sprintf(__("Container type '%s' not recognized", 'broken-link-checker'), $container_type);
889
+ } else {
890
+ return $manager->ui_bulk_delete_message($n);
891
+ }
892
+ }
893
+
894
+ /**
895
+ * Get the message to display after $n containers of a specific type have been moved to the trash.
896
+ *
897
+ * @see blcContainerHelper::ui_bulk_delete_message()
898
+ *
899
+ * @param string $container_type
900
+ * @param int $n
901
+ * @return string
902
+ */
903
+ static function ui_bulk_trash_message($container_type, $n){
904
+ $manager = blcContainerHelper::get_manager($container_type);
905
+ if ( is_null($manager) ){
906
+ return sprintf(__("Container type '%s' not recognized", 'broken-link-checker'), $container_type);
907
+ } else {
908
+ return $manager->ui_bulk_trash_message($n);
909
+ }
910
+ }
911
+ }
includes/extra-strings.php CHANGED
File without changes
includes/instances.php CHANGED
@@ -1,639 +1,639 @@
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 blcContainer */
29
- var $_container = null;
30
- var $_parser = null;
31
- /** @var blcLink|null */
32
- var $_link = null;
33
-
34
- /**
35
- * blcLinkInstance::__construct()
36
- * Class constructor
37
- *
38
- * @param int|array $arg Either the instance ID or an associate array representing the instance's DB record. Should be NULL for new instances.
39
- */
40
- function __construct($arg = null){
41
- global $wpdb; /** @var wpdb $wpdb */
42
-
43
- if (is_int($arg)){
44
- //Load an instance with ID = $arg from the DB.
45
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d 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 {
51
- //Link instance not found. The object is invalid.
52
- }
53
-
54
- } else if (is_array($arg)){
55
- $this->set_values($arg);
56
-
57
- //Is this a new instance?
58
- $this->is_new = empty($this->instance_id);
59
-
60
- } else {
61
- $this->is_new = true;
62
- }
63
- }
64
-
65
- /**
66
- * blcLinkInstance::blcLinkInstance()
67
- * Old-style constructor for PHP 4. Do not use.
68
- *
69
- * @param mixed $arg
70
- * @return void
71
- */
72
- function blcLinkInstance($arg = null){
73
- $this->__construct($arg);
74
- }
75
-
76
- /**
77
- * blcLinkInstance::set_values()
78
- * Set property values to the ones provided in an array (doesn't sanitize).
79
- *
80
- * @param array $arr An associative array
81
- * @return void
82
- */
83
- function set_values($arr){
84
- foreach( $arr as $key => $value ){
85
- $this->$key = $value;
86
- }
87
- }
88
-
89
- /**
90
- * Replace this instance's URL with a new one.
91
- * Warning : this shouldn't be called directly. Use blcLink->edit() instead.
92
- *
93
- * @param string $new_url
94
- * @param string $old_url
95
- * @param string $new_text
96
- * @return bool|WP_Error True on success, or an instance of WP_Error if something went wrong.
97
- */
98
- function edit($new_url, $old_url = '', $new_text = null){
99
-
100
- //Get the container that contains this link
101
- $container = $this->get_container();
102
- if ( is_null($container) ){
103
- return new WP_Error(
104
- 'container_not_found',
105
- sprintf(__("Container %s[%d] not found", 'broken-link-checker'), $this->container_type, $this->container_id)
106
- );
107
- }
108
-
109
- //Get the parser.
110
- $parser = $this->get_parser();
111
- if ( is_null($parser) ){
112
- return new WP_Error(
113
- 'parser_not_found',
114
- sprintf(__("Parser '%s' not found.", 'broken-link-checker'), $this->parser_type)
115
- );
116
- }
117
-
118
- //If the old URL isn't specified get it from the link record
119
- if ( empty($old_url) ){
120
- $old_url = $this->get_url();
121
- }
122
-
123
- //Attempt to modify the link(s)
124
- $result = $container->edit_link($this->container_field, $parser, $new_url, $old_url, $this->raw_url, $new_text);
125
- if ( is_string($result) ){
126
- //If the modification was successful, the container will return
127
- //the new raw_url for the instance. Save the URL and return true,
128
- //indicating success.
129
- $this->raw_url = $result;
130
- return true;
131
- } elseif ( is_array($result) ){
132
- //More advanced containers/parsers may return an array of values to
133
- //modify several fields at once.
134
- $allowed_fields = array('raw_url', 'link_text', 'link_context');
135
- foreach($result as $key => $value){
136
- if ( in_array($key, $allowed_fields) ){
137
- $this->$key = $value;
138
- }
139
- }
140
- return true;
141
- } else {
142
- //Otherwise, it will return an error object. In this case we'll
143
- //just pass it back to the caller and let them sort it out.
144
- return $result;
145
- }
146
- }
147
-
148
- /**
149
- * blcLinkInstance::unlink()
150
- * Remove this instance from the post/blogroll/etc. Also deletes the appropriate DB record(s).
151
- *
152
- * @return bool|WP_Error
153
- */
154
- function unlink( $url = null ) {
155
-
156
- //Get the container that contains this link
157
- $container = $this->get_container();
158
- if ( is_null($container) ){
159
- return new WP_Error(
160
- 'container_not_found',
161
- sprintf(__("Container %s[%d] not found", 'broken-link-checker'), $this->container_type, $this->container_id)
162
- );
163
- }
164
-
165
- //Get the parser.
166
- $parser = $this->get_parser();
167
- if ( is_null($parser) ){
168
- return new WP_Error(
169
- 'parser_not_found',
170
- sprintf(__("Parser '%s' not found.", 'broken-link-checker'), $this->parser_type)
171
- );
172
- }
173
-
174
- //If the old URL isn't specified get it from the link record
175
- if ( empty($url) ){
176
- $url = $this->get_url();
177
- }
178
-
179
- //Attempt to remove the link(s)
180
- return $container->unlink($this->container_field, $parser, $url, $this->raw_url);
181
- }
182
-
183
- /**
184
- * Remove the link instance record from database. Doesn't affect the thing that contains the link.
185
- *
186
- * @return mixed 1 on success, 0 if the instance wasn't found, false on error
187
- */
188
- function forget(){
189
- global $wpdb; /** @var wpdb $wpdb */
190
-
191
- if ( !empty($this->instance_id) ) {
192
- $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d", $this->instance_id) );
193
- return $rez;
194
- } else {
195
- return false;
196
- }
197
- }
198
-
199
- /**
200
- * Store the link instance in the database.
201
- * Saving the instance will also implicitly save the link record associated with it, if it wasn't already saved.
202
- *
203
- * @return bool TRUE on success, FALSE on error
204
- */
205
- function save(){
206
- global $wpdb; /** @var wpdb $wpdb */
207
-
208
- //Refresh the locally cached link & container properties, in case
209
- //the objects have changed since they were set.
210
-
211
- if ( !is_null($this->_link) ){
212
-
213
- //If we have a link object assigned, but it's new, it won't have a DB ID yet.
214
- //We need to save the link to get the ID and be able to maintain the link <-> instance
215
- //association.
216
- if ( $this->_link->is_new ){
217
- $rez = $this->_link->save();
218
- if ( !$rez ){
219
- return false;
220
- }
221
- }
222
-
223
- $this->link_id = $this->_link->link_id;
224
- }
225
-
226
- if ( !is_null($this->_container) ){
227
- $this->container_type = $this->_container->container_type;
228
- $this->container_id = $this->_container->container_id;
229
- }
230
-
231
- //If the link is new, insert a new row into the DB. Otherwise update the existing row.
232
- if ( $this->is_new ){
233
-
234
- $q = "
235
- INSERT INTO {$wpdb->prefix}blc_instances
236
- ( link_id, container_type, container_id, container_field, parser_type, link_text, link_context, raw_url )
237
- VALUES( %d, %s, %d, %s, %s, %s, %s, %s )";
238
-
239
- $q = $wpdb->prepare(
240
- $q,
241
-
242
- $this->link_id,
243
- $this->container_type,
244
- $this->container_id,
245
- $this->container_field,
246
- $this->parser_type,
247
- $this->link_text,
248
- $this->link_context,
249
- $this->raw_url
250
- );
251
-
252
- $rez = $wpdb->query($q) !== false;
253
-
254
- if ($rez){
255
- $this->instance_id = $wpdb->insert_id;
256
- //If the instance was successfully saved then it's no longer "new".
257
- $this->is_new = !$rez;
258
- }
259
-
260
- return $rez;
261
-
262
- } else {
263
-
264
- $q = "UPDATE {$wpdb->prefix}blc_instances
265
-
266
- SET
267
- link_id = %d,
268
- container_type = %s,
269
- container_id = %d,
270
- container_field = %s,
271
- parser_type = %s,
272
- link_text = %s,
273
- link_context = %s,
274
- raw_url = %s
275
-
276
- WHERE instance_id = %d";
277
-
278
- $q = $wpdb->prepare(
279
- $q,
280
-
281
- $this->link_id,
282
- $this->container_type,
283
- $this->container_id,
284
- $this->container_field,
285
- $this->parser_type,
286
- $this->link_text,
287
- $this->link_context,
288
- $this->raw_url,
289
-
290
- $this->instance_id
291
- );
292
-
293
- $rez = $wpdb->query($q) !== false;
294
-
295
- if ($rez){
296
- //FB::info($this, "Instance updated");
297
- } else {
298
- //FB::error("DB error while updating instance {$this->instance_id} : {$wpdb->last_error}");
299
- }
300
-
301
- return $rez;
302
-
303
- }
304
- }
305
-
306
- /**
307
- * Get the URL associated with this instance.
308
- *
309
- * @return string The associated URL, or an empty string if the instance is currently not assigned to any link.
310
- */
311
- function get_url(){
312
- $link = $this->get_link();
313
-
314
- if ( !is_null($link) ){
315
- return $link->url;
316
- } else {
317
- return '';
318
- }
319
- }
320
-
321
- /**
322
- * Get the container object associated with this link instance
323
- *
324
- * @return blcContainer|null
325
- */
326
- function get_container(){
327
- if( is_null($this->_container) ){
328
- $this->_container = blcContainerHelper::get_container( array($this->container_type, $this->container_id) );
329
- }
330
-
331
- return $this->_container;
332
- }
333
-
334
- /**
335
- * Set a new container for the link instance.
336
- *
337
- * @param blcContainer $new_container
338
- * @param string $field
339
- * @return void
340
- */
341
- function set_container(&$new_container, $field = ''){
342
- $this->_container = &$new_container;
343
-
344
- $this->container_field = $field;
345
-
346
- if( !is_null($new_container) ){
347
- $this->container_type = $new_container->container_type;
348
- $this->container_id = $new_container->container_id;
349
- } else {
350
- $this->container_type = '';
351
- $this->container_id = 0;
352
- }
353
- }
354
-
355
- /**
356
- * Get the parser associated with this link instance.
357
- *
358
- * @return blcParser|null
359
- */
360
- function get_parser(){
361
- if ( is_null($this->_parser) ){
362
- $this->_parser = blcParserHelper::get_parser($this->parser_type);
363
- }
364
-
365
- return $this->_parser;
366
- }
367
-
368
- /**
369
- * Set a new parser fo this link instance.
370
- *
371
- * @param blcParser|null $new_parser
372
- * @return void
373
- */
374
- function set_parser(&$new_parser){
375
- $this->_parser = &$new_parser;
376
-
377
- if ( is_null($new_parser) ){
378
- $this->parser_type = '';
379
- } else {
380
- $this->parser_type = $new_parser->parser_type;
381
- }
382
- }
383
-
384
- /**
385
- * Get the link object associated with this link intance.
386
- *
387
- * @return blcLink|null
388
- */
389
- function get_link(){
390
- if ( !is_null($this->_link) ){
391
- return $this->_link;
392
- }
393
-
394
- if ( empty($this->link_id) ) {
395
- return null;
396
- }
397
-
398
- $this->_link = new blcLink($this->link_id);
399
- return $this->_link;
400
- }
401
-
402
- /**
403
- * Set the link associated with this link instance.
404
- *
405
- * @param blcLink $new_link
406
- * @return void
407
- */
408
- function set_link($new_link){
409
- $this->_link = $new_link;
410
-
411
- if ( is_null($new_link) ){
412
- $this->link_id = 0;
413
- } else {
414
- $this->link_id = $new_link->link_id;
415
- }
416
- }
417
-
418
- /**
419
- * Get the link text for printing in the "Broken Links" table.
420
- *
421
- * @param string $context How to filter the link text. Optional, defaults to 'display'.
422
- * @return string HTML
423
- */
424
- function ui_get_link_text($context = 'display'){
425
- $parser = $this->get_parser();
426
-
427
- if ( !is_null($parser) ){
428
- $text = $parser->ui_get_link_text($this, $context);
429
- } else {
430
- $text = strip_tags($this->link_text);
431
- }
432
-
433
- if ( empty($text) ){
434
- $text = "<em>(None)</em>";
435
- }
436
-
437
- return $text;
438
- }
439
-
440
- /**
441
- * Get action links that should be displayed in the "Source" column of the "Broken Links" table.
442
- *
443
- * @return array An array of HTML links.
444
- */
445
- function ui_get_action_links(){
446
- //The container is responsible for generating the links.
447
- $container = $this->get_container();
448
- if ( !is_null($container) ){
449
- return $container->ui_get_action_links($this->container_field);
450
- } else {
451
- //No valid container = no links.
452
- return array();
453
- }
454
- }
455
-
456
- /**
457
- * Get the HTML describing the "source" of the instance. For example, for links found in posts,
458
- * this could be the post title.
459
- *
460
- * @param string $context How to filter the output. Optional, defaults to 'display'.
461
- * @return string HTML
462
- */
463
- function ui_get_source($context = 'display'){
464
- //The container is also responsible for generating the "Source" column HTML.
465
- $container = $this->get_container();
466
- if ( !is_null($container) ){
467
- return $container->ui_get_source($this->container_field, $context);
468
- } else {
469
- //No valid container = generate some bare-bones debug output.
470
- return sprintf('%s[%d] : %s', $this->container_type, $this->container_id, $this->container_field);
471
- }
472
- }
473
-
474
- /**
475
- * Check if the link text associated with this instance can be edited.
476
- *
477
- * @return bool
478
- */
479
- public function is_link_text_editable() {
480
- $parser = $this->get_parser();
481
- if ( $parser === null ) {
482
- return false;
483
- }
484
- return $parser->is_link_text_editable();
485
- }
486
-
487
- /**
488
- * Check if the URL of this instance can be edited.
489
- *
490
- * @return bool
491
- */
492
- public function is_url_editable() {
493
- $parser = $this->get_parser();
494
- if ( $parser === null ) {
495
- return false;
496
- }
497
- return $parser->is_url_editable();
498
- }
499
-
500
- }
501
-
502
- /**
503
- * Get all link instances associated with one or more links.
504
- *
505
- * @param array $link_ids Array of link IDs.
506
- * @param string $purpose An optional code indicating how the instances will be used. Available predefined constants : BLC_FOR_DISPLAY, BLC_FOR_EDITING
507
- * @param bool $load_containers Preload containers regardless of purpose. Defaults to false.
508
- * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose. Defaults to false.
509
- * @param bool $include_invalid Include instances that refer to not-loaded containers or parsers. Defaults to false.
510
- * @return blcLinkInstance[] An array indexed by link ID. Each item of the array will be an array of blcLinkInstance objects.
511
- */
512
- function blc_get_instances( $link_ids, $purpose = '', $load_containers = false, $load_wrapped_objects = false, $include_invalid = false ){
513
- global $wpdb; /** @var wpdb $wpdb */
514
-
515
- if ( empty($link_ids) ){
516
- return array();
517
- }
518
-
519
- $link_ids_in = implode(', ', $link_ids);
520
-
521
- $q = "SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id IN ($link_ids_in)";
522
-
523
- //Skip instances that reference containers or parsers that aren't currently loaded
524
- if ( !$include_invalid ){
525
- $manager = blcModuleManager::getInstance();
526
- $active_containers = $manager->get_escaped_ids('container');
527
- $active_parsers = $manager->get_escaped_ids('parser');
528
-
529
- $q .= " AND container_type IN ({$active_containers}) ";
530
- $q .= " AND parser_type IN ({$active_parsers}) ";
531
- }
532
-
533
- $results = $wpdb->get_results($q, ARRAY_A);
534
-
535
- if ( empty($results) ) {
536
- return array();
537
- }
538
-
539
- //Also retrieve the containers, if it could be useful.
540
- $load_containers = $load_containers || in_array( $purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING) );
541
- if ( $load_containers ){
542
- //Collect a list of (container_type, container_id) pairs
543
- $container_ids = array();
544
-
545
- foreach($results as $result){
546
- array_push(
547
- $container_ids,
548
- array( $result['container_type'], intval($result['container_id']) )
549
- );
550
- }
551
- $containers = blcContainerHelper::get_containers($container_ids, $purpose, '', $load_wrapped_objects);
552
- }
553
-
554
- //Create an object for each instance and group them by link ID
555
- $instances = array();
556
- foreach ($results as $result){
557
- $instance = new blcLinkInstance($result);
558
-
559
- //Assign a container to the link instance, if available
560
- if( $load_containers && !empty($containers) ){
561
- $key = $instance->container_type . '|' . $instance->container_id;
562
- if( isset($containers[$key]) ){
563
- $instance->_container = $containers[$key];
564
- }
565
- }
566
-
567
- if ( isset($instances[$instance->link_id]) ){
568
- array_push( $instances[$instance->link_id], $instance );
569
- } else {
570
- $instances[$instance->link_id] = array($instance);
571
- }
572
- }
573
-
574
- return $instances;
575
- }
576
-
577
- /**
578
- * Get the number of instances that reference only currently loaded containers and parsers.
579
- *
580
- * @return int
581
- */
582
- function blc_get_usable_instance_count(){
583
- global $wpdb; /** @var wpdb $wpdb */
584
-
585
- $q = "SELECT COUNT(instance_id) FROM {$wpdb->prefix}blc_instances WHERE 1";
586
-
587
- //Skip instances that reference containers or parsers that aren't currently loaded
588
- $manager = blcModuleManager::getInstance();
589
- $active_containers = $manager->get_escaped_ids('container');
590
- $active_parsers = $manager->get_escaped_ids('parser');
591
-
592
- $q .= " AND container_type IN ({$active_containers}) ";
593
- $q .= " AND parser_type IN ({$active_parsers}) ";
594
-
595
- return $wpdb->get_var($q);
596
- }
597
-
598
- /**
599
- * Remove instances that reference invalid containers or containers/parsers that are not currently loaded
600
- *
601
- * @return bool
602
- */
603
- function blc_cleanup_instances(){
604
- global $wpdb; /** @var wpdb $wpdb */
605
- global $blclog;
606
-
607
- //Delete all instances that reference non-existent containers
608
- $start = microtime(true);
609
- $q = "DELETE instances.*
610
- FROM
611
- {$wpdb->prefix}blc_instances AS instances LEFT JOIN {$wpdb->prefix}blc_synch AS synch
612
- ON instances.container_type = synch.container_type AND instances.container_id = synch.container_id
613
- WHERE
614
- synch.container_id IS NULL";
615
- $rez = $wpdb->query($q);
616
- $elapsed = microtime(true) - $start;
617
- $blclog->log(sprintf('... %d instances deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
618
-
619
- //Delete instances that reference containers and parsers that are no longer active
620
- $start = microtime(true);
621
- $manager = blcModuleManager::getInstance();
622
- $active_containers = $manager->get_escaped_ids('container');
623
- $active_parsers = $manager->get_escaped_ids('parser');
624
-
625
- $q = "DELETE instances.*
626
- FROM {$wpdb->prefix}blc_instances AS instances
627
- WHERE
628
- instances.container_type NOT IN ({$active_containers}) OR
629
- instances.parser_type NOT IN ({$active_parsers})";
630
- $rez2 = $wpdb->query($q);
631
- $elapsed = microtime(true) - $start;
632
- $blclog->log(sprintf('... %d more instances deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
633
-
634
- return ($rez !== false) && ($rez2 !== false);
635
- }
636
-
637
-
638
- }//class_exists
639
-
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 blcContainer */
29
+ var $_container = null;
30
+ var $_parser = null;
31
+ /** @var blcLink|null */
32
+ var $_link = null;
33
+
34
+ /**
35
+ * blcLinkInstance::__construct()
36
+ * Class constructor
37
+ *
38
+ * @param int|array $arg Either the instance ID or an associate array representing the instance's DB record. Should be NULL for new instances.
39
+ */
40
+ function __construct($arg = null){
41
+ global $wpdb; /** @var wpdb $wpdb */
42
+
43
+ if (is_int($arg)){
44
+ //Load an instance with ID = $arg from the DB.
45
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d 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 {
51
+ //Link instance not found. The object is invalid.
52
+ }
53
+
54
+ } else if (is_array($arg)){
55
+ $this->set_values($arg);
56
+
57
+ //Is this a new instance?
58
+ $this->is_new = empty($this->instance_id);
59
+
60
+ } else {
61
+ $this->is_new = true;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * blcLinkInstance::blcLinkInstance()
67
+ * Old-style constructor for PHP 4. Do not use.
68
+ *
69
+ * @param mixed $arg
70
+ * @return void
71
+ */
72
+ function blcLinkInstance($arg = null){
73
+ $this->__construct($arg);
74
+ }
75
+
76
+ /**
77
+ * blcLinkInstance::set_values()
78
+ * Set property values to the ones provided in an array (doesn't sanitize).
79
+ *
80
+ * @param array $arr An associative array
81
+ * @return void
82
+ */
83
+ function set_values($arr){
84
+ foreach( $arr as $key => $value ){
85
+ $this->$key = $value;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Replace this instance's URL with a new one.
91
+ * Warning : this shouldn't be called directly. Use blcLink->edit() instead.
92
+ *
93
+ * @param string $new_url
94
+ * @param string $old_url
95
+ * @param string $new_text
96
+ * @return bool|WP_Error True on success, or an instance of WP_Error if something went wrong.
97
+ */
98
+ function edit($new_url, $old_url = '', $new_text = null){
99
+
100
+ //Get the container that contains this link
101
+ $container = $this->get_container();
102
+ if ( is_null($container) ){
103
+ return new WP_Error(
104
+ 'container_not_found',
105
+ sprintf(__("Container %s[%d] not found", 'broken-link-checker'), $this->container_type, $this->container_id)
106
+ );
107
+ }
108
+
109
+ //Get the parser.
110
+ $parser = $this->get_parser();
111
+ if ( is_null($parser) ){
112
+ return new WP_Error(
113
+ 'parser_not_found',
114
+ sprintf(__("Parser '%s' not found.", 'broken-link-checker'), $this->parser_type)
115
+ );
116
+ }
117
+
118
+ //If the old URL isn't specified get it from the link record
119
+ if ( empty($old_url) ){
120
+ $old_url = $this->get_url();
121
+ }
122
+
123
+ //Attempt to modify the link(s)
124
+ $result = $container->edit_link($this->container_field, $parser, $new_url, $old_url, $this->raw_url, $new_text);
125
+ if ( is_string($result) ){
126
+ //If the modification was successful, the container will return
127
+ //the new raw_url for the instance. Save the URL and return true,
128
+ //indicating success.
129
+ $this->raw_url = $result;
130
+ return true;
131
+ } elseif ( is_array($result) ){
132
+ //More advanced containers/parsers may return an array of values to
133
+ //modify several fields at once.
134
+ $allowed_fields = array('raw_url', 'link_text', 'link_context');
135
+ foreach($result as $key => $value){
136
+ if ( in_array($key, $allowed_fields) ){
137
+ $this->$key = $value;
138
+ }
139
+ }
140
+ return true;
141
+ } else {
142
+ //Otherwise, it will return an error object. In this case we'll
143
+ //just pass it back to the caller and let them sort it out.
144
+ return $result;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * blcLinkInstance::unlink()
150
+ * Remove this instance from the post/blogroll/etc. Also deletes the appropriate DB record(s).
151
+ *
152
+ * @return bool|WP_Error
153
+ */
154
+ function unlink( $url = null ) {
155
+
156
+ //Get the container that contains this link
157
+ $container = $this->get_container();
158
+ if ( is_null($container) ){
159
+ return new WP_Error(
160
+ 'container_not_found',
161
+ sprintf(__("Container %s[%d] not found", 'broken-link-checker'), $this->container_type, $this->container_id)
162
+ );
163
+ }
164
+
165
+ //Get the parser.
166
+ $parser = $this->get_parser();
167
+ if ( is_null($parser) ){
168
+ return new WP_Error(
169
+ 'parser_not_found',
170
+ sprintf(__("Parser '%s' not found.", 'broken-link-checker'), $this->parser_type)
171
+ );
172
+ }
173
+
174
+ //If the old URL isn't specified get it from the link record
175
+ if ( empty($url) ){
176
+ $url = $this->get_url();
177
+ }
178
+
179
+ //Attempt to remove the link(s)
180
+ return $container->unlink($this->container_field, $parser, $url, $this->raw_url);
181
+ }
182
+
183
+ /**
184
+ * Remove the link instance record from database. Doesn't affect the thing that contains the link.
185
+ *
186
+ * @return mixed 1 on success, 0 if the instance wasn't found, false on error
187
+ */
188
+ function forget(){
189
+ global $wpdb; /** @var wpdb $wpdb */
190
+
191
+ if ( !empty($this->instance_id) ) {
192
+ $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE instance_id=%d", $this->instance_id) );
193
+ return $rez;
194
+ } else {
195
+ return false;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Store the link instance in the database.
201
+ * Saving the instance will also implicitly save the link record associated with it, if it wasn't already saved.
202
+ *
203
+ * @return bool TRUE on success, FALSE on error
204
+ */
205
+ function save(){
206
+ global $wpdb; /** @var wpdb $wpdb */
207
+
208
+ //Refresh the locally cached link & container properties, in case
209
+ //the objects have changed since they were set.
210
+
211
+ if ( !is_null($this->_link) ){
212
+
213
+ //If we have a link object assigned, but it's new, it won't have a DB ID yet.
214
+ //We need to save the link to get the ID and be able to maintain the link <-> instance
215
+ //association.
216
+ if ( $this->_link->is_new ){
217
+ $rez = $this->_link->save();
218
+ if ( !$rez ){
219
+ return false;
220
+ }
221
+ }
222
+
223
+ $this->link_id = $this->_link->link_id;
224
+ }
225
+
226
+ if ( !is_null($this->_container) ){
227
+ $this->container_type = $this->_container->container_type;
228
+ $this->container_id = $this->_container->container_id;
229
+ }
230
+
231
+ //If the link is new, insert a new row into the DB. Otherwise update the existing row.
232
+ if ( $this->is_new ){
233
+
234
+ $q = "
235
+ INSERT INTO {$wpdb->prefix}blc_instances
236
+ ( link_id, container_type, container_id, container_field, parser_type, link_text, link_context, raw_url )
237
+ VALUES( %d, %s, %d, %s, %s, %s, %s, %s )";
238
+
239
+ $q = $wpdb->prepare(
240
+ $q,
241
+
242
+ $this->link_id,
243
+ $this->container_type,
244
+ $this->container_id,
245
+ $this->container_field,
246
+ $this->parser_type,
247
+ $this->link_text,
248
+ $this->link_context,
249
+ $this->raw_url
250
+ );
251
+
252
+ $rez = $wpdb->query($q) !== false;
253
+
254
+ if ($rez){
255
+ $this->instance_id = $wpdb->insert_id;
256
+ //If the instance was successfully saved then it's no longer "new".
257
+ $this->is_new = !$rez;
258
+ }
259
+
260
+ return $rez;
261
+
262
+ } else {
263
+
264
+ $q = "UPDATE {$wpdb->prefix}blc_instances
265
+
266
+ SET
267
+ link_id = %d,
268
+ container_type = %s,
269
+ container_id = %d,
270
+ container_field = %s,
271
+ parser_type = %s,
272
+ link_text = %s,
273
+ link_context = %s,
274
+ raw_url = %s
275
+
276
+ WHERE instance_id = %d";
277
+
278
+ $q = $wpdb->prepare(
279
+ $q,
280
+
281
+ $this->link_id,
282
+ $this->container_type,
283
+ $this->container_id,
284
+ $this->container_field,
285
+ $this->parser_type,
286
+ $this->link_text,
287
+ $this->link_context,
288
+ $this->raw_url,
289
+
290
+ $this->instance_id
291
+ );
292
+
293
+ $rez = $wpdb->query($q) !== false;
294
+
295
+ if ($rez){
296
+ //FB::info($this, "Instance updated");
297
+ } else {
298
+ //FB::error("DB error while updating instance {$this->instance_id} : {$wpdb->last_error}");
299
+ }
300
+
301
+ return $rez;
302
+
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Get the URL associated with this instance.
308
+ *
309
+ * @return string The associated URL, or an empty string if the instance is currently not assigned to any link.
310
+ */
311
+ function get_url(){
312
+ $link = $this->get_link();
313
+
314
+ if ( !is_null($link) ){
315
+ return $link->url;
316
+ } else {
317
+ return '';
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Get the container object associated with this link instance
323
+ *
324
+ * @return blcContainer|null
325
+ */
326
+ function get_container(){
327
+ if( is_null($this->_container) ){
328
+ $this->_container = blcContainerHelper::get_container( array($this->container_type, $this->container_id) );
329
+ }
330
+
331
+ return $this->_container;
332
+ }
333
+
334
+ /**
335
+ * Set a new container for the link instance.
336
+ *
337
+ * @param blcContainer $new_container
338
+ * @param string $field
339
+ * @return void
340
+ */
341
+ function set_container(&$new_container, $field = ''){
342
+ $this->_container = &$new_container;
343
+
344
+ $this->container_field = $field;
345
+
346
+ if( !is_null($new_container) ){
347
+ $this->container_type = $new_container->container_type;
348
+ $this->container_id = $new_container->container_id;
349
+ } else {
350
+ $this->container_type = '';
351
+ $this->container_id = 0;
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Get the parser associated with this link instance.
357
+ *
358
+ * @return blcParser|null
359
+ */
360
+ function get_parser(){
361
+ if ( is_null($this->_parser) ){
362
+ $this->_parser = blcParserHelper::get_parser($this->parser_type);
363
+ }
364
+
365
+ return $this->_parser;
366
+ }
367
+
368
+ /**
369
+ * Set a new parser fo this link instance.
370
+ *
371
+ * @param blcParser|null $new_parser
372
+ * @return void
373
+ */
374
+ function set_parser(&$new_parser){
375
+ $this->_parser = &$new_parser;
376
+
377
+ if ( is_null($new_parser) ){
378
+ $this->parser_type = '';
379
+ } else {
380
+ $this->parser_type = $new_parser->parser_type;
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Get the link object associated with this link intance.
386
+ *
387
+ * @return blcLink|null
388
+ */
389
+ function get_link(){
390
+ if ( !is_null($this->_link) ){
391
+ return $this->_link;
392
+ }
393
+
394
+ if ( empty($this->link_id) ) {
395
+ return null;
396
+ }
397
+
398
+ $this->_link = new blcLink($this->link_id);
399
+ return $this->_link;
400
+ }
401
+
402
+ /**
403
+ * Set the link associated with this link instance.
404
+ *
405
+ * @param blcLink $new_link
406
+ * @return void
407
+ */
408
+ function set_link($new_link){
409
+ $this->_link = $new_link;
410
+
411
+ if ( is_null($new_link) ){
412
+ $this->link_id = 0;
413
+ } else {
414
+ $this->link_id = $new_link->link_id;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Get the link text for printing in the "Broken Links" table.
420
+ *
421
+ * @param string $context How to filter the link text. Optional, defaults to 'display'.
422
+ * @return string HTML
423
+ */
424
+ function ui_get_link_text($context = 'display'){
425
+ $parser = $this->get_parser();
426
+
427
+ if ( !is_null($parser) ){
428
+ $text = $parser->ui_get_link_text($this, $context);
429
+ } else {
430
+ $text = strip_tags($this->link_text);
431
+ }
432
+
433
+ if ( empty($text) ){
434
+ $text = "<em>(None)</em>";
435
+ }
436
+
437
+ return $text;
438
+ }
439
+
440
+ /**
441
+ * Get action links that should be displayed in the "Source" column of the "Broken Links" table.
442
+ *
443
+ * @return array An array of HTML links.
444
+ */
445
+ function ui_get_action_links(){
446
+ //The container is responsible for generating the links.
447
+ $container = $this->get_container();
448
+ if ( !is_null($container) ){
449
+ return $container->ui_get_action_links($this->container_field);
450
+ } else {
451
+ //No valid container = no links.
452
+ return array();
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Get the HTML describing the "source" of the instance. For example, for links found in posts,
458
+ * this could be the post title.
459
+ *
460
+ * @param string $context How to filter the output. Optional, defaults to 'display'.
461
+ * @return string HTML
462
+ */
463
+ function ui_get_source($context = 'display'){
464
+ //The container is also responsible for generating the "Source" column HTML.
465
+ $container = $this->get_container();
466
+ if ( !is_null($container) ){
467
+ return $container->ui_get_source($this->container_field, $context);
468
+ } else {
469
+ //No valid container = generate some bare-bones debug output.
470
+ return sprintf('%s[%d] : %s', $this->container_type, $this->container_id, $this->container_field);
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Check if the link text associated with this instance can be edited.
476
+ *
477
+ * @return bool
478
+ */
479
+ public function is_link_text_editable() {
480
+ $parser = $this->get_parser();
481
+ if ( $parser === null ) {
482
+ return false;
483
+ }
484
+ return $parser->is_link_text_editable();
485
+ }
486
+
487
+ /**
488
+ * Check if the URL of this instance can be edited.
489
+ *
490
+ * @return bool
491
+ */
492
+ public function is_url_editable() {
493
+ $parser = $this->get_parser();
494
+ if ( $parser === null ) {
495
+ return false;
496
+ }
497
+ return $parser->is_url_editable();
498
+ }
499
+
500
+ }
501
+
502
+ /**
503
+ * Get all link instances associated with one or more links.
504
+ *
505
+ * @param array $link_ids Array of link IDs.
506
+ * @param string $purpose An optional code indicating how the instances will be used. Available predefined constants : BLC_FOR_DISPLAY, BLC_FOR_EDITING
507
+ * @param bool $load_containers Preload containers regardless of purpose. Defaults to false.
508
+ * @param bool $load_wrapped_objects Preload wrapped objects regardless of purpose. Defaults to false.
509
+ * @param bool $include_invalid Include instances that refer to not-loaded containers or parsers. Defaults to false.
510
+ * @return blcLinkInstance[] An array indexed by link ID. Each item of the array will be an array of blcLinkInstance objects.
511
+ */
512
+ function blc_get_instances( $link_ids, $purpose = '', $load_containers = false, $load_wrapped_objects = false, $include_invalid = false ){
513
+ global $wpdb; /** @var wpdb $wpdb */
514
+
515
+ if ( empty($link_ids) ){
516
+ return array();
517
+ }
518
+
519
+ $link_ids_in = implode(', ', $link_ids);
520
+
521
+ $q = "SELECT * FROM {$wpdb->prefix}blc_instances WHERE link_id IN ($link_ids_in)";
522
+
523
+ //Skip instances that reference containers or parsers that aren't currently loaded
524
+ if ( !$include_invalid ){
525
+ $manager = blcModuleManager::getInstance();
526
+ $active_containers = $manager->get_escaped_ids('container');
527
+ $active_parsers = $manager->get_escaped_ids('parser');
528
+
529
+ $q .= " AND container_type IN ({$active_containers}) ";
530
+ $q .= " AND parser_type IN ({$active_parsers}) ";
531
+ }
532
+
533
+ $results = $wpdb->get_results($q, ARRAY_A);
534
+
535
+ if ( empty($results) ) {
536
+ return array();
537
+ }
538
+
539
+ //Also retrieve the containers, if it could be useful.
540
+ $load_containers = $load_containers || in_array( $purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING) );
541
+ if ( $load_containers ){
542
+ //Collect a list of (container_type, container_id) pairs
543
+ $container_ids = array();
544
+
545
+ foreach($results as $result){
546
+ array_push(
547
+ $container_ids,
548
+ array( $result['container_type'], intval($result['container_id']) )
549
+ );
550
+ }
551
+ $containers = blcContainerHelper::get_containers($container_ids, $purpose, '', $load_wrapped_objects);
552
+ }
553
+
554
+ //Create an object for each instance and group them by link ID
555
+ $instances = array();
556
+ foreach ($results as $result){
557
+ $instance = new blcLinkInstance($result);
558
+
559
+ //Assign a container to the link instance, if available
560
+ if( $load_containers && !empty($containers) ){
561
+ $key = $instance->container_type . '|' . $instance->container_id;
562
+ if( isset($containers[$key]) ){
563
+ $instance->_container = $containers[$key];
564
+ }
565
+ }
566
+
567
+ if ( isset($instances[$instance->link_id]) ){
568
+ array_push( $instances[$instance->link_id], $instance );
569
+ } else {
570
+ $instances[$instance->link_id] = array($instance);
571
+ }
572
+ }
573
+
574
+ return $instances;
575
+ }
576
+
577
+ /**
578
+ * Get the number of instances that reference only currently loaded containers and parsers.
579
+ *
580
+ * @return int
581
+ */
582
+ function blc_get_usable_instance_count(){
583
+ global $wpdb; /** @var wpdb $wpdb */
584
+
585
+ $q = "SELECT COUNT(instance_id) FROM {$wpdb->prefix}blc_instances WHERE 1";
586
+
587
+ //Skip instances that reference containers or parsers that aren't currently loaded
588
+ $manager = blcModuleManager::getInstance();
589
+ $active_containers = $manager->get_escaped_ids('container');
590
+ $active_parsers = $manager->get_escaped_ids('parser');
591
+
592
+ $q .= " AND container_type IN ({$active_containers}) ";
593
+ $q .= " AND parser_type IN ({$active_parsers}) ";
594
+
595
+ return $wpdb->get_var($q);
596
+ }
597
+
598
+ /**
599
+ * Remove instances that reference invalid containers or containers/parsers that are not currently loaded
600
+ *
601
+ * @return bool
602
+ */
603
+ function blc_cleanup_instances(){
604
+ global $wpdb; /** @var wpdb $wpdb */
605
+ global $blclog;
606
+
607
+ //Delete all instances that reference non-existent containers
608
+ $start = microtime(true);
609
+ $q = "DELETE instances.*
610
+ FROM
611
+ {$wpdb->prefix}blc_instances AS instances LEFT JOIN {$wpdb->prefix}blc_synch AS synch
612
+ ON instances.container_type = synch.container_type AND instances.container_id = synch.container_id
613
+ WHERE
614
+ synch.container_id IS NULL";
615
+ $rez = $wpdb->query($q);
616
+ $elapsed = microtime(true) - $start;
617
+ $blclog->log(sprintf('... %d instances deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
618
+
619
+ //Delete instances that reference containers and parsers that are no longer active
620
+ $start = microtime(true);
621
+ $manager = blcModuleManager::getInstance();
622
+ $active_containers = $manager->get_escaped_ids('container');
623
+ $active_parsers = $manager->get_escaped_ids('parser');
624
+
625
+ $q = "DELETE instances.*
626
+ FROM {$wpdb->prefix}blc_instances AS instances
627
+ WHERE
628
+ instances.container_type NOT IN ({$active_containers}) OR
629
+ instances.parser_type NOT IN ({$active_parsers})";
630
+ $rez2 = $wpdb->query($q);
631
+ $elapsed = microtime(true) - $start;
632
+ $blclog->log(sprintf('... %d more instances deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
633
+
634
+ return ($rez !== false) && ($rez2 !== false);
635
+ }
636
+
637
+
638
+ }//class_exists
639
+
includes/link-query.php CHANGED
@@ -1,866 +1,866 @@
1
- <?php
2
-
3
- /**
4
- * Class for querying, sorting and filtering links.
5
- * Used as a singleton.
6
- *
7
- * @package Broken Link Checker
8
- * @access public
9
- */
10
- class blcLinkQuery {
11
-
12
- var $native_filters;
13
- var $search_filter;
14
- var $custom_filters = array();
15
-
16
- var $valid_url_params = array();
17
-
18
- function __construct(){
19
- //Init. the available native filters.
20
- $this->native_filters = array(
21
- 'all' => array(
22
- 'params' => array(
23
- 'where_expr' => '1',
24
- ),
25
- 'name' => __('All', 'broken-link-checker'),
26
- 'heading' => __('Detected Links', 'broken-link-checker'),
27
- 'heading_zero' => __('No links found (yet)', 'broken-link-checker'),
28
- 'native' => true,
29
- ),
30
-
31
- 'broken' => array(
32
- 'params' => array(
33
- 'where_expr' => '( broken = 1 )',
34
- 's_include_dismissed' => false,
35
- ),
36
- 'name' => __('Broken', 'broken-link-checker'),
37
- 'heading' => __('Broken Links', 'broken-link-checker'),
38
- 'heading_zero' => __('No broken links found', 'broken-link-checker'),
39
- 'native' => true,
40
- ),
41
- 'warnings' => array(
42
- 'params' => array(
43
- 'where_expr' => '( warning = 1 )',
44
- 's_include_dismissed' => false,
45
- ),
46
- 'name' => _x('Warnings', 'filter name', 'broken-link-checker'),
47
- 'heading' => __('Warnings', 'filter heading', 'broken-link-checker'),
48
- 'heading_zero' => __('No warnings found', 'broken-link-checker'),
49
- 'native' => true,
50
- ),
51
- 'redirects' => array(
52
- 'params' => array(
53
- 'where_expr' => '( redirect_count > 0 )',
54
- 's_include_dismissed' => false,
55
- ),
56
- 'name' => __('Redirects', 'broken-link-checker'),
57
- 'heading' => __('Redirected Links', 'broken-link-checker'),
58
- 'heading_zero' => __('No redirects found', 'broken-link-checker'),
59
- 'native' => true,
60
- ),
61
-
62
- 'dismissed' => array(
63
- 'params' => array(
64
- 'where_expr' => '( dismissed = 1 )',
65
- ),
66
- 'name' => __('Dismissed', 'broken-link-checker'),
67
- 'heading' => __('Dismissed Links', 'broken-link-checker'),
68
- 'heading_zero' => __('No dismissed links found', 'broken-link-checker'),
69
- 'native' => true,
70
- ),
71
- );
72
-
73
- //The user can turn off warnings. In that case, all errors will show up in the "broken" filter instead.
74
- $conf = blc_get_configuration();
75
- if ( !$conf->get('warnings_enabled') ) {
76
- unset($this->native_filters['warnings']);
77
- }
78
-
79
- //Create the special "search" filter
80
- $this->search_filter = array(
81
- 'name' => __('Search', 'broken-link-checker'),
82
- 'heading' => __('Search Results', 'broken-link-checker'),
83
- 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
84
- 'params' => array(),
85
- 'use_url_params' => true,
86
- 'hidden' => true,
87
- );
88
-
89
- //These search arguments may be passed via the URL if the filter's 'use_url_params' field is set to True.
90
- //They map to the fields of the search form on the Tools -> Broken Links page. Only these arguments
91
- //can be used in user-defined filters.
92
- $this->valid_url_params = array(
93
- 's_link_text',
94
- 's_link_url',
95
- 's_parser_type',
96
- 's_container_type',
97
- 's_link_type',
98
- 's_http_code',
99
- 's_filter',
100
- );
101
- }
102
-
103
- static function getInstance(){
104
- static $instance = null;
105
- if ( is_null($instance) ){
106
- $instance = new blcLinkQuery;
107
- }
108
- return $instance;
109
- }
110
-
111
- /**
112
- * Load and return the list of user-defined link filters.
113
- *
114
- * @return array An array of custom filter definitions. If there are no custom filters defined returns an empty array.
115
- */
116
- function load_custom_filters(){
117
- global $wpdb; /** @var wpdb $wpdb */
118
-
119
- $filter_data = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}blc_filters ORDER BY name ASC", ARRAY_A);
120
- $filters = array();
121
-
122
- if ( !empty($filter_data) ) {
123
- foreach($filter_data as $data){
124
- wp_parse_str($data['params'], $params);
125
-
126
- $filters[ 'f'.$data['id'] ] = array(
127
- 'name' => $data['name'],
128
- 'params' => $params,
129
- 'heading' => ucwords($data['name']),
130
- 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
131
- 'custom' => true,
132
- );
133
- }
134
- }
135
-
136
- $this->custom_filters = $filters;
137
-
138
- return $filters;
139
- }
140
-
141
- /**
142
- * Add a custom link filter.
143
- *
144
- * @param string $name Filter name.
145
- * @param string|array $params Filter params. Either as a query string, or an array.
146
- * @return string|bool The ID of the newly added filter, or False.
147
- */
148
- function create_custom_filter($name, $params){
149
- global $wpdb; /** @var wpdb $wpdb */
150
-
151
- if ( is_array($params) ){
152
- $params = http_build_query($params, null, '&');
153
- }
154
-
155
- //Save the new filter
156
- $q = $wpdb->prepare(
157
- "INSERT INTO {$wpdb->prefix}blc_filters(name, params) VALUES (%s, %s)",
158
- $name, $params
159
- );
160
-
161
- if ( $wpdb->query($q) !== false ){
162
- $filter_id = 'f'.$wpdb->insert_id;
163
- return $filter_id;
164
- } else {
165
- return false;
166
- }
167
- }
168
-
169
- /**
170
- * Delete a custom filter
171
- *
172
- * @param string $filter_id
173
- * @return bool True on success, False if a database error occured.
174
- */
175
- function delete_custom_filter($filter_id){
176
- global $wpdb; /** @var wpdb $wpdb */
177
-
178
- if ( !isset($filter_id) ) {
179
- $filter_id = $_POST['filter_id'];
180
- }
181
- //Remove the "f" character from the filter ID to get its database key
182
- $filter_id = intval(ltrim($filter_id, 'f'));
183
-
184
- //Try to delete the filter
185
- $q = $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_filters WHERE id = %d", $filter_id);
186
- if ( $wpdb->query($q) !== false ){
187
- return true;
188
- } else {
189
- return false;
190
- }
191
- }
192
-
193
- function get_filters(){
194
- $filters = array_merge($this->native_filters, $this->custom_filters);
195
- $filters['search'] = $this->search_filter;
196
- return $filters;
197
- }
198
-
199
- /**
200
- * Get a link search filter by filter ID.
201
- *
202
- * @param string $filter_id
203
- * @return array|null
204
- */
205
- function get_filter($filter_id){
206
- $filters = $this->get_filters();
207
- if ( isset($filters[$filter_id]) ){
208
- return $filters[$filter_id];
209
- } else {
210
- return null;
211
- }
212
- }
213
-
214
- /**
215
- * Get link search parameters from the specified filter.
216
- *
217
- * @param array $filter
218
- * @return array An array of parameters suitable for use with blcLinkQuery::get_links()
219
- */
220
- function get_search_params( $filter = null ){
221
- //If present, the filter's parameters may be saved either as an array or a string.
222
- $params = array();
223
- if ( !empty($filter) && !empty($filter['params']) ){
224
- $params = $filter['params'];
225
- if ( is_string( $params ) ){
226
- wp_parse_str($params, $params);
227
- }
228
- }
229
-
230
- //Merge in the parameters from the current request, if required
231
- if ( isset($filter['use_url_params']) && $filter['use_url_params'] ){
232
- $params = array_merge($params, $this->get_url_search_params());
233
- }
234
-
235
- return $params;
236
- }
237
-
238
- /**
239
- * Extract search query parameters from the current URL
240
- *
241
- * @return array
242
- */
243
- function get_url_search_params(){
244
- $url_params = array();
245
- foreach ($_GET as $param => $value){
246
- if ( in_array($param, $this->valid_url_params) ){
247
- $url_params[$param] = $value;
248
- }
249
- }
250
- return $url_params;
251
- }
252
-
253
-
254
-
255
- /**
256
- * A helper method for parsing a list of search criteria and generating the parts of the SQL query.
257
- *
258
- * @see blcLinkQuery::get_links()
259
- *
260
- * @param array $params An array of search criteria.
261
- * @return array 'where_exprs' - an array of search expressions, 'join_instances' - whether joining the instance table is required.
262
- */
263
- function compile_search_params($params){
264
- global $wpdb; /** @var wpdb $wpdb */
265
-
266
- //Track whether we'll need to left-join the instance table to run the query.
267
- $join_instances = false;
268
-
269
- //Generate the individual clauses of the WHERE expression and store them in an array.
270
- $pieces = array();
271
-
272
- //Convert parser and container type lists to arrays of valid values
273
- $s_parser_type = array();
274
- if ( !empty($params['s_parser_type']) ){
275
- $s_parser_type = $params['s_parser_type'];
276
- if ( is_string($s_parser_type) ){
277
- $s_parser_type = preg_split('/[,\s]+/', $s_parser_type);
278
- }
279
- }
280
-
281
- $s_container_type = array();
282
- if ( !empty($params['s_container_type']) ){
283
- $s_container_type = $params['s_container_type'];
284
- if ( is_string($s_container_type) ){
285
- $s_container_type = preg_split('/[,\s]+/', $s_container_type);
286
- }
287
- }
288
-
289
- //Don't include links with instances that reference invalid (not currently loaded)
290
- //containers and parsers (unless specifically told to also include invalid links).
291
- if ( empty($params['include_invalid']) ){
292
- $join_instances = true;
293
-
294
- $module_manager = blcModuleManager::getInstance();
295
- $loaded_containers = array_keys($module_manager->get_active_by_category('container'));
296
- $loaded_parsers = array_keys($module_manager->get_active_by_category('parser'));
297
-
298
- if ( empty($s_parser_type) ){
299
- $s_parser_type = $loaded_parsers;
300
- } else {
301
- $s_parser_type = array_intersect($s_parser_type, $loaded_parsers);
302
- }
303
-
304
- if ( empty($s_container_type) ){
305
- $s_container_type = $loaded_containers;
306
- } else {
307
- $s_container_type = array_intersect($s_container_type, $loaded_containers);
308
- }
309
- }
310
-
311
- //Parser type should match the parser_type column in the instance table.
312
- if ( !empty($s_parser_type) ){
313
- $s_parser_type = array_map('trim', array_unique($s_parser_type));
314
- $s_parser_type = array_map('esc_sql', $s_parser_type);
315
-
316
- if ( count($s_parser_type) == 1 ){
317
- $pieces[] = sprintf("instances.parser_type = '%s'", reset($s_parser_type));
318
- } else {
319
- $pieces[] = "instances.parser_type IN ('" . implode("', '", $s_parser_type) . "')";
320
- }
321
-
322
- $join_instances = true;
323
- }
324
-
325
- //Container type should match the container_type column in the instance table.
326
- if ( !empty($s_container_type) ){
327
- //Sanitize for use in SQL
328
- $s_container_type = array_map('trim', array_unique($s_container_type));
329
- $s_container_type = array_map('esc_sql', $s_container_type);
330
-
331
- if ( count($s_container_type) == 1 ){
332
- $pieces[] = sprintf("instances.container_type = '%s'", reset($s_container_type));
333
- } else {
334
- $pieces[] = "instances.container_type IN ('" . implode("', '", $s_container_type) . "')";
335
- }
336
-
337
- $join_instances = true;
338
- }
339
-
340
- //A part of the WHERE expression can be specified explicitly
341
- if ( !empty($params['where_expr']) ){
342
- $pieces[] = $params['where_expr'];
343
- $join_instances = $join_instances || ( stripos($params['where_expr'], 'instances') !== false );
344
- }
345
-
346
- //List of allowed link ids (either an array or comma-separated)
347
- if ( !empty($params['link_ids']) ){
348
- $link_ids = $params['link_ids'];
349
-
350
- if ( is_string($link_ids) ){
351
- $link_ids = preg_split('/[,\s]+/', $link_ids);
352
- }
353
-
354
- //Only accept non-zero integers
355
- $sanitized_link_ids = array();
356
- foreach($link_ids as $id){
357
- $id = intval($id);
358
- if ( $id != 0 ){
359
- $sanitized_link_ids[] = $id;
360
- }
361
- }
362
-
363
- $pieces[] = 'links.link_id IN (' . implode(', ', $sanitized_link_ids) . ')';
364
- }
365
-
366
- //Anchor text - use LIKE search
367
- if ( !empty($params['s_link_text']) ){
368
- $s_link_text = esc_sql($this->esc_like($params['s_link_text']));
369
- $s_link_text = str_replace('*', '%', $s_link_text);
370
-
371
- $pieces[] = '(instances.link_text LIKE "%' . $s_link_text . '%")';
372
- $join_instances = true;
373
- }
374
-
375
- //URL - try to match both the initial URL and the final URL.
376
- //There is limited wildcard support, e.g. "google.*/search" will match both
377
- //"google.com/search" and "google.lv/search"
378
- if ( !empty($params['s_link_url']) ){
379
- $s_link_url = esc_sql($this->esc_like($params['s_link_url']));
380
- $s_link_url = str_replace('*', '%', $s_link_url);
381
-
382
- $pieces[] = '(links.url LIKE "%'. $s_link_url .'%") OR '.
383
- '(links.final_url LIKE "%'. $s_link_url .'%")';
384
- }
385
-
386
- //Container ID should match... you guessed it - container_id
387
- if ( !empty($params['s_container_id']) ){
388
- $s_container_id = intval($params['s_container_id']);
389
- if ( $s_container_id != 0 ){
390
- $pieces[] = "instances.container_id = $s_container_id";
391
- $join_instances = true;
392
- }
393
- }
394
-
395
- //Link type can match either the the parser_type or the container_type.
396
- if ( !empty($params['s_link_type']) ){
397
- $s_link_type = esc_sql($params['s_link_type']);
398
- $pieces[] = "instances.parser_type = '$s_link_type' OR instances.container_type='$s_link_type'";
399
- $join_instances = true;
400
- }
401
-
402
- //HTTP code - the user can provide a list of HTTP response codes and code ranges.
403
- //Example : 201,400-410,500
404
- if ( !empty($params['s_http_code']) ){
405
- //Strip spaces.
406
- $params['s_http_code'] = str_replace(' ', '', $params['s_http_code']);
407
- //Split by comma
408
- $codes = explode(',', $params['s_http_code']);
409
-
410
- $individual_codes = array();
411
- $ranges = array();
412
-
413
- //Try to parse each response code or range. Invalid ones are simply ignored.
414
- foreach($codes as $code){
415
- if ( is_numeric($code) ){
416
- //It's a single number
417
- $individual_codes[] = abs(intval($code));
418
- } elseif ( strpos($code, '-') !== false ) {
419
- //Try to parse it as a range
420
- $range = explode( '-', $code, 2 );
421
- if ( (count($range) == 2) && is_numeric($range[0]) && is_numeric($range[0]) ){
422
- //Make sure the smaller code comes first
423
- $range = array( intval($range[0]), intval($range[1]) );
424
- $ranges[] = array( min($range), max($range) );
425
- }
426
- }
427
- }
428
-
429
- $piece = array();
430
-
431
- //All individual response codes get one "http_code IN (...)" clause
432
- if ( !empty($individual_codes) ){
433
- $piece[] = '(links.http_code IN ('. implode(', ', $individual_codes) .'))';
434
- }
435
-
436
- //Ranges get a "http_code BETWEEN min AND max" clause each
437
- if ( !empty($ranges) ){
438
- $range_strings = array();
439
- foreach($ranges as $range){
440
- $range_strings[] = "(links.http_code BETWEEN $range[0] AND $range[1])";
441
- }
442
- $piece[] = '( ' . implode(' OR ', $range_strings) . ' )';
443
- }
444
-
445
- //Finally, generate a composite WHERE clause for both types of response code queries
446
- if ( !empty($piece) ){
447
- $pieces[] = implode(' OR ', $piece);
448
- }
449
-
450
- }
451
-
452
- //Dismissed links are included by default, but can explicitly included
453
- //or filtered out by passing a special param.
454
- if ( isset($params['s_include_dismissed']) ) {
455
- $s_include_dismissed = !empty($params['s_include_dismissed']);
456
- $pieces['filter_dismissed'] = $s_include_dismissed ? '1' : '(dismissed = 0)';
457
- }
458
-
459
- //Optionally sorting is also possible
460
- $order_exprs = array();
461
- if ( !empty($params['orderby']) ) {
462
- $allowed_columns = array(
463
- 'url' => 'links.url',
464
- 'link_text' => 'instances.link_text',
465
- 'redirect_url' => 'links.final_url',
466
- );
467
- $column = $params['orderby'];
468
-
469
- $direction = !empty($params['order']) ? strtolower($params['order']) : 'asc';
470
- if ( !in_array($direction, array('asc', 'desc')) ) {
471
- $direction = 'asc';
472
- }
473
-
474
- if ( array_key_exists($column, $allowed_columns) ) {
475
- if ( $column === 'redirect_url' ) {
476
- //Sort links that are not redirects last.
477
- $order_exprs[] = '(links.redirect_count > 0) DESC';
478
- }
479
-
480
- $order_exprs[] = $allowed_columns[$column] . ' ' . $direction;
481
- }
482
- }
483
-
484
- //Custom filters can optionally call one of the native filters
485
- //to narrow down the result set.
486
- if ( !empty($params['s_filter']) && isset($this->native_filters[$params['s_filter']]) ){
487
- $the_filter = $this->native_filters[$params['s_filter']];
488
- $extra_criteria = $this->compile_search_params($the_filter['params']);
489
-
490
- $pieces = array_merge($extra_criteria['where_exprs'], $pieces);
491
- $join_instances = $join_instances || $extra_criteria['join_instances'];
492
- }
493
-
494
- return array(
495
- 'where_exprs' => $pieces,
496
- 'join_instances' => $join_instances,
497
- 'order_exprs' => $order_exprs,
498
- );
499
- }
500
-
501
- private function esc_like($input) {
502
- global $wpdb; /** @var wpdb $wpdb */
503
- if ( method_exists($wpdb, 'esc_like') ) {
504
- return $wpdb->esc_like($input);
505
- } else {
506
- return like_escape($input);
507
- }
508
- }
509
-
510
- /**
511
- * blcLinkQuery::get_links()
512
- *
513
- * @see blc_get_links()
514
- *
515
- * @param array $params
516
- * @return array|int
517
- */
518
- function get_links($params = null){
519
- global $wpdb; /** @var wpdb $wpdb */
520
-
521
- if( !is_array($params) ){
522
- $params = array();
523
- }
524
-
525
- $defaults = array(
526
- 'offset' => 0,
527
- 'max_results' => 0,
528
- 'load_instances' => false,
529
- 'load_containers' => false,
530
- 'load_wrapped_objects' => false,
531
- 'count_only' => false,
532
- 'purpose' => '',
533
- 'include_invalid' => false,
534
- 'orderby' => '',
535
- 'order' => '',
536
- );
537
-
538
- $params = array_merge($defaults, $params);
539
-
540
- //Compile the search-related params into search expressions usable in a WHERE clause
541
- $criteria = $this->compile_search_params($params);
542
-
543
- //Build the WHERE clause
544
- if ( !empty($criteria['where_exprs']) ){
545
- $where_expr = "\t( " . implode(" ) AND\n\t( ", $criteria['where_exprs']) . ' ) ';
546
- } else {
547
- $where_expr = '1';
548
- }
549
-
550
- //Join the blc_instances table if it's required to perform the search.
551
- $joins = "";
552
- if ( $criteria['join_instances'] ){
553
- $joins = "JOIN {$wpdb->prefix}blc_instances AS instances ON links.link_id = instances.link_id";
554
- }
555
-
556
- //Optional sorting
557
- if ( !empty($criteria['order_exprs']) ) {
558
- $order_clause = 'ORDER BY ' . implode(', ', $criteria['order_exprs']);
559
- } else {
560
- $order_clause = '';
561
- }
562
-
563
- if ( $params['count_only'] ){
564
- //Only get the number of matching links.
565
- $q = "
566
- SELECT COUNT(*)
567
- FROM (
568
- SELECT 0
569
-
570
- FROM
571
- {$wpdb->prefix}blc_links AS links
572
- $joins
573
-
574
- WHERE
575
- $where_expr
576
-
577
- GROUP BY links.link_id) AS foo";
578
-
579
- return $wpdb->get_var($q);
580
- }
581
-
582
- //Select the required links.
583
- $q = "SELECT
584
- links.*
585
-
586
- FROM
587
- {$wpdb->prefix}blc_links AS links
588
- $joins
589
-
590
- WHERE
591
- $where_expr
592
-
593
- GROUP BY links.link_id
594
-
595
- {$order_clause}"; //Note: would be a lot faster without GROUP BY
596
-
597
- //Add the LIMIT clause
598
- if ( $params['max_results'] || $params['offset'] ){
599
- $q .= sprintf("\nLIMIT %d, %d", $params['offset'], $params['max_results']);
600
- }
601
-
602
- $results = $wpdb->get_results($q, ARRAY_A);
603
- if ( empty($results) ){
604
- return array();
605
- }
606
-
607
- //Create the link objects
608
- $links = array();
609
-
610
- foreach($results as $result){
611
- $link = new blcLink($result);
612
- $links[$link->link_id] = $link;
613
- }
614
-
615
- $purpose = $params['purpose'];
616
- /*
617
- Preload instances if :
618
- * It has been requested via the 'load_instances' argument.
619
- * The links are going to be displayed or edited, which involves instances.
620
- */
621
- $load_instances = $params['load_instances'] || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING));
622
-
623
- if ( $load_instances ){
624
- $link_ids = array_keys($links);
625
- $all_instances = blc_get_instances($link_ids, $purpose, $params['load_containers'], $params['load_wrapped_objects']);
626
- //Assign each batch of instances to the right link
627
- foreach($all_instances as $link_id => $instances){
628
- foreach($instances as $instance) { /** @var blcLinkInstance $instance */
629
- $instance->_link = $links[$link_id];
630
- }
631
- $links[$link_id]->_instances = $instances;
632
- }
633
- }
634
-
635
- return $links;
636
- }
637
-
638
- /**
639
- * Calculate the number of results for all known filters
640
- *
641
- * @return void
642
- */
643
- function count_filter_results(){
644
- foreach($this->native_filters as $filter_id => $filter){
645
- $this->native_filters[$filter_id]['count'] = $this->get_filter_links(
646
- $filter, array('count_only' => true)
647
- );
648
- }
649
-
650
- foreach($this->custom_filters as $filter_id => $filter){
651
- $this->custom_filters[$filter_id]['count'] = $this->get_filter_links(
652
- $filter, array('count_only' => true)
653
- );
654
- }
655
-
656
- $this->search_filter['count'] = $this->get_filter_links($this->search_filter, array('count_only' => true));
657
- }
658
-
659
- /**
660
- * Retrieve a list of links matching a filter.
661
- *
662
- * @uses blcLinkQuery::get_links()
663
- *
664
- * @param string|array $filter Either a filter ID or an array containing filter data.
665
- * @param array $extra_params Optional extra criteria that will override those set by the filter. See blc_get_links() for details.
666
- * @return array|int Either an array of blcLink objects, or an integer indicating the number of links that match the filter.
667
- */
668
- function get_filter_links($filter, $extra_params = null){
669
- if ( is_string($filter) ){
670
- $filter = $this->get_filter($filter);
671
- }
672
-
673
- $params = $this->get_search_params($filter);
674
-
675
-
676
- if ( !empty($extra_params) ){
677
- $params = array_merge($params, $extra_params);
678
- }
679
-
680
- return $this->get_links($params);
681
- }
682
-
683
- /**
684
- * Print a menu of available filters, both native and user-created.
685
- *
686
- * @param string $current Current filter ID.
687
- * @return void
688
- */
689
- function print_filter_menu($current = ''){
690
- $filters = $this->get_filters();
691
-
692
- echo '<ul class="subsubsub">';
693
-
694
- //Construct a submenu of filter types
695
- $items = array();
696
- foreach ($filters as $filter => $data){
697
- if ( !empty($data['hidden']) ) continue; //skip hidden filters
698
-
699
- $class = '';
700
- $number_class = 'filter-' . $filter . '-link-count';
701
-
702
- if ( $current == $filter ) {
703
- $class = 'class="current"';
704
- $number_class .= ' current-link-count';
705
- }
706
-
707
- $items[] = sprintf(
708
- "<li><a href='tools.php?page=view-broken-links&filter_id=%s' %s>%s</a> <span class='count'>(<span class='%s'>%d</span>)</span>",
709
- esc_attr($filter),
710
- $class,
711
- esc_html($data['name']),
712
- $number_class,
713
- $data['count']
714
- );
715
- }
716
- echo implode(' |</li>', $items);
717
-
718
- echo '</ul>';
719
- }
720
-
721
- /**
722
- * Print the appropriate heading for the given filter.
723
- *
724
- * @param array $current_filter
725
- * @return void
726
- */
727
- function print_filter_heading($current_filter){
728
- echo '<h2>';
729
- //Output a header matching the current filter
730
- if ( $current_filter['count'] > 0 ){
731
- echo $current_filter['heading'] . " (<span class='current-link-count'>{$current_filter['count']}</span>)";
732
- } else {
733
- echo $current_filter['heading_zero'] . "<span class='current-link-count'></span>";
734
- }
735
- echo '</h2>';
736
- }
737
-
738
- /**
739
- * Execute a filter.
740
- *
741
- * Gathers paging and search parameters from $_GET and executes the specified filter.
742
- * The returned array contains standard filter data plus several additional fields :
743
- * 'filter_id' - Which filter was used. May differ from the specified $filter_id due to fallback settings.
744
- * 'per_page' - How many results per page the method tried to retrieve.
745
- * 'page' - Which page of results was retrieved.
746
- * 'max_pages' - The total number of results pages, calculated using the above 'per_page' value.
747
- * 'links' - An array of retrieved links (blcLink objects).
748
- * 'search_params' - An associative array of the current search parameters as extracted either from the current URL or the filter itself.
749
- * 'is_broken_filter' - TRUE if the filter was set to retrieve only broken links, FALSE otherwise.
750
- *
751
- * @param string $filter_id Filter ID.
752
- * @param int $page Optional. Which page of results to retrieve. Defaults to returning the first page of results.
753
- * @param int $per_page Optional. The number of results per page. Defaults to 30.
754
- * @param string $fallback Optional. Which filter to use if none match the specified $filter_id. Defaults to the native broken link filter.
755
- * @param string $orderby Optional. Sort results by this column.
756
- * @param string $order Optional. Sort direction ('asc' or 'desc').
757
- * @return array Associative array of filter data and the results of its execution.
758
- */
759
- function exec_filter($filter_id, $page = 1, $per_page = 30, $fallback = 'broken', $orderby = '', $order = 'asc'){
760
- //The only valid sort directions are 'asc' and 'desc'.
761
- if ( !in_array($order, array('asc', 'desc')) ) {
762
- $order = 'asc';
763
- }
764
-
765
- //Get the selected filter (defaults to displaying broken links)
766
- $current_filter = $this->get_filter($filter_id);
767
- if ( empty($current_filter) ){
768
- $current_filter = $this->get_filter($fallback);
769
- $filter_id = $fallback;
770
- }
771
-
772
- //Page number must be > 0
773
- if ($page < 1) $page = 1;
774
-
775
- //Links per page [1 - 500]
776
- if ($per_page < 1){
777
- $per_page = 30;
778
- } else if ($per_page > 500){
779
- $per_page = 500;
780
- }
781
-
782
- //Calculate the maximum number of pages.
783
- $max_pages = ceil($current_filter['count'] / $per_page);
784
-
785
- //Select the required links
786
- $extra_params = array(
787
- 'offset' => ( ($page-1) * $per_page ),
788
- 'max_results' => $per_page,
789
- 'purpose' => BLC_FOR_DISPLAY,
790
- 'orderby' => $orderby,
791
- 'order' => $order,
792
- );
793
- $links = $this->get_filter_links($current_filter, $extra_params);
794
-
795
- //If the current request is a user-initiated search query (either directly or
796
- //via a custom filter), save the search params. They can later be used to pre-fill
797
- //the search form or build a new/modified custom filter.
798
- $search_params = array();
799
- if ( !empty($current_filter['custom']) || ($filter_id == 'search') ){
800
- $search_params = $this->get_search_params($current_filter);
801
- }
802
-
803
- $base_filter = '';
804
- if ( array_key_exists($filter_id, $this->native_filters) ) {
805
- $base_filter = $filter_id;
806
- } else if ( isset($current_filter['params']['s_filter']) && !empty($current_filter['params']['s_filter']) ) {
807
- $base_filter = $current_filter['params']['s_filter'];
808
- } else if ( isset($_GET['s_filter']) && !empty($_GET['s_filter']) ) {
809
- $base_filter = $_GET['s_filter'];
810
- }
811
-
812
- $is_broken_filter = ($base_filter == 'broken');
813
-
814
- //Save the effective filter data in the filter array.
815
- //It can be used later to print the link table.
816
- $current_filter = array_merge(array(
817
- 'filter_id' => $filter_id,
818
- 'page' => $page,
819
- 'per_page' => $per_page,
820
- 'max_pages' => $max_pages,
821
- 'links' => $links,
822
- 'search_params' => $search_params,
823
- 'is_broken_filter' => $is_broken_filter,
824
- 'base_filter' => $base_filter,
825
- ), $current_filter);
826
-
827
- return $current_filter;
828
- }
829
- }
830
-
831
- /**
832
- * Retrieve a list of links matching some criteria.
833
- *
834
- * The function argument should be an associative array describing the criteria.
835
- * The supported keys are :
836
- * 'offset' - Skip the first X results. Default is 0.
837
- * 'max_results' - The maximum number of links to return. Defaults to returning all results.
838
- * 'link_ids' - Retrieve only links with these IDs. This should either be a comma-separated list or an array.
839
- * 's_link_text' - Link text must match this keyphrase (performs a fulltext search).
840
- * 's_link_url' - Link URL must contain this string. You can use "*" as a wildcard.
841
- * 's_parser_type' - Filter links by the type of link parser that was used to find them.
842
- * 's_container_type' - Filter links by where they were found, e.g. 'post'.
843
- * 's_container_id' - Find links that belong to a container with this ID (should be used together with s_container_type).
844
- * 's_link_type' - Either parser type or container type must match this.
845
- * 's_http_code' - Filter by HTTP code. Example : 201,400-410,500
846
- * 's_filter' - Use a built-in filter. Available filters : 'broken', 'redirects', 'all'
847
- * 'where_expr' - Advanced. Lets you directly specify a part of the WHERE clause.
848
- * 'load_instances' - Pre-load all link instance data for each link. Default is false.
849
- * 'load_containers' - Pre-load container data for each instance. Default is false.
850
- * 'load_wrapped_objects' - Pre-load wrapped object data (e.g. posts, comments, etc) for each container. Default is false.
851
- * '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.
852
- * 'purpose' - An optional code indicating how the links will be used.
853
- * 'include_invalid' - Include links that have no instances and links that only have instances that reference not-loaded containers or parsers. Defaults to false.
854
- *
855
- * All keys are optional.
856
- *
857
- * @uses blcLinkQuery::get_links();
858
- *
859
- * @param array $params
860
- * @return int|blcLink[] Either an array of blcLink objects, or the number of results for the query.
861
- */
862
- function blc_get_links($params = null){
863
- $instance = blcLinkQuery::getInstance();
864
- return $instance->get_links($params);
865
- }
866
-
1
+ <?php
2
+
3
+ /**
4
+ * Class for querying, sorting and filtering links.
5
+ * Used as a singleton.
6
+ *
7
+ * @package Broken Link Checker
8
+ * @access public
9
+ */
10
+ class blcLinkQuery {
11
+
12
+ var $native_filters;
13
+ var $search_filter;
14
+ var $custom_filters = array();
15
+
16
+ var $valid_url_params = array();
17
+
18
+ function __construct(){
19
+ //Init. the available native filters.
20
+ $this->native_filters = array(
21
+ 'all' => array(
22
+ 'params' => array(
23
+ 'where_expr' => '1',
24
+ ),
25
+ 'name' => __('All', 'broken-link-checker'),
26
+ 'heading' => __('Detected Links', 'broken-link-checker'),
27
+ 'heading_zero' => __('No links found (yet)', 'broken-link-checker'),
28
+ 'native' => true,
29
+ ),
30
+
31
+ 'broken' => array(
32
+ 'params' => array(
33
+ 'where_expr' => '( broken = 1 )',
34
+ 's_include_dismissed' => false,
35
+ ),
36
+ 'name' => __('Broken', 'broken-link-checker'),
37
+ 'heading' => __('Broken Links', 'broken-link-checker'),
38
+ 'heading_zero' => __('No broken links found', 'broken-link-checker'),
39
+ 'native' => true,
40
+ ),
41
+ 'warnings' => array(
42
+ 'params' => array(
43
+ 'where_expr' => '( warning = 1 )',
44
+ 's_include_dismissed' => false,
45
+ ),
46
+ 'name' => _x('Warnings', 'filter name', 'broken-link-checker'),
47
+ 'heading' => __('Warnings', 'filter heading', 'broken-link-checker'),
48
+ 'heading_zero' => __('No warnings found', 'broken-link-checker'),
49
+ 'native' => true,
50
+ ),
51
+ 'redirects' => array(
52
+ 'params' => array(
53
+ 'where_expr' => '( redirect_count > 0 )',
54
+ 's_include_dismissed' => false,
55
+ ),
56
+ 'name' => __('Redirects', 'broken-link-checker'),
57
+ 'heading' => __('Redirected Links', 'broken-link-checker'),
58
+ 'heading_zero' => __('No redirects found', 'broken-link-checker'),
59
+ 'native' => true,
60
+ ),
61
+
62
+ 'dismissed' => array(
63
+ 'params' => array(
64
+ 'where_expr' => '( dismissed = 1 )',
65
+ ),
66
+ 'name' => __('Dismissed', 'broken-link-checker'),
67
+ 'heading' => __('Dismissed Links', 'broken-link-checker'),
68
+ 'heading_zero' => __('No dismissed links found', 'broken-link-checker'),
69
+ 'native' => true,
70
+ ),
71
+ );
72
+
73
+ //The user can turn off warnings. In that case, all errors will show up in the "broken" filter instead.
74
+ $conf = blc_get_configuration();
75
+ if ( !$conf->get('warnings_enabled') ) {
76
+ unset($this->native_filters['warnings']);
77
+ }
78
+
79
+ //Create the special "search" filter
80
+ $this->search_filter = array(
81
+ 'name' => __('Search', 'broken-link-checker'),
82
+ 'heading' => __('Search Results', 'broken-link-checker'),
83
+ 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
84
+ 'params' => array(),
85
+ 'use_url_params' => true,
86
+ 'hidden' => true,
87
+ );
88
+
89
+ //These search arguments may be passed via the URL if the filter's 'use_url_params' field is set to True.
90
+ //They map to the fields of the search form on the Tools -> Broken Links page. Only these arguments
91
+ //can be used in user-defined filters.
92
+ $this->valid_url_params = array(
93
+ 's_link_text',
94
+ 's_link_url',
95
+ 's_parser_type',
96
+ 's_container_type',
97
+ 's_link_type',
98
+ 's_http_code',
99
+ 's_filter',
100
+ );
101
+ }
102
+
103
+ static function getInstance(){
104
+ static $instance = null;
105
+ if ( is_null($instance) ){
106
+ $instance = new blcLinkQuery;
107
+ }
108
+ return $instance;
109
+ }
110
+
111
+ /**
112
+ * Load and return the list of user-defined link filters.
113
+ *
114
+ * @return array An array of custom filter definitions. If there are no custom filters defined returns an empty array.
115
+ */
116
+ function load_custom_filters(){
117
+ global $wpdb; /** @var wpdb $wpdb */
118
+
119
+ $filter_data = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}blc_filters ORDER BY name ASC", ARRAY_A);
120
+ $filters = array();
121
+
122
+ if ( !empty($filter_data) ) {
123
+ foreach($filter_data as $data){
124
+ wp_parse_str($data['params'], $params);
125
+
126
+ $filters[ 'f'.$data['id'] ] = array(
127
+ 'name' => $data['name'],
128
+ 'params' => $params,
129
+ 'heading' => ucwords($data['name']),
130
+ 'heading_zero' => __('No links found for your query', 'broken-link-checker'),
131
+ 'custom' => true,
132
+ );
133
+ }
134
+ }
135
+
136
+ $this->custom_filters = $filters;
137
+
138
+ return $filters;
139
+ }
140
+
141
+ /**
142
+ * Add a custom link filter.
143
+ *
144
+ * @param string $name Filter name.
145
+ * @param string|array $params Filter params. Either as a query string, or an array.
146
+ * @return string|bool The ID of the newly added filter, or False.
147
+ */
148
+ function create_custom_filter($name, $params){
149
+ global $wpdb; /** @var wpdb $wpdb */
150
+
151
+ if ( is_array($params) ){
152
+ $params = http_build_query($params, null, '&');
153
+ }
154
+
155
+ //Save the new filter
156
+ $q = $wpdb->prepare(
157
+ "INSERT INTO {$wpdb->prefix}blc_filters(name, params) VALUES (%s, %s)",
158
+ $name, $params
159
+ );
160
+
161
+ if ( $wpdb->query($q) !== false ){
162
+ $filter_id = 'f'.$wpdb->insert_id;
163
+ return $filter_id;
164
+ } else {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Delete a custom filter
171
+ *
172
+ * @param string $filter_id
173
+ * @return bool True on success, False if a database error occured.
174
+ */
175
+ function delete_custom_filter($filter_id){
176
+ global $wpdb; /** @var wpdb $wpdb */
177
+
178
+ if ( !isset($filter_id) ) {
179
+ $filter_id = $_POST['filter_id'];
180
+ }
181
+ //Remove the "f" character from the filter ID to get its database key
182
+ $filter_id = intval(ltrim($filter_id, 'f'));
183
+
184
+ //Try to delete the filter
185
+ $q = $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_filters WHERE id = %d", $filter_id);
186
+ if ( $wpdb->query($q) !== false ){
187
+ return true;
188
+ } else {
189
+ return false;
190
+ }
191
+ }
192
+
193
+ function get_filters(){
194
+ $filters = array_merge($this->native_filters, $this->custom_filters);
195
+ $filters['search'] = $this->search_filter;
196
+ return $filters;
197
+ }
198
+
199
+ /**
200
+ * Get a link search filter by filter ID.
201
+ *
202
+ * @param string $filter_id
203
+ * @return array|null
204
+ */
205
+ function get_filter($filter_id){
206
+ $filters = $this->get_filters();
207
+ if ( isset($filters[$filter_id]) ){
208
+ return $filters[$filter_id];
209
+ } else {
210
+ return null;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Get link search parameters from the specified filter.
216
+ *
217
+ * @param array $filter
218
+ * @return array An array of parameters suitable for use with blcLinkQuery::get_links()
219
+ */
220
+ function get_search_params( $filter = null ){
221
+ //If present, the filter's parameters may be saved either as an array or a string.
222
+ $params = array();
223
+ if ( !empty($filter) && !empty($filter['params']) ){
224
+ $params = $filter['params'];
225
+ if ( is_string( $params ) ){
226
+ wp_parse_str($params, $params);
227
+ }
228
+ }
229
+
230
+ //Merge in the parameters from the current request, if required
231
+ if ( isset($filter['use_url_params']) && $filter['use_url_params'] ){
232
+ $params = array_merge($params, $this->get_url_search_params());
233
+ }
234
+
235
+ return $params;
236
+ }
237
+
238
+ /**
239
+ * Extract search query parameters from the current URL
240
+ *
241
+ * @return array
242
+ */
243
+ function get_url_search_params(){
244
+ $url_params = array();
245
+ foreach ($_GET as $param => $value){
246
+ if ( in_array($param, $this->valid_url_params) ){
247
+ $url_params[$param] = $value;
248
+ }
249
+ }
250
+ return $url_params;
251
+ }
252
+
253
+
254
+
255
+ /**
256
+ * A helper method for parsing a list of search criteria and generating the parts of the SQL query.
257
+ *
258
+ * @see blcLinkQuery::get_links()
259
+ *
260
+ * @param array $params An array of search criteria.
261
+ * @return array 'where_exprs' - an array of search expressions, 'join_instances' - whether joining the instance table is required.
262
+ */
263
+ function compile_search_params($params){
264
+ global $wpdb; /** @var wpdb $wpdb */
265
+
266
+ //Track whether we'll need to left-join the instance table to run the query.
267
+ $join_instances = false;
268
+
269
+ //Generate the individual clauses of the WHERE expression and store them in an array.
270
+ $pieces = array();
271
+
272
+ //Convert parser and container type lists to arrays of valid values
273
+ $s_parser_type = array();
274
+ if ( !empty($params['s_parser_type']) ){
275
+ $s_parser_type = $params['s_parser_type'];
276
+ if ( is_string($s_parser_type) ){
277
+ $s_parser_type = preg_split('/[,\s]+/', $s_parser_type);
278
+ }
279
+ }
280
+
281
+ $s_container_type = array();
282
+ if ( !empty($params['s_container_type']) ){
283
+ $s_container_type = $params['s_container_type'];
284
+ if ( is_string($s_container_type) ){
285
+ $s_container_type = preg_split('/[,\s]+/', $s_container_type);
286
+ }
287
+ }
288
+
289
+ //Don't include links with instances that reference invalid (not currently loaded)
290
+ //containers and parsers (unless specifically told to also include invalid links).
291
+ if ( empty($params['include_invalid']) ){
292
+ $join_instances = true;
293
+
294
+ $module_manager = blcModuleManager::getInstance();
295
+ $loaded_containers = array_keys($module_manager->get_active_by_category('container'));
296
+ $loaded_parsers = array_keys($module_manager->get_active_by_category('parser'));
297
+
298
+ if ( empty($s_parser_type) ){
299
+ $s_parser_type = $loaded_parsers;
300
+ } else {
301
+ $s_parser_type = array_intersect($s_parser_type, $loaded_parsers);
302
+ }
303
+
304
+ if ( empty($s_container_type) ){
305
+ $s_container_type = $loaded_containers;
306
+ } else {
307
+ $s_container_type = array_intersect($s_container_type, $loaded_containers);
308
+ }
309
+ }
310
+
311
+ //Parser type should match the parser_type column in the instance table.
312
+ if ( !empty($s_parser_type) ){
313
+ $s_parser_type = array_map('trim', array_unique($s_parser_type));
314
+ $s_parser_type = array_map('esc_sql', $s_parser_type);
315
+
316
+ if ( count($s_parser_type) == 1 ){
317
+ $pieces[] = sprintf("instances.parser_type = '%s'", reset($s_parser_type));
318
+ } else {
319
+ $pieces[] = "instances.parser_type IN ('" . implode("', '", $s_parser_type) . "')";
320
+ }
321
+
322
+ $join_instances = true;
323
+ }
324
+
325
+ //Container type should match the container_type column in the instance table.
326
+ if ( !empty($s_container_type) ){
327
+ //Sanitize for use in SQL
328
+ $s_container_type = array_map('trim', array_unique($s_container_type));
329
+ $s_container_type = array_map('esc_sql', $s_container_type);
330
+
331
+ if ( count($s_container_type) == 1 ){
332
+ $pieces[] = sprintf("instances.container_type = '%s'", reset($s_container_type));
333
+ } else {
334
+ $pieces[] = "instances.container_type IN ('" . implode("', '", $s_container_type) . "')";
335
+ }
336
+
337
+ $join_instances = true;
338
+ }
339
+
340
+ //A part of the WHERE expression can be specified explicitly
341
+ if ( !empty($params['where_expr']) ){
342
+ $pieces[] = $params['where_expr'];
343
+ $join_instances = $join_instances || ( stripos($params['where_expr'], 'instances') !== false );
344
+ }
345
+
346
+ //List of allowed link ids (either an array or comma-separated)
347
+ if ( !empty($params['link_ids']) ){
348
+ $link_ids = $params['link_ids'];
349
+
350
+ if ( is_string($link_ids) ){
351
+ $link_ids = preg_split('/[,\s]+/', $link_ids);
352
+ }
353
+
354
+ //Only accept non-zero integers
355
+ $sanitized_link_ids = array();
356
+ foreach($link_ids as $id){
357
+ $id = intval($id);
358
+ if ( $id != 0 ){
359
+ $sanitized_link_ids[] = $id;
360
+ }
361
+ }
362
+
363
+ $pieces[] = 'links.link_id IN (' . implode(', ', $sanitized_link_ids) . ')';
364
+ }
365
+
366
+ //Anchor text - use LIKE search
367
+ if ( !empty($params['s_link_text']) ){
368
+ $s_link_text = esc_sql($this->esc_like($params['s_link_text']));
369
+ $s_link_text = str_replace('*', '%', $s_link_text);
370
+
371
+ $pieces[] = '(instances.link_text LIKE "%' . $s_link_text . '%")';
372
+ $join_instances = true;
373
+ }
374
+
375
+ //URL - try to match both the initial URL and the final URL.
376
+ //There is limited wildcard support, e.g. "google.*/search" will match both
377
+ //"google.com/search" and "google.lv/search"
378
+ if ( !empty($params['s_link_url']) ){
379
+ $s_link_url = esc_sql($this->esc_like($params['s_link_url']));
380
+ $s_link_url = str_replace('*', '%', $s_link_url);
381
+
382
+ $pieces[] = '(links.url LIKE "%'. $s_link_url .'%") OR '.
383
+ '(links.final_url LIKE "%'. $s_link_url .'%")';
384
+ }
385
+
386
+ //Container ID should match... you guessed it - container_id
387
+ if ( !empty($params['s_container_id']) ){
388
+ $s_container_id = intval($params['s_container_id']);
389
+ if ( $s_container_id != 0 ){
390
+ $pieces[] = "instances.container_id = $s_container_id";
391
+ $join_instances = true;
392
+ }
393
+ }
394
+
395
+ //Link type can match either the the parser_type or the container_type.
396
+ if ( !empty($params['s_link_type']) ){
397
+ $s_link_type = esc_sql($params['s_link_type']);
398
+ $pieces[] = "instances.parser_type = '$s_link_type' OR instances.container_type='$s_link_type'";
399
+ $join_instances = true;
400
+ }
401
+
402
+ //HTTP code - the user can provide a list of HTTP response codes and code ranges.
403
+ //Example : 201,400-410,500
404
+ if ( !empty($params['s_http_code']) ){
405
+ //Strip spaces.
406
+ $params['s_http_code'] = str_replace(' ', '', $params['s_http_code']);
407
+ //Split by comma
408
+ $codes = explode(',', $params['s_http_code']);
409
+
410
+ $individual_codes = array();
411
+ $ranges = array();
412
+
413
+ //Try to parse each response code or range. Invalid ones are simply ignored.
414
+ foreach($codes as $code){
415
+ if ( is_numeric($code) ){
416
+ //It's a single number
417
+ $individual_codes[] = abs(intval($code));
418
+ } elseif ( strpos($code, '-') !== false ) {
419
+ //Try to parse it as a range
420
+ $range = explode( '-', $code, 2 );
421
+ if ( (count($range) == 2) && is_numeric($range[0]) && is_numeric($range[0]) ){
422
+ //Make sure the smaller code comes first
423
+ $range = array( intval($range[0]), intval($range[1]) );
424
+ $ranges[] = array( min($range), max($range) );
425
+ }
426
+ }
427
+ }
428
+
429
+ $piece = array();
430
+
431
+ //All individual response codes get one "http_code IN (...)" clause
432
+ if ( !empty($individual_codes) ){
433
+ $piece[] = '(links.http_code IN ('. implode(', ', $individual_codes) .'))';
434
+ }
435
+
436
+ //Ranges get a "http_code BETWEEN min AND max" clause each
437
+ if ( !empty($ranges) ){
438
+ $range_strings = array();
439
+ foreach($ranges as $range){
440
+ $range_strings[] = "(links.http_code BETWEEN $range[0] AND $range[1])";
441
+ }
442
+ $piece[] = '( ' . implode(' OR ', $range_strings) . ' )';
443
+ }
444
+
445
+ //Finally, generate a composite WHERE clause for both types of response code queries
446
+ if ( !empty($piece) ){
447
+ $pieces[] = implode(' OR ', $piece);
448
+ }
449
+
450
+ }
451
+
452
+ //Dismissed links are included by default, but can explicitly included
453
+ //or filtered out by passing a special param.
454
+ if ( isset($params['s_include_dismissed']) ) {
455
+ $s_include_dismissed = !empty($params['s_include_dismissed']);
456
+ $pieces['filter_dismissed'] = $s_include_dismissed ? '1' : '(dismissed = 0)';
457
+ }
458
+
459
+ //Optionally sorting is also possible
460
+ $order_exprs = array();
461
+ if ( !empty($params['orderby']) ) {
462
+ $allowed_columns = array(
463
+ 'url' => 'links.url',
464
+ 'link_text' => 'instances.link_text',
465
+ 'redirect_url' => 'links.final_url',
466
+ );
467
+ $column = $params['orderby'];
468
+
469
+ $direction = !empty($params['order']) ? strtolower($params['order']) : 'asc';
470
+ if ( !in_array($direction, array('asc', 'desc')) ) {
471
+ $direction = 'asc';
472
+ }
473
+
474
+ if ( array_key_exists($column, $allowed_columns) ) {
475
+ if ( $column === 'redirect_url' ) {
476
+ //Sort links that are not redirects last.
477
+ $order_exprs[] = '(links.redirect_count > 0) DESC';
478
+ }
479
+
480
+ $order_exprs[] = $allowed_columns[$column] . ' ' . $direction;
481
+ }
482
+ }
483
+
484
+ //Custom filters can optionally call one of the native filters
485
+ //to narrow down the result set.
486
+ if ( !empty($params['s_filter']) && isset($this->native_filters[$params['s_filter']]) ){
487
+ $the_filter = $this->native_filters[$params['s_filter']];
488
+ $extra_criteria = $this->compile_search_params($the_filter['params']);
489
+
490
+ $pieces = array_merge($extra_criteria['where_exprs'], $pieces);
491
+ $join_instances = $join_instances || $extra_criteria['join_instances'];
492
+ }
493
+
494
+ return array(
495
+ 'where_exprs' => $pieces,
496
+ 'join_instances' => $join_instances,
497
+ 'order_exprs' => $order_exprs,
498
+ );
499
+ }
500
+
501
+ private function esc_like($input) {
502
+ global $wpdb; /** @var wpdb $wpdb */
503
+ if ( method_exists($wpdb, 'esc_like') ) {
504
+ return $wpdb->esc_like($input);
505
+ } else {
506
+ return like_escape($input);
507
+ }
508
+ }
509
+
510
+ /**
511
+ * blcLinkQuery::get_links()
512
+ *
513
+ * @see blc_get_links()
514
+ *
515
+ * @param array $params
516
+ * @return array|int
517
+ */
518
+ function get_links($params = null){
519
+ global $wpdb; /** @var wpdb $wpdb */
520
+
521
+ if( !is_array($params) ){
522
+ $params = array();
523
+ }
524
+
525
+ $defaults = array(
526
+ 'offset' => 0,
527
+ 'max_results' => 0,
528
+ 'load_instances' => false,
529
+ 'load_containers' => false,
530
+ 'load_wrapped_objects' => false,
531
+ 'count_only' => false,
532
+ 'purpose' => '',
533
+ 'include_invalid' => false,
534
+ 'orderby' => '',
535
+ 'order' => '',
536
+ );
537
+
538
+ $params = array_merge($defaults, $params);
539
+
540
+ //Compile the search-related params into search expressions usable in a WHERE clause
541
+ $criteria = $this->compile_search_params($params);
542
+
543
+ //Build the WHERE clause
544
+ if ( !empty($criteria['where_exprs']) ){
545
+ $where_expr = "\t( " . implode(" ) AND\n\t( ", $criteria['where_exprs']) . ' ) ';
546
+ } else {
547
+ $where_expr = '1';
548
+ }
549
+
550
+ //Join the blc_instances table if it's required to perform the search.
551
+ $joins = "";
552
+ if ( $criteria['join_instances'] ){
553
+ $joins = "JOIN {$wpdb->prefix}blc_instances AS instances ON links.link_id = instances.link_id";
554
+ }
555
+
556
+ //Optional sorting
557
+ if ( !empty($criteria['order_exprs']) ) {
558
+ $order_clause = 'ORDER BY ' . implode(', ', $criteria['order_exprs']);
559
+ } else {
560
+ $order_clause = '';
561
+ }
562
+
563
+ if ( $params['count_only'] ){
564
+ //Only get the number of matching links.
565
+ $q = "
566
+ SELECT COUNT(*)
567
+ FROM (
568
+ SELECT 0
569
+
570
+ FROM
571
+ {$wpdb->prefix}blc_links AS links
572
+ $joins
573
+
574
+ WHERE
575
+ $where_expr
576
+
577
+ GROUP BY links.link_id) AS foo";
578
+
579
+ return $wpdb->get_var($q);
580
+ }
581
+
582
+ //Select the required links.
583
+ $q = "SELECT
584
+ links.*
585
+
586
+ FROM
587
+ {$wpdb->prefix}blc_links AS links
588
+ $joins
589
+
590
+ WHERE
591
+ $where_expr
592
+
593
+ GROUP BY links.link_id
594
+
595
+ {$order_clause}"; //Note: would be a lot faster without GROUP BY
596
+
597
+ //Add the LIMIT clause
598
+ if ( $params['max_results'] || $params['offset'] ){
599
+ $q .= sprintf("\nLIMIT %d, %d", $params['offset'], $params['max_results']);
600
+ }
601
+
602
+ $results = $wpdb->get_results($q, ARRAY_A);
603
+ if ( empty($results) ){
604
+ return array();
605
+ }
606
+
607
+ //Create the link objects
608
+ $links = array();
609
+
610
+ foreach($results as $result){
611
+ $link = new blcLink($result);
612
+ $links[$link->link_id] = $link;
613
+ }
614
+
615
+ $purpose = $params['purpose'];
616
+ /*
617
+ Preload instances if :
618
+ * It has been requested via the 'load_instances' argument.
619
+ * The links are going to be displayed or edited, which involves instances.
620
+ */
621
+ $load_instances = $params['load_instances'] || in_array($purpose, array(BLC_FOR_DISPLAY, BLC_FOR_EDITING));
622
+
623
+ if ( $load_instances ){
624
+ $link_ids = array_keys($links);
625
+ $all_instances = blc_get_instances($link_ids, $purpose, $params['load_containers'], $params['load_wrapped_objects']);
626
+ //Assign each batch of instances to the right link
627
+ foreach($all_instances as $link_id => $instances){
628
+ foreach($instances as $instance) { /** @var blcLinkInstance $instance */
629
+ $instance->_link = $links[$link_id];
630
+ }
631
+ $links[$link_id]->_instances = $instances;
632
+ }
633
+ }
634
+
635
+ return $links;
636
+ }
637
+
638
+ /**
639
+ * Calculate the number of results for all known filters
640
+ *
641
+ * @return void
642
+ */
643
+ function count_filter_results(){
644
+ foreach($this->native_filters as $filter_id => $filter){
645
+ $this->native_filters[$filter_id]['count'] = $this->get_filter_links(
646
+ $filter, array('count_only' => true)
647
+ );
648
+ }
649
+
650
+ foreach($this->custom_filters as $filter_id => $filter){
651
+ $this->custom_filters[$filter_id]['count'] = $this->get_filter_links(
652
+ $filter, array('count_only' => true)
653
+ );
654
+ }
655
+
656
+ $this->search_filter['count'] = $this->get_filter_links($this->search_filter, array('count_only' => true));
657
+ }
658
+
659
+ /**
660
+ * Retrieve a list of links matching a filter.
661
+ *
662
+ * @uses blcLinkQuery::get_links()
663
+ *
664
+ * @param string|array $filter Either a filter ID or an array containing filter data.
665
+ * @param array $extra_params Optional extra criteria that will override those set by the filter. See blc_get_links() for details.
666
+ * @return array|int Either an array of blcLink objects, or an integer indicating the number of links that match the filter.
667
+ */
668
+ function get_filter_links($filter, $extra_params = null){
669
+ if ( is_string($filter) ){
670
+ $filter = $this->get_filter($filter);
671
+ }
672
+
673
+ $params = $this->get_search_params($filter);
674
+
675
+
676
+ if ( !empty($extra_params) ){
677
+ $params = array_merge($params, $extra_params);
678
+ }
679
+
680
+ return $this->get_links($params);
681
+ }
682
+
683
+ /**
684
+ * Print a menu of available filters, both native and user-created.
685
+ *
686
+ * @param string $current Current filter ID.
687
+ * @return void
688
+ */
689
+ function print_filter_menu($current = ''){
690
+ $filters = $this->get_filters();
691
+
692
+ echo '<ul class="subsubsub">';
693
+
694
+ //Construct a submenu of filter types
695
+ $items = array();
696
+ foreach ($filters as $filter => $data){
697
+ if ( !empty($data['hidden']) ) continue; //skip hidden filters
698
+
699
+ $class = '';
700
+ $number_class = 'filter-' . $filter . '-link-count';
701
+
702
+ if ( $current == $filter ) {
703
+ $class = 'class="current"';
704
+ $number_class .= ' current-link-count';
705
+ }
706
+
707
+ $items[] = sprintf(
708
+ "<li><a href='tools.php?page=view-broken-links&filter_id=%s' %s>%s</a> <span class='count'>(<span class='%s'>%d</span>)</span>",
709
+ esc_attr($filter),
710
+ $class,
711
+ esc_html($data['name']),
712
+ $number_class,
713
+ $data['count']
714
+ );
715
+ }
716
+ echo implode(' |</li>', $items);
717
+
718
+ echo '</ul>';
719
+ }
720
+
721
+ /**
722
+ * Print the appropriate heading for the given filter.
723
+ *
724
+ * @param array $current_filter
725
+ * @return void
726
+ */
727
+ function print_filter_heading($current_filter){
728
+ echo '<h2>';
729
+ //Output a header matching the current filter
730
+ if ( $current_filter['count'] > 0 ){
731
+ echo $current_filter['heading'] . " (<span class='current-link-count'>{$current_filter['count']}</span>)";
732
+ } else {
733
+ echo $current_filter['heading_zero'] . "<span class='current-link-count'></span>";
734
+ }
735
+ echo '</h2>';
736
+ }
737
+
738
+ /**
739
+ * Execute a filter.
740
+ *
741
+ * Gathers paging and search parameters from $_GET and executes the specified filter.
742
+ * The returned array contains standard filter data plus several additional fields :
743
+ * 'filter_id' - Which filter was used. May differ from the specified $filter_id due to fallback settings.
744
+ * 'per_page' - How many results per page the method tried to retrieve.
745
+ * 'page' - Which page of results was retrieved.
746
+ * 'max_pages' - The total number of results pages, calculated using the above 'per_page' value.
747
+ * 'links' - An array of retrieved links (blcLink objects).
748
+ * 'search_params' - An associative array of the current search parameters as extracted either from the current URL or the filter itself.
749
+ * 'is_broken_filter' - TRUE if the filter was set to retrieve only broken links, FALSE otherwise.
750
+ *
751
+ * @param string $filter_id Filter ID.
752
+ * @param int $page Optional. Which page of results to retrieve. Defaults to returning the first page of results.
753
+ * @param int $per_page Optional. The number of results per page. Defaults to 30.
754
+ * @param string $fallback Optional. Which filter to use if none match the specified $filter_id. Defaults to the native broken link filter.
755
+ * @param string $orderby Optional. Sort results by this column.
756
+ * @param string $order Optional. Sort direction ('asc' or 'desc').
757
+ * @return array Associative array of filter data and the results of its execution.
758
+ */
759
+ function exec_filter($filter_id, $page = 1, $per_page = 30, $fallback = 'broken', $orderby = '', $order = 'asc'){
760
+ //The only valid sort directions are 'asc' and 'desc'.
761
+ if ( !in_array($order, array('asc', 'desc')) ) {
762
+ $order = 'asc';
763
+ }
764
+
765
+ //Get the selected filter (defaults to displaying broken links)
766
+ $current_filter = $this->get_filter($filter_id);
767
+ if ( empty($current_filter) ){
768
+ $current_filter = $this->get_filter($fallback);
769
+ $filter_id = $fallback;
770
+ }
771
+
772
+ //Page number must be > 0
773
+ if ($page < 1) $page = 1;
774
+
775
+ //Links per page [1 - 500]
776
+ if ($per_page < 1){
777
+ $per_page = 30;
778
+ } else if ($per_page > 500){
779
+ $per_page = 500;
780
+ }
781
+
782
+ //Calculate the maximum number of pages.
783
+ $max_pages = ceil($current_filter['count'] / $per_page);
784
+
785
+ //Select the required links
786
+ $extra_params = array(
787
+ 'offset' => ( ($page-1) * $per_page ),
788
+ 'max_results' => $per_page,
789
+ 'purpose' => BLC_FOR_DISPLAY,
790
+ 'orderby' => $orderby,
791
+ 'order' => $order,
792
+ );
793
+ $links = $this->get_filter_links($current_filter, $extra_params);
794
+
795
+ //If the current request is a user-initiated search query (either directly or
796
+ //via a custom filter), save the search params. They can later be used to pre-fill
797
+ //the search form or build a new/modified custom filter.
798
+ $search_params = array();
799
+ if ( !empty($current_filter['custom']) || ($filter_id == 'search') ){
800
+ $search_params = $this->get_search_params($current_filter);
801
+ }
802
+
803
+ $base_filter = '';
804
+ if ( array_key_exists($filter_id, $this->native_filters) ) {
805
+ $base_filter = $filter_id;
806
+ } else if ( isset($current_filter['params']['s_filter']) && !empty($current_filter['params']['s_filter']) ) {
807
+ $base_filter = $current_filter['params']['s_filter'];
808
+ } else if ( isset($_GET['s_filter']) && !empty($_GET['s_filter']) ) {
809
+ $base_filter = $_GET['s_filter'];
810
+ }
811
+
812
+ $is_broken_filter = ($base_filter == 'broken');
813
+
814
+ //Save the effective filter data in the filter array.
815
+ //It can be used later to print the link table.
816
+ $current_filter = array_merge(array(
817
+ 'filter_id' => $filter_id,
818
+ 'page' => $page,
819
+ 'per_page' => $per_page,
820
+ 'max_pages' => $max_pages,
821
+ 'links' => $links,
822
+ 'search_params' => $search_params,
823
+ 'is_broken_filter' => $is_broken_filter,
824
+ 'base_filter' => $base_filter,
825
+ ), $current_filter);
826
+
827
+ return $current_filter;
828
+ }
829
+ }
830
+
831
+ /**
832
+ * Retrieve a list of links matching some criteria.
833
+ *
834
+ * The function argument should be an associative array describing the criteria.
835
+ * The supported keys are :
836
+ * 'offset' - Skip the first X results. Default is 0.
837
+ * 'max_results' - The maximum number of links to return. Defaults to returning all results.
838
+ * 'link_ids' - Retrieve only links with these IDs. This should either be a comma-separated list or an array.
839
+ * 's_link_text' - Link text must match this keyphrase (performs a fulltext search).
840
+ * 's_link_url' - Link URL must contain this string. You can use "*" as a wildcard.
841
+ * 's_parser_type' - Filter links by the type of link parser that was used to find them.
842
+ * 's_container_type' - Filter links by where they were found, e.g. 'post'.
843
+ * 's_container_id' - Find links that belong to a container with this ID (should be used together with s_container_type).
844
+ * 's_link_type' - Either parser type or container type must match this.
845
+ * 's_http_code' - Filter by HTTP code. Example : 201,400-410,500
846
+ * 's_filter' - Use a built-in filter. Available filters : 'broken', 'redirects', 'all'
847
+ * 'where_expr' - Advanced. Lets you directly specify a part of the WHERE clause.
848
+ * 'load_instances' - Pre-load all link instance data for each link. Default is false.
849
+ * 'load_containers' - Pre-load container data for each instance. Default is false.
850
+ * 'load_wrapped_objects' - Pre-load wrapped object data (e.g. posts, comments, etc) for each container. Default is false.
851
+ * '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.
852
+ * 'purpose' - An optional code indicating how the links will be used.
853
+ * 'include_invalid' - Include links that have no instances and links that only have instances that reference not-loaded containers or parsers. Defaults to false.
854
+ *
855
+ * All keys are optional.
856
+ *
857
+ * @uses blcLinkQuery::get_links();
858
+ *
859
+ * @param array $params
860
+ * @return int|blcLink[] Either an array of blcLink objects, or the number of results for the query.
861
+ */
862
+ function blc_get_links($params = null){
863
+ $instance = blcLinkQuery::getInstance();
864
+ return $instance->get_links($params);
865
+ }
866
+
includes/links.php CHANGED
@@ -1,1138 +1,1140 @@
1
- <?php
2
-
3
- /**
4
- * @author W-Shadow
5
- * @copyright 2010
6
- */
7
-
8
- if (!class_exists('blcLink')){
9
-
10
- define('BLC_LINK_STATUS_UNKNOWN', 'unknown');
11
- define('BLC_LINK_STATUS_OK', 'ok');
12
- define('BLC_LINK_STATUS_INFO', 'info');
13
- define('BLC_LINK_STATUS_WARNING', 'warning');
14
- define('BLC_LINK_STATUS_ERROR', 'error');
15
-
16
- class blcLink {
17
-
18
- //Object state
19
- var $is_new = false;
20
-
21
- //DB fields
22
- var $link_id = 0;
23
- var $url = '';
24
-
25
- var $being_checked = false;
26
- var $last_check = 0;
27
- var $last_check_attempt = 0;
28
- var $check_count = 0;
29
- var $http_code = 0;
30
- var $request_duration = 0;
31
- var $timeout = false;
32
-
33
- var $redirect_count = 0;
34
- var $final_url = '';
35
-
36
- var $broken = false;
37
- public $warning = false;
38
- var $first_failure = 0;
39
- var $last_success = 0;
40
- var $may_recheck = 1;
41
-
42
- var $false_positive = false;
43
- var $result_hash = '';
44
-
45
- var $dismissed = false;
46
-
47
- var $status_text = '';
48
- var $status_code = '';
49
-
50
- var $log = '';
51
-
52
- //A list of DB fields and their storage formats
53
- var $field_format;
54
-
55
- //A cached list of the link's instances
56
- var $_instances = null;
57
-
58
- var $http_status_codes = array(
59
- // [Informational 1xx]
60
- 100=>'Continue',
61
- 101=>'Switching Protocols',
62
- // [Successful 2xx]
63
- 200=>'OK',
64
- 201=>'Created',
65
- 202=>'Accepted',
66
- 203=>'Non-Authoritative Information',
67
- 204=>'No Content',
68
- 205=>'Reset Content',
69
- 206=>'Partial Content',
70
- // [Redirection 3xx]
71
- 300=>'Multiple Choices',
72
- 301=>'Moved Permanently',
73
- 302=>'Found',
74
- 303=>'See Other',
75
- 304=>'Not Modified',
76
- 305=>'Use Proxy',
77
- //306=>'(Unused)',
78
- 307=>'Temporary Redirect',
79
- // [Client Error 4xx]
80
- 400=>'Bad Request',
81
- 401=>'Unauthorized',
82
- 402=>'Payment Required',
83
- 403=>'Forbidden',
84
- 404=>'Not Found',
85
- 405=>'Method Not Allowed',
86
- 406=>'Not Acceptable',
87
- 407=>'Proxy Authentication Required',
88
- 408=>'Request Timeout',
89
- 409=>'Conflict',
90
- 410=>'Gone',
91
- 411=>'Length Required',
92
- 412=>'Precondition Failed',
93
- 413=>'Request Entity Too Large',
94
- 414=>'Request-URI Too Long',
95
- 415=>'Unsupported Media Type',
96
- 416=>'Requested Range Not Satisfiable',
97
- 417=>'Expectation Failed',
98
- // [Server Error 5xx]
99
- 500=>'Internal Server Error',
100
- 501=>'Not Implemented',
101
- 502=>'Bad Gateway',
102
- 503=>'Service Unavailable',
103
- 504=>'Gateway Timeout',
104
- 505=>'HTTP Version Not Supported',
105
- 509=>'Bandwidth Limit Exceeded',
106
- 510=>'Not Extended',
107
- );
108
- var $isOptionLinkChanged = false;
109
- function __construct($arg = null){
110
- global $wpdb, $blclog; /** @var wpdb $wpdb */
111
-
112
- $this->field_format = array(
113
- 'url' => '%s',
114
- 'first_failure' => 'datetime',
115
- 'last_check' => 'datetime',
116
- 'last_success' => 'datetime',
117
- 'last_check_attempt' => 'datetime',
118
- 'check_count' => '%d',
119
- 'final_url' => '%s',
120
- 'redirect_count' => '%d',
121
- 'log' => '%s',
122
- 'http_code' => '%d',
123
- 'request_duration' => '%F',
124
- 'timeout' => 'bool',
125
- 'result_hash' => '%s',
126
- 'broken' => 'bool',
127
- 'warning' => 'bool',
128
- 'false_positive' => 'bool',
129
- 'may_recheck' => 'bool',
130
- 'being_checked' => 'bool',
131
- 'status_text' => '%s',
132
- 'status_code' => '%s',
133
- 'dismissed' => 'bool',
134
- );
135
-
136
- if (is_numeric($arg)){
137
- //Load a link with ID = $arg from the DB.
138
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id=%d LIMIT 1", $arg);
139
- $arr = $wpdb->get_row( $q, ARRAY_A );
140
-
141
- if ( is_array($arr) ){ //Loaded successfully
142
- $this->set_values($arr);
143
- } else {
144
- //Link not found. The object is invalid.
145
- //I'd throw an error, but that wouldn't be PHP 4 compatible...
146
- $blclog->warn(__CLASS__ .':' . __FUNCTION__ . ' Link not found.', $arg);
147
- }
148
-
149
- } else if (is_string($arg)){
150
- //Load a link with URL = $arg from the DB. Create a new one if the record isn't found.
151
- // $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Trying to load a link by URL:', $arg);
152
- $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE url=%s LIMIT 1", $arg);
153
- $arr = $wpdb->get_row( $q, ARRAY_A );
154
-
155
- if ( is_array($arr) ){ //Loaded successfully
156
- // $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Success!');
157
- $this->set_values($arr);
158
- } else { //Link not found, treat as new
159
- // $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Link not found.');
160
- $this->url = $arg;
161
- $this->is_new = true;
162
- }
163
-
164
- } else if (is_array($arg)){
165
- $this->set_values($arg);
166
- //Is this a new link?
167
- $this->is_new = empty($this->link_id);
168
- } else {
169
- $this->is_new = true;
170
- }
171
- }
172
-
173
- function blcLink($arg = null){
174
- $this->__construct($arg);
175
- }
176
-
177
- /**
178
- * blcLink::set_values()
179
- * Set the internal values to the ones provided in an array (doesn't sanitize).
180
- *
181
- * @param array $arr An associative array of values
182
- * @return void
183
- */
184
- function set_values($arr){
185
- $arr = $this->to_native_format($arr);
186
-
187
- foreach( $arr as $key => $value ){
188
- $this->$key = $value;
189
- }
190
- }
191
-
192
- /**
193
- * Check whether the object represents a valid link
194
- *
195
- * @return bool
196
- */
197
- function valid(){
198
- return !empty( $this->url ) && ( !empty($this->link_id) || $this->is_new );
199
- }
200
-
201
- /**
202
- * Check if the link is working.
203
- *
204
- * @param bool $save_results Automatically save the results of the check.
205
- * @return bool
206
- */
207
- function check( $save_results = true ){
208
- if ( !$this->valid() ) return false;
209
-
210
- $this->last_check_attempt = time();
211
-
212
- /*
213
- If the link is still marked as in the process of being checked, that probably means
214
- that the last time the plugin tried to check it the script got terminated by PHP for
215
- running over the execution time limit or causing a fatal error.
216
-
217
- This problem is likely to be temporary for most links, so we leave it be and treat it
218
- as any other link (i.e. check it again later using the default recheck periodicity).
219
- */
220
- if ( $this->being_checked ) {
221
- $this->being_checked = false;
222
-
223
- //Add an explanatory notice to the link's log
224
- $error_notice = "[" . __("The plugin script was terminated while trying to check the link.", 'broken-link-checker') . "]";
225
- if ( strpos($this->log, $error_notice) === false ){
226
- $this->log = $error_notice . "\r\n" . $this->log;
227
- }
228
-
229
- if ( $save_results ){
230
- $this->save();
231
- }
232
-
233
- return false;
234
- }
235
-
236
- $this->being_checked = true;
237
- $this->check_count++;
238
-
239
- if ( $save_results ) {
240
-
241
- //Update the DB record before actually performing the check.
242
- //Useful if something goes terribly wrong while checking this particular URL
243
- //(e.g. the server might kill the script for running over the exec. time limit).
244
- //Note : might be unnecessary.
245
- $this->save();
246
- }
247
-
248
- $defaults = array(
249
- 'broken' => false,
250
- 'warning' => false,
251
- 'http_code' => 0,
252
- 'redirect_count' => 0,
253
- 'final_url' => $this->url,
254
- 'request_duration' => 0,
255
- 'timeout' => false,
256
- 'may_recheck' => true,
257
- 'log' => '',
258
- 'result_hash' => '',
259
- 'status_text' => '',
260
- 'status_code' => '',
261
- );
262
-
263
-
264
- $checker = blcCheckerHelper::get_checker_for($this->get_ascii_url());
265
-
266
- if ( is_null($checker) ){
267
- //Oops, there are no checker implementations that can handle this link.
268
- //Assume the link is working, but leave a note in the log.
269
- $this->broken = false;
270
- $this->being_checked = false;
271
- $this->log = __("The plugin doesn't know how to check this type of link.", 'broken-link-checker');
272
-
273
- if ( $save_results ){
274
- $this->save();
275
- }
276
-
277
- return true;
278
- }
279
-
280
- //Check the link
281
- $rez = $checker->check($this->get_ascii_url());
282
- //FB::info($rez, "Check results");
283
-
284
- $results = array_merge($defaults, $rez);
285
-
286
- //Some HTTP errors can be treated as warnings.
287
- $results = $this->decide_warning_state($results);
288
-
289
- //Filter the returned array to leave only the restricted set of keys that we're interested in.
290
- $results = array_intersect_key($results, $defaults);
291
-
292
- //The result hash is special - see blcLink::status_changed()
293
- $new_result_hash = $results['result_hash'];
294
- unset($results['result_hash']);
295
-
296
- //Update the object's fields with the new results
297
- $this->set_values($results);
298
-
299
- //Update timestamps & state-dependent fields
300
- $this->status_changed($results['broken'], $new_result_hash);
301
- $this->being_checked = false;
302
-
303
- //Save results to the DB
304
- if($save_results){
305
- $this->save();
306
- }
307
-
308
- return $this->broken;
309
- }
310
-
311
- /**
312
- * Decide whether the result of the latest check means that the link is really broken
313
- * or should just be reported as a warning.
314
- *
315
- * @param array $check_results
316
- * @return array
317
- */
318
- private function decide_warning_state($check_results) {
319
- if ( !$check_results['broken'] && !$check_results['warning'] ) {
320
- //Nothing to do, this is a working link.
321
- return $check_results;
322
- }
323
-
324
- $configuration = blc_get_configuration();
325
- if ( !$configuration->get('warnings_enabled', true) ) {
326
- //The user wants all failures to be reported as "broken", regardless of severity.
327
- if ( $check_results['warning'] ) {
328
- $check_results['broken'] = true;
329
- $check_results['warning'] = false;
330
- }
331
- return $check_results;
332
- }
333
-
334
- $warning_reason = null;
335
- $failure_count = $this->check_count;
336
- $failure_duration = ($this->first_failure != 0) ? (time() - $this->first_failure) : 0;
337
- //These could be configurable, but lets put that off until someone actually asks for it.
338
- $duration_threshold = 24 * 3600;
339
- $count_threshold = 3;
340
-
341
- //We can't just use ($check_results['status_code'] == 'warning' because some "warning" problems are not
342
- //temporary. For example, region-restricted YouTube videos use the "warning" status code.
343
- $maybe_temporary_error = false;
344
-
345
- //Some basic heuristics to determine if this failure might be temporary.
346
- //----------------------------------------------------------------------
347
- if ( $check_results['timeout'] ) {
348
- $maybe_temporary_error = true;
349
- $warning_reason = 'Timeouts are sometimes caused by high server load or other temporary issues.';
350
- }
351
-
352
- $error_code = isset($check_results['error_code']) ? $check_results['error_code'] : '';
353
- if ( $error_code === 'connection_failed' ) {
354
- $maybe_temporary_error = true;
355
- $warning_reason = 'Connection failures are sometimes caused by high server load or other temporary issues.';
356
- }
357
-
358
- $http_code = intval($check_results['http_code']);
359
- $temporary_http_errors = array(
360
- 408, //Request timeout. Probably a plugin bug, but could just be an overloaded client server.
361
- 420, //Custom Twitter code returned when the client gets rate-limited.
362
- 429, //Client has sent too many requests in a given amount of time.
363
- 502, //Bad Gateway. Often a sign of a temporarily overloaded or misconfigured server.
364
- 503, //Service Unavailable.
365
- 504, //Gateway Timeout.
366
- 509, //Bandwidth Limit Exceeded.
367
- 520, //CloudFlare-specific "Origin Error" code.
368
- 522, //CloudFlare-specific "Connection timed out" code.
369
- 524, //Another CloudFlare-specific timeout code.
370
- );
371
- if ( in_array($http_code, $temporary_http_errors) ) {
372
- $maybe_temporary_error = true;
373
-
374
- if ( in_array($http_code, array(502, 503, 504, 509)) ) {
375
- $warning_reason = sprintf(
376
- 'HTTP error %d usually means that the site is down due to high server load or a configuration problem. '
377
- . 'This error is often temporary and will go away after while.',
378
- $http_code
379
- );
380
- } else {
381
- $warning_reason = 'This HTTP error is often temporary.';
382
- }
383
- }
384
-
385
- //----------------------------------------------------------------------
386
-
387
- //Attempt to detect false positives.
388
- $suspected_false_positive = false;
389
-
390
- //A "403 Forbidden" error on an internal link usually means something on the site is blocking automated
391
- //requests. Possible culprits include hotlink protection rules in .htaccess, badly configured IDS, and so on.
392
- $is_internal_link = $this->is_internal_to_domain();
393
- if ( $is_internal_link && ($http_code == 403) ) {
394
- $suspected_false_positive = true;
395
- $warning_reason = 'This might be a false positive. Make sure the link is not password-protected, '
396
- . 'and that your server is not set up to block automated requests.';
397
- }
398
-
399
- //Some hosting providers turn off loopback connections. This causes all internal links to be reported as broken.
400
- if ( $is_internal_link && in_array($error_code, array('connection_failed', 'couldnt_resolve_host')) ) {
401
- $suspected_false_positive = true;
402
- $warning_reason = 'This is probably a false positive. ';
403
- if ( $error_code === 'connection_failed' ) {
404
- $warning_reason .= 'The plugin could not connect to your site. That usually means that your '
405
- . 'hosting provider has disabled loopback connections.';
406
- } elseif ( $error_code === 'couldnt_resolve_host' ) {
407
- $warning_reason .= 'The plugin could not connect to your site because DNS resolution failed. '
408
- . 'This could mean DNS is configured incorrectly on your server.';
409
- }
410
- }
411
-
412
- //----------------------------------------------------------------------
413
-
414
- //Temporary problems and suspected false positives start out as warnings. False positives stay that way
415
- //indefinitely because they are usually caused by bugs and server configuration issues, not temporary downtime.
416
- if ( ($maybe_temporary_error && ($failure_count < $count_threshold)) || $suspected_false_positive ) {
417
- $check_results['warning'] = true;
418
- $check_results['broken'] = false;
419
- }
420
-
421
- //Upgrade temporary warnings to "broken" after X consecutive failures or Y hours, whichever comes first.
422
- $threshold_reached = ($failure_count >= $count_threshold) || ($failure_duration >= $duration_threshold);
423
- if ( $check_results['warning'] ) {
424
- if ( ($maybe_temporary_error && $threshold_reached) && !$suspected_false_positive ) {
425
- $check_results['warning'] = false;
426
- $check_results['broken'] = true;
427
- }
428
- }
429
-
430
- if ( !empty($warning_reason) && $check_results['warning'] ) {
431
- $formatted_reason = "\n==========\n"
432
- . 'Severity: Warning' . "\n"
433
- . 'Reason: ' . trim($warning_reason)
434
- . "\n==========\n";
435
-
436
- $check_results['log'] .= $formatted_reason;
437
- }
438
-
439
- return $check_results;
440
- }
441
-
442
- /**
443
- * A helper method used to update timestamps & other state-dependent fields
444
- * after the state of the link (broken vs working) has just been determined.
445
- *
446
- * @access private
447
- *
448
- * @param bool $broken
449
- * @param string $new_result_hash
450
- * @return void
451
- */
452
- private function status_changed($broken, $new_result_hash = ''){
453
- //If a link's status changes, un-dismiss it.
454
- if ( $this->result_hash != $new_result_hash ) {
455
- if ( $this->dismissed ) {
456
- $this->log .= sprintf(
457
- "Restoring a dismissed link. \nOld status: \n%s\nNew status: \n%s\n",
458
- $this->result_hash,
459
- $new_result_hash
460
- );
461
- }
462
- $this->dismissed = false;
463
- }
464
-
465
- if ( $this->false_positive && !empty($new_result_hash) ){
466
- //If the link has been marked as a (probable) false positive,
467
- //mark it as broken *only* if the new result is different from
468
- //the one that caused the user to mark it as a false positive.
469
- if ( $broken || $this->warning ){
470
- if ( $this->result_hash == $new_result_hash ){
471
- //Got the same result as before, assume it's still incorrect and the link actually works.
472
- $broken = false;
473
- $this->warning = false;
474
- } else {
475
- //Got a new result. Assume (quite optimistically) that it's not a false positive.
476
- $this->false_positive = false;
477
- }
478
- } else {
479
- //The plugin now thinks the link is working,
480
- //so it's no longer a false positive.
481
- $this->false_positive = false;
482
- }
483
- }
484
-
485
- $this->broken = $broken;
486
- $this->result_hash = $new_result_hash;
487
-
488
- //Update timestamps
489
- $this->last_check = $this->last_check_attempt;
490
- if ( $this->broken || $this->warning ){
491
- if ( empty($this->first_failure) ){
492
- $this->first_failure = $this->last_check;
493
- }
494
- } else {
495
- $this->first_failure = 0;
496
- $this->last_success = $this->last_check;
497
- $this->check_count = 0;
498
- }
499
-
500
- //Add a line indicating link status to the log
501
- if ( $this->broken || $this->warning ) {
502
- $this->log .= "\n" . __("Link is broken.", 'broken-link-checker');
503
- } else {
504
- $this->log .= "\n" . __("Link is valid.", 'broken-link-checker');
505
- }
506
- }
507
-
508
- /**
509
- * blcLink::save()
510
- * Save link data to DB.
511
- *
512
- * @return bool True if saved successfully, false otherwise.
513
- */
514
- function save(){
515
- global $wpdb, $blclog; /** @var wpdb $wpdb */
516
-
517
- if ( !$this->valid() ) return false;
518
-
519
- //A link can't be broken and treated as a warning at the same time.
520
- if ( $this->broken && $this->warning ) {
521
- $this->warning = false;
522
- }
523
-
524
- //Make a list of fields to be saved and their values in DB format
525
- $values = array();
526
- foreach($this->field_format as $field => $format){
527
- $values[$field] = $this->$field;
528
- }
529
- $values = $this->to_db_format($values);
530
-
531
- if ( $this->is_new ){
532
-
533
- TransactionManager::getInstance()->commit();
534
-
535
- //BUG: Technically, there should be a 'LOCK TABLES wp_blc_links WRITE' here. In fact,
536
- //the plugin should probably lock all involved tables whenever it parses something, lest
537
- //the user (ot another plugin) modify the thing being parsed while we're working.
538
- //The problem with table locking, though, is that parsing takes a long time and having
539
- //all of WP freeze while the plugin is working would be a Bad Thing. Food for thought.
540
-
541
- //Check if there's already a link with this URL present
542
- $q = $wpdb->prepare(
543
- "SELECT link_id FROM {$wpdb->prefix}blc_links WHERE url = %s",
544
- $this->url
545
- );
546
- $existing_id = $wpdb->get_var($q);
547
-
548
- if ( !empty($existing_id) ){
549
- //Dammit.
550
- $this->link_id = $existing_id;
551
- $this->is_new = false;
552
- return true;
553
- }
554
-
555
- //Insert a new row
556
- $q = sprintf(
557
- "INSERT INTO {$wpdb->prefix}blc_links( %s ) VALUES( %s )",
558
- implode(', ', array_keys($values)),
559
- implode(', ', array_values($values))
560
- );
561
- //FB::log($q, 'Link add query');
562
- $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Adding a new link. SQL query:' . "\n", $q);
563
-
564
- $rez = $wpdb->query($q) !== false;
565
-
566
- if ($rez){
567
- $this->link_id = $wpdb->insert_id;
568
- $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Database record created. ID = ' . $this->link_id);
569
- //FB::info($this->link_id, "Link added");
570
- //If the link was successfully saved then it's no longer "new"
571
- $this->is_new = false;
572
- } else {
573
- $blclog->error(__CLASS__ .':' . __FUNCTION__ . ' Error adding link', $this->url);
574
- //FB::error($wpdb->last_error, "Error adding link {$this->url}");
575
- }
576
-
577
- return $rez;
578
-
579
- } else {
580
- if ($this->isOptionLinkChanged !== true ) {
581
- TransactionManager::getInstance()->start();
582
- }
583
- $this->isOptionLinkChanged = false;
584
- //Generate the field = dbvalue expressions
585
- $set_exprs = array();
586
- foreach($values as $name => $value){
587
- $set_exprs[] = "$name = $value";
588
- }
589
- $set_exprs = implode(', ', $set_exprs);
590
-
591
- //Update an existing DB record
592
- $q = sprintf(
593
- "UPDATE {$wpdb->prefix}blc_links SET %s WHERE link_id=%d",
594
- $set_exprs,
595
- intval($this->link_id)
596
- );
597
- //FB::log($q, 'Link update query');
598
- $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Updating a link. SQL query:'. "\n", $q);
599
-
600
- $rez = $wpdb->query($q) !== false;
601
- if ( $rez ){
602
- //FB::log($this->link_id, "Link updated");
603
- $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Link updated.');
604
- } else {
605
- $blclog->error(__CLASS__ .':' . __FUNCTION__ . ' Error updating link', $this->url);
606
- //FB::error($wpdb->last_error, "Error updating link {$this->url}");
607
- }
608
-
609
- return $rez;
610
- }
611
- }
612
-
613
- /**
614
- * A helper method for converting the link's field values to DB format and escaping them
615
- * for use in SQL queries.
616
- *
617
- * @param array $values
618
- * @return array
619
- */
620
- function to_db_format($values){
621
- global $wpdb; /** @var wpdb $wpdb */
622
-
623
- $dbvalues = array();
624
-
625
- foreach($values as $name => $value){
626
- //Skip fields that don't exist in the blc_links table.
627
- if ( !isset($this->field_format[$name]) ){
628
- continue;
629
- }
630
-
631
- $format = $this->field_format[$name];
632
-
633
- //Convert native values to a format comprehensible to the DB
634
- switch($format){
635
-
636
- case 'datetime' :
637
- if ( empty($value) ){
638
- $value = '0000-00-00 00:00:00';
639
- } else {
640
- $value = date('Y-m-d H:i:s', $value);
641
- }
642
- $format = '%s';
643
- break;
644
-
645
- case 'bool':
646
- if ( $value ){
647
- $value = 1;
648
- } else {
649
- $value = 0;
650
- }
651
- $format = '%d';
652
- break;
653
- }
654
-
655
- //Escapize
656
- $value = $wpdb->prepare($format, $value);
657
-
658
- $dbvalues[$name] = $value;
659
- }
660
-
661
- return $dbvalues;
662
- }
663
-
664
- /**
665
- * A helper method for converting values fetched from the database to native datatypes.
666
- *
667
- * @param array $values
668
- * @return array
669
- */
670
- function to_native_format($values){
671
-
672
- foreach($values as $name => $value){
673
- //Don't process fields that don't exist in the blc_links table.
674
- if ( !isset($this->field_format[$name]) ){
675
- continue;
676
- }
677
-
678
- $format = $this->field_format[$name];
679
-
680
- //Convert values in DB format to native datatypes.
681
- switch($format){
682
-
683
- case 'datetime' :
684
- if ( $value == '0000-00-00 00:00:00' ){
685
- $value = 0;
686
- } elseif (is_string($value)) {
687
- $value = strtotime($value);
688
- }
689
- break;
690
-
691
- case 'bool':
692
- $value = (bool)$value;
693
- break;
694
-
695
- case '%d':
696
- $value = intval($value);
697
- break;
698
-
699
- case '%f':
700
- $value = floatval($value);
701
- break;
702
-
703
- }
704
-
705
- $values[$name] = $value;
706
- }
707
-
708
- return $values;
709
- }
710
-
711
- /**
712
- * blcLink::edit()
713
- * Edit all instances of the link by changing the URL.
714
- *
715
- * Here's how this really works : create a new link with the new URL. Then edit()
716
- * all instances and point them to the new link record. If some instance can't be
717
- * edited they will still point to the old record. The old record is deleted
718
- * if all instances were edited successfully.
719
- *
720
- * @param string $new_url
721
- * @param string $new_text Optional.
722
- * @return array An associative array with these keys :
723
- * new_link_id - the database ID of the new link.
724
- * new_link - the new link (an instance of blcLink).
725
- * cnt_okay - the number of successfully edited link instances.
726
- * cnt_error - the number of instances that caused problems.
727
- * errors - an array of WP_Error objects corresponding to the failed edits.
728
- */
729
- function edit($new_url, $new_text = null){
730
- if ( !$this->valid() ){
731
- return new WP_Error(
732
- 'link_invalid',
733
- __("Link is not valid", 'broken-link-checker')
734
- );
735
- }
736
-
737
- //FB::info('Changing link '.$this->link_id .' to URL "'.$new_url.'"');
738
-
739
- $instances = $this->get_instances();
740
- //Fail if there are no instances
741
- if (empty($instances)) {
742
- return array(
743
- 'new_link_id' => $this->link_id,
744
- 'new_link' => $this,
745
- 'cnt_okay' => 0,
746
- 'cnt_error' => 0,
747
- 'errors' => array(
748
- new WP_Error(
749
- 'no_instances_found',
750
- __('This link can not be edited because it is not used anywhere on this site.', 'broken-link-checker')
751
- )
752
- )
753
- );
754
- };
755
-
756
- //Load or create a link with the URL = $new_url
757
- $new_link = new blcLink($new_url);
758
- $was_new = $new_link->is_new;
759
- if ($new_link->is_new) {
760
- //FB::log($new_link, 'Saving a new link');
761
- $new_link->save(); //so that we get a valid link_id
762
- }
763
-
764
- //FB::log("Changing link to $new_url");
765
-
766
- if ( empty($new_link->link_id) ){
767
- //FB::error("Failed to create a new link record");
768
- return array(
769
- 'new_link_id' => $this->link_id,
770
- 'new_link' => $this,
771
- 'cnt_okay' => 0,
772
- 'cnt_error' => 0,
773
- 'errors' => array(
774
- new WP_Error(
775
- 'link_creation_failed',
776
- __('Failed to create a DB entry for the new URL.', 'broken-link-checker')
777
- )
778
- )
779
- );
780
- }
781
-
782
- $cnt_okay = $cnt_error = 0;
783
- $errors = array();
784
-
785
- //Edit each instance.
786
- //FB::info('Editing ' . count($instances) . ' instances');
787
- foreach ( $instances as $instance ){
788
- $rez = $instance->edit( $new_url, $this->url, $new_text );
789
- if ( is_wp_error($rez) ){
790
- $cnt_error++;
791
- array_push($errors, $rez);
792
- //FB::error($instance, 'Failed to edit instance ' . $instance->instance_id);
793
- } else {
794
- $cnt_okay++;
795
- $instance->link_id = $new_link->link_id;
796
- $instance->save();
797
- //FB::info($instance, 'Successfully edited instance ' . $instance->instance_id);
798
- }
799
- }
800
-
801
- //If all instances were edited successfully we can delete the old link record.
802
- //UNLESS this link is equal to the new link (which should never happen, but whatever).
803
- if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) && ( $this->link_id != $new_link->link_id ) ){
804
- $this->forget( false );
805
- }
806
-
807
- //On the other hand, if no instances could be edited and the $new_link was really new,
808
- //then delete it.
809
- if ( ( $cnt_okay == 0 ) && $was_new ){
810
- $new_link->forget( false );
811
- $new_link = $this;
812
- }
813
-
814
- return array(
815
- 'new_link_id' => $new_link->link_id,
816
- 'new_link' => $new_link,
817
- 'cnt_okay' => $cnt_okay,
818
- 'cnt_error' => $cnt_error,
819
- 'errors' => $errors,
820
- );
821
- }
822
-
823
- /**
824
- * Edit all of of this link's instances and replace the URL with the URL that it redirects to.
825
- * This method does nothing if the link isn't a redirect.
826
- *
827
- * @see blcLink::edit()
828
- *
829
- * @return array|WP_Error
830
- */
831
- function deredirect(){
832
- if ( !$this->valid() ){
833
- return new WP_Error(
834
- 'link_invalid',
835
- __("Link is not valid", 'broken-link-checker')
836
- );
837
- }
838
-
839
- if ( ($this->redirect_count <= 0) || empty($this->final_url) ){
840
- return new WP_Error(
841
- 'not_redirect',
842
- __("This link is not a redirect", 'broken-link-checker')
843
- );
844
- }
845
-
846
- //Preserve the existing #anchor if the redirect doesn't include one.
847
- $new_url = $this->final_url;
848
- $anchor = @parse_url($this->url, PHP_URL_FRAGMENT);
849
- if ( !empty($anchor) && (strrpos($new_url, '#') === false) ) {
850
- $new_url .= '#' . $anchor;
851
- }
852
-
853
- return $this->edit($new_url);
854
- }
855
-
856
- /**
857
- * Unlink all instances and delete the link record.
858
- *
859
- * @return array|WP_Error An associative array with these keys :
860
- * cnt_okay - the number of successfully removed instances.
861
- * cnt_error - the number of instances that couldn't be removed.
862
- * link_deleted - true if the link record was deleted.
863
- * errors - an array of WP_Error objects describing the errors that were encountered, if any.
864
- */
865
- function unlink(){
866
- if ( !$this->valid() ){
867
- return new WP_Error(
868
- 'link_invalid',
869
- __("Link is not valid", 'broken-link-checker')
870
- );
871
- }
872
-
873
- //FB::info($this, 'Removing link');
874
- $instances = $this->get_instances();
875
-
876
- //No instances? Just remove the link then.
877
- if (empty($instances)) {
878
- //FB::warn("This link has no instances. Deleting the link.");
879
- $rez = $this->forget( false ) !== false;
880
-
881
- if ( $rez ){
882
- return array(
883
- 'cnt_okay' => 1,
884
- 'cnt_error' => 0,
885
- 'link_deleted' => true,
886
- 'errors' => array(),
887
- );
888
- } else {
889
- return array(
890
- 'cnt_okay' => 0,
891
- 'cnt_error' => 0,
892
- 'link_deleted' => false,
893
- 'errors' => array(
894
- new WP_Error(
895
- "deletion_failed",
896
- __("Couldn't delete the link's database record", 'broken-link-checker')
897
- )
898
- ),
899
- );
900
- }
901
- }
902
-
903
-
904
- //FB::info('Unlinking ' . count($instances) . ' instances');
905
-
906
- $cnt_okay = $cnt_error = 0;
907
- $errors = array();
908
-
909
- //Unlink each instance.
910
- foreach ( $instances as $instance ){
911
- $rez = $instance->unlink( $this->url );
912
-
913
- if ( is_wp_error($rez) ){
914
- $cnt_error++;
915
- array_push($errors, $rez);
916
- //FB::error( $instance, 'Failed to unlink instance' );
917
- } else {
918
- $cnt_okay++;
919
- //FB::info( $instance, 'Successfully unlinked instance' );
920
- }
921
- }
922
-
923
- //If all instances were unlinked successfully we can delete the link record.
924
- if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) ){
925
- //FB::log('Instances removed, deleting the link.');
926
- $link_deleted = $this->forget() !== false;
927
-
928
- if ( !$link_deleted ){
929
- array_push(
930
- $errors,
931
- new WP_Error(
932
- "deletion_failed",
933
- __("Couldn't delete the link's database record", 'broken-link-checker')
934
- )
935
- );
936
- }
937
-
938
- } else {
939
- //FB::error("Something went wrong. Unlinked instances : $cnt_okay, errors : $cnt_error");
940
- $link_deleted = false;
941
- }
942
-
943
- return array(
944
- 'cnt_okay' => $cnt_okay,
945
- 'cnt_error' => $cnt_error,
946
- 'link_deleted' => $link_deleted,
947
- 'errors' => $errors,
948
- );
949
- }
950
-
951
- /**
952
- * Remove the link and (optionally) its instance records from the DB. Doesn't alter posts/etc.
953
- *
954
- * @param bool $remove_instances
955
- * @return mixed 1 on success, 0 if link not found, false on error.
956
- */
957
- function forget($remove_instances = true){
958
- global $wpdb; /** @var wpdb $wpdb */
959
- if ( !$this->valid() ) return false;
960
-
961
- if ( !empty($this->link_id) ){
962
- //FB::info($this, 'Deleting link from DB');
963
-
964
- if ( $remove_instances ){
965
- //Remove instances, if any
966
- $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE link_id=%d", $this->link_id) );
967
- }
968
-
969
- //Remove the link itself
970
- $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_links WHERE link_id=%d", $this->link_id) );
971
- $this->link_id = 0;
972
-
973
- return $rez;
974
- } else {
975
- return false;
976
- }
977
-
978
- }
979
-
980
- /**
981
- * Get a list of the link's instances
982
- *
983
- * @param bool $ignore_cache Don't use the internally cached instance list.
984
- * @param string $purpose
985
- * @return blcLinkInstance[] An array of instance objects or FALSE on failure.
986
- */
987
- function get_instances( $ignore_cache = false, $purpose = '' ){
988
- if ( !$this->valid() || empty($this->link_id) ) return false;
989
-
990
- if ( $ignore_cache || is_null($this->_instances) ){
991
- $instances = blc_get_instances( array($this->link_id), $purpose );
992
- if ( !empty($instances) ){
993
- $this->_instances = $instances[$this->link_id];
994
- }
995
- }
996
-
997
- return $this->_instances;
998
- }
999
-
1000
- /**
1001
- * Determine the status text and status code corresponding to the current state of this link.
1002
- *
1003
- * @return array Associative array with two keys, 'text' and 'code'.
1004
- */
1005
- function analyse_status(){
1006
- $code = BLC_LINK_STATUS_UNKNOWN;
1007
- $text = _x('Unknown', 'link status', 'broken-link-checker');
1008
-
1009
- //Status text
1010
- if ( isset($this->status_text) && !empty($this->status_text) && !empty($this->status_code) ){
1011
-
1012
- //Lucky, the checker module has already set it for us.
1013
- $text = $this->status_text;
1014
- $code = $this->status_code;
1015
-
1016
- } else {
1017
-
1018
- if ( $this->broken || $this->warning ){
1019
- $code = BLC_LINK_STATUS_WARNING;
1020
- $text = __('Unknown Error', 'broken-link-checker');
1021
-
1022
- if ( $this->timeout ){
1023
-
1024
- $text = __('Timeout', 'broken-link-checker');
1025
- $code = BLC_LINK_STATUS_WARNING;
1026
-
1027
- } elseif ( $this->http_code ) {
1028
-
1029
- //Only 404 (Not Found) and 410 (Gone) are treated as broken-for-sure.
1030
- if ( in_array($this->http_code, array(404, 410)) ){
1031
- $code = BLC_LINK_STATUS_ERROR;
1032
- } else {
1033
- $code = BLC_LINK_STATUS_WARNING;
1034
- }
1035
-
1036
- if ( array_key_exists(intval($this->http_code), $this->http_status_codes) ){
1037
- $text = $this->http_status_codes[intval($this->http_code)];
1038
- }
1039
- }
1040
-
1041
- } else {
1042
-
1043
- if ( !$this->last_check ) {
1044
- $text = __('Not checked', 'broken-link-checker');
1045
- $code = BLC_LINK_STATUS_UNKNOWN;
1046
- } elseif ( $this->false_positive ) {
1047
- $text = __('False positive', 'broken-link-checker');
1048
- $code = BLC_LINK_STATUS_UNKNOWN;
1049
- } else {
1050
- $text = _x('OK', 'link status', 'broken-link-checker');
1051
- $code = BLC_LINK_STATUS_OK;
1052
- }
1053
-
1054
- }
1055
- }
1056
-
1057
- return compact('text', 'code');
1058
- }
1059
-
1060
- /**
1061
- * Get the link URL in ASCII-compatible encoding.
1062
- *
1063
- * @return string
1064
- */
1065
- function get_ascii_url(){
1066
- return blcUtility::idn_to_ascii($this->url);
1067
- }
1068
-
1069
- /**
1070
- * Check if this link points to a page on the same domain as the current site.
1071
- *
1072
- * Note: Only checks the domain name, not subdirectory. If there are two separate WP sites A and B installed
1073
- * in two different subdirectories of the same domain, this method will treat a link from site A to B as internal.
1074
- *
1075
- * @return bool
1076
- */
1077
- public function is_internal_to_domain() {
1078
- $host = @parse_url($this->url, PHP_URL_HOST);
1079
- if ( empty($host) ) {
1080
- return false;
1081
- }
1082
-
1083
- $site_host = @parse_url(get_site_url(), PHP_URL_HOST);
1084
- if ( empty($site_host) ) {
1085
- return false;
1086
- }
1087
-
1088
- //Some users are inconsistent with using/not using the www prefix, so get rid of it.
1089
- $site_host = preg_replace('@^www\.@', '', $site_host, 1);
1090
-
1091
- //Check if $host ends with $site_host. This means blah.example.com will match example.com.
1092
- return (substr($host, -strlen($site_host)) === $site_host);
1093
- }
1094
-
1095
- /**
1096
- * Remove the query string from an URL.
1097
- *
1098
- * @param string $url
1099
- * @return string
1100
- */
1101
- public static function remove_query_string($url) {
1102
- return preg_replace('@\?[^#]*?(#|$)@', '$1', $url);
1103
- }
1104
- }
1105
-
1106
- } //class_exists
1107
-
1108
- /**
1109
- * Remove orphaned links that have no corresponding instances.
1110
- *
1111
- * @param int|array $link_id (optional) Only check these links
1112
- * @return bool
1113
- */
1114
- function blc_cleanup_links( $link_id = null ){
1115
- global $wpdb; /* @var wpdb $wpdb */
1116
- global $blclog;
1117
-
1118
- $start = microtime(true);
1119
- $q = "DELETE FROM {$wpdb->prefix}blc_links
1120
- USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1121
- ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1122
- WHERE
1123
- {$wpdb->prefix}blc_instances.link_id IS NULL";
1124
-
1125
- if ( $link_id !== null ) {
1126
- if ( !is_array($link_id) ){
1127
- $link_id = array( intval($link_id) );
1128
- }
1129
- $q .= " AND {$wpdb->prefix}blc_links.link_id IN (" . implode(', ', $link_id) . ')';
1130
- }
1131
-
1132
- $rez = $wpdb->query( $q );
1133
- $elapsed = microtime(true) - $start;
1134
- $blclog->log(sprintf('... %d links deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
1135
-
1136
- return $rez !== false;
1137
- }
1138
-
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2010
6
+ */
7
+
8
+ if (!class_exists('blcLink')){
9
+
10
+ define('BLC_LINK_STATUS_UNKNOWN', 'unknown');
11
+ define('BLC_LINK_STATUS_OK', 'ok');
12
+ define('BLC_LINK_STATUS_INFO', 'info');
13
+ define('BLC_LINK_STATUS_WARNING', 'warning');
14
+ define('BLC_LINK_STATUS_ERROR', 'error');
15
+
16
+ class blcLink {
17
+
18
+ //Object state
19
+ var $is_new = false;
20
+
21
+ //DB fields
22
+ var $link_id = 0;
23
+ var $url = '';
24
+
25
+ var $being_checked = false;
26
+ var $last_check = 0;
27
+ var $last_check_attempt = 0;
28
+ var $check_count = 0;
29
+ var $http_code = 0;
30
+ var $request_duration = 0;
31
+ var $timeout = false;
32
+
33
+ var $redirect_count = 0;
34
+ var $final_url = '';
35
+
36
+ var $broken = false;
37
+ public $warning = false;
38
+ var $first_failure = 0;
39
+ var $last_success = 0;
40
+ var $may_recheck = 1;
41
+
42
+ var $false_positive = false;
43
+ var $result_hash = '';
44
+
45
+ var $dismissed = false;
46
+
47
+ var $status_text = '';
48
+ var $status_code = '';
49
+
50
+ var $log = '';
51
+
52
+ //A list of DB fields and their storage formats
53
+ var $field_format;
54
+
55
+ //A cached list of the link's instances
56
+ var $_instances = null;
57
+
58
+ var $http_status_codes = array(
59
+ // [Informational 1xx]
60
+ 100=>'Continue',
61
+ 101=>'Switching Protocols',
62
+ // [Successful 2xx]
63
+ 200=>'OK',
64
+ 201=>'Created',
65
+ 202=>'Accepted',
66
+ 203=>'Non-Authoritative Information',
67
+ 204=>'No Content',
68
+ 205=>'Reset Content',
69
+ 206=>'Partial Content',
70
+ // [Redirection 3xx]
71
+ 300=>'Multiple Choices',
72
+ 301=>'Moved Permanently',
73
+ 302=>'Moved Temporarily',
74
+ 303=>'See Other',
75
+ 304=>'Not Modified',
76
+ 305=>'Use Proxy',
77
+ //306=>'(Unused)',
78
+ 307=>'Temporary Redirect',
79
+ // [Client Error 4xx]
80
+ 400=>'Bad Request',
81
+ 401=>'Unauthorized',
82
+ 402=>'Payment Required',
83
+ 403=>'Forbidden',
84
+ 404=>'Not Found',
85
+ 405=>'Method Not Allowed',
86
+ 406=>'Not Acceptable',
87
+ 407=>'Proxy Authentication Required',
88
+ 408=>'Request Timeout',
89
+ 409=>'Conflict',
90
+ 410=>'Gone',
91
+ 411=>'Length Required',
92
+ 412=>'Precondition Failed',
93
+ 413=>'Request Entity Too Large',
94
+ 414=>'Request-URI Too Long',
95
+ 415=>'Unsupported Media Type',
96
+ 416=>'Requested Range Not Satisfiable',
97
+ 417=>'Expectation Failed',
98
+ // [Server Error 5xx]
99
+ 500=>'Internal Server Error',
100
+ 501=>'Not Implemented',
101
+ 502=>'Bad Gateway',
102
+ 503=>'Service Unavailable',
103
+ 504=>'Gateway Timeout',
104
+ 505=>'HTTP Version Not Supported',
105
+ 509=>'Bandwidth Limit Exceeded',
106
+ 510=>'Not Extended',
107
+ );
108
+ var $isOptionLinkChanged = false;
109
+ function __construct($arg = null){
110
+ global $wpdb, $blclog; /** @var wpdb $wpdb */
111
+
112
+ $this->field_format = array(
113
+ 'url' => '%s',
114
+ 'first_failure' => 'datetime',
115
+ 'last_check' => 'datetime',
116
+ 'last_success' => 'datetime',
117
+ 'last_check_attempt' => 'datetime',
118
+ 'check_count' => '%d',
119
+ 'final_url' => '%s',
120
+ 'redirect_count' => '%d',
121
+ 'log' => '%s',
122
+ 'http_code' => '%d',
123
+ 'request_duration' => '%F',
124
+ 'timeout' => 'bool',
125
+ 'result_hash' => '%s',
126
+ 'broken' => 'bool',
127
+ 'warning' => 'bool',
128
+ 'false_positive' => 'bool',
129
+ 'may_recheck' => 'bool',
130
+ 'being_checked' => 'bool',
131
+ 'status_text' => '%s',
132
+ 'status_code' => '%s',
133
+ 'dismissed' => 'bool',
134
+ );
135
+
136
+ if (is_numeric($arg)){
137
+ //Load a link with ID = $arg from the DB.
138
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE link_id=%d LIMIT 1", $arg);
139
+ $arr = $wpdb->get_row( $q, ARRAY_A );
140
+
141
+ if ( is_array($arr) ){ //Loaded successfully
142
+ $this->set_values($arr);
143
+ } else {
144
+ //Link not found. The object is invalid.
145
+ //I'd throw an error, but that wouldn't be PHP 4 compatible...
146
+ $blclog->warn(__CLASS__ .':' . __FUNCTION__ . ' Link not found.', $arg);
147
+ }
148
+
149
+ } else if (is_string($arg)){
150
+ //Load a link with URL = $arg from the DB. Create a new one if the record isn't found.
151
+ // $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Trying to load a link by URL:', $arg);
152
+ $q = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}blc_links WHERE url=%s LIMIT 1", $arg);
153
+ $arr = $wpdb->get_row( $q, ARRAY_A );
154
+
155
+ if ( is_array($arr) ){ //Loaded successfully
156
+ // $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Success!');
157
+ $this->set_values($arr);
158
+ } else { //Link not found, treat as new
159
+ // $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Link not found.');
160
+ $this->url = $arg;
161
+ $this->is_new = true;
162
+ }
163
+
164
+ } else if (is_array($arg)){
165
+ $this->set_values($arg);
166
+ //Is this a new link?
167
+ $this->is_new = empty($this->link_id);
168
+ } else {
169
+ $this->is_new = true;
170
+ }
171
+ }
172
+
173
+ function blcLink($arg = null){
174
+ $this->__construct($arg);
175
+ }
176
+
177
+ /**
178
+ * blcLink::set_values()
179
+ * Set the internal values to the ones provided in an array (doesn't sanitize).
180
+ *
181
+ * @param array $arr An associative array of values
182
+ * @return void
183
+ */
184
+ function set_values($arr){
185
+ $arr = $this->to_native_format($arr);
186
+
187
+ foreach( $arr as $key => $value ){
188
+ $this->$key = $value;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Check whether the object represents a valid link
194
+ *
195
+ * @return bool
196
+ */
197
+ function valid(){
198
+ return !empty( $this->url ) && ( !empty($this->link_id) || $this->is_new );
199
+ }
200
+
201
+ /**
202
+ * Check if the link is working.
203
+ *
204
+ * @param bool $save_results Automatically save the results of the check.
205
+ * @return bool
206
+ */
207
+ function check( $save_results = true ){
208
+ if ( !$this->valid() ) return false;
209
+
210
+ $this->last_check_attempt = time();
211
+
212
+ /*
213
+ If the link is still marked as in the process of being checked, that probably means
214
+ that the last time the plugin tried to check it the script got terminated by PHP for
215
+ running over the execution time limit or causing a fatal error.
216
+
217
+ This problem is likely to be temporary for most links, so we leave it be and treat it
218
+ as any other link (i.e. check it again later using the default recheck periodicity).
219
+ */
220
+ if ( $this->being_checked ) {
221
+ $this->being_checked = false;
222
+
223
+ //Add an explanatory notice to the link's log
224
+ $error_notice = "[" . __("The plugin script was terminated while trying to check the link.", 'broken-link-checker') . "]";
225
+ if ( strpos($this->log, $error_notice) === false ){
226
+ $this->log = $error_notice . "\r\n" . $this->log;
227
+ }
228
+
229
+ if ( $save_results ){
230
+ $this->save();
231
+ }
232
+
233
+ return false;
234
+ }
235
+
236
+ $this->being_checked = true;
237
+ $this->check_count++;
238
+
239
+ if ( $save_results ) {
240
+
241
+ //Update the DB record before actually performing the check.
242
+ //Useful if something goes terribly wrong while checking this particular URL
243
+ //(e.g. the server might kill the script for running over the exec. time limit).
244
+ //Note : might be unnecessary.
245
+ $this->save();
246
+ }
247
+
248
+ $defaults = array(
249
+ 'broken' => false,
250
+ 'warning' => false,
251
+ 'http_code' => 0,
252
+ 'redirect_count' => 0,
253
+ 'final_url' => $this->url,
254
+ 'request_duration' => 0,
255
+ 'timeout' => false,
256
+ 'may_recheck' => true,
257
+ 'log' => '',
258
+ 'result_hash' => '',
259
+ 'status_text' => '',
260
+ 'status_code' => '',
261
+ );
262
+
263
+
264
+ $checker = blcCheckerHelper::get_checker_for($this->get_ascii_url());
265
+
266
+ if ( is_null($checker) ){
267
+ //Oops, there are no checker implementations that can handle this link.
268
+ //Assume the link is working, but leave a note in the log.
269
+ $this->broken = false;
270
+ $this->being_checked = false;
271
+ $this->log = __("The plugin doesn't know how to check this type of link.", 'broken-link-checker');
272
+
273
+ if ( $save_results ){
274
+ $this->save();
275
+ }
276
+
277
+ return true;
278
+ }
279
+
280
+ //Check the link
281
+ $rez = $checker->check($this->get_ascii_url());
282
+ //FB::info($rez, "Check results");
283
+
284
+ $results = array_merge($defaults, $rez);
285
+
286
+ //Some HTTP errors can be treated as warnings.
287
+ $results = $this->decide_warning_state($results);
288
+
289
+ //Filter the returned array to leave only the restricted set of keys that we're interested in.
290
+ $results = array_intersect_key($results, $defaults);
291
+
292
+ //The result hash is special - see blcLink::status_changed()
293
+ $new_result_hash = $results['result_hash'];
294
+ unset($results['result_hash']);
295
+
296
+ //Update the object's fields with the new results
297
+ $this->set_values($results);
298
+
299
+ //Update timestamps & state-dependent fields
300
+ $this->status_changed($results['broken'], $new_result_hash);
301
+ $this->being_checked = false;
302
+
303
+ //Save results to the DB
304
+ if($save_results){
305
+ $this->save();
306
+ }
307
+
308
+ return $this->broken;
309
+ }
310
+
311
+ /**
312
+ * Decide whether the result of the latest check means that the link is really broken
313
+ * or should just be reported as a warning.
314
+ *
315
+ * @param array $check_results
316
+ * @return array
317
+ */
318
+ private function decide_warning_state($check_results) {
319
+ if ( !$check_results['broken'] && !$check_results['warning'] ) {
320
+ //Nothing to do, this is a working link.
321
+ return $check_results;
322
+ }
323
+
324
+ $configuration = blc_get_configuration();
325
+ if ( !$configuration->get('warnings_enabled', true) ) {
326
+ //The user wants all failures to be reported as "broken", regardless of severity.
327
+ if ( $check_results['warning'] ) {
328
+ $check_results['broken'] = true;
329
+ $check_results['warning'] = false;
330
+ }
331
+ return $check_results;
332
+ }
333
+
334
+ $warning_reason = null;
335
+ $failure_count = $this->check_count;
336
+ $failure_duration = ($this->first_failure != 0) ? (time() - $this->first_failure) : 0;
337
+ //These could be configurable, but lets put that off until someone actually asks for it.
338
+ $duration_threshold = 24 * 3600;
339
+ $count_threshold = 3;
340
+
341
+ //We can't just use ($check_results['status_code'] == 'warning' because some "warning" problems are not
342
+ //temporary. For example, region-restricted YouTube videos use the "warning" status code.
343
+ $maybe_temporary_error = false;
344
+
345
+ //Some basic heuristics to determine if this failure might be temporary.
346
+ //----------------------------------------------------------------------
347
+ if ( $check_results['timeout'] ) {
348
+ $maybe_temporary_error = true;
349
+ $warning_reason = 'Timeouts are sometimes caused by high server load or other temporary issues.';
350
+ }
351
+
352
+ $error_code = isset($check_results['error_code']) ? $check_results['error_code'] : '';
353
+ if ( $error_code === 'connection_failed' ) {
354
+ $maybe_temporary_error = true;
355
+ $warning_reason = 'Connection failures are sometimes caused by high server load or other temporary issues.';
356
+ }
357
+
358
+ $http_code = intval($check_results['http_code']);
359
+ $temporary_http_errors = array(
360
+ 408, //Request timeout. Probably a plugin bug, but could just be an overloaded client server.
361
+ 420, //Custom Twitter code returned when the client gets rate-limited.
362
+ 429, //Client has sent too many requests in a given amount of time.
363
+ 502, //Bad Gateway. Often a sign of a temporarily overloaded or misconfigured server.
364
+ 503, //Service Unavailable.
365
+ 504, //Gateway Timeout.
366
+ 509, //Bandwidth Limit Exceeded.
367
+ 520, //CloudFlare-specific "Origin Error" code.
368
+ 522, //CloudFlare-specific "Connection timed out" code.
369
+ 524, //Another CloudFlare-specific timeout code.
370
+ );
371
+ if ( in_array($http_code, $temporary_http_errors) ) {
372
+ $maybe_temporary_error = true;
373
+
374
+ if ( in_array($http_code, array(502, 503, 504, 509)) ) {
375
+ $warning_reason = sprintf(
376
+ 'HTTP error %d usually means that the site is down due to high server load or a configuration problem. '
377
+ . 'This error is often temporary and will go away after while.',
378
+ $http_code
379
+ );
380
+ } else {
381
+ $warning_reason = 'This HTTP error is often temporary.';
382
+ }
383
+ }
384
+
385
+ //----------------------------------------------------------------------
386
+
387
+ //Attempt to detect false positives.
388
+ $suspected_false_positive = false;
389
+
390
+ //A "403 Forbidden" error on an internal link usually means something on the site is blocking automated
391
+ //requests. Possible culprits include hotlink protection rules in .htaccess, badly configured IDS, and so on.
392
+ $is_internal_link = $this->is_internal_to_domain();
393
+ if ( $is_internal_link && ($http_code == 403) ) {
394
+ $suspected_false_positive = true;
395
+ $warning_reason = 'This might be a false positive. Make sure the link is not password-protected, '
396
+ . 'and that your server is not set up to block automated requests.';
397
+ }
398
+
399
+ //Some hosting providers turn off loopback connections. This causes all internal links to be reported as broken.
400
+ if ( $is_internal_link && in_array($error_code, array('connection_failed', 'couldnt_resolve_host')) ) {
401
+ $suspected_false_positive = true;
402
+ $warning_reason = 'This is probably a false positive. ';
403
+ if ( $error_code === 'connection_failed' ) {
404
+ $warning_reason .= 'The plugin could not connect to your site. That usually means that your '
405
+ . 'hosting provider has disabled loopback connections.';
406
+ } elseif ( $error_code === 'couldnt_resolve_host' ) {
407
+ $warning_reason .= 'The plugin could not connect to your site because DNS resolution failed. '
408
+ . 'This could mean DNS is configured incorrectly on your server.';
409
+ }
410
+ }
411
+
412
+ //----------------------------------------------------------------------
413
+
414
+ //Temporary problems and suspected false positives start out as warnings. False positives stay that way
415
+ //indefinitely because they are usually caused by bugs and server configuration issues, not temporary downtime.
416
+ if ( ($maybe_temporary_error && ($failure_count < $count_threshold)) || $suspected_false_positive ) {
417
+ $check_results['warning'] = true;
418
+ $check_results['broken'] = false;
419
+ }
420
+
421
+ //Upgrade temporary warnings to "broken" after X consecutive failures or Y hours, whichever comes first.
422
+ $threshold_reached = ($failure_count >= $count_threshold) || ($failure_duration >= $duration_threshold);
423
+ if ( $check_results['warning'] ) {
424
+ if ( ($maybe_temporary_error && $threshold_reached) && !$suspected_false_positive ) {
425
+ $check_results['warning'] = false;
426
+ $check_results['broken'] = true;
427
+ }
428
+ }
429
+
430
+ if ( !empty($warning_reason) && $check_results['warning'] ) {
431
+ $formatted_reason = "\n==========\n"
432
+ . 'Severity: Warning' . "\n"
433
+ . 'Reason: ' . trim($warning_reason)
434
+ . "\n==========\n";
435
+
436
+ $check_results['log'] .= $formatted_reason;
437
+ }
438
+
439
+ return $check_results;
440
+ }
441
+
442
+ /**
443
+ * A helper method used to update timestamps & other state-dependent fields
444
+ * after the state of the link (broken vs working) has just been determined.
445
+ *
446
+ * @access private
447
+ *
448
+ * @param bool $broken
449
+ * @param string $new_result_hash
450
+ * @return void
451
+ */
452
+ private function status_changed($broken, $new_result_hash = ''){
453
+ //If a link's status changes, un-dismiss it.
454
+ if ( $this->result_hash != $new_result_hash ) {
455
+ if ( $this->dismissed ) {
456
+ $this->log .= sprintf(
457
+ "Restoring a dismissed link. \nOld status: \n%s\nNew status: \n%s\n",
458
+ $this->result_hash,
459
+ $new_result_hash
460
+ );
461
+ }
462
+ $this->dismissed = false;
463
+ }
464
+
465
+ if ( $this->false_positive && !empty($new_result_hash) ){
466
+ //If the link has been marked as a (probable) false positive,
467
+ //mark it as broken *only* if the new result is different from
468
+ //the one that caused the user to mark it as a false positive.
469
+ if ( $broken || $this->warning ){
470
+ if ( $this->result_hash == $new_result_hash ){
471
+ //Got the same result as before, assume it's still incorrect and the link actually works.
472
+ $broken = false;
473
+ $this->warning = false;
474
+ } else {
475
+ //Got a new result. Assume (quite optimistically) that it's not a false positive.
476
+ $this->false_positive = false;
477
+ }
478
+ } else {
479
+ //The plugin now thinks the link is working,
480
+ //so it's no longer a false positive.
481
+ $this->false_positive = false;
482
+ }
483
+ }
484
+
485
+ $this->broken = $broken;
486
+ $this->result_hash = $new_result_hash;
487
+
488
+ //Update timestamps
489
+ $this->last_check = $this->last_check_attempt;
490
+ if ( $this->broken || $this->warning ){
491
+ if ( empty($this->first_failure) ){
492
+ $this->first_failure = $this->last_check;
493
+ }
494
+ } else {
495
+ $this->first_failure = 0;
496
+ $this->last_success = $this->last_check;
497
+ $this->check_count = 0;
498
+ }
499
+
500
+ //Add a line indicating link status to the log
501
+ if ( $this->broken || $this->warning ) {
502
+ $this->log .= "\n" . __("Link is broken.", 'broken-link-checker');
503
+ } else {
504
+ $this->log .= "\n" . __("Link is valid.", 'broken-link-checker');
505
+ }
506
+ }
507
+
508
+ /**
509
+ * blcLink::save()
510
+ * Save link data to DB.
511
+ *
512
+ * @return bool True if saved successfully, false otherwise.
513
+ */
514
+ function save(){
515
+ global $wpdb, $blclog; /** @var wpdb $wpdb */
516
+
517
+ if ( !$this->valid() ) return false;
518
+
519
+ //A link can't be broken and treated as a warning at the same time.
520
+ if ( $this->broken && $this->warning ) {
521
+ $this->warning = false;
522
+ }
523
+
524
+ //Make a list of fields to be saved and their values in DB format
525
+ $values = array();
526
+ foreach($this->field_format as $field => $format){
527
+ $values[$field] = $this->$field;
528
+ }
529
+ $values = $this->to_db_format($values);
530
+
531
+ if ( $this->is_new ){
532
+
533
+ //BUG: Technically, there should be a 'LOCK TABLES wp_blc_links WRITE' here. In fact,
534
+ //the plugin should probably lock all involved tables whenever it parses something, lest
535
+ //the user (ot another plugin) modify the thing being parsed while we're working.
536
+ //The problem with table locking, though, is that parsing takes a long time and having
537
+ //all of WP freeze while the plugin is working would be a Bad Thing. Food for thought.
538
+
539
+ //Check if there's already a link with this URL present
540
+ $q = $wpdb->prepare(
541
+ "SELECT link_id FROM {$wpdb->prefix}blc_links WHERE url = %s",
542
+ $this->url
543
+ );
544
+ $existing_id = $wpdb->get_var($q);
545
+
546
+ if ( !empty($existing_id) ){
547
+ //Dammit.
548
+ $this->link_id = $existing_id;
549
+ $this->is_new = false;
550
+ return true;
551
+ }
552
+
553
+ //Insert a new row
554
+ $q = sprintf(
555
+ "INSERT INTO {$wpdb->prefix}blc_links( %s ) VALUES( %s )",
556
+ implode(', ', array_keys($values)),
557
+ implode(', ', array_values($values))
558
+ );
559
+ //FB::log($q, 'Link add query');
560
+ $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Adding a new link. SQL query:' . "\n", $q);
561
+
562
+ $rez = $wpdb->query($q) !== false;
563
+
564
+ if ($rez){
565
+ $this->link_id = $wpdb->insert_id;
566
+ $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Database record created. ID = ' . $this->link_id);
567
+ //FB::info($this->link_id, "Link added");
568
+ //If the link was successfully saved then it's no longer "new"
569
+ $this->is_new = false;
570
+ } else {
571
+ $blclog->error(__CLASS__ .':' . __FUNCTION__ . ' Error adding link', $this->url);
572
+ //FB::error($wpdb->last_error, "Error adding link {$this->url}");
573
+ }
574
+
575
+ TransactionManager::getInstance()->commit();
576
+
577
+ return $rez;
578
+
579
+ } else {
580
+ if ($this->isOptionLinkChanged !== true ) {
581
+ TransactionManager::getInstance()->start();
582
+ }
583
+ $this->isOptionLinkChanged = false;
584
+ //Generate the field = dbvalue expressions
585
+ $set_exprs = array();
586
+ foreach($values as $name => $value){
587
+ $set_exprs[] = "$name = $value";
588
+ }
589
+ $set_exprs = implode(', ', $set_exprs);
590
+
591
+ //Update an existing DB record
592
+ $q = sprintf(
593
+ "UPDATE {$wpdb->prefix}blc_links SET %s WHERE link_id=%d",
594
+ $set_exprs,
595
+ intval($this->link_id)
596
+ );
597
+ //FB::log($q, 'Link update query');
598
+ $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Updating a link. SQL query:'. "\n", $q);
599
+
600
+ $rez = $wpdb->query($q) !== false;
601
+ if ( $rez ){
602
+ //FB::log($this->link_id, "Link updated");
603
+ $blclog->debug(__CLASS__ .':' . __FUNCTION__ . ' Link updated.');
604
+ } else {
605
+ $blclog->error(__CLASS__ .':' . __FUNCTION__ . ' Error updating link', $this->url);
606
+ //FB::error($wpdb->last_error, "Error updating link {$this->url}");
607
+ }
608
+
609
+ TransactionManager::getInstance()->commit();
610
+
611
+ return $rez;
612
+ }
613
+ }
614
+
615
+ /**
616
+ * A helper method for converting the link's field values to DB format and escaping them
617
+ * for use in SQL queries.
618
+ *
619
+ * @param array $values
620
+ * @return array
621
+ */
622
+ function to_db_format($values){
623
+ global $wpdb; /** @var wpdb $wpdb */
624
+
625
+ $dbvalues = array();
626
+
627
+ foreach($values as $name => $value){
628
+ //Skip fields that don't exist in the blc_links table.
629
+ if ( !isset($this->field_format[$name]) ){
630
+ continue;
631
+ }
632
+
633
+ $format = $this->field_format[$name];
634
+
635
+ //Convert native values to a format comprehensible to the DB
636
+ switch($format){
637
+
638
+ case 'datetime' :
639
+ if ( empty($value) ){
640
+ $value = '0000-00-00 00:00:00';
641
+ } else {
642
+ $value = date('Y-m-d H:i:s', $value);
643
+ }
644
+ $format = '%s';
645
+ break;
646
+
647
+ case 'bool':
648
+ if ( $value ){
649
+ $value = 1;
650
+ } else {
651
+ $value = 0;
652
+ }
653
+ $format = '%d';
654
+ break;
655
+ }
656
+
657
+ //Escapize
658
+ $value = $wpdb->prepare($format, $value);
659
+
660
+ $dbvalues[$name] = $value;
661
+ }
662
+
663
+ return $dbvalues;
664
+ }
665
+
666
+ /**
667
+ * A helper method for converting values fetched from the database to native datatypes.
668
+ *
669
+ * @param array $values
670
+ * @return array
671
+ */
672
+ function to_native_format($values){
673
+
674
+ foreach($values as $name => $value){
675
+ //Don't process fields that don't exist in the blc_links table.
676
+ if ( !isset($this->field_format[$name]) ){
677
+ continue;
678
+ }
679
+
680
+ $format = $this->field_format[$name];
681
+
682
+ //Convert values in DB format to native datatypes.
683
+ switch($format){
684
+
685
+ case 'datetime' :
686
+ if ( $value == '0000-00-00 00:00:00' ){
687
+ $value = 0;
688
+ } elseif (is_string($value)) {
689
+ $value = strtotime($value);
690
+ }
691
+ break;
692
+
693
+ case 'bool':
694
+ $value = (bool)$value;
695
+ break;
696
+
697
+ case '%d':
698
+ $value = intval($value);
699
+ break;
700
+
701
+ case '%f':
702
+ $value = floatval($value);
703
+ break;
704
+
705
+ }
706
+
707
+ $values[$name] = $value;
708
+ }
709
+
710
+ return $values;
711
+ }
712
+
713
+ /**
714
+ * blcLink::edit()
715
+ * Edit all instances of the link by changing the URL.
716
+ *
717
+ * Here's how this really works : create a new link with the new URL. Then edit()
718
+ * all instances and point them to the new link record. If some instance can't be
719
+ * edited they will still point to the old record. The old record is deleted
720
+ * if all instances were edited successfully.
721
+ *
722
+ * @param string $new_url
723
+ * @param string $new_text Optional.
724
+ * @return array An associative array with these keys :
725
+ * new_link_id - the database ID of the new link.
726
+ * new_link - the new link (an instance of blcLink).
727
+ * cnt_okay - the number of successfully edited link instances.
728
+ * cnt_error - the number of instances that caused problems.
729
+ * errors - an array of WP_Error objects corresponding to the failed edits.
730
+ */
731
+ function edit($new_url, $new_text = null){
732
+ if ( !$this->valid() ){
733
+ return new WP_Error(
734
+ 'link_invalid',
735
+ __("Link is not valid", 'broken-link-checker')
736
+ );
737
+ }
738
+
739
+ //FB::info('Changing link '.$this->link_id .' to URL "'.$new_url.'"');
740
+
741
+ $instances = $this->get_instances();
742
+ //Fail if there are no instances
743
+ if (empty($instances)) {
744
+ return array(
745
+ 'new_link_id' => $this->link_id,
746
+ 'new_link' => $this,
747
+ 'cnt_okay' => 0,
748
+ 'cnt_error' => 0,
749
+ 'errors' => array(
750
+ new WP_Error(
751
+ 'no_instances_found',
752
+ __('This link can not be edited because it is not used anywhere on this site.', 'broken-link-checker')
753
+ )
754
+ )
755
+ );
756
+ };
757
+
758
+ //Load or create a link with the URL = $new_url
759
+ $new_link = new blcLink($new_url);
760
+ $was_new = $new_link->is_new;
761
+ if ($new_link->is_new) {
762
+ //FB::log($new_link, 'Saving a new link');
763
+ $new_link->save(); //so that we get a valid link_id
764
+ }
765
+
766
+ //FB::log("Changing link to $new_url");
767
+
768
+ if ( empty($new_link->link_id) ){
769
+ //FB::error("Failed to create a new link record");
770
+ return array(
771
+ 'new_link_id' => $this->link_id,
772
+ 'new_link' => $this,
773
+ 'cnt_okay' => 0,
774
+ 'cnt_error' => 0,
775
+ 'errors' => array(
776
+ new WP_Error(
777
+ 'link_creation_failed',
778
+ __('Failed to create a DB entry for the new URL.', 'broken-link-checker')
779
+ )
780
+ )
781
+ );
782
+ }
783
+
784
+ $cnt_okay = $cnt_error = 0;
785
+ $errors = array();
786
+
787
+ //Edit each instance.
788
+ //FB::info('Editing ' . count($instances) . ' instances');
789
+ foreach ( $instances as $instance ){
790
+ $rez = $instance->edit( $new_url, $this->url, $new_text );
791
+ if ( is_wp_error($rez) ){
792
+ $cnt_error++;
793
+ array_push($errors, $rez);
794
+ //FB::error($instance, 'Failed to edit instance ' . $instance->instance_id);
795
+ } else {
796
+ $cnt_okay++;
797
+ $instance->link_id = $new_link->link_id;
798
+ $instance->save();
799
+ //FB::info($instance, 'Successfully edited instance ' . $instance->instance_id);
800
+ }
801
+ }
802
+
803
+ //If all instances were edited successfully we can delete the old link record.
804
+ //UNLESS this link is equal to the new link (which should never happen, but whatever).
805
+ if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) && ( $this->link_id != $new_link->link_id ) ){
806
+ $this->forget( false );
807
+ }
808
+
809
+ //On the other hand, if no instances could be edited and the $new_link was really new,
810
+ //then delete it.
811
+ if ( ( $cnt_okay == 0 ) && $was_new ){
812
+ $new_link->forget( false );
813
+ $new_link = $this;
814
+ }
815
+
816
+ return array(
817
+ 'new_link_id' => $new_link->link_id,
818
+ 'new_link' => $new_link,
819
+ 'cnt_okay' => $cnt_okay,
820
+ 'cnt_error' => $cnt_error,
821
+ 'errors' => $errors,
822
+ );
823
+ }
824
+
825
+ /**
826
+ * Edit all of of this link's instances and replace the URL with the URL that it redirects to.
827
+ * This method does nothing if the link isn't a redirect.
828
+ *
829
+ * @see blcLink::edit()
830
+ *
831
+ * @return array|WP_Error
832
+ */
833
+ function deredirect(){
834
+ if ( !$this->valid() ){
835
+ return new WP_Error(
836
+ 'link_invalid',
837
+ __("Link is not valid", 'broken-link-checker')
838
+ );
839
+ }
840
+
841
+ if ( ($this->redirect_count <= 0) || empty($this->final_url) ){
842
+ return new WP_Error(
843
+ 'not_redirect',
844
+ __("This link is not a redirect", 'broken-link-checker')
845
+ );
846
+ }
847
+
848
+ //Preserve the existing #anchor if the redirect doesn't include one.
849
+ $new_url = $this->final_url;
850
+ $anchor = @parse_url($this->url, PHP_URL_FRAGMENT);
851
+ if ( !empty($anchor) && (strrpos($new_url, '#') === false) ) {
852
+ $new_url .= '#' . $anchor;
853
+ }
854
+
855
+ return $this->edit($new_url);
856
+ }
857
+
858
+ /**
859
+ * Unlink all instances and delete the link record.
860
+ *
861
+ * @return array|WP_Error An associative array with these keys :
862
+ * cnt_okay - the number of successfully removed instances.
863
+ * cnt_error - the number of instances that couldn't be removed.
864
+ * link_deleted - true if the link record was deleted.
865
+ * errors - an array of WP_Error objects describing the errors that were encountered, if any.
866
+ */
867
+ function unlink(){
868
+ if ( !$this->valid() ){
869
+ return new WP_Error(
870
+ 'link_invalid',
871
+ __("Link is not valid", 'broken-link-checker')
872
+ );
873
+ }
874
+
875
+ //FB::info($this, 'Removing link');
876
+ $instances = $this->get_instances();
877
+
878
+ //No instances? Just remove the link then.
879
+ if (empty($instances)) {
880
+ //FB::warn("This link has no instances. Deleting the link.");
881
+ $rez = $this->forget( false ) !== false;
882
+
883
+ if ( $rez ){
884
+ return array(
885
+ 'cnt_okay' => 1,
886
+ 'cnt_error' => 0,
887
+ 'link_deleted' => true,
888
+ 'errors' => array(),
889
+ );
890
+ } else {
891
+ return array(
892
+ 'cnt_okay' => 0,
893
+ 'cnt_error' => 0,
894
+ 'link_deleted' => false,
895
+ 'errors' => array(
896
+ new WP_Error(
897
+ "deletion_failed",
898
+ __("Couldn't delete the link's database record", 'broken-link-checker')
899
+ )
900
+ ),
901
+ );
902
+ }
903
+ }
904
+
905
+
906
+ //FB::info('Unlinking ' . count($instances) . ' instances');
907
+
908
+ $cnt_okay = $cnt_error = 0;
909
+ $errors = array();
910
+
911
+ //Unlink each instance.
912
+ foreach ( $instances as $instance ){
913
+ $rez = $instance->unlink( $this->url );
914
+
915
+ if ( is_wp_error($rez) ){
916
+ $cnt_error++;
917
+ array_push($errors, $rez);
918
+ //FB::error( $instance, 'Failed to unlink instance' );
919
+ } else {
920
+ $cnt_okay++;
921
+ //FB::info( $instance, 'Successfully unlinked instance' );
922
+ }
923
+ }
924
+
925
+ //If all instances were unlinked successfully we can delete the link record.
926
+ if ( ( $cnt_error == 0 ) && ( $cnt_okay > 0 ) ){
927
+ //FB::log('Instances removed, deleting the link.');
928
+ $link_deleted = $this->forget() !== false;
929
+
930
+ if ( !$link_deleted ){
931
+ array_push(
932
+ $errors,
933
+ new WP_Error(
934
+ "deletion_failed",
935
+ __("Couldn't delete the link's database record", 'broken-link-checker')
936
+ )
937
+ );
938
+ }
939
+
940
+ } else {
941
+ //FB::error("Something went wrong. Unlinked instances : $cnt_okay, errors : $cnt_error");
942
+ $link_deleted = false;
943
+ }
944
+
945
+ return array(
946
+ 'cnt_okay' => $cnt_okay,
947
+ 'cnt_error' => $cnt_error,
948
+ 'link_deleted' => $link_deleted,
949
+ 'errors' => $errors,
950
+ );
951
+ }
952
+
953
+ /**
954
+ * Remove the link and (optionally) its instance records from the DB. Doesn't alter posts/etc.
955
+ *
956
+ * @param bool $remove_instances
957
+ * @return mixed 1 on success, 0 if link not found, false on error.
958
+ */
959
+ function forget($remove_instances = true){
960
+ global $wpdb; /** @var wpdb $wpdb */
961
+ if ( !$this->valid() ) return false;
962
+
963
+ if ( !empty($this->link_id) ){
964
+ //FB::info($this, 'Deleting link from DB');
965
+
966
+ if ( $remove_instances ){
967
+ //Remove instances, if any
968
+ $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_instances WHERE link_id=%d", $this->link_id) );
969
+ }
970
+
971
+ //Remove the link itself
972
+ $rez = $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}blc_links WHERE link_id=%d", $this->link_id) );
973
+ $this->link_id = 0;
974
+
975
+ return $rez;
976
+ } else {
977
+ return false;
978
+ }
979
+
980
+ }
981
+
982
+ /**
983
+ * Get a list of the link's instances
984
+ *
985
+ * @param bool $ignore_cache Don't use the internally cached instance list.
986
+ * @param string $purpose
987
+ * @return blcLinkInstance[] An array of instance objects or FALSE on failure.
988
+ */
989
+ function get_instances( $ignore_cache = false, $purpose = '' ){
990
+ if ( !$this->valid() || empty($this->link_id) ) return false;
991
+
992
+ if ( $ignore_cache || is_null($this->_instances) ){
993
+ $instances = blc_get_instances( array($this->link_id), $purpose );
994
+ if ( !empty($instances) ){
995
+ $this->_instances = $instances[$this->link_id];
996
+ }
997
+ }
998
+
999
+ return $this->_instances;
1000
+ }
1001
+
1002
+ /**
1003
+ * Determine the status text and status code corresponding to the current state of this link.
1004
+ *
1005
+ * @return array Associative array with two keys, 'text' and 'code'.
1006
+ */
1007
+ function analyse_status(){
1008
+ $code = BLC_LINK_STATUS_UNKNOWN;
1009
+ $text = _x('Unknown', 'link status', 'broken-link-checker');
1010
+
1011
+ //Status text
1012
+ if ( isset($this->status_text) && !empty($this->status_text) && !empty($this->status_code) ){
1013
+
1014
+ //Lucky, the checker module has already set it for us.
1015
+ $text = $this->status_text;
1016
+ $code = $this->status_code;
1017
+
1018
+ } else {
1019
+
1020
+ if ( $this->broken || $this->warning ){
1021
+ $code = BLC_LINK_STATUS_WARNING;
1022
+ $text = __('Unknown Error', 'broken-link-checker');
1023
+
1024
+ if ( $this->timeout ){
1025
+
1026
+ $text = __('Timeout', 'broken-link-checker');
1027
+ $code = BLC_LINK_STATUS_WARNING;
1028
+
1029
+ } elseif ( $this->http_code ) {
1030
+
1031
+ //Only 404 (Not Found) and 410 (Gone) are treated as broken-for-sure.
1032
+ if ( in_array($this->http_code, array(404, 410)) ){
1033
+ $code = BLC_LINK_STATUS_ERROR;
1034
+ } else {
1035
+ $code = BLC_LINK_STATUS_WARNING;
1036
+ }
1037
+
1038
+ if ( array_key_exists(intval($this->http_code), $this->http_status_codes) ){
1039
+ $text = $this->http_status_codes[intval($this->http_code)];
1040
+ }
1041
+ }
1042
+
1043
+ } else {
1044
+
1045
+ if ( !$this->last_check ) {
1046
+ $text = __('Not checked', 'broken-link-checker');
1047
+ $code = BLC_LINK_STATUS_UNKNOWN;
1048
+ } elseif ( $this->false_positive ) {
1049
+ $text = __('False positive', 'broken-link-checker');
1050
+ $code = BLC_LINK_STATUS_UNKNOWN;
1051
+ } else {
1052
+ $text = _x('OK', 'link status', 'broken-link-checker');
1053
+ $code = BLC_LINK_STATUS_OK;
1054
+ }
1055
+
1056
+ }
1057
+ }
1058
+
1059
+ return compact('text', 'code');
1060
+ }
1061
+
1062
+ /**
1063
+ * Get the link URL in ASCII-compatible encoding.
1064
+ *
1065
+ * @return string
1066
+ */
1067
+ function get_ascii_url(){
1068
+ return blcUtility::idn_to_ascii($this->url);
1069
+ }
1070
+
1071
+ /**
1072
+ * Check if this link points to a page on the same domain as the current site.
1073
+ *
1074
+ * Note: Only checks the domain name, not subdirectory. If there are two separate WP sites A and B installed
1075
+ * in two different subdirectories of the same domain, this method will treat a link from site A to B as internal.
1076
+ *
1077
+ * @return bool
1078
+ */
1079
+ public function is_internal_to_domain() {
1080
+ $host = @parse_url($this->url, PHP_URL_HOST);
1081
+ if ( empty($host) ) {
1082
+ return false;
1083
+ }
1084
+
1085
+ $site_host = @parse_url(get_site_url(), PHP_URL_HOST);
1086
+ if ( empty($site_host) ) {
1087
+ return false;
1088
+ }
1089
+
1090
+ //Some users are inconsistent with using/not using the www prefix, so get rid of it.
1091
+ $site_host = preg_replace('@^www\.@', '', $site_host, 1);
1092
+
1093
+ //Check if $host ends with $site_host. This means blah.example.com will match example.com.
1094
+ return (substr($host, -strlen($site_host)) === $site_host);
1095
+ }
1096
+
1097
+ /**
1098
+ * Remove the query string from an URL.
1099
+ *
1100
+ * @param string $url
1101
+ * @return string
1102
+ */
1103
+ public static function remove_query_string($url) {
1104
+ return preg_replace('@\?[^#]*?(#|$)@', '$1', $url);
1105
+ }
1106
+ }
1107
+
1108
+ } //class_exists
1109
+
1110
+ /**
1111
+ * Remove orphaned links that have no corresponding instances.
1112
+ *
1113
+ * @param int|array $link_id (optional) Only check these links
1114
+ * @return bool
1115
+ */
1116
+ function blc_cleanup_links( $link_id = null ){
1117
+ global $wpdb; /* @var wpdb $wpdb */
1118
+ global $blclog;
1119
+
1120
+ $start = microtime(true);
1121
+ $q = "DELETE FROM {$wpdb->prefix}blc_links
1122
+ USING {$wpdb->prefix}blc_links LEFT JOIN {$wpdb->prefix}blc_instances
1123
+ ON {$wpdb->prefix}blc_instances.link_id = {$wpdb->prefix}blc_links.link_id
1124
+ WHERE
1125
+ {$wpdb->prefix}blc_instances.link_id IS NULL";
1126
+
1127
+ if ( $link_id !== null ) {
1128
+ if ( !is_array($link_id) ){
1129
+ $link_id = array( intval($link_id) );
1130
+ }
1131
+ $q .= " AND {$wpdb->prefix}blc_links.link_id IN (" . implode(', ', $link_id) . ')';
1132
+ }
1133
+
1134
+ $rez = $wpdb->query( $q );
1135
+ $elapsed = microtime(true) - $start;
1136
+ $blclog->log(sprintf('... %d links deleted in %.3f seconds', $wpdb->rows_affected, $elapsed));
1137
+
1138
+ return $rez !== false;
1139
+ }
1140
+
includes/logger.php CHANGED
@@ -1,188 +1,188 @@
1
- <?php
2
-
3
- if ( !class_exists('blcLogger') ):
4
-
5
- define('BLC_LEVEL_DEBUG', 0);
6
- define('BLC_LEVEL_INFO', 1);
7
- define('BLC_LEVEL_WARNING', 2);
8
- define('BLC_LEVEL_ERROR', 3);
9
-
10
- /**
11
- * Base class for loggers. Doesn't actually log anything anywhere.
12
- *
13
- * @package Broken Link Checker
14
- * @author Janis Elsts
15
- */
16
- class blcLogger {
17
- protected $log_level = BLC_LEVEL_DEBUG;
18
-
19
- function __construct($param = ''){
20
-
21
- }
22
-
23
- function blcLogger($param = ''){
24
- $this->__construct($param);
25
- }
26
-
27
- function log($message, $object = null, $level = BLC_LEVEL_INFO){
28
-
29
- }
30
-
31
- function debug($message, $object = null){
32
- $this->log($message, $object, BLC_LEVEL_DEBUG);
33
- }
34
-
35
- function info($message, $object = null){
36
- $this->log($message, $object, BLC_LEVEL_INFO);
37
- }
38
-
39
- function warn($message, $object = null){
40
- $this->log($message, $object, BLC_LEVEL_WARNING);
41
- }
42
-
43
- function error($message, $object = null){
44
- $this->log($message, $object, BLC_LEVEL_ERROR);
45
- }
46
-
47
- function get_messages($min_level = BLC_LEVEL_DEBUG){
48
- return array();
49
- }
50
-
51
- function get_log($min_level = BLC_LEVEL_DEBUG){
52
- return array();
53
- }
54
-
55
- function clear(){
56
-
57
- }
58
-
59
- public function set_log_level($level) {
60
- $this->log_level = $level;
61
- }
62
- }
63
-
64
- /**
65
- * A basic logger that uses WP options for permanent storage.
66
- *
67
- * Log entries are initially stored in memory and need to explicitly
68
- * flushed to the database by calling blcCachedOptionLogger::save().
69
- *
70
- * @package Broken Link Checker
71
- * @author Janis Elsts
72
- */
73
- class blcCachedOptionLogger extends blcLogger {
74
- var $option_name = '';
75
- var $log;
76
- var $filter_level = BLC_LEVEL_DEBUG;
77
-
78
- function __construct($option_name = ''){
79
- $this->option_name = $option_name;
80
- $oldLog = get_option($this->option_name);
81
- if ( is_array($oldLog) && !empty($oldLog) ){
82
- $this->log = $oldLog;
83
- } else {
84
- $this->log = array();
85
- }
86
- }
87
-
88
- function log($message, $object = null, $level = BLC_LEVEL_DEBUG){
89
- $new_entry = array($level, $message, $object);
90
- array_push($this->log, $new_entry);
91
- }
92
-
93
- function get_log($min_level = BLC_LEVEL_DEBUG){
94
- $this->filter_level = $min_level;
95
- return array_filter($this->log, array($this, '_filter_log'));
96
- }
97
-
98
- function _filter_log($entry){
99
- return ( $entry[0] >= $this->filter_level );
100
- }
101
-
102
- function get_messages($min_level = BLC_LEVEL_DEBUG){
103
- $messages = $this->get_log($min_level);
104
- return array_map( array($this, '_get_log_message'), $messages );
105
- }
106
-
107
- function _get_log_message($entry){
108
- return $entry[1];
109
- }
110
-
111
- function clear(){
112
- $this->log = array();
113
- delete_option($this->option_name);
114
- }
115
-
116
- function save(){
117
- update_option($this->option_name, $this->log);
118
- }
119
- }
120
-
121
- /**
122
- * A dummy logger that doesn't log anything.
123
- */
124
- class blcDummyLogger extends blcLogger { }
125
-
126
- /**
127
- * A basic logger that logs messages to a file.
128
- */
129
- class blcFileLogger extends blcLogger {
130
- protected $fileName;
131
-
132
- public function __construct($fileName = ''){
133
- $this->fileName = $fileName;
134
- }
135
-
136
- function log($message, $object = null, $level = BLC_LEVEL_INFO){
137
- if ( $level < $this->log_level ) {
138
- return;
139
- }
140
-
141
- $line = sprintf(
142
- '[%1$s] %2$s %3$s',
143
- date('Y-m-d H:i:s P'),
144
- $this->get_level_string($level),
145
- $message
146
- );
147
-
148
- if ( isset($object) ) {
149
- $line .= ' ' . var_export($object, true);
150
- }
151
-
152
- $line .= "\n";
153
-
154
- error_log($line, 3, $this->fileName);
155
- }
156
-
157
- function get_messages($min_level = BLC_LEVEL_DEBUG){
158
- return array(__CLASS__ . ':get_messages() is not implemented');
159
- }
160
-
161
- function get_log($min_level = BLC_LEVEL_DEBUG){
162
- return array(__CLASS__ . ':get_log() is not implemented');
163
- }
164
-
165
- public function clear(){
166
- if ( is_file($this->fileName) && is_writable($this->fileName) ) {
167
- $handle = fopen($this->fileName, 'w');
168
- fclose($handle);
169
- }
170
- }
171
-
172
- protected function get_level_string($level) {
173
- switch ($level) {
174
- case BLC_LEVEL_DEBUG:
175
- return 'DEBUG:';
176
- case BLC_LEVEL_ERROR:
177
- return 'ERROR:';
178
- case BLC_LEVEL_WARNING:
179
- return 'WARN:';
180
- case BLC_LEVEL_INFO:
181
- return 'INFO:';
182
- }
183
- return 'LOG:';
184
- }
185
- }
186
-
187
- endif;
188
-
1
+ <?php
2
+
3
+ if ( !class_exists('blcLogger') ):
4
+
5
+ define('BLC_LEVEL_DEBUG', 0);
6
+ define('BLC_LEVEL_INFO', 1);
7
+ define('BLC_LEVEL_WARNING', 2);
8
+ define('BLC_LEVEL_ERROR', 3);
9
+
10
+ /**
11
+ * Base class for loggers. Doesn't actually log anything anywhere.
12
+ *
13
+ * @package Broken Link Checker
14
+ * @author Janis Elsts
15
+ */
16
+ class blcLogger {
17
+ protected $log_level = BLC_LEVEL_DEBUG;
18
+
19
+ function __construct($param = ''){
20
+
21
+ }
22
+
23
+ function blcLogger($param = ''){
24
+ $this->__construct($param);
25
+ }
26
+
27
+ function log($message, $object = null, $level = BLC_LEVEL_INFO){
28
+
29
+ }
30
+
31
+ function debug($message, $object = null){
32
+ $this->log($message, $object, BLC_LEVEL_DEBUG);
33
+ }
34
+
35
+ function info($message, $object = null){
36
+ $this->log($message, $object, BLC_LEVEL_INFO);
37
+ }
38
+
39
+ function warn($message, $object = null){
40
+ $this->log($message, $object, BLC_LEVEL_WARNING);
41
+ }
42
+
43
+ function error($message, $object = null){
44
+ $this->log($message, $object, BLC_LEVEL_ERROR);
45
+ }
46
+
47
+ function get_messages($min_level = BLC_LEVEL_DEBUG){
48
+ return array();
49
+ }
50
+
51
+ function get_log($min_level = BLC_LEVEL_DEBUG){
52
+ return array();
53
+ }
54
+
55
+ function clear(){
56
+
57
+ }
58
+
59
+ public function set_log_level($level) {
60
+ $this->log_level = $level;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * A basic logger that uses WP options for permanent storage.
66
+ *
67
+ * Log entries are initially stored in memory and need to explicitly
68
+ * flushed to the database by calling blcCachedOptionLogger::save().
69
+ *
70
+ * @package Broken Link Checker
71
+ * @author Janis Elsts
72
+ */
73
+ class blcCachedOptionLogger extends blcLogger {
74
+ var $option_name = '';
75
+ var $log;
76
+ var $filter_level = BLC_LEVEL_DEBUG;
77
+
78
+ function __construct($option_name = ''){
79
+ $this->option_name = $option_name;
80
+ $oldLog = get_option($this->option_name);
81
+ if ( is_array($oldLog) && !empty($oldLog) ){
82
+ $this->log = $oldLog;
83
+ } else {
84
+ $this->log = array();
85
+ }
86
+ }
87
+
88
+ function log($message, $object = null, $level = BLC_LEVEL_DEBUG){
89
+ $new_entry = array($level, $message, $object);
90
+ array_push($this->log, $new_entry);
91
+ }
92
+
93
+ function get_log($min_level = BLC_LEVEL_DEBUG){
94
+ $this->filter_level = $min_level;
95
+ return array_filter($this->log, array($this, '_filter_log'));
96
+ }
97
+
98
+ function _filter_log($entry){
99
+ return ( $entry[0] >= $this->filter_level );
100
+ }
101
+
102
+ function get_messages($min_level = BLC_LEVEL_DEBUG){
103
+ $messages = $this->get_log($min_level);
104
+ return array_map( array($this, '_get_log_message'), $messages );
105
+ }
106
+
107
+ function _get_log_message($entry){
108
+ return $entry[1];
109
+ }
110
+
111
+ function clear(){
112
+ $this->log = array();
113
+ delete_option($this->option_name);
114
+ }
115
+
116
+ function save(){
117
+ update_option($this->option_name, $this->log);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * A dummy logger that doesn't log anything.
123
+ */
124
+ class blcDummyLogger extends blcLogger { }
125
+
126
+ /**
127
+ * A basic logger that logs messages to a file.
128
+ */
129
+ class blcFileLogger extends blcLogger {
130
+ protected $fileName;
131
+
132
+ public function __construct($fileName = ''){
133
+ $this->fileName = $fileName;
134
+ }
135
+
136
+ function log($message, $object = null, $level = BLC_LEVEL_INFO){
137
+ if ( $level < $this->log_level ) {
138
+ return;
139
+ }
140
+
141
+ $line = sprintf(
142
+ '[%1$s] %2$s %3$s',
143
+ date('Y-m-d H:i:s P'),
144
+ $this->get_level_string($level),
145
+ $message
146
+ );
147
+
148
+ if ( isset($object) ) {
149
+ $line .= ' ' . var_export($object, true);
150
+ }
151
+
152
+ $line .= "\n";
153
+
154
+ error_log($line, 3, $this->fileName);
155
+ }
156
+
157
+ function get_messages($min_level = BLC_LEVEL_DEBUG){
158
+ return array(__CLASS__ . ':get_messages() is not implemented');
159
+ }
160
+
161
+ function get_log($min_level = BLC_LEVEL_DEBUG){
162
+ return array(__CLASS__ . ':get_log() is not implemented');
163
+ }
164
+
165
+ public function clear(){
166
+ if ( is_file($this->fileName) && is_writable($this->fileName) ) {
167
+ $handle = fopen($this->fileName, 'w');
168
+ fclose($handle);
169
+ }
170
+ }
171
+
172
+ protected function get_level_string($level) {
173
+ switch ($level) {
174
+ case BLC_LEVEL_DEBUG:
175
+ return 'DEBUG:';
176
+ case BLC_LEVEL_ERROR:
177
+ return 'ERROR:';
178
+ case BLC_LEVEL_WARNING:
179
+ return 'WARN:';
180
+ case BLC_LEVEL_INFO:
181
+ return 'INFO:';
182
+ }
183
+ return 'LOG:';
184
+ }
185
+ }
186
+
187
+ endif;
188
+
includes/module-base.php CHANGED
@@ -1,80 +1,80 @@
1
- <?php
2
-
3
- /**
4
- * @author Janis Elsts
5
- * @copyright 2010
6
- */
7
-
8
- /**
9
- * Base class for BLC modules.
10
- *
11
- * @package Broken Link Checker
12
- * @author Janis Elsts
13
- * @access public
14
- */
15
- class blcModule {
16
-
17
- var $module_id; //The ID of this module. Usually a lowercase string.
18
- var $cached_header; //An associative array containing the header data of the module file.
19
- /** @var blcConfigurationManager $plugin__conf */
20
- var $plugin_conf; //A reference to the plugin's global configuration object.
21
- var $module_manager; //A reference to the module manager.
22
-
23
- /**
24
- * Class constructor
25
- *
26
- * @param string $module_id
27
- * @param array $cached_header
28
- * @param blcConfigurationManager $plugin_conf
29
- * @param blcModuleManager $module_manager
30
- * @return void
31
- */
32
- function __construct($module_id, $cached_header, &$plugin_conf, &$module_manager){
33
- $this->module_id = $module_id;
34
- $this->cached_header = $cached_header;
35
- $this->plugin_conf = &$plugin_conf;
36
- $this->module_manager = &$module_manager;
37
-
38
- $this->init();
39
- }
40
-
41
- /**
42
- * Module initializer. Called when the module is first instantiated.
43
- * The default implementation does nothing. Override it in a subclass to
44
- * specify some sort of start-up behaviour.
45
- *
46
- * @return void
47
- */
48
- function init(){
49
- //Should be overridden in a sub-class.
50
- }
51
-
52
- /**
53
- * Called when the module is activated.
54
- * Should be overridden in a sub-class.
55
- *
56
- * @return void
57
- */
58
- function activated(){
59
- //Should be overridden in a sub-class.
60
- }
61
-
62
- /**
63
- * Called when the module is deactivated.
64
- * Should be overridden in a sub-class.
65
- *
66
- * @return void
67
- */
68
- function deactivated(){
69
- //Should be overridden in a sub-class.
70
- }
71
-
72
- /**
73
- * Called when BLC itself is activated.
74
- * Usually this method just calls activated(), but subclasses could override it for special handling.
75
- */
76
- function plugin_activated() {
77
- $this->activated();
78
- }
79
- }
80
-
1
+ <?php
2
+
3
+ /**
4
+ * @author Janis Elsts
5
+ * @copyright 2010
6
+ */
7
+
8
+ /**
9
+ * Base class for BLC modules.
10
+ *
11
+ * @package Broken Link Checker
12
+ * @author Janis Elsts
13
+ * @access public
14
+ */
15
+ class blcModule {
16
+
17
+ var $module_id; //The ID of this module. Usually a lowercase string.
18
+ var $cached_header; //An associative array containing the header data of the module file.
19
+ /** @var blcConfigurationManager $plugin__conf */
20
+ var $plugin_conf; //A reference to the plugin's global configuration object.
21
+ var $module_manager; //A reference to the module manager.
22
+
23
+ /**
24
+ * Class constructor
25
+ *
26
+ * @param string $module_id
27
+ * @param array $cached_header
28
+ * @param blcConfigurationManager $plugin_conf
29
+ * @param blcModuleManager $module_manager
30
+ * @return void
31
+ */
32
+ function __construct($module_id, $cached_header, &$plugin_conf, &$module_manager){
33
+ $this->module_id = $module_id;
34
+ $this->cached_header = $cached_header;
35
+ $this->plugin_conf = &$plugin_conf;
36
+ $this->module_manager = &$module_manager;
37
+
38
+ $this->init();
39
+ }
40
+
41
+ /**
42
+ * Module initializer. Called when the module is first instantiated.
43
+ * The default implementation does nothing. Override it in a subclass to
44
+ * specify some sort of start-up behaviour.
45
+ *
46
+ * @return void
47
+ */
48
+ function init(){
49
+ //Should be overridden in a sub-class.
50
+ }
51
+
52
+ /**
53
+ * Called when the module is activated.
54
+ * Should be overridden in a sub-class.
55
+ *
56
+ * @return void
57
+ */
58
+ function activated(){
59
+ //Should be overridden in a sub-class.
60
+ }
61
+
62
+ /**
63
+ * Called when the module is deactivated.
64
+ * Should be overridden in a sub-class.
65
+ *
66
+ * @return void
67
+ */
68
+ function deactivated(){
69
+ //Should be overridden in a sub-class.
70
+ }
71
+
72
+ /**
73
+ * Called when BLC itself is activated.
74
+ * Usually this method just calls activated(), but subclasses could override it for special handling.
75
+ */
76
+ function plugin_activated() {
77
+ $this->activated();
78
+ }
79
+ }
80
+
includes/module-manager.php CHANGED
@@ -1,860 +1,860 @@
1
- <?php
2
-
3
- class blcModuleManager {
4
-
5
- /* @var blcConfigurationManager */
6
- var $plugin_conf;
7
- var $module_dir = '';
8
-
9
- var $_module_cache;
10
- var $_category_cache;
11
- var $_category_cache_active;
12
- var $_virtual_modules = array();
13
-
14
- var $loaded;
15
- var $instances;
16
- var $default_active_modules;
17
-
18
-
19
- /**
20
- * Class "constructor".
21
- *
22
- * @param array $default_active_modules An array of module ids specifying which modules are active by default.
23
- * @return void
24
- */
25
- function init($default_active_modules = null){
26
- $this->module_dir = realpath(dirname(__FILE__) . '/../modules');
27
-
28
- $this->plugin_conf = blc_get_configuration();
29
- $this->default_active_modules = $default_active_modules;
30
-
31
- $this->loaded = array();
32
- $this->instances = array();
33
-
34
- add_filter('extra_plugin_headers', array(&$this, 'inject_module_headers'));
35
- }
36
-
37
- /**
38
- * Get an instance of the module manager.
39
- *
40
- * @param array|null $default_active_modules
41
- * @return blcModuleManager
42
- */
43
- static function getInstance($default_active_modules = null){
44
- static $instance = null;
45
- if ( is_null($instance) ){
46
- $instance = new blcModuleManager();
47
- $instance->init($default_active_modules);
48
- }
49
- return $instance;
50
- }
51
-
52
- /**
53
- * Retrieve a list of all installed BLC modules.
54
- *
55
- * This is essentially a slightly modified copy of get_plugins().
56
- *
57
- * @return array An associative array of module headers indexed by module ID.
58
- */
59
- function get_modules(){
60
- if ( !isset($this->_module_cache) ){
61
- //Refresh the module cache.
62
-
63
- $relative_path = '/' . plugin_basename($this->module_dir);
64
- if ( !function_exists('get_plugins') ){
65
- //BUG: Potentional security flaw/bug. plugin.php is not meant to be loaded outside admin panel.
66
- require_once(ABSPATH . 'wp-admin/includes/plugin.php');
67
- }
68
- $modules = get_plugins( $relative_path );
69
-
70
- $this->_module_cache = array();
71
-
72
- foreach($modules as $module_filename => $module_header){
73
- //Figure out the module ID. If not specified, it is equal to module's filename (sans the .php)
74
- if ( !empty($module_header['ModuleID']) ){
75
- $module_id = strtolower(trim($module_header['ModuleID']));
76
- } else {
77
- $module_id = strtolower(basename($module_filename, '.php'));
78
- }
79
-
80
- $module_header = $this->normalize_module_header($module_header, $module_id, $module_filename);
81
- $this->_module_cache[$module_id] = $module_header;
82
- }
83
-
84
- $this->_category_cache = null;
85
- }
86
-
87
- return array_merge($this->_module_cache, $this->_virtual_modules);
88
- }
89
-
90
- /**
91
- * Retrieve modules that match a specific category, or all modules sorted by categories.
92
- *
93
- * If a category ID is specified, this method returns the modules that have the "ModuleCategory:"
94
- * file header set to that value, or an empty array if no modules match that category. The
95
- * return array is indexed by module id :
96
- * [module_id1 => module1_data, module_id1 => module2_data, ...]
97
- *
98
- * If category is omitted, this method returns a list of all categories plus the modules
99
- * they contain. Only categories that have at least one module will be included. The return
100
- * value is an array of arrays, indexed by category ID :
101
- * [category1 => [module1_id => module1_data, module2_id => module2_data, ...], ...]
102
- *
103
- *
104
- * @param string $category Category id, e.g. "parser" or "container". Optional.
105
- * @param bool $markup Apply markup to module headers. Not implemented.
106
- * @param bool $translate Translate module headers. Defaults to false.
107
- * @return array An array of categories or module data.
108
- */
109
- function get_modules_by_category($category = '', $markup = false, $translate = false){
110
- if ( !isset($this->_category_cache) ){
111
- $this->_category_cache = $this->sort_into_categories($this->get_modules());
112
- }
113
-
114
- if ( empty($category) ){
115
- if ( $markup || $translate ){
116
-
117
- //Translate/apply markup to module headers
118
- $processed = array();
119
- foreach($this->_category_cache as $category_id => $modules){
120
- $processed[$category_id] = array();
121
- foreach($modules as $module_id => $module_data){
122
- if ( $translate ){
123
- $module_data['Name'] = _x($module_data['Name'], 'module name', 'broken-link-checker');
124
- }
125
- $processed[$category_id][$module_id] = $module_data;
126
- }
127
- }
128
-
129
- return $processed;
130
- } else {
131
- return $this->_category_cache;
132
- }
133
- } else {
134
- if ( isset($this->_category_cache[$category]) ){
135
- if ( $markup || $translate ){
136
- //Translate/apply markup to module headers
137
- $processed = array();
138
- foreach($this->_category_cache[$category] as $module_id => $module_data){
139
- if ( $translate ){
140
- $module_data['Name'] = _x($module_data['Name'], 'module name', 'broken-link-checker');
141
- }
142
- $processed[$module_id] = $module_data;
143
- }
144
- return $processed;
145
- } else {
146
- return $this->_category_cache[$category];
147
- }
148
- } else {
149
- return array();
150
- }
151
- }
152
- }
153
-
154
- /**
155
- * Retrieve active modules that match a specific category, or all active modules sorted by categories.
156
- *
157
- * @see blcModuleManager::get_modules_by_category()
158
- *
159
- * @param string $category Category id. Optional.
160
- * @return array An associative array of categories or module data.
161
- */
162
- function get_active_by_category($category = ''){
163
- if ( !isset($this->_category_cache_active) ){
164
- $this->_category_cache_active = $this->sort_into_categories($this->get_active_modules());
165
- }
166
-
167
- if ( empty($category) ){
168
- return $this->_category_cache_active;
169
- } else {
170
- if ( isset($this->_category_cache_active[$category]) ){
171
- return $this->_category_cache_active[$category];
172
- } else {
173
- return array();
174
- }
175
- }
176
- }
177
-
178
- /**
179
- * Get the module ids of all active modules that belong to a specific category,
180
- * quoted and ready for use in SQL.
181
- *
182
- * @param string $category Category ID. If not specified, a list of all active modules will be returned.
183
- * @return string A comma separated list of single-quoted module ids, e.g. 'module-foo','module-bar','modbaz'
184
- */
185
- function get_escaped_ids($category = ''){
186
- global $wpdb;
187
-
188
- if ( empty($category) ){
189
- $modules = $this->get_active_modules();
190
- } else {
191
- $modules = $this->get_active_by_category($category);
192
- }
193
-
194
- $modules = array_map('esc_sql', array_keys($modules));
195
- $modules = "'" . implode("', '", $modules) . "'";
196
-
197
- return $modules;
198
- }
199
-
200
- /**
201
- * Sort a list of modules into categories. Inside each category, modules are sorted by priority (descending).
202
- *
203
- * @access private
204
- *
205
- * @param array $modules
206
- * @return array
207
- */
208
- function sort_into_categories($modules){
209
- $categories = array();
210
-
211
- foreach($modules as $module_id => $module_data){
212
- $cat = $module_data['ModuleCategory'];
213
- if ( isset($categories[$cat]) ){
214
- $categories[$cat][$module_id] = $module_data;
215
- } else {
216
- $categories[$cat] = array($module_id => $module_data);
217
- }
218
- }
219
-
220
- foreach($categories as $cat => $cat_modules){
221
- uasort($categories[$cat], array(&$this, 'compare_priorities'));
222
- }
223
-
224
- return $categories;
225
- }
226
-
227
- /**
228
- * Callback for sorting modules by priority.
229
- *
230
- * @access private
231
- *
232
- * @param array $a First module header.
233
- * @param array $b Second module header.
234
- * @return int
235
- */
236
- function compare_priorities($a, $b){
237
- return $b['ModulePriority'] - $a['ModulePriority'];
238
- }
239
-
240
- /**
241
- * Retrieve a reference to an active module.
242
- *
243
- * Each module is instantiated only once, so if the module was already loaded you'll get back
244
- * a reference to the existing module object. If the module isn't loaded or instantiated yet,
245
- * the function will do it automatically (but only for active modules).
246
- *
247
- * @param string $module_id Module ID.
248
- * @param bool $autoload Optional. Whether to load the module file if it's not loaded yet. Defaults to TRUE.
249
- * @param string $category Optional. Return the module only if it's in a specific category. Categories are ignored by default.
250
- * @return blcModule A reference to a module object, or NULL on error.
251
- */
252
- function get_module($module_id, $autoload = true, $category=''){
253
- $no_result = null;
254
- if ( !is_string($module_id) ){
255
- //$backtrace = debug_backtrace();
256
- //FB::error($backtrace, "get_module called with a non-string argument");
257
- return $no_result;
258
- }
259
-
260
- if ( empty($this->loaded[$module_id]) ){
261
- if ( $autoload && $this->is_active($module_id) ){
262
- if ( !$this->load_module($module_id) ){
263
- return $no_result;
264
- }
265
- } else {
266
- return $no_result;
267
- }
268
- }
269
-
270
- if ( !empty($category) ){
271
- $data = $this->get_module_data($module_id);
272
- if ( $data['ModuleCategory'] != $category ){
273
- return $no_result;
274
- }
275
- }
276
-
277
- $module = $this->init_module($module_id);
278
- return $module;
279
- }
280
-
281
- /**
282
- * Retrieve the header data of a specific module.
283
- * Uses cached module info if available.
284
- *
285
- * @param string $module_id
286
- * @param bool $use_active_cache Check the active module cache before the general one. Defaults to true.
287
- * @return array Associative array of module data, or FALSE if the specified module was not found.
288
- */
289
- function get_module_data($module_id, $use_active_cache = true){
290
- //Check virtual modules
291
- if ( isset($this->_virtual_modules[$module_id]) ){
292
- return $this->_virtual_modules[$module_id];
293
- }
294
-
295
- //Check active modules
296
- if ( $use_active_cache && isset($this->plugin_conf->options['active_modules'][$module_id]) ){
297
- return $this->plugin_conf->options['active_modules'][$module_id];
298
- }
299
-
300
- //Otherwise, use the general module cache
301
- if ( !isset($this->_module_cache) ){
302
- $this->get_modules(); //Populates the cache
303
- }
304
-
305
- if ( isset($this->_module_cache[$module_id]) ){
306
- return $this->_module_cache[$module_id];
307
- } else {
308
- return false;
309
- }
310
- }
311
-
312
- /**
313
- * Retrieve a list of active modules.
314
- *
315
- * The list of active modules is stored in the 'active_modules' key of the
316
- * plugin configuration object. If this key is not set, this function will
317
- * create it and populate it using the list of default active modules passed
318
- * to the module manager's constructor.
319
- *
320
- * @return array Associative array of module data indexed by module ID.
321
- */
322
- function get_active_modules(){
323
- if ( isset($this->plugin_conf->options['active_modules']) ){
324
- return $this->plugin_conf->options['active_modules'];
325
- }
326
-
327
- $active = array();
328
- $modules = $this->get_modules();
329
-
330
- if ( is_array($this->default_active_modules) ){
331
- foreach($this->default_active_modules as $module_id){
332
- if ( array_key_exists($module_id, $modules) ){
333
- $active[$module_id] = $modules[$module_id];
334
- }
335
- }
336
- }
337
-
338
- $this->plugin_conf->options['active_modules'] = $active;
339
- $this->plugin_conf->save_options();
340
-
341
- return $this->plugin_conf->options['active_modules'];
342
- }
343
-
344
- /**
345
- * Determine if module is active.
346
- *
347
- * @param string $module_id
348
- * @return bool
349
- */
350
- function is_active($module_id){
351
- return array_key_exists($module_id, $this->get_active_modules());
352
- }
353
-
354
- /**
355
- * Activate a module.
356
- * Does nothing if the module is already active.
357
- *
358
- * @param string $module_id
359
- * @return bool True if module was activated successfully, false otherwise.
360
- */
361
- function activate($module_id){
362
- if ( $this->is_active($module_id) ){
363
- return true;
364
- }
365
-
366
- //Retrieve the module header
367
- $module_data = $this->get_module_data($module_id, false);
368
- if ( !$module_data ){
369
- return false;
370
- }
371
-
372
- //Attempt to load the module
373
- if ( $this->load_module($module_id, $module_data) ){
374
- //Okay, if it loads, we can assume it works.
375
- $this->plugin_conf->options['active_modules'][$module_id] = $module_data;
376
- $this->plugin_conf->save_options();
377
- //Invalidate the per-category active module cache
378
- $this->_category_cache_active = null;
379
-
380
- //Notify the module that it's been activated
381
- $module = $this->get_module($module_id);
382
- if ( $module ){
383
- $module->activated();
384
- }
385
- return true;
386
- } else {
387
- return false;
388
- }
389
- }
390
-
391
- /**
392
- * Deactivate a module.
393
- * Does nothing if the module is already inactive.
394
- *
395
- * @param string $module_id
396
- * @return bool
397
- */
398
- function deactivate($module_id){
399
- if ( !$this->is_active($module_id) ){
400
- return true;
401
- }
402
-
403
- //Some modules are supposed to be always active and thus can't be deactivated
404
- $module_data = $this->get_module_data($module_id, false);
405
- if ( isset($module_data['ModuleAlwaysActive']) && $module_data['ModuleAlwaysActive'] ){
406
- return false;
407
- }
408
-
409
- //Notify the module that it's being deactivated
410
- $module = $this->get_module($module_id);
411
- if ( $module ){
412
- $module->deactivated();
413
- }
414
-
415
- unset($this->plugin_conf->options['active_modules'][$module_id]);
416
-
417
- //Keep track of when each module was last deactivated. Used for parser resynchronization.
418
- if ( isset($this->plugin_conf->options['module_deactivated_when']) ){
419
- $this->plugin_conf->options['module_deactivated_when'][$module_id] = current_time('timestamp');
420
- } else {
421
- $this->plugin_conf->options['module_deactivated_when'] = array(
422
- $module_id => current_time('timestamp'),
423
- );
424
- }
425
- $this->plugin_conf->save_options();
426
-
427
- $this->_category_cache_active = null; //Invalidate the by-category cache since we just changed something
428
- return true;
429
- }
430
-
431
- /**
432
- * Determine when a module was last deactivated.
433
- *
434
- * @param string $module_id Module ID.
435
- * @return int Timestamp of last deactivation, or 0 if the module has never been deactivated.
436
- */
437
- function get_last_deactivation_time($module_id){
438
- if ( isset($this->plugin_conf->options['module_deactivated_when'][$module_id]) ){
439
- return $this->plugin_conf->options['module_deactivated_when'][$module_id];
440
- } else {
441
- return 0;
442
- }
443
- }
444
-
445
- /**
446
- * Set the current list of active modules. If any of the modules are not currently active,
447
- * they will be activated. Any currently active modules that are not on the new list will
448
- * be deactivated.
449
- *
450
- * @param array $ids An array of module IDs.
451
- * @return void
452
- */
453
- function set_active_modules($ids){
454
- $current_active = array_keys($this->get_active_modules());
455
-
456
- $activate = array_diff($ids, $current_active);
457
- $deactivate = array_diff($current_active, $ids);
458
-
459
- //Deactivate any modules not present in the new list
460
- foreach($deactivate as $module_id){
461
- $this->deactivate($module_id);
462
- }
463
-
464
- //Activate modules present in the new list but not in the old list
465
- foreach($activate as $module_id){
466
- $this->activate($module_id);
467
- }
468
-
469
- //Ensure all active modules have the latest headers
470
- $this->refresh_active_module_cache();
471
-
472
- //Invalidate the per-category active module cache
473
- $this->_category_cache_active = null;
474
- }
475
-
476
- /**
477
- * Send the activation message to all currently active plugins when the plugin is activated.
478
- *
479
- * @return void
480
- */
481
- function plugin_activated(){
482
- global $blclog;
483
-
484
- //Ensure all active modules have the latest headers
485
- $blclog->log('... Updating module cache');
486
- $start = microtime(true);
487
- $this->refresh_active_module_cache();
488
- $blclog->info(sprintf('... Cache refresh took %.3f seconds', microtime(true) - $start));
489
-
490
- //Notify them that we've been activated
491
- $blclog->log('... Loading modules');
492
- $start = microtime(true);
493
- $this->load_modules();
494
- $blclog->info(sprintf('... %d modules loaded in %.3f seconds', count($this->loaded), microtime(true) - $start));
495
-
496
- $active = $this->get_active_modules();
497
- foreach($active as $module_id => $module_data){
498
- $blclog->log( sprintf('... Notifying module "%s"', $module_id) );
499
- $module = $this->get_module($module_id);
500
- if ( $module ){
501
- $module->plugin_activated();
502
- } else {
503
- $blclog->warn(sprintf('... Module "%s" failed to load!', $module_id));
504
- }
505
- }
506
- }
507
-
508
- /**
509
- * Refresh the cached data of all active modules.
510
- *
511
- * @return array An updated list of active modules.
512
- */
513
- function refresh_active_module_cache(){
514
- $modules = $this->get_modules();
515
- foreach($this->plugin_conf->options['active_modules'] as $module_id => $module_header){
516
- if ( array_key_exists($module_id, $modules) ){
517
- $this->plugin_conf->options['active_modules'][$module_id] = $modules[$module_id];
518
- }
519
- }
520
- $this->plugin_conf->save_options();
521
- $this->_category_cache_active = null; //Invalidate the by-category active module cache
522
- return $this->plugin_conf->options['active_modules'];
523
- }
524
-
525
- /**
526
- * Load active modules.
527
- *
528
- * @param string $context Optional. If specified, only the modules that match this context (or the "all" context) will be loaded.
529
- * @return void
530
- */
531
- function load_modules($context = ''){
532
- $active = $this->get_active_modules();
533
- //Avoid trying to load a virtual module before the module that registered it has been loaded.
534
- $active = $this->put_virtual_last($active);
535
-
536
- foreach($active as $module_id => $module_data){
537
- //Load the module
538
- $should_load = ($module_data['ModuleContext'] == 'all') || (!empty($context) && $module_data['ModuleContext'] == $context);
539
- if ( $should_load ){
540
- $this->load_module($module_id, $module_data);
541
- }
542
- }
543
- }
544
-
545
- /**
546
- * Load and possibly instantiate a specific module.
547
- *
548
- * @access private
549
- *
550
- * @param string $module_id
551
- * @param array $module_data
552
- * @return bool True if the module was successfully loaded, false otherwise.
553
- */
554
- function load_module($module_id, $module_data = null){
555
-
556
- //Only load each module once.
557
- if ( !empty($this->loaded[$module_id]) ){
558
- return true;
559
- }
560
-
561
- if ( !isset($module_data) ){
562
- $module_data = $this->get_module_data($module_id);
563
- if ( empty($module_data) ){
564
- return false;
565
- }
566
- }
567
-
568
- //Load a normal module
569
- if ( empty($module_data['virtual']) ){
570
-
571
- //Skip invalid and missing modules
572
- if ( empty($module_data['file']) ){
573
- return false;
574
- }
575
-
576
- //Get the full path to the module file
577
- $filename = $this->module_dir . '/' . $module_data['file'];
578
- if ( !file_exists($filename) ){
579
- return false;
580
- }
581
-
582
- //Load it
583
- include $filename;
584
- $this->loaded[$module_id] = true;
585
-
586
- } else {
587
-
588
- //Virtual modules don't need to be explicitly loaded, but they must
589
- //be registered.
590
- if ( !array_key_exists($module_id, $this->_virtual_modules) ) {
591
- return false;
592
- }
593
- $this->loaded[$module_id] = true;
594
-
595
- }
596
-
597
- //Instantiate the main module class unless lazy init is on (default is off)
598
- if ( !array_key_exists($module_id, $this->instances) ){ //Only try to instantiate once
599
- if ( !$module_data['ModuleLazyInit'] ){
600
- $this->init_module($module_id, $module_data);
601
- }
602
- }
603
-
604
- return true;
605
- }
606
-
607
- /**
608
- * Instantiate a certain module.
609
- *
610
- * @param string $module_id
611
- * @param array $module_data Optional. The header data of the module that needs to be instantiated. If not specified, it will be retrieved automatically.
612
- * @return object The newly instantiated module object (extends blcModule), or NULL on error.
613
- */
614
- function init_module($module_id, $module_data = null){
615
- //Each module is only instantiated once.
616
- if ( isset($this->instances[$module_id]) ){
617
- return $this->instances[$module_id];
618
- }
619
-
620
- if ( !isset($module_data) ){
621
- $module_data = $this->get_module_data($module_id);
622
- if ( empty($module_data) ){
623
- return null;
624
- }
625
- }
626
-
627
- if ( !empty($module_data['ModuleClassName']) && class_exists($module_data['ModuleClassName']) ){
628
- $className = $module_data['ModuleClassName'];
629
- $this->instances[$module_id] = new $className(
630
- $module_id,
631
- $module_data,
632
- $this->plugin_conf,
633
- $this
634
- );
635
- return $this->instances[$module_id];
636
- };
637
-
638
- return null;
639
- }
640
-
641
- function is_instantiated($module_id){
642
- return !empty($this->instances[$module_id]);
643
- }
644
-
645
- /**
646
- * Register a virtual module.
647
- *
648
- * Virtual modules are the same as normal modules, except that they can be added
649
- * on the fly, e.g. by other modules.
650
- *
651
- * @param string $module_id Module Id.
652
- * @param string $module_data Associative array of module data. All module header fields are allowed, except ModuleID.
653
- * @return void
654
- */
655
- function register_virtual_module($module_id, $module_data){
656
- $module_data = $this->normalize_module_header($module_data, $module_id);
657
- $module_data['virtual'] = true;
658
- $this->_virtual_modules[$module_id] = $module_data;
659
- }
660
-
661
- /**
662
- * Sort an array of modules so that all virtual modules are placed at its end.
663
- *
664
- * @param array $modules Input array, [module_id => module_data, ...].
665
- * @return array Sorted array.
666
- */
667
- function put_virtual_last($modules){
668
- uasort($modules, array(&$this, 'compare_virtual_flags'));
669
- return $modules;
670
- }
671
-
672
- /**
673
- * Callback function for sorting modules by the state of their 'virtual' flag.
674
- *
675
- * @param array $a Associative array of module A data
676
- * @param array $b Associative array of module B data
677
- * @return int
678
- */
679
- function compare_virtual_flags($a, $b){
680
- if ( empty($a['virtual']) ){
681
- return empty($b['virtual'])?0:-1;
682
- } else {
683
- return empty($b['virtual'])?1:0;
684
- }
685
- }
686
-
687
- /**
688
- * Validate active modules.
689
- *
690
- * Validates all active modules, deactivates invalid ones and returns
691
- * an array of deactivated modules.
692
- *
693
- * @return array
694
- */
695
- function validate_active_modules(){
696
- $active = $this->get_active_modules();
697
- if ( empty($active) ){
698
- return array();
699
- }
700
-
701
- $invalid = array();
702
- foreach($active as $module_id => $module_data){
703
- $rez = $this->validate_module($module_data);
704
- if ( is_wp_error($rez) ){
705
- $invalid[$module_id] = $rez;
706
- $this->deactivate($module_id);
707
- }
708
- }
709
-
710
- return $invalid;
711
- }
712
-
713
- /**
714
- * Validate module data.
715
- *
716
- * Checks that the module file exists or that the module
717
- * is a currently registered virtual module.
718
- *
719
- * @param array $module_data Associative array of module data.
720
- * @return bool|WP_Error True on success, an error object if the module fails validation
721
- */
722
- function validate_module($module_data){
723
- if ( empty($module_data['ModuleID']) ){
724
- return new WP_Error('invalid_cached_header', 'The cached module header is invalid');
725
- }
726
-
727
- if ( empty($module_data['virtual']) ){
728
- //Normal modules must have a valid filename
729
- if ( empty($module_data['file']) ){
730
- return new WP_Error('module_not_found', 'Invalid module file');
731
- }
732
-
733
- $filename = $this->module_dir . '/' . $module_data['file'];
734
- if ( !file_exists($filename) ){
735
- return new WP_Error('module_not_found', 'Module file not found');
736
- }
737
-
738
- //The module file header must be in the proper format. While $module_data comes
739
- //from cache and can be assumed to be correct, get_modules() will attempt to load
740
- //the current headers and only return modules with semi-valid headers.
741
- $installed = $this->get_modules();
742
- if ( !array_key_exists($module_data['ModuleID'], $installed) ){
743
- return new WP_Error('invalid_module_header', 'Invalid module header');
744
- }
745
- } else {
746
- //Virtual modules need to be currently registered
747
- if ( !array_key_exists($module_data['ModuleID'], $this->_virtual_modules) ){
748
- return new WP_Error('module_not_registered', 'The virtual module is not registered');
749
- }
750
- }
751
-
752
- return true;
753
- }
754
-
755
- /**
756
- * Add BLC-module specific headers to the list of allowed plugin headers. This
757
- * lets us use get_plugins() to retrieve the list of BLC modules.
758
- *
759
- * @param array $headers Currently known plugin headers.
760
- * @return array New plugin headers.
761
- */
762
- function inject_module_headers($headers){
763
- $module_headers = array(
764
- 'ModuleID',
765
- 'ModuleCategory',
766
- 'ModuleContext',
767
- 'ModuleLazyInit',
768
- 'ModuleClassName',
769
- 'ModulePriority',
770
- 'ModuleCheckerUrlPattern',
771
- 'ModuleHidden', //Don't show the module in the Settings page
772
- 'ModuleAlwaysActive', //Module can't be deactivated.
773
- 'ModuleRequiresPro', //Can only be activated in the Pro version
774
- );
775
-
776
- return array_merge($headers, $module_headers);
777
- }
778
-
779
- /**
780
- * Normalize a module header, using defaults where necessary.
781
- *
782
- * @param array $module_header Module header, as read from the module's .php file.
783
- * @param string $module_id Module ID.
784
- * @param string $module_filename Module filename. Optional.
785
- * @return array Normalized module header.
786
- */
787
- function normalize_module_header($module_header, $module_id, $module_filename = ''){
788
- //Default values for optional module header fields
789
- $defaults = array(
790
- 'ModuleContext' => 'all',
791
- 'ModuleCategory' => 'other',
792
- 'ModuleLazyInit' => 'false',
793
- 'ModulePriority' => '0',
794
- 'ModuleHidden' => 'false',
795
- 'ModuleAlwaysActive' => 'false',
796
- 'ModuleRequiresPro' => 'false',
797
- 'TextDomain' => 'broken-link-checker', //For translating module headers
798
- );
799
-
800
- $module_header['ModuleID'] = $module_id; //Just for consistency
801
- $module_header['file'] = $module_filename; //Used later to load the module
802
-
803
- //Apply defaults
804
- foreach($defaults as $field => $default_value){
805
- if ( empty($module_header[$field]) ){
806
- $module_header[$field] = $default_value;
807
- }
808
- }
809
-
810
- //Convert bool/int fields from strings to native datatypes
811
- $module_header['ModuleLazyInit'] = $this->str_to_bool($module_header['ModuleLazyInit']);
812
- $module_header['ModuleHidden'] = $this->str_to_bool($module_header['ModuleHidden']);
813
- $module_header['ModuleAlwaysActive'] = $this->str_to_bool($module_header['ModuleAlwaysActive']);
814
- $module_header['ModuleRequiresPro'] = $this->str_to_bool($module_header['ModuleRequiresPro']);
815
- $module_header['ModulePriority'] = intval($module_header['ModulePriority']);
816
-
817
- return $module_header;
818
- }
819
-
820
- /**
821
- * Converts the strings "true" and "false" to boolean TRUE and FALSE, respectively.
822
- * Any other string will yield FALSE.
823
- *
824
- * @param string $value "true" or "false", case-insensitive.
825
- * @return bool
826
- */
827
- function str_to_bool($value){
828
- $value = trim(strtolower($value));
829
- return $value == 'true';
830
- }
831
-
832
- /**
833
- * Generates a PHP script that calls the __() i18n function with
834
- * the name and description of each available module. The generated
835
- * script is used to make module headers show up in the .POT file.
836
- *
837
- * @access private
838
- *
839
- * @return string
840
- */
841
- function _build_header_translation_code(){
842
- $this->_module_cache = null; //Clear the cache
843
- $modules = $this->get_modules();
844
-
845
- $strings = array();
846
- foreach($modules as $module_id => $module_header){
847
- if ( $module_header['ModuleHidden'] || ($module_id == 'write-module-placeholders')) {
848
- continue;
849
- }
850
- if ( !empty($module_header['Name']) ){
851
- $strings[] = sprintf(
852
- '_x("%s", "module name", "broken-link-checker");',
853
- str_replace('"', '\"', $module_header['Name'])
854
- );
855
- }
856
- }
857
-
858
- return "<?php\n" . implode("\n", $strings) . "\n";
859
- }
860
- }
1
+ <?php
2
+
3
+ class blcModuleManager {
4
+
5
+ /* @var blcConfigurationManager */
6
+ var $plugin_conf;
7
+ var $module_dir = '';
8
+
9
+ var $_module_cache;
10
+ var $_category_cache;
11
+ var $_category_cache_active;
12
+ var $_virtual_modules = array();
13
+
14
+ var $loaded;
15
+ var $instances;
16
+ var $default_active_modules;
17
+
18
+
19
+ /**
20
+ * Class "constructor".
21
+ *
22
+ * @param array $default_active_modules An array of module ids specifying which modules are active by default.
23
+ * @return void
24
+ */
25
+ function init($default_active_modules = null){
26
+ $this->module_dir = realpath(dirname(__FILE__) . '/../modules');
27
+
28
+ $this->plugin_conf = blc_get_configuration();
29
+ $this->default_active_modules = $default_active_modules;
30
+
31
+ $this->loaded = array();
32
+ $this->instances = array();
33
+
34
+ add_filter('extra_plugin_headers', array(&$this, 'inject_module_headers'));
35
+ }
36
+
37
+ /**
38
+ * Get an instance of the module manager.
39
+ *
40
+ * @param array|null $default_active_modules
41
+ * @return blcModuleManager
42
+ */
43
+ static function getInstance($default_active_modules = null){
44
+ static $instance = null;
45
+ if ( is_null($instance) ){
46
+ $instance = new blcModuleManager();
47
+ $instance->init($default_active_modules);
48
+ }
49
+ return $instance;
50
+ }
51
+
52
+ /**
53
+ * Retrieve a list of all installed BLC modules.
54
+ *
55
+ * This is essentially a slightly modified copy of get_plugins().
56
+ *
57
+ * @return array An associative array of module headers indexed by module ID.
58
+ */
59
+ function get_modules(){
60
+ if ( !isset($this->_module_cache) ){
61
+ //Refresh the module cache.
62
+
63
+ $relative_path = '/' . plugin_basename($this->module_dir);
64
+ if ( !function_exists('get_plugins') ){
65
+ //BUG: Potentional security flaw/bug. plugin.php is not meant to be loaded outside admin panel.
66
+ require_once(ABSPATH . 'wp-admin/includes/plugin.php');
67
+ }
68
+ $modules = get_plugins( $relative_path );
69
+
70
+ $this->_module_cache = array();
71
+
72
+ foreach($modules as $module_filename => $module_header){
73
+ //Figure out the module ID. If not specified, it is equal to module's filename (sans the .php)
74
+ if ( !empty($module_header['ModuleID']) ){
75
+ $module_id = strtolower(trim($module_header['ModuleID']));
76
+ } else {
77
+ $module_id = strtolower(basename($module_filename, '.php'));
78
+ }
79
+
80
+ $module_header = $this->normalize_module_header($module_header, $module_id, $module_filename);
81
+ $this->_module_cache[$module_id] = $module_header;
82
+ }
83
+
84
+ $this->_category_cache = null;
85
+ }
86
+
87
+ return array_merge($this->_module_cache, $this->_virtual_modules);
88
+ }
89
+
90
+ /**
91
+ * Retrieve modules that match a specific category, or all modules sorted by categories.
92
+ *
93
+ * If a category ID is specified, this method returns the modules that have the "ModuleCategory:"
94
+ * file header set to that value, or an empty array if no modules match that category. The
95
+ * return array is indexed by module id :
96
+ * [module_id1 => module1_data, module_id1 => module2_data, ...]
97
+ *
98
+ * If category is omitted, this method returns a list of all categories plus the modules
99
+ * they contain. Only categories that have at least one module will be included. The return
100
+ * value is an array of arrays, indexed by category ID :
101
+ * [category1 => [module1_id => module1_data, module2_id => module2_data, ...], ...]
102
+ *
103
+ *
104
+ * @param string $category Category id, e.g. "parser" or "container". Optional.
105
+ * @param bool $markup Apply markup to module headers. Not implemented.
106
+ * @param bool $translate Translate module headers. Defaults to false.
107
+ * @return array An array of categories or module data.
108
+ */
109
+ function get_modules_by_category($category = '', $markup = false, $translate = false){
110
+ if ( !isset($this->_category_cache) ){
111
+ $this->_category_cache = $this->sort_into_categories($this->get_modules());
112
+ }
113
+
114
+ if ( empty($category) ){
115
+ if ( $markup || $translate ){
116
+
117
+ //Translate/apply markup to module headers
118
+ $processed = array();
119
+ foreach($this->_category_cache as $category_id => $modules){
120
+ $processed[$category_id] = array();
121
+ foreach($modules as $module_id => $module_data){
122
+ if ( $translate ){
123
+ $module_data['Name'] = _x($module_data['Name'], 'module name', 'broken-link-checker');
124
+ }
125
+ $processed[$category_id][$module_id] = $module_data;
126
+ }
127
+ }
128
+
129
+ return $processed;
130
+ } else {
131
+ return $this->_category_cache;
132
+ }
133
+ } else {
134
+ if ( isset($this->_category_cache[$category]) ){
135
+ if ( $markup || $translate ){
136
+ //Translate/apply markup to module headers
137
+ $processed = array();
138
+ foreach($this->_category_cache[$category] as $module_id => $module_data){
139
+ if ( $translate ){
140
+ $module_data['Name'] = _x($module_data['Name'], 'module name', 'broken-link-checker');
141
+ }
142
+ $processed[$module_id] = $module_data;
143
+ }
144
+ return $processed;
145
+ } else {
146
+ return $this->_category_cache[$category];
147
+ }
148
+ } else {
149
+ return array();
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Retrieve active modules that match a specific category, or all active modules sorted by categories.
156
+ *
157
+ * @see blcModuleManager::get_modules_by_category()
158
+ *
159
+ * @param string $category Category id. Optional.
160
+ * @return array An associative array of categories or module data.
161
+ */
162
+ function get_active_by_category($category = ''){
163
+ if ( !isset($this->_category_cache_active) ){
164
+ $this->_category_cache_active = $this->sort_into_categories($this->get_active_modules());
165
+ }
166
+
167
+ if ( empty($category) ){
168
+ return $this->_category_cache_active;
169
+ } else {
170
+ if ( isset($this->_category_cache_active[$category]) ){
171
+ return $this->_category_cache_active[$category];
172
+ } else {
173
+ return array();
174
+ }
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Get the module ids of all active modules that belong to a specific category,
180
+ * quoted and ready for use in SQL.
181
+ *
182
+ * @param string $category Category ID. If not specified, a list of all active modules will be returned.
183
+ * @return string A comma separated list of single-quoted module ids, e.g. 'module-foo','module-bar','modbaz'
184
+ */
185
+ function get_escaped_ids($category = ''){
186
+ global $wpdb;
187
+
188
+ if ( empty($category) ){
189
+ $modules = $this->get_active_modules();
190
+ } else {
191
+ $modules = $this->get_active_by_category($category);
192
+ }
193
+
194
+ $modules = array_map('esc_sql', array_keys($modules));
195
+ $modules = "'" . implode("', '", $modules) . "'";
196
+
197
+ return $modules;
198
+ }
199
+
200
+ /**
201
+ * Sort a list of modules into categories. Inside each category, modules are sorted by priority (descending).
202
+ *
203
+ * @access private
204
+ *
205
+ * @param array $modules
206
+ * @return array
207
+ */
208
+ function sort_into_categories($modules){
209
+ $categories = array();
210
+
211
+ foreach($modules as $module_id => $module_data){
212
+ $cat = $module_data['ModuleCategory'];
213
+ if ( isset($categories[$cat]) ){
214
+ $categories[$cat][$module_id] = $module_data;
215
+ } else {
216
+ $categories[$cat] = array($module_id => $module_data);
217
+ }
218
+ }
219
+
220
+ foreach($categories as $cat => $cat_modules){
221
+ uasort($categories[$cat], array(&$this, 'compare_priorities'));
222
+ }
223
+
224
+ return $categories;
225
+ }
226
+
227
+ /**
228
+ * Callback for sorting modules by priority.
229
+ *
230
+ * @access private
231
+ *
232
+ * @param array $a First module header.
233
+ * @param array $b Second module header.
234
+ * @return int
235
+ */
236
+ function compare_priorities($a, $b){
237
+ return $b['ModulePriority'] - $a['ModulePriority'];
238
+ }
239
+
240
+ /**
241
+ * Retrieve a reference to an active module.
242
+ *
243
+ * Each module is instantiated only once, so if the module was already loaded you'll get back
244
+ * a reference to the existing module object. If the module isn't loaded or instantiated yet,
245
+ * the function will do it automatically (but only for active modules).
246
+ *
247
+ * @param string $module_id Module ID.
248
+ * @param bool $autoload Optional. Whether to load the module file if it's not loaded yet. Defaults to TRUE.
249
+ * @param string $category Optional. Return the module only if it's in a specific category. Categories are ignored by default.
250
+ * @return blcModule A reference to a module object, or NULL on error.
251
+ */
252
+ function get_module($module_id, $autoload = true, $category=''){
253
+ $no_result = null;
254
+ if ( !is_string($module_id) ){
255
+ //$backtrace = debug_backtrace();
256
+ //FB::error($backtrace, "get_module called with a non-string argument");
257
+ return $no_result;
258
+ }
259
+
260
+ if ( empty($this->loaded[$module_id]) ){
261
+ if ( $autoload && $this->is_active($module_id) ){
262
+ if ( !$this->load_module($module_id) ){
263
+ return $no_result;
264
+ }
265
+ } else {
266
+ return $no_result;
267
+ }
268
+ }
269
+
270
+ if ( !empty($category) ){
271
+ $data = $this->get_module_data($module_id);
272
+ if ( $data['ModuleCategory'] != $category ){
273
+ return $no_result;
274
+ }
275
+ }
276
+
277
+ $module = $this->init_module($module_id);
278
+ return $module;
279
+ }
280
+
281
+ /**
282
+ * Retrieve the header data of a specific module.
283
+ * Uses cached module info if available.
284
+ *
285
+ * @param string $module_id
286
+ * @param bool $use_active_cache Check the active module cache before the general one. Defaults to true.
287
+ * @return array Associative array of module data, or FALSE if the specified module was not found.
288
+ */
289
+ function get_module_data($module_id, $use_active_cache = true){
290
+ //Check virtual modules
291
+ if ( isset($this->_virtual_modules[$module_id]) ){
292
+ return $this->_virtual_modules[$module_id];
293
+ }
294
+
295
+ //Check active modules
296
+ if ( $use_active_cache && isset($this->plugin_conf->options['active_modules'][$module_id]) ){
297
+ return $this->plugin_conf->options['active_modules'][$module_id];
298
+ }
299
+
300
+ //Otherwise, use the general module cache
301
+ if ( !isset($this->_module_cache) ){
302
+ $this->get_modules(); //Populates the cache
303
+ }
304
+
305
+ if ( isset($this->_module_cache[$module_id]) ){
306
+ return $this->_module_cache[$module_id];
307
+ } else {
308
+ return false;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Retrieve a list of active modules.
314
+ *
315
+ * The list of active modules is stored in the 'active_modules' key of the
316
+ * plugin configuration object. If this key is not set, this function will
317
+ * create it and populate it using the list of default active modules passed
318
+ * to the module manager's constructor.
319
+ *
320
+ * @return array Associative array of module data indexed by module ID.
321
+ */
322
+ function get_active_modules(){
323
+ if ( isset($this->plugin_conf->options['active_modules']) ){
324
+ return $this->plugin_conf->options['active_modules'];
325
+ }
326
+
327
+ $active = array();
328
+ $modules = $this->get_modules();
329
+
330
+ if ( is_array($this->default_active_modules) ){
331
+ foreach($this->default_active_modules as $module_id){
332
+ if ( array_key_exists($module_id, $modules) ){
333
+ $active[$module_id] = $modules[$module_id];
334
+ }
335
+ }
336
+ }
337
+
338
+ $this->plugin_conf->options['active_modules'] = $active;
339
+ $this->plugin_conf->save_options();
340
+
341
+ return $this->plugin_conf->options['active_modules'];
342
+ }
343
+
344
+ /**
345
+ * Determine if module is active.
346
+ *
347
+ * @param string $module_id
348
+ * @return bool
349
+ */
350
+ function is_active($module_id){
351
+ return array_key_exists($module_id, $this->get_active_modules());
352
+ }
353
+
354
+ /**
355
+ * Activate a module.
356
+ * Does nothing if the module is already active.
357
+ *
358
+ * @param string $module_id
359
+ * @return bool True if module was activated successfully, false otherwise.
360
+ */
361
+ function activate($module_id){
362
+ if ( $this->is_active($module_id) ){
363
+ return true;
364
+ }
365
+
366
+ //Retrieve the module header
367
+ $module_data = $this->get_module_data($module_id, false);
368
+ if ( !$module_data ){
369
+ return false;
370
+ }
371
+
372
+ //Attempt to load the module
373
+ if ( $this->load_module($module_id, $module_data) ){
374
+ //Okay, if it loads, we can assume it works.
375
+ $this->plugin_conf->options['active_modules'][$module_id] = $module_data;
376
+ $this->plugin_conf->save_options();
377
+ //Invalidate the per-category active module cache
378
+ $this->_category_cache_active = null;
379
+
380
+ //Notify the module that it's been activated
381
+ $module = $this->get_module($module_id);
382
+ if ( $module ){
383
+ $module->activated();
384
+ }
385
+ return true;
386
+ } else {
387
+ return false;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Deactivate a module.
393
+ * Does nothing if the module is already inactive.
394
+ *
395
+ * @param string $module_id
396
+ * @return bool
397
+ */
398
+ function deactivate($module_id){
399
+ if ( !$this->is_active($module_id) ){
400
+ return true;
401
+ }
402
+
403
+ //Some modules are supposed to be always active and thus can't be deactivated
404
+ $module_data = $this->get_module_data($module_id, false);
405
+ if ( isset($module_data['ModuleAlwaysActive']) && $module_data['ModuleAlwaysActive'] ){
406
+ return false;
407
+ }
408
+
409
+ //Notify the module that it's being deactivated
410
+ $module = $this->get_module($module_id);
411
+ if ( $module ){
412
+ $module->deactivated();
413
+ }
414
+
415
+ unset($this->plugin_conf->options['active_modules'][$module_id]);
416
+
417
+ //Keep track of when each module was last deactivated. Used for parser resynchronization.
418
+ if ( isset($this->plugin_conf->options['module_deactivated_when']) ){
419
+ $this->plugin_conf->options['module_deactivated_when'][$module_id] = current_time('timestamp');
420
+ } else {
421
+ $this->plugin_conf->options['module_deactivated_when'] = array(
422
+ $module_id => current_time('timestamp'),
423
+ );
424
+ }
425
+ $this->plugin_conf->save_options();
426
+
427
+ $this->_category_cache_active = null; //Invalidate the by-category cache since we just changed something
428
+ return true;
429
+ }
430
+
431
+ /**
432
+ * Determine when a module was last deactivated.
433
+ *
434
+ * @param string $module_id Module ID.
435
+ * @return int Timestamp of last deactivation, or 0 if the module has never been deactivated.
436
+ */
437
+ function get_last_deactivation_time($module_id){
438
+ if ( isset($this->plugin_conf->options['module_deactivated_when'][$module_id]) ){
439
+ return $this->plugin_conf->options['module_deactivated_when'][$module_id];
440
+ } else {
441
+ return 0;
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Set the current list of active modules. If any of the modules are not currently active,
447
+ * they will be activated. Any currently active modules that are not on the new list will
448
+ * be deactivated.
449
+ *
450
+ * @param array $ids An array of module IDs.
451
+ * @return void
452
+ */
453
+ function set_active_modules($ids){
454
+ $current_active = array_keys($this->get_active_modules());
455
+
456
+ $activate = array_diff($ids, $current_active);
457
+ $deactivate = array_diff($current_active, $ids);
458
+
459
+ //Deactivate any modules not present in the new list
460
+ foreach($deactivate as $module_id){
461
+ $this->deactivate($module_id);
462
+ }
463
+
464
+ //Activate modules present in the new list but not in the old list
465
+ foreach($activate as $module_id){
466
+ $this->activate($module_id);
467
+ }
468
+
469
+ //Ensure all active modules have the latest headers
470
+ $this->refresh_active_module_cache();
471
+
472
+ //Invalidate the per-category active module cache
473
+ $this->_category_cache_active = null;
474
+ }
475
+
476
+ /**
477
+ * Send the activation message to all currently active plugins when the plugin is activated.
478
+ *
479
+ * @return void
480
+ */
481
+ function plugin_activated(){
482
+ global $blclog;
483
+
484
+ //Ensure all active modules have the latest headers
485
+ $blclog->log('... Updating module cache');
486
+ $start = microtime(true);
487
+ $this->refresh_active_module_cache();
488
+ $blclog->info(sprintf('... Cache refresh took %.3f seconds', microtime(true) - $start));
489
+
490
+ //Notify them that we've been activated
491
+ $blclog->log('... Loading modules');
492
+ $start = microtime(true);
493
+ $this->load_modules();
494
+ $blclog->info(sprintf('... %d modules loaded in %.3f seconds', count($this->loaded), microtime(true) - $start));
495
+
496
+ $active = $this->get_active_modules();
497
+ foreach($active as $module_id => $module_data){
498
+ $blclog->log( sprintf('... Notifying module "%s"', $module_id) );
499
+ $module = $this->get_module($module_id);
500
+ if ( $module ){
501
+ $module->plugin_activated();
502
+ } else {
503
+ $blclog->warn(sprintf('... Module "%s" failed to load!', $module_id));
504
+ }
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Refresh the cached data of all active modules.
510
+ *
511
+ * @return array An updated list of active modules.
512
+ */
513
+ function refresh_active_module_cache(){
514
+ $modules = $this->get_modules();
515
+ foreach($this->plugin_conf->options['active_modules'] as $module_id => $module_header){
516
+ if ( array_key_exists($module_id, $modules) ){
517
+ $this->plugin_conf->options['active_modules'][$module_id] = $modules[$module_id];
518
+ }
519
+ }
520
+ $this->plugin_conf->save_options();
521
+ $this->_category_cache_active = null; //Invalidate the by-category active module cache
522
+ return $this->plugin_conf->options['active_modules'];
523
+ }
524
+
525
+ /**
526
+ * Load active modules.
527
+ *
528
+ * @param string $context Optional. If specified, only the modules that match this context (or the "all" context) will be loaded.
529
+ * @return void
530
+ */
531
+ function load_modules($context = ''){
532
+ $active = $this->get_active_modules();
533
+ //Avoid trying to load a virtual module before the module that registered it has been loaded.
534
+ $active = $this->put_virtual_last($active);
535
+
536
+ foreach($active as $module_id => $module_data){
537
+ //Load the module
538
+ $should_load = ($module_data['ModuleContext'] == 'all') || (!empty($context) && $module_data['ModuleContext'] == $context);
539
+ if ( $should_load ){
540
+ $this->load_module($module_id, $module_data);
541
+ }
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Load and possibly instantiate a specific module.
547
+ *
548
+ * @access private
549
+ *
550
+ * @param string $module_id
551
+ * @param array $module_data
552
+ * @return bool True if the module was successfully loaded, false otherwise.
553
+ */
554
+ function load_module($module_id, $module_data = null){
555
+
556
+ //Only load each module once.
557
+ if ( !empty($this->loaded[$module_id]) ){
558
+ return true;
559
+ }
560
+
561
+ if ( !isset($module_data) ){
562
+ $module_data = $this->get_module_data($module_id);
563
+ if ( empty($module_data) ){
564
+ return false;
565
+ }
566
+ }
567
+
568
+ //Load a normal module
569
+ if ( empty($module_data['virtual']) ){
570
+
571
+ //Skip invalid and missing modules
572
+ if ( empty($module_data['file']) ){
573
+ return false;
574
+ }
575
+
576
+ //Get the full path to the module file
577
+ $filename = $this->module_dir . '/' . $module_data['file'];
578
+ if ( !file_exists($filename) ){
579
+ return false;
580
+ }
581
+
582
+ //Load it
583
+ include $filename;
584
+ $this->loaded[$module_id] = true;
585
+
586
+ } else {
587
+
588
+ //Virtual modules don't need to be explicitly loaded, but they must
589
+ //be registered.
590
+ if ( !array_key_exists($module_id, $this->_virtual_modules) ) {
591
+ return false;
592
+ }
593
+ $this->loaded[$module_id] = true;
594
+
595
+ }
596
+
597
+ //Instantiate the main module class unless lazy init is on (default is off)
598
+ if ( !array_key_exists($module_id, $this->instances) ){ //Only try to instantiate once
599
+ if ( !$module_data['ModuleLazyInit'] ){
600
+ $this->init_module($module_id, $module_data);
601
+ }
602
+ }
603
+
604
+ return true;
605
+ }
606
+
607
+ /**
608
+ * Instantiate a certain module.
609
+ *
610
+ * @param string $module_id
611
+ * @param array $module_data Optional. The header data of the module that needs to be instantiated. If not specified, it will be retrieved automatically.
612
+ * @return object The newly instantiated module object (extends blcModule), or NULL on error.
613
+ */
614
+ function init_module($module_id, $module_data = null){
615
+ //Each module is only instantiated once.
616
+ if ( isset($this->instances[$module_id]) ){
617
+ return $this->instances[$module_id];
618
+ }
619
+
620
+ if ( !isset($module_data) ){
621
+ $module_data = $this->get_module_data($module_id);
622
+ if ( empty($module_data) ){
623
+ return null;
624
+ }
625
+ }
626
+
627
+ if ( !empty($module_data['ModuleClassName']) && class_exists($module_data['ModuleClassName']) ){
628
+ $className = $module_data['ModuleClassName'];
629
+ $this->instances[$module_id] = new $className(
630
+ $module_id,
631
+ $module_data,
632
+ $this->plugin_conf,
633
+ $this
634
+ );
635
+ return $this->instances[$module_id];
636
+ };
637
+
638
+ return null;
639
+ }
640
+
641
+ function is_instantiated($module_id){
642
+ return !empty($this->instances[$module_id]);
643
+ }
644
+
645
+ /**
646
+ * Register a virtual module.
647
+ *
648
+ * Virtual modules are the same as normal modules, except that they can be added
649
+ * on the fly, e.g. by other modules.
650
+ *
651
+ * @param string $module_id Module Id.
652
+ * @param string $module_data Associative array of module data. All module header fields are allowed, except ModuleID.
653
+ * @return void
654
+ */
655
+ function register_virtual_module($module_id, $module_data){
656
+ $module_data = $this->normalize_module_header($module_data, $module_id);
657
+ $module_data['virtual'] = true;
658
+ $this->_virtual_modules[$module_id] = $module_data;
659
+ }
660
+
661
+ /**
662
+ * Sort an array of modules so that all virtual modules are placed at its end.
663
+ *
664
+ * @param array $modules Input array, [module_id => module_data, ...].
665
+ * @return array Sorted array.
666
+ */
667
+ function put_virtual_last($modules){
668
+ uasort($modules, array(&$this, 'compare_virtual_flags'));
669
+ return $modules;
670
+ }
671
+
672
+ /**
673
+ * Callback function for sorting modules by the state of their 'virtual' flag.
674
+ *
675
+ * @param array $a Associative array of module A data
676
+ * @param array $b Associative array of module B data
677
+ * @return int
678
+ */
679
+ function compare_virtual_flags($a, $b){
680
+ if ( empty($a['virtual']) ){
681
+ return empty($b['virtual'])?0:-1;
682
+ } else {
683
+ return empty($b['virtual'])?1:0;
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Validate active modules.
689
+ *
690
+ * Validates all active modules, deactivates invalid ones and returns
691
+ * an array of deactivated modules.
692
+ *
693
+ * @return array
694
+ */
695
+ function validate_active_modules(){
696
+ $active = $this->get_active_modules();
697
+ if ( empty($active) ){
698
+ return array();
699
+ }
700
+
701
+ $invalid = array();
702
+ foreach($active as $module_id => $module_data){
703
+ $rez = $this->validate_module($module_data);
704
+ if ( is_wp_error($rez) ){
705
+ $invalid[$module_id] = $rez;
706
+ $this->deactivate($module_id);
707
+ }
708
+ }
709
+
710
+ return $invalid;
711
+ }
712
+
713
+ /**
714
+ * Validate module data.
715
+ *
716
+ * Checks that the module file exists or that the module
717
+ * is a currently registered virtual module.
718
+ *
719
+ * @param array $module_data Associative array of module data.
720
+ * @return bool|WP_Error True on success, an error object if the module fails validation
721
+ */
722
+ function validate_module($module_data){
723
+ if ( empty($module_data['ModuleID']) ){
724
+ return new WP_Error('invalid_cached_header', 'The cached module header is invalid');
725
+ }
726
+
727
+ if ( empty($module_data['virtual']) ){
728
+ //Normal modules must have a valid filename
729
+ if ( empty($module_data['file']) ){
730
+ return new WP_Error('module_not_found', 'Invalid module file');
731
+ }
732
+
733
+ $filename = $this->module_dir . '/' . $module_data['file'];
734
+ if ( !file_exists($filename) ){
735
+ return new WP_Error('module_not_found', 'Module file not found');
736
+ }
737
+
738
+ //The module file header must be in the proper format. While $module_data comes
739
+ //from cache and can be assumed to be correct, get_modules() will attempt to load
740
+ //the current headers and only return modules with semi-valid headers.
741
+ $installed = $this->get_modules();
742
+ if ( !array_key_exists($module_data['ModuleID'], $installed) ){
743
+ return new WP_Error('invalid_module_header', 'Invalid module header');
744
+ }
745
+ } else {
746
+ //Virtual modules need to be currently registered
747
+ if ( !array_key_exists($module_data['ModuleID'], $this->_virtual_modules) ){
748
+ return new WP_Error('module_not_registered', 'The virtual module is not registered');
749
+ }
750
+ }
751
+
752
+ return true;
753
+ }
754
+
755
+ /**
756
+ * Add BLC-module specific headers to the list of allowed plugin headers. This
757
+ * lets us use get_plugins() to retrieve the list of BLC modules.
758
+ *
759
+ * @param array $headers Currently known plugin headers.
760
+ * @return array New plugin headers.
761
+ */
762
+ function inject_module_headers($headers){
763
+ $module_headers = array(
764
+ 'ModuleID',
765
+ 'ModuleCategory',
766
+ 'ModuleContext',
767
+ 'ModuleLazyInit',
768
+ 'ModuleClassName',
769
+ 'ModulePriority',
770
+ 'ModuleCheckerUrlPattern',
771
+ 'ModuleHidden', //Don't show the module in the Settings page
772
+ 'ModuleAlwaysActive', //Module can't be deactivated.
773
+ 'ModuleRequiresPro', //Can only be activated in the Pro version
774
+ );
775
+
776
+ return array_merge($headers, $module_headers);
777
+ }
778
+
779
+ /**
780
+ * Normalize a module header, using defaults where necessary.
781
+ *
782
+ * @param array $module_header Module header, as read from the module's .php file.
783
+ * @param string $module_id Module ID.
784
+ * @param string $module_filename Module filename. Optional.
785
+ * @return array Normalized module header.
786
+ */
787
+ function normalize_module_header($module_header, $module_id, $module_filename = ''){
788
+ //Default values for optional module header fields
789
+ $defaults = array(
790
+ 'ModuleContext' => 'all',
791
+ 'ModuleCategory' => 'other',
792
+ 'ModuleLazyInit' => 'false',
793
+ 'ModulePriority' => '0',
794
+ 'ModuleHidden' => 'false',
795
+ 'ModuleAlwaysActive' => 'false',
796
+ 'ModuleRequiresPro' => 'false',
797
+ 'TextDomain' => 'broken-link-checker', //For translating module headers
798
+ );
799
+
800
+ $module_header['ModuleID'] = $module_id; //Just for consistency
801
+ $module_header['file'] = $module_filename; //Used later to load the module
802
+
803
+ //Apply defaults
804
+ foreach($defaults as $field => $default_value){
805
+ if ( empty($module_header[$field]) ){
806
+ $module_header[$field] = $default_value;
807
+ }
808
+ }
809
+
810
+ //Convert bool/int fields from strings to native datatypes
811
+ $module_header['ModuleLazyInit'] = $this->str_to_bool($module_header['ModuleLazyInit']);
812
+ $module_header['ModuleHidden'] = $this->str_to_bool($module_header['ModuleHidden']);
813
+ $module_header['ModuleAlwaysActive'] = $this->str_to_bool($module_header['ModuleAlwaysActive']);
814
+ $module_header['ModuleRequiresPro'] = $this->str_to_bool($module_header['ModuleRequiresPro']);
815
+ $module_header['ModulePriority'] = intval($module_header['ModulePriority']);
816
+
817
+ return $module_header;
818
+ }
819
+
820
+ /**
821
+ * Converts the strings "true" and "false" to boolean TRUE and FALSE, respectively.
822
+ * Any other string will yield FALSE.
823
+ *
824
+ * @param string $value "true" or "false", case-insensitive.
825
+ * @return bool
826
+ */
827
+ function str_to_bool($value){
828
+ $value = trim(strtolower($value));
829
+ return $value == 'true';
830
+ }
831
+
832
+ /**
833
+ * Generates a PHP script that calls the __() i18n function with
834
+ * the name and description of each available module. The generated
835
+ * script is used to make module headers show up in the .POT file.
836
+ *
837
+ * @access private
838
+ *
839
+ * @return string
840
+ */
841
+ function _build_header_translation_code(){
842
+ $this->_module_cache = null; //Clear the cache
843
+ $modules = $this->get_modules();
844
+
845
+ $strings = array();
846
+ foreach($modules as $module_id => $module_header){
847
+ if ( $module_header['ModuleHidden'] || ($module_id == 'write-module-placeholders')) {
848
+ continue;
849
+ }
850
+ if ( !empty($module_header['Name']) ){
851
+ $strings[] = sprintf(
852
+ '_x("%s", "module name", "broken-link-checker");',
853
+ str_replace('"', '\"', $module_header['Name'])
854
+ );
855
+ }
856
+ }
857
+
858
+ return "<?php\n" . implode("\n", $strings) . "\n";
859
+ }
860
+ }
includes/modules.php CHANGED
@@ -1,35 +1,35 @@
1
- <?php
2
-
3
- /**
4
- * Load all files pertaining to BLC's module subsystem
5
- */
6
-
7
- require 'module-manager.php';
8
- require 'module-base.php';
9
-
10
- require 'containers.php';
11
- require 'checkers.php';
12
- require 'parsers.php';
13
-
14
- $blc_module_manager = blcModuleManager::getInstance(array(
15
- //List of modules active by default
16
- 'http', //Link checker for the HTTP(s) protocol
17
- 'link', //HTML link parser
18
- 'image', //HTML image parser
19
- 'metadata', //Metadata (custom field) parser
20
- 'url_field', //URL field parser
21
- 'comment', //Comment container
22
- 'custom_field', //Post metadata container (aka custom fields)
23
- 'acf_field', //Post acf container (aka advanced custom fields)
24
- 'acf', //acf parser
25
- 'post', //Post content container
26
- 'page', //Page content container
27
- 'youtube-checker', //Video checker using the YouTube API
28
- 'youtube-iframe', //Embedded YouTube video container
29
- 'dummy', //Dummy container used as a fallback
30
- ));
31
-
32
- require 'any-post.php';
33
-
34
- //Let other plugins register virtual modules.
35
  do_action('blc_register_modules', $blc_module_manager);
1
+ <?php
2
+
3
+ /**
4
+ * Load all files pertaining to BLC's module subsystem
5
+ */
6
+
7
+ require 'module-manager.php';
8
+ require 'module-base.php';
9
+
10
+ require 'containers.php';
11
+ require 'checkers.php';
12
+ require 'parsers.php';
13
+
14
+ $blc_module_manager = blcModuleManager::getInstance(array(
15
+ //List of modules active by default
16
+ 'http', //Link checker for the HTTP(s) protocol
17
+ 'link', //HTML link parser
18
+ 'image', //HTML image parser
19
+ 'metadata', //Metadata (custom field) parser
20
+ 'url_field', //URL field parser
21
+ 'comment', //Comment container
22
+ 'custom_field', //Post metadata container (aka custom fields)
23
+ 'acf_field', //Post acf container (aka advanced custom fields)
24
+ 'acf', //acf parser
25
+ 'post', //Post content container
26
+ 'page', //Page content container
27
+ 'youtube-checker', //Video checker using the YouTube API
28
+ 'youtube-iframe', //Embedded YouTube video container
29
+ 'dummy', //Dummy container used as a fallback
30
+ ));
31
+
32
+ require 'any-post.php';
33
+
34
+ //Let other plugins register virtual modules.
35
  do_action('blc_register_modules', $blc_module_manager);
includes/parsers.php CHANGED
@@ -1,365 +1,365 @@
1
- <?php
2
-
3
- /**
4
- * A base class for parsers.
5
- *
6
- * In the context of this plugin, a "parser" is a class that knows how to extract or modify
7
- * a specific type of links from a given piece of text. For example, there could be a "HTML Link"
8
- * parser that knows how to find and modify standard HTML links such as this one :
9
- * <a href="http://example.com/">Example</a>
10
- *
11
- * Other parsers could extract plaintext URLs or handle metadata fields.
12
- *
13
- * Each parser has a list of supported formats (e.g. "html", "plaintext", etc) and container types
14
- * (e.g. "post", "comment", "blogroll", etc). When something needs to be parsed, the involved
15
- * container class will look up the parsers that support the relevant format or the container's type,
16
- * and apply them to the to-be-parsed string.
17
- *
18
- * All sub-classes of blcParser should override at least the blcParser::parse() method.
19
- *
20
- * @see blcContainer::$fields
21
- *
22
- * @package Broken Link Checker
23
- * @access public
24
- */
25
- class blcParser extends blcModule {
26
-
27
- var $parser_type;
28
- var $supported_formats = array();
29
- var $supported_containers = array();
30
-
31
- /**
32
- * Initialize the parser. Nothing much here.
33
- *
34
- * @return void
35
- */
36
- function init(){
37
- parent::init();
38
- $this->parser_type = $this->module_id;
39
- }
40
-
41
- /**
42
- * Called when the parser is activated.
43
- *
44
- * @return void
45
- */
46
- function activated(){
47
- parent::activated();
48
- $this->resynch_relevant_containers();
49
- }
50
-
51
- /**
52
- * Called when BLC is activated.
53
- */
54
- function plugin_activated() {
55
- //Intentionally do nothing. BLC can not parse containers while it's inactive, so we can be
56
- //pretty sure that there are no already-parsed containers that need to be resynchronized.
57
- }
58
-
59
- /**
60
- * Mark containers that this parser might be interested in as unparsed.
61
- *
62
- * @uses blcContainerHelper::mark_as_unsynched_where()
63
- *
64
- * @param bool $only_return If true, just return the list of formats and container types without actually modifying any synch. records.
65
- * @return void|array Either nothing or an array in the form [ [format1=>timestamp1, ...], [container_type1=>timestamp1, ...] ]
66
- */
67
- function resynch_relevant_containers($only_return = false){
68
- global $blclog;
69
- $blclog->log(sprintf('...... Parser "%s" is marking relevant items as unsynched', $this->module_id));
70
-
71
- $last_deactivated = $this->module_manager->get_last_deactivation_time($this->module_id);
72
-
73
- $formats = array();
74
- foreach($this->supported_formats as $format){
75
- $formats[$format] = $last_deactivated;
76
- }
77
-
78
- $container_types = array();
79
- foreach($this->supported_containers as $container_type){
80
- $container_types[$container_type] = $last_deactivated;
81
- }
82
-
83
- if ( $only_return ){
84
- return array($formats, $container_types);
85
- } else {
86
- blcContainerHelper::mark_as_unsynched_where($formats, $container_types);
87
- }
88
- }
89
-
90
- /**
91
- * Parse a string for links.
92
- *
93
- * @param string $content The text to parse.
94
- * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
95
- * @param string $default_link_text
96
- * @return array An array of new blcLinkInstance objects. The objects will include info about the links found, but not about the corresponding container entity.
97
- */
98
- function parse($content, $base_url = '', $default_link_text = ''){
99
- return array();
100
- }
101
-
102
- /**
103
- * Change all links that have a certain URL to a new URL.
104
- *
105
- * @param string $content Look for links in this string.
106
- * @param string $new_url Change the links to this URL.
107
- * @param string $old_url The URL to look for.
108
- * @param string $old_raw_url The raw, not-normalized URL of the links to look for. Optional.
109
- *
110
- * @return array|WP_Error If successful, the return value will be an associative array with two
111
- * keys : 'content' - the modified content, and 'raw_url' - the new raw, non-normalized URL used
112
- * for the modified links. In most cases, the returned raw_url will be equal to the new_url.
113
- */
114
- function edit($content, $new_url, $old_url, $old_raw_url){
115
- return new WP_Error(
116
- 'not_implemented',
117
- sprintf(__("Editing is not implemented in the '%s' parser", 'broken-link-checker'), $this->parser_type)
118
- );
119
- }
120
-
121
- /**
122
- * Remove all links that have a certain URL, leaving anchor text intact.
123
- *
124
- * @param string $content Look for links in this string.
125
- * @param string $url The URL to look for.
126
- * @param string $raw_url The raw, non-normalized version of the URL to look for. Optional.
127
- * @return string Input string with all matching links removed.
128
- */
129
- function unlink($content, $url, $raw_url){
130
- return new WP_Error(
131
- 'not_implemented',
132
- sprintf(__("Unlinking is not implemented in the '%s' parser", 'broken-link-checker'), $this->parser_type)
133
- );
134
- }
135
-
136
- /**
137
- * Get the link text for printing in the "Broken Links" table.
138
- * Sub-classes should override this method and display the link text in a way appropriate for the link type.
139
- *
140
- * @param blcLinkInstance $instance
141
- * @param string $context
142
- * @return string HTML
143
- */
144
- function ui_get_link_text($instance, $context = 'display'){
145
- return $instance->link_text;
146
- }
147
-
148
- /**
149
- * Check if the parser supports editing the link text.
150
- *
151
- * @return bool
152
- */
153
- public function is_link_text_editable() {
154
- return false;
155
- }
156
-
157
- /**
158
- * Check if the parser supports editing the link URL.
159
- *
160
- * @return bool
161
- */
162
- public function is_url_editable() {
163
- return true;
164
- }
165
-
166
- /**
167
- * Turn a relative URL into an absolute one.
168
- *
169
- * WordPress 3.4 has WP_Http::make_absolute_url() which is well-tested but not as comprehensive
170
- * as this implementation. For example, WP_Http doesn't properly handle directory traversal with "..",
171
- * and it removes #anchors for no good reason. The BLC implementation deals with both pretty well.
172
- *
173
- * @param string $url Relative URL.
174
- * @param string $base_url Base URL. If omitted, the blog's root URL will be used.
175
- * @return string
176
- */
177
- function relative2absolute($url, $base_url = ''){
178
- if ( empty($base_url) ){
179
- $base_url = home_url();
180
- }
181
-
182
- $p = @parse_url($url);
183
- if(!$p) {
184
- //URL is a malformed
185
- return false;
186
- }
187
- if( isset($p["scheme"]) ) return $url;
188
-
189
- //If the relative URL is just a query string or anchor, simply attach it to the absolute URL and return
190
- $first_char = substr($url, 0, 1);
191
- if ( ($first_char == '?') || ($first_char == '#') ){
192
- return $base_url . $url;
193
- }
194
-
195
- $parts=(parse_url($base_url));
196
-
197
- //Protocol-relative URLs start with "//". We just need to prepend the right protocol.
198
- if ( substr($url, 0, 2) === '//' ) {
199
- $scheme = isset($parts['scheme']) ? $parts['scheme'] : 'http';
200
- return $scheme . ':'. $url;
201
- }
202
-
203
- if(substr($url,0,1)=='/') {
204
- //Relative URL starts with a slash => ignore the base path and jump straight to the root.
205
- $path_segments = explode("/", $url);
206
- array_shift($path_segments);
207
- } else {
208
- if(isset($parts['path'])){
209
- $aparts=explode('/',$parts['path']);
210
- array_pop($aparts);
211
- $aparts=array_filter($aparts);
212
- } else {
213
- $aparts=array();
214
- }
215
-
216
- //Merge together the base path & the relative path
217
- $aparts = array_merge($aparts, explode("/", $url));
218
-
219
- //Filter the merged path
220
- $path_segments = array();
221
- foreach($aparts as $part){
222
- if ( $part == '.' ){
223
- continue; //. = "this directory". It's basically a no-op, so we skip it.
224
- } elseif ( $part == '..' ) {
225
- array_pop($path_segments); //.. = one directory up. Remove the last seen path segment.
226
- } else {
227
- array_push($path_segments, $part); //Normal directory -> add it to the path.
228
- }
229
- }
230
- }
231
- $path = implode("/", $path_segments);
232
-
233
- //Build the absolute URL.
234
- $url = '';
235
- if($parts['scheme']) {
236
- $url = "$parts[scheme]://";
237
- }
238
- if(isset($parts['user'])) {
239
- $url .= $parts['user'];
240
- if(isset($parts['pass'])) {
241
- $url .= ":".$parts['pass'];
242
- }
243
- $url .= "@";
244
- }
245
- if(isset($parts['host'])) {
246
- $url .= $parts['host'];
247
- if(isset($parts['port'])) {
248
- $url .= ':' . $parts['port'];
249
- }
250
- $url .= '/';
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
- * A helper class for working with parsers. All its methods should be called statically.
314
- *
315
- * @see blcParser
316
- *
317
- * @package Broken Link Checker
318
- * @access public
319
- */
320
- class blcParserHelper {
321
-
322
- /**
323
- * Get the parser matching a parser type ID.
324
- *
325
- * @uses blcModuleManager::get_module()
326
- *
327
- * @param string $parser_type
328
- * @return blcParser|null
329
- */
330
- static function get_parser( $parser_type ){
331
- $manager = blcModuleManager::getInstance();
332
- return $manager->get_module($parser_type, true, 'parser');
333
- }
334
-
335
- /**
336
- * Get all parsers that support either the specified format or the container type.
337
- * If a parser supports both, it will still be included only once.
338
- *
339
- * @param string $format
340
- * @param string $container_type
341
- * @return blcParser[]
342
- */
343
- static function get_parsers( $format, $container_type ){
344
- $found = array();
345
-
346
- //Retrieve a list of active parsers
347
- $manager = blcModuleManager::getInstance();
348
- $active_parsers = $manager->get_modules_by_category('parser');
349
-
350
- //Try each one
351
- foreach($active_parsers as $module_id => $module_data){
352
- $parser = $manager->get_module($module_id); //Will autoload if necessary
353
- if ( !$parser ){
354
- continue;
355
- }
356
-
357
- if ( in_array($format, $parser->supported_formats) || in_array($container_type, $parser->supported_containers) ){
358
- array_push($found, $parser);
359
- }
360
- }
361
-
362
- return $found;
363
- }
364
- }
365
-
1
+ <?php
2
+
3
+ /**
4
+ * A base class for parsers.
5
+ *
6
+ * In the context of this plugin, a "parser" is a class that knows how to extract or modify
7
+ * a specific type of links from a given piece of text. For example, there could be a "HTML Link"
8
+ * parser that knows how to find and modify standard HTML links such as this one :
9
+ * <a href="http://example.com/">Example</a>
10
+ *
11
+ * Other parsers could extract plaintext URLs or handle metadata fields.
12
+ *
13
+ * Each parser has a list of supported formats (e.g. "html", "plaintext", etc) and container types
14
+ * (e.g. "post", "comment", "blogroll", etc). When something needs to be parsed, the involved
15
+ * container class will look up the parsers that support the relevant format or the container's type,
16
+ * and apply them to the to-be-parsed string.
17
+ *
18
+ * All sub-classes of blcParser should override at least the blcParser::parse() method.
19
+ *
20
+ * @see blcContainer::$fields
21
+ *
22
+ * @package Broken Link Checker
23
+ * @access public
24
+ */
25
+ class blcParser extends blcModule {
26
+
27
+ var $parser_type;
28
+ var $supported_formats = array();
29
+ var $supported_containers = array();
30
+
31
+ /**
32
+ * Initialize the parser. Nothing much here.
33
+ *
34
+ * @return void
35
+ */
36
+ function init(){
37
+ parent::init();
38
+ $this->parser_type = $this->module_id;
39
+ }
40
+
41
+ /**
42
+ * Called when the parser is activated.
43
+ *
44
+ * @return void
45
+ */
46
+ function activated(){
47
+ parent::activated();
48
+ $this->resynch_relevant_containers();
49
+ }
50
+
51
+ /**
52
+ * Called when BLC is activated.
53
+ */
54
+ function plugin_activated() {
55
+ //Intentionally do nothing. BLC can not parse containers while it's inactive, so we can be
56
+ //pretty sure that there are no already-parsed containers that need to be resynchronized.
57
+ }
58
+
59
+ /**
60
+ * Mark containers that this parser might be interested in as unparsed.
61
+ *
62
+ * @uses blcContainerHelper::mark_as_unsynched_where()
63
+ *
64
+ * @param bool $only_return If true, just return the list of formats and container types without actually modifying any synch. records.
65
+ * @return void|array Either nothing or an array in the form [ [format1=>timestamp1, ...], [container_type1=>timestamp1, ...] ]
66
+ */
67
+ function resynch_relevant_containers($only_return = false){
68
+ global $blclog;
69
+ $blclog->log(sprintf('...... Parser "%s" is marking relevant items as unsynched', $this->module_id));
70
+
71
+ $last_deactivated = $this->module_manager->get_last_deactivation_time($this->module_id);
72
+
73
+ $formats = array();
74
+ foreach($this->supported_formats as $format){
75
+ $formats[$format] = $last_deactivated;
76
+ }
77
+
78
+ $container_types = array();
79
+ foreach($this->supported_containers as $container_type){
80
+ $container_types[$container_type] = $last_deactivated;
81
+ }
82
+
83
+ if ( $only_return ){
84
+ return array($formats, $container_types);
85
+ } else {
86
+ blcContainerHelper::mark_as_unsynched_where($formats, $container_types);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Parse a string for links.
92
+ *
93
+ * @param string $content The text to parse.
94
+ * @param string $base_url The base URL to use for normalizing relative URLs. If ommitted, the blog's root URL will be used.
95
+ * @param string $default_link_text
96
+ * @return array An array of new blcLinkInstance objects. The objects will include info about the links found, but not about the corresponding container entity.
97
+ */
98
+ function parse($content, $base_url = '', $default_link_text = ''){
99
+ return array();
100
+ }
101
+
102
+ /**
103
+ * Change all links that have a certain URL to a new URL.
104
+ *
105
+ * @param string $content Look for links in this string.
106
+ * @param string $new_url Change the links to this URL.
107
+ * @param string $old_url The URL to look for.
108
+ * @param string $old_raw_url The raw, not-normalized URL of the links to look for. Optional.
109
+ *
110
+ * @return array|WP_Error If successful, the return value will be an associative array with two
111
+ * keys : 'content' - the modified content, and 'raw_url' - the new raw, non-normalized URL used
112
+ * for the modified links. In most cases, the returned raw_url will be equal to the new_url.
113
+ */
114
+ function edit($content, $new_url, $old_url, $old_raw_url){
115
+ return new WP_Error(
116
+ 'not_implemented',
117
+ sprintf(__("Editing is not implemented in the '%s' parser", 'broken-link-checker'), $this->parser_type)
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Remove all links that have a certain URL, leaving anchor text intact.
123
+ *
124
+ * @param string $content Look for links in this string.
125
+ * @param string $url The URL to look for.
126
+ * @param string $raw_url The raw, non-normalized version of the URL to look for. Optional.
127
+ * @return string Input string with all matching links removed.
128
+ */
129
+ function unlink($content, $url, $raw_url){
130
+ return new WP_Error(
131
+ 'not_implemented',
132
+ sprintf(__("Unlinking is not implemented in the '%s' parser", 'broken-link-checker'), $this->parser_type)
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Get the link text for printing in the "Broken Links" table.
138
+ * Sub-classes should override this method and display the link text in a way appropriate for the link type.
139
+ *
140
+ * @param blcLinkInstance $instance
141
+ * @param string $context
142
+ * @return string HTML
143
+ */
144
+ function ui_get_link_text($instance, $context = 'display'){
145
+ return $instance->link_text;
146
+ }
147
+
148
+ /**
149
+ * Check if the parser supports editing the link text.
150
+ *
151
+ * @return bool
152
+ */
153
+ public function is_link_text_editable() {
154
+ return false;
155
+ }
156
+
157
+ /**
158
+ * Check if the parser supports editing the link URL.
159
+ *
160
+ * @return bool
161
+ */
162
+ public function is_url_editable() {
163
+ return true;
164
+ }
165
+
166
+ /**
167
+ * Turn a relative URL into an absolute one.
168
+ *
169
+ * WordPress 3.4 has WP_Http::make_absolute_url() which is well-tested but not as comprehensive
170
+ * as this implementation. For example, WP_Http doesn't properly handle directory traversal with "..",
171
+ * and it removes #anchors for no good reason. The BLC implementation deals with both pretty well.
172
+ *
173
+ * @param string $url Relative URL.
174
+ * @param string $base_url Base URL. If omitted, the blog's root URL will be used.
175
+ * @return string
176
+ */
177
+ function relative2absolute($url, $base_url = ''){
178
+ if ( empty($base_url) ){
179
+ $base_url = home_url();
180
+ }
181
+
182
+ $p = @parse_url($url);
183
+ if(!$p) {
184
+ //URL is a malformed
185
+ return false;
186
+ }
187
+ if( isset($p["scheme"]) ) return $url;
188
+
189
+ //If the relative URL is just a query string or anchor, simply attach it to the absolute URL and return
190
+ $first_char = substr($url, 0, 1);
191
+ if ( ($first_char == '?') || ($first_char == '#') ){
192
+ return $base_url . $url;
193
+ }
194
+
195
+ $parts=(parse_url($base_url));
196
+
197
+ //Protocol-relative URLs start with "//". We just need to prepend the right protocol.
198
+ if ( substr($url, 0, 2) === '//' ) {
199
+ $scheme = isset($parts['scheme']) ? $parts['scheme'] : 'http';
200
+ return $scheme . ':'. $url;
201
+ }
202
+
203
+ if(substr($url,0,1)=='/') {
204
+ //Relative URL starts with a slash => ignore the base path and jump straight to the root.
205
+ $path_segments = explode("/", $url);
206
+ array_shift($path_segments);
207
+ } else {
208
+ if(isset($parts['path'])){
209
+ $aparts=explode('/',$parts['path']);
210
+ array_pop($aparts);
211
+ $aparts=array_filter($aparts);
212
+ } else {
213
+ $aparts=array();
214
+ }
215
+
216
+ //Merge together the base path & the relative path
217
+ $aparts = array_merge($aparts, explode("/", $url));
218
+
219
+ //Filter the merged path
220
+ $path_segments = array();
221
+ foreach($aparts as $part){
222
+ if ( $part == '.' ){
223
+ continue; //. = "this directory". It's basically a no-op, so we skip it.
224
+ } elseif ( $part == '..' ) {
225
+ array_pop($path_segments); //.. = one directory up. Remove the last seen path segment.
226
+ } else {
227
+ array_push($path_segments, $part); //Normal directory -> add it to the path.
228
+ }
229
+ }
230
+ }
231
+ $path = implode("/", $path_segments);
232
+
233
+ //Build the absolute URL.
234
+ $url = '';
235
+ if($parts['scheme']) {
236
+ $url = "$parts[scheme]://";
237
+ }
238
+ if(isset($parts['user'])) {
239
+ $url .= $parts['user'];
240
+ if(isset($parts['pass'])) {
241
+ $url .= ":".$parts['pass'];
242
+ }
243
+ $url .= "@";
244
+ }
245
+ if(isset($parts['host'])) {
246
+ $url .= $parts['host'];
247
+ if(isset($parts['port'])) {
248
+ $url .= ':' . $parts['port'];
249
+ }
250
+ $url .= '/';
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
+ * A helper class for working with parsers. All its methods should be called statically.
314
+ *
315
+ * @see blcParser
316
+ *
317
+ * @package Broken Link Checker
318
+ * @access public
319
+ */
320
+ class blcParserHelper {
321
+
322
+ /**
323
+ * Get the parser matching a parser type ID.
324
+ *
325
+ * @uses blcModuleManager::get_module()
326
+ *
327
+ * @param string $parser_type
328
+ * @return blcParser|null
329
+ */
330
+ static function get_parser( $parser_type ){
331
+ $manager = blcModuleManager::getInstance();
332
+ return $manager->get_module($parser_type, true, 'parser');
333
+ }
334
+
335
+ /**
336
+ * Get all parsers that support either the specified format or the container type.
337
+ * If a parser supports both, it will still be included only once.
338
+ *
339
+ * @param string $format
340
+ * @param string $container_type
341
+ * @return blcParser[]
342
+ */
343
+ static function get_parsers( $format, $container_type ){
344
+ $found = array();
345
+
346
+ //Retrieve a list of active parsers
347
+ $manager = blcModuleManager::getInstance();
348
+ $active_parsers = $manager->get_modules_by_category('parser');
349
+
350
+ //Try each one
351
+ foreach($active_parsers as $module_id => $module_data){
352
+ $parser = $manager->get_module($module_id); //Will autoload if necessary
353
+ if ( !$parser ){
354
+ continue;
355
+ }
356
+
357
+ if ( in_array($format, $parser->supported_formats) || in_array($container_type, $parser->supported_containers) ){
358
+ array_push($found, $parser);
359
+ }
360
+ }
361
+
362
+ return $found;
363
+ }
364
+ }
365
+
includes/screen-meta-links.php CHANGED
@@ -1,312 +1,312 @@
1
- <?php
2
-
3
- /**
4
- * @author Janis Elsts
5
- * @copyright 2011
6
- */
7
-
8
-
9
- if ( !class_exists('wsScreenMetaLinks11') ):
10
-
11
- //Load JSON functions for PHP < 5.2
12
- if ( !(function_exists('json_encode') && function_exists('json_decode')) && !(class_exists('Services_JSON') || class_exists('Moxiecode_JSON')) ){
13
- $class_json_path = ABSPATH.WPINC.'/class-json.php';
14
- $class_moxiecode_json_path = ABSPATH.WPINC.'/js/tinymce/plugins/spellchecker/classes/utils/JSON.php';
15
- if ( file_exists($class_json_path) ){
16
- require $class_json_path;
17
-
18
- } elseif ( file_exists($class_moxiecode_json_path) ) {
19
- require $class_moxiecode_json_path;
20
- }
21
- }
22
-
23
- class wsScreenMetaLinks11 {
24
- var $registered_links; //List of meta links registered for each page.
25
-
26
- /**
27
- * Class constructor.
28
- *
29
- * @return void
30
- */
31
- function __construct(){
32
- $this->registered_links = array();
33
-
34
- add_action('admin_notices', array(&$this, 'append_meta_links'));
35
- add_action('admin_print_styles', array(&$this, 'add_link_styles'));
36
- }
37
-
38
- /**
39
- * Add a new link to the screen meta area.
40
- *
41
- * Do not call this method directly. Instead, use the global add_screen_meta_link() function.
42
- *
43
- * @param string $id Link ID. Should be unique and a valid value for a HTML ID attribute.
44
- * @param string $text Link text.
45
- * @param string $href Link URL.
46
- * @param string|array $page The page(s) where you want to add the link.
47
- * @param array $attributes Optional. Additional attributes for the link tag.
48
- * @return void
49
- */
50
- function add_screen_meta_link($id, $text, $href, $page, $attributes = null){
51
- if ( !is_array($page) ){
52
- $page = array($page);
53
- }
54
- if ( is_null($attributes) ){
55
- $attributes = array();
56
- }
57
-
58
- //Basically a list of props for a jQuery() call
59
- $link = compact('id', 'text', 'href');
60
- $link = array_merge($link, $attributes);
61
-
62
- //Add the CSS classes that will make the look like a proper meta link
63
- if ( empty($link['class']) ){
64
- $link['class'] = '';
65
- }
66
- $link['class'] = 'show-settings custom-screen-meta-link ' . $link['class'];
67
-
68
- //Save the link in each relevant page's list
69
- foreach($page as $page_id){
70
- if ( !isset($this->registered_links[$page_id]) ){
71
- $this->registered_links[$page_id] = array();
72
- }
73
- $this->registered_links[$page_id][] = $link;
74
- }
75
- }
76
-
77
- /**
78
- * Output the JS that appends the custom meta links to the page.
79
- * Callback for the 'admin_notices' action.
80
- *
81
- * @access private
82
- * @return void
83
- */
84
- function append_meta_links(){
85
- global $hook_suffix;
86
-
87
- //Find links registered for this page
88
- $links = $this->get_links_for_page($hook_suffix);
89
- if ( empty($links) ){
90
- return;
91
- }
92
-
93
- ?>
94
- <script type="text/javascript">
95
- (function($, links){
96
- var container = $('#screen-meta-links');
97
- if ( container.length == 0 ) {
98
- container = $('<div />').attr('id', 'screen-meta-links').insertAfter('#screen-meta');
99
- }
100
- for(var i = 0; i < links.length; i++){
101
- container.append(
102
- $('<div/>')
103
- .attr({
104
- 'id' : links[i].id + '-wrap',
105
- 'class' : 'hide-if-no-js custom-screen-meta-link-wrap'
106
- })
107
- .append( $('<a/>', links[i]) )
108
- );
109
- }
110
- })(jQuery, <?php echo $this->json_encode($links); ?>);
111
- </script>
112
- <?php
113
- }
114
-
115
- /**
116
- * Get a list of custom screen meta links registered for a specific page.
117
- *
118
- * @param string $page
119
- * @return array
120
- */
121
- function get_links_for_page($page){
122
- $links = array();
123
-
124
- if ( isset($this->registered_links[$page]) ){
125
- $links = array_merge($links, $this->registered_links[$page]);
126
- }
127
- $page_as_screen = $this->page_to_screen_id($page);
128
- if ( ($page_as_screen != $page) && isset($this->registered_links[$page_as_screen]) ){
129
- $links = array_merge($links, $this->registered_links[$page_as_screen]);
130
- }
131
-
132
- return $links;
133
- }
134
-
135
- /**
136
- * Output the CSS code for custom screen meta links. Required because WP only
137
- * has styles for specific meta links (by #id), not meta links in general.
138
- *
139
- * Callback for 'admin_print_styles'.
140
- *
141
- * @access private
142
- * @return void
143
- */
144
- function add_link_styles(){
145
- global $hook_suffix;
146
- //Don't output the CSS if there are no custom meta links for this page.
147
- $links = $this->get_links_for_page($hook_suffix);
148
- if ( empty($links) ){
149
- return;
150
- }
151
-
152
- if ( !isset($GLOBALS['wp_version']) || version_compare($GLOBALS['wp_version'], '3.8-RC1', '<') ) {
153
- $this->print_old_link_styles();
154
- } else {
155
- $this->print_link_styles();
156
- }
157
- }
158
-
159
- /**
160
- * Print screen meta button styles (WP 3.8+).
161
- */
162
- private function print_link_styles() {
163
- ?>
164
- <style type="text/css">
165
- .custom-screen-meta-link-wrap {
166
- float: right;
167
- height: 28px;
168
- margin: 0 0 0 6px;
169
-
170
- border: 1px solid #ddd;
171
- border-top: none;
172
- background: #fff;
173
- -webkit-box-shadow: 0 1px 1px -1px rgba(0,0,0,0.1);
174
- box-shadow: 0 1px 1px -1px rgba(0,0,0,0.1);
175
- }
176
-
177
- #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
178
- #screen-meta-links .custom-screen-meta-link-wrap a.custom-screen-meta-link
179
- {
180
- padding: 3px 16px 3px 16px;
181
- }
182
-
183
- #screen-meta-links a.custom-screen-meta-link::after {
184
- display: none;
185
- }
186
- </style>
187
- <?php
188
- }
189
-
190
- /**
191
- * Print old screen meta button styles (WP 3.7.x and older).
192
- */
193
- private function print_old_link_styles() {
194
- ?>
195
- <style type="text/css">
196
- .custom-screen-meta-link-wrap {
197
- float: right;
198
- height: 22px;
199
- padding: 0;
200
- margin: 0 0 0 6px;
201
- font-family: sans-serif;
202
- -moz-border-radius-bottomleft: 3px;
203
- -moz-border-radius-bottomright: 3px;
204
- -webkit-border-bottom-left-radius: 3px;
205
- -webkit-border-bottom-right-radius: 3px;
206
- border-bottom-left-radius: 3px;
207
- border-bottom-right-radius: 3px;
208
-
209
- background: #e3e3e3;
210
-
211
- border-right: 1px solid transparent;
212
- border-left: 1px solid transparent;
213
- border-bottom: 1px solid transparent;
214
- background-image: -ms-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* IE10 */
215
- background-image: -moz-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Firefox */
216
- background-image: -o-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Opera */
217
- background-image: -webkit-gradient(linear, left bottom, left top, from(#dfdfdf), to(#f1f1f1)); /* old Webkit */
218
- background-image: -webkit-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* new Webkit */
219
- background-image: linear-gradient(bottom, #dfdfdf, #f1f1f1); /* proposed W3C Markup */
220
- }
221
-
222
- #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
223
- #screen-meta-links .custom-screen-meta-link-wrap a.custom-screen-meta-link
224
- {
225
- background-image: none;
226
- padding-right: 6px;
227
- color: #777;
228
- }
229
- </style>
230
- <?php
231
- }
232
-
233
- /**
234
- * Convert a page hook name to a screen ID.
235
- *
236
- * @uses convert_to_screen()
237
- * @access private
238
- *
239
- * @param string $page
240
- * @return string
241
- */
242
- function page_to_screen_id($page){
243
- if ( function_exists('convert_to_screen') ){
244
- $screen = convert_to_screen($page);
245
- if ( isset($screen->id) ){
246
- return $screen->id;
247
- } else {
248
- return '';
249
- }
250
- } else {
251
- return str_replace( array('.php', '-new', '-add' ), '', $page);
252
- }
253
- }
254
-
255
- /**
256
- * Back-wards compatible json_encode(). Used to encode link data before
257
- * passing it to the JavaScript that actually creates the links.
258
- *
259
- * @param mixed $data
260
- * @return string
261
- */
262
- function json_encode($data){
263
- if ( function_exists('json_encode') ){
264
- return json_encode($data);
265
- }
266
- if ( class_exists('Services_JSON') ){
267
- $json = new Services_JSON();
268
- return( $json->encodeUnsafe($data) );
269
- } elseif ( class_exists('Moxiecode_JSON') ){
270
- $json = new Moxiecode_JSON();
271
- return $json->encode($data);
272
- } else {
273
- trigger_error('No JSON parser available', E_USER_ERROR);
274
- return null;
275
- }
276
- }
277
-
278
- }
279
-
280
- global $ws_screen_meta_links_versions;
281
- if ( !isset($ws_screen_meta_links_versions) ){
282
- $ws_screen_meta_links_versions = array();
283
- }
284
- $ws_screen_meta_links_versions['1.1'] = 'wsScreenMetaLinks11';
285
-
286
- endif;
287
-
288
- /**
289
- * Add a new link to the screen meta area.
290
- *
291
- * @param string $id Link ID. Should be unique and a valid value for a HTML ID attribute.
292
- * @param string $text Link text.
293
- * @param string $href Link URL.
294
- * @param string|array $page The page(s) where you want to add the link.
295
- * @param array $attributes Optional. Additional attributes for the link tag.
296
- * @return void
297
- */
298
- function add_screen_meta_link($id, $text, $href, $page, $attributes = null){
299
- global $ws_screen_meta_links_versions;
300
-
301
- static $instance = null;
302
- if ( is_null($instance) ){
303
- //Instantiate the latest version of the wsScreenMetaLinks class
304
- uksort($ws_screen_meta_links_versions, 'version_compare');
305
- $className = end($ws_screen_meta_links_versions);
306
- $instance = new $className;
307
- }
308
-
309
- $instance->add_screen_meta_link($id, $text, $href, $page, $attributes);
310
- }
311
-
312
- ?>
1
+ <?php
2
+
3
+ /**
4
+ * @author Janis Elsts
5
+ * @copyright 2011
6
+ */
7
+
8
+
9
+ if ( !class_exists('wsScreenMetaLinks11') ):
10
+
11
+ //Load JSON functions for PHP < 5.2
12
+ if ( !(function_exists('json_encode') && function_exists('json_decode')) && !(class_exists('Services_JSON') || class_exists('Moxiecode_JSON')) ){
13
+ $class_json_path = ABSPATH.WPINC.'/class-json.php';
14
+ $class_moxiecode_json_path = ABSPATH.WPINC.'/js/tinymce/plugins/spellchecker/classes/utils/JSON.php';
15
+ if ( file_exists($class_json_path) ){
16
+ require $class_json_path;
17
+
18
+ } elseif ( file_exists($class_moxiecode_json_path) ) {
19
+ require $class_moxiecode_json_path;
20
+ }
21
+ }
22
+
23
+ class wsScreenMetaLinks11 {
24
+ var $registered_links; //List of meta links registered for each page.
25
+
26
+ /**
27
+ * Class constructor.
28
+ *
29
+ * @return void
30
+ */
31
+ function __construct(){
32
+ $this->registered_links = array();
33
+
34
+ add_action('admin_notices', array(&$this, 'append_meta_links'));
35
+ add_action('admin_print_styles', array(&$this, 'add_link_styles'));
36
+ }
37
+
38
+ /**
39
+ * Add a new link to the screen meta area.
40
+ *
41
+ * Do not call this method directly. Instead, use the global add_screen_meta_link() function.
42
+ *
43
+ * @param string $id Link ID. Should be unique and a valid value for a HTML ID attribute.
44
+ * @param string $text Link text.
45
+ * @param string $href Link URL.
46
+ * @param string|array $page The page(s) where you want to add the link.
47
+ * @param array $attributes Optional. Additional attributes for the link tag.
48
+ * @return void
49
+ */
50
+ function add_screen_meta_link($id, $text, $href, $page, $attributes = null){
51
+ if ( !is_array($page) ){
52
+ $page = array($page);
53
+ }
54
+ if ( is_null($attributes) ){
55
+ $attributes = array();
56
+ }
57
+
58
+ //Basically a list of props for a jQuery() call
59
+ $link = compact('id', 'text', 'href');
60
+ $link = array_merge($link, $attributes);
61
+
62
+ //Add the CSS classes that will make the look like a proper meta link
63
+ if ( empty($link['class']) ){
64
+ $link['class'] = '';
65
+ }
66
+ $link['class'] = 'show-settings custom-screen-meta-link ' . $link['class'];
67
+
68
+ //Save the link in each relevant page's list
69
+ foreach($page as $page_id){
70
+ if ( !isset($this->registered_links[$page_id]) ){
71
+ $this->registered_links[$page_id] = array();
72
+ }
73
+ $this->registered_links[$page_id][] = $link;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Output the JS that appends the custom meta links to the page.
79
+ * Callback for the 'admin_notices' action.
80
+ *
81
+ * @access private
82
+ * @return void
83
+ */
84
+ function append_meta_links(){
85
+ global $hook_suffix;
86
+
87
+ //Find links registered for this page
88
+ $links = $this->get_links_for_page($hook_suffix);
89
+ if ( empty($links) ){
90
+ return;
91
+ }
92
+
93
+ ?>
94
+ <script type="text/javascript">
95
+ (function($, links){
96
+ var container = $('#screen-meta-links');
97
+ if ( container.length == 0 ) {
98
+ container = $('<div />').attr('id', 'screen-meta-links').insertAfter('#screen-meta');
99
+ }
100
+ for(var i = 0; i < links.length; i++){
101
+ container.append(
102
+ $('<div/>')
103
+ .attr({
104
+ 'id' : links[i].id + '-wrap',
105
+ 'class' : 'hide-if-no-js custom-screen-meta-link-wrap'
106
+ })
107
+ .append( $('<a/>', links[i]) )
108
+ );
109
+ }
110
+ })(jQuery, <?php echo $this->json_encode($links); ?>);
111
+ </script>
112
+ <?php
113
+ }
114
+
115
+ /**
116
+ * Get a list of custom screen meta links registered for a specific page.
117
+ *
118
+ * @param string $page
119
+ * @return array
120
+ */
121
+ function get_links_for_page($page){
122
+ $links = array();
123
+
124
+ if ( isset($this->registered_links[$page]) ){
125
+ $links = array_merge($links, $this->registered_links[$page]);
126
+ }
127
+ $page_as_screen = $this->page_to_screen_id($page);
128
+ if ( ($page_as_screen != $page) && isset($this->registered_links[$page_as_screen]) ){
129
+ $links = array_merge($links, $this->registered_links[$page_as_screen]);
130
+ }
131
+
132
+ return $links;
133
+ }
134
+
135
+ /**
136
+ * Output the CSS code for custom screen meta links. Required because WP only
137
+ * has styles for specific meta links (by #id), not meta links in general.
138
+ *
139
+ * Callback for 'admin_print_styles'.
140
+ *
141
+ * @access private
142
+ * @return void
143
+ */
144
+ function add_link_styles(){
145
+ global $hook_suffix;
146
+ //Don't output the CSS if there are no custom meta links for this page.
147
+ $links = $this->get_links_for_page($hook_suffix);
148
+ if ( empty($links) ){
149
+ return;
150
+ }
151
+
152
+ if ( !isset($GLOBALS['wp_version']) || version_compare($GLOBALS['wp_version'], '3.8-RC1', '<') ) {
153
+ $this->print_old_link_styles();
154
+ } else {
155
+ $this->print_link_styles();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Print screen meta button styles (WP 3.8+).
161
+ */
162
+ private function print_link_styles() {
163
+ ?>
164
+ <style type="text/css">
165
+ .custom-screen-meta-link-wrap {
166
+ float: right;
167
+ height: 28px;
168
+ margin: 0 0 0 6px;
169
+
170
+ border: 1px solid #ddd;
171
+ border-top: none;
172
+ background: #fff;
173
+ -webkit-box-shadow: 0 1px 1px -1px rgba(0,0,0,0.1);
174
+ box-shadow: 0 1px 1px -1px rgba(0,0,0,0.1);
175
+ }
176
+
177
+ #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
178
+ #screen-meta-links .custom-screen-meta-link-wrap a.custom-screen-meta-link
179
+ {
180
+ padding: 3px 16px 3px 16px;
181
+ }
182
+
183
+ #screen-meta-links a.custom-screen-meta-link::after {
184
+ display: none;
185
+ }
186
+ </style>
187
+ <?php
188
+ }
189
+
190
+ /**
191
+ * Print old screen meta button styles (WP 3.7.x and older).
192
+ */
193
+ private function print_old_link_styles() {
194
+ ?>
195
+ <style type="text/css">
196
+ .custom-screen-meta-link-wrap {
197
+ float: right;
198
+ height: 22px;
199
+ padding: 0;
200
+ margin: 0 0 0 6px;
201
+ font-family: sans-serif;
202
+ -moz-border-radius-bottomleft: 3px;
203
+ -moz-border-radius-bottomright: 3px;
204
+ -webkit-border-bottom-left-radius: 3px;
205
+ -webkit-border-bottom-right-radius: 3px;
206
+ border-bottom-left-radius: 3px;
207
+ border-bottom-right-radius: 3px;
208
+
209
+ background: #e3e3e3;
210
+
211
+ border-right: 1px solid transparent;
212
+ border-left: 1px solid transparent;
213
+ border-bottom: 1px solid transparent;
214
+ background-image: -ms-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* IE10 */
215
+ background-image: -moz-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Firefox */
216
+ background-image: -o-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* Opera */
217
+ background-image: -webkit-gradient(linear, left bottom, left top, from(#dfdfdf), to(#f1f1f1)); /* old Webkit */
218
+ background-image: -webkit-linear-gradient(bottom, #dfdfdf, #f1f1f1); /* new Webkit */
219
+ background-image: linear-gradient(bottom, #dfdfdf, #f1f1f1); /* proposed W3C Markup */
220
+ }
221
+
222
+ #screen-meta .custom-screen-meta-link-wrap a.custom-screen-meta-link,
223
+ #screen-meta-links .custom-screen-meta-link-wrap a.custom-screen-meta-link
224
+ {
225
+ background-image: none;
226
+ padding-right: 6px;
227
+ color: #777;
228
+ }
229
+ </style>
230
+ <?php
231
+ }
232
+
233
+ /**
234
+ * Convert a page hook name to a screen ID.
235
+ *
236
+ * @uses convert_to_screen()
237
+ * @access private
238
+ *
239
+ * @param string $page
240
+ * @return string
241
+ */
242
+ function page_to_screen_id($page){
243
+ if ( function_exists('convert_to_screen') ){
244
+ $screen = convert_to_screen($page);
245
+ if ( isset($screen->id) ){
246
+ return $screen->id;
247
+ } else {
248
+ return '';
249
+ }
250
+ } else {
251
+ return str_replace( array('.php', '-new', '-add' ), '', $page);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Back-wards compatible json_encode(). Used to encode link data before
257
+ * passing it to the JavaScript that actually creates the links.
258
+ *
259
+ * @param mixed $data
260
+ * @return string
261
+ */
262
+ function json_encode($data){
263
+ if ( function_exists('json_encode') ){
264
+ return json_encode($data);
265
+ }
266
+ if ( class_exists('Services_JSON') ){
267
+ $json = new Services_JSON();
268
+ return( $json->encodeUnsafe($data) );
269
+ } elseif ( class_exists('Moxiecode_JSON') ){
270
+ $json = new Moxiecode_JSON();
271
+ return $json->encode($data);
272
+ } else {
273
+ trigger_error('No JSON parser available', E_USER_ERROR);
274
+ return null;
275
+ }
276
+ }
277
+
278
+ }
279
+
280
+ global $ws_screen_meta_links_versions;
281
+ if ( !isset($ws_screen_meta_links_versions) ){
282
+ $ws_screen_meta_links_versions = array();
283
+ }
284
+ $ws_screen_meta_links_versions['1.1'] = 'wsScreenMetaLinks11';
285
+
286
+ endif;
287
+
288
+ /**
289
+ * Add a new link to the screen meta area.
290
+ *
291
+ * @param string $id Link ID. Should be unique and a valid value for a HTML ID attribute.
292
+ * @param string $text Link text.
293
+ * @param string $href Link URL.
294
+ * @param string|array $page The page(s) where you want to add the link.
295
+ * @param array $attributes Optional. Additional attributes for the link tag.
296
+ * @return void
297
+ */
298
+ function add_screen_meta_link($id, $text, $href, $page, $attributes = null){
299
+ global $ws_screen_meta_links_versions;
300
+
301
+ static $instance = null;
302
+ if ( is_null($instance) ){
303
+ //Instantiate the latest version of the wsScreenMetaLinks class
304
+ uksort($ws_screen_meta_links_versions, 'version_compare');
305
+ $className = end($ws_screen_meta_links_versions);
306
+ $instance = new $className;
307
+ }
308
+
309
+ $instance->add_screen_meta_link($id, $text, $href, $page, $attributes);
310
+ }
311
+
312
+ ?>
includes/screen-options/screen-options.js CHANGED
@@ -1,13 +1,13 @@
1
- jQuery(function($){
2
- function performAutosave(){
3
- var panel = $(this).parents('div.custom-options-panel');
4
- var params = panel.find('input, select, textarea').serialize();
5
- params = params + '&action=save_settings-' + panel.attr('id');
6
- $.post(
7
- 'admin-ajax.php',
8
- params
9
- );
10
- }
11
-
12
- $('#screen-options-wrap div.requires-autosave').find('input, select, textarea').change(performAutosave);
13
  });
1
+ jQuery(function($){
2
+ function performAutosave(){
3
+ var panel = $(this).parents('div.custom-options-panel');
4
+ var params = panel.find('input, select, textarea').serialize();
5
+ params = params + '&action=save_settings-' + panel.attr('id');
6
+ $.post(
7
+ 'admin-ajax.php',
8
+ params
9
+ );
10
+ }
11
+
12
+ $('#screen-options-wrap div.requires-autosave').find('input, select, textarea').change(performAutosave);
13
  });
includes/screen-options/screen-options.php CHANGED
@@ -1,297 +1,297 @@
1
- <?php
2
-
3
- if ( !class_exists('wsScreenOptions12') ):
4
-
5
- /**
6
- * Class for adding new panels to the "Screen Options" box.
7
- *
8
- * Do not access this class directly. Instead, use the add_screen_options_panel() function.
9
- *
10
- * @author Janis Elsts
11
- * @copyright 2014
12
- * @version 1.3
13
- * @access public
14
- */
15
- class wsScreenOptions13 {
16
- var $registered_panels; //List of custom "Screen Options" panels
17
- var $page_panels; //Index of panels registered for each page ($page => array of panel ids).
18
-
19
- /**
20
- * Class constructor
21
- *
22
- * @return void
23
- */
24
- function init(){
25
- $this->registered_panels = array();
26
- $this->page_panels = array();
27
-
28
- add_action('current_screen', array($this, 'populate_page_panels'));
29
- add_filter('screen_settings', array(&$this, 'append_screen_settings'), 10, 2);
30
- add_action('admin_print_scripts', array(&$this, 'add_autosave_script'));
31
- }
32
-
33
- /**
34
- * Add a new settings panel to the "Screen Options" box.
35
- *
36
- * @param string $id String to use in the 'id' attribute of the settings panel. Should be unique.
37
- * @param string $title Title of the settings panel. Set to an empty string to omit title.
38
- * @param callback $callback Function that fills the panel with the desired content. Should return its output.
39
- * @param string|array $page The page(s) on which to show the panel (similar to add_meta_box()).
40
- * @param callback $save_callback Optional. Function that saves the settings.
41
- * @param bool $autosave Optional. If set, settings will be automatically saved (via AJAX) when the value of any input element in the panel changes. Defaults to false.
42
- * @return void
43
- */
44
- function add_screen_options_panel($id, $title, $callback, $page, $save_callback = null, $autosave = false){
45
- if ( !is_array($page) ){
46
- $page = array($page);
47
- }
48
-
49
- $new_panel = array(
50
- 'title' => $title,
51
- 'callback' => $callback,
52
- 'page' => $page,
53
- 'save_callback' => $save_callback,
54
- 'autosave' => $autosave,
55
- );
56
- $this->registered_panels[$id] = $new_panel;
57
-
58
- if ( $save_callback ){
59
- add_action('wp_ajax_save_settings-' . $id, array($this, 'ajax_save_callback'));
60
- }
61
- }
62
-
63
- /**
64
- * Populate a lookup array for screen -> panels queries.
65
- *
66
- * This is a callback for the "current_screen" action. We have to do it in this hook or WordPress will
67
- * complain about "doing it wrong" and incorrectly suggest using the "add_meta_boxes" action.
68
- *
69
- * "add_meta_boxes" doesn't work here because it only gets called on CPT pages and we want the ability
70
- * to add screen options to any page.
71
- */
72
- function populate_page_panels() {
73
- foreach($this->registered_panels as $id => $panel) {
74
- $page = $panel['page'];
75
-
76
- //Convert page hooks/slugs to screen IDs
77
- $page = array_map(array($this, 'page_to_screen_id'), $page);
78
- $page = array_unique($page);
79
-
80
- //Store the panel ID in each relevant page's list
81
- foreach($page as $page_id){
82
- if ( !isset($this->page_panels[$page_id]) ){
83
- $this->page_panels[$page_id] = array();
84
- }
85
- $this->page_panels[$page_id][] = $id;
86
- }
87
- }
88
- }
89
-
90
- /**
91
- * Convert a page hook name to a screen ID.
92
- *
93
- * @uses convert_to_screen()
94
- * @access private
95
- *
96
- * @param string $page
97
- * @return string
98
- */
99
- function page_to_screen_id($page){
100
- if ( function_exists('convert_to_screen') ){
101
- $screen = convert_to_screen($page);
102
- if ( isset($screen->id) ){
103
- return $screen->id;
104
- } else {
105
- return '';
106
- }
107
- } else {
108
- return str_replace( array('.php', '-new', '-add' ), '', $page);
109
- }
110
- }
111
-
112
- /**
113
- * Append custom panel HTML to the "Screen Options" box of the current page.
114
- * Callback for the 'screen_settings' filter (available in WP 3.0 and up).
115
- *
116
- * @access private
117
- *
118
- * @param string $current
119
- * @param string $screen Screen object (undocumented).
120
- * @return string The HTML code to append to "Screen Options"
121
- */
122
- function append_screen_settings($current, $screen){
123
- global $hook_suffix;
124
-
125
- //Sanity check
126
- if ( !isset($screen->id) ) {
127
- return $current;
128
- }
129
-
130
- //Are there any panels that want to appear on this page?
131
- $panels = $this->get_panels_for_screen($screen->id, $hook_suffix);
132
- if ( empty($panels) ){
133
- return $current;
134
- }
135
-
136
- //Append all panels registered for this screen
137
- foreach($panels as $panel_id){
138
- $panel = $this->registered_panels[$panel_id];
139
-
140
- //Add panel title
141
- if ( !empty($panel['title']) ){
142
- $current .= "\n<h5>".$panel['title']."</h5>\n";
143
- }
144
- //Generate panel contents
145
- if ( is_callable($panel['callback']) ){
146
- $contents = call_user_func($panel['callback']);
147
- $classes = array(
148
- 'custom-options-panel',
149
- );
150
- if ( $panel['autosave'] ){
151
- $classes[] = 'requires-autosave';
152
- }
153
-
154
- $contents = sprintf(
155
- '<div id="%s" class="%s"><input type="hidden" name="_wpnonce-%s" value="%s" />%s</div>',
156
- esc_attr($panel_id),
157
- implode(' ',$classes),
158
- esc_attr($panel_id),
159
- wp_create_nonce('save_settings-'.$panel_id),
160
- $contents
161
- );
162
-
163
- $current .= $contents;
164
- }
165
- }
166
-
167
- return $current;
168
- }
169
-
170
- /**
171
- * AJAX callback for the "Screen Options" autosave.
172
- *
173
- * @access private
174
- * @return void
175
- */
176
- function ajax_save_callback(){
177
- if ( empty($_POST['action']) ){
178
- die('0');
179
- }
180
-
181
- //The 'action' argument is in the form "save_settings-panel_id"
182
- $id = end(explode('-', $_POST['action'], 2));
183
-
184
- //Basic security check.
185
- check_ajax_referer('save_settings-' . $id, '_wpnonce-' . $id);
186
-
187
- //Hand the request to the registered callback, if any
188
- if ( !isset($this->registered_panels[$id]) ){
189
- exit('0');
190
- }
191
- $panel = $this->registered_panels[$id];
192
- if ( is_callable($panel['save_callback']) ){
193
- call_user_func($panel['save_callback'], $_POST);
194
- die('1');
195
- } else {
196
- die('0');
197
- }
198
- }
199
-
200
- /**
201
- * Add/enqueue supporting JavaScript for the autosave function of custom "Screen Options" panels.
202
- *
203
- * Checks if the current page is supposed to contain any autosave-enabled
204
- * panels and adds the script only if that's the case.
205
- *
206
- * @return void
207
- */
208
- function add_autosave_script(){
209
- //Get the page id/hook/slug/whatever.
210
- global $hook_suffix;
211
-
212
- //Check if we have some panels with autosave registered for this page.
213
- $panels = $this->get_panels_for_screen('', $hook_suffix);
214
- if ( empty($panels) ){
215
- return;
216
- }
217
-
218
- $got_autosave = false;
219
- foreach($panels as $panel_id){
220
- if ( $this->registered_panels[$panel_id]['autosave'] ){
221
- $got_autosave = true;
222
- break;
223
- }
224
- }
225
-
226
- if ( $got_autosave ){
227
- //Enqueue the script itself
228
- $url = plugins_url('screen-options.js', __FILE__);
229
- wp_enqueue_script('screen-options-custom-autosave', $url, array('jquery'));
230
- }
231
- }
232
-
233
- /**
234
- * Get custom panels registered for a particular screen and/or page.
235
- *
236
- * @param string $screen_id Screen ID.
237
- * @param string $page Optional. Page filename or hook name.
238
- * @return array Array of custom panels.
239
- */
240
- function get_panels_for_screen($screen_id, $page = ''){
241
- if ( isset($this->page_panels[$screen_id]) && !empty($this->page_panels[$screen_id]) ){
242
- $panels = $this->page_panels[$screen_id];
243
- } else {
244
- $panels = array();
245
- }
246
- if ( !empty($page) ){
247
- $page_as_screen = $this->page_to_screen_id($page);
248
- if ( isset($this->page_panels[$page_as_screen]) && !empty($this->page_panels[$page_as_screen]) ){
249
- $panels = array_merge($panels, $this->page_panels[$page_as_screen]);
250
- }
251
- }
252
- return array_unique($panels);
253
- }
254
- }
255
-
256
- //All versions of the class are stored in a global array
257
- //and only the latest version is actually used.
258
- global $ws_screen_options_versions;
259
- if ( !isset($ws_screen_options_versions) ){
260
- $ws_screen_options_versions = array();
261
- }
262
- $ws_screen_options_versions['1.3'] = 'wsScreenOptions13';
263
-
264
- endif;
265
-
266
- if ( !function_exists('add_screen_options_panel') ){
267
-
268
- /**
269
- * Add a new settings panel to the "Screen Options" box.
270
- *
271
- * @see wsScreenOptions10::add_screen_options_panel()
272
- *
273
- * @param string $id String to use in the 'id' attribute of the settings panel. Should be unique.
274
- * @param string $title Title of the settings panel. Set to an empty string to omit title.
275
- * @param callback $callback Function that fills the panel with the desired content. Should return its output.
276
- * @param string|array $page The page(s) on which to show the panel (similar to add_meta_box()).
277
- * @param callback $save_callback Optional. Function that saves the settings contained in the panel.
278
- * @param bool $autosave Optional. If set, settings will be automatically saved (via AJAX) when the value of any input element in the panel changes. Defaults to false.
279
- * @return void
280
- */
281
- function add_screen_options_panel($id, $title, $callback, $page, $save_callback = null, $autosave = false){
282
- global $ws_screen_options_versions;
283
-
284
- static $instance = null; /** @var wsScreenOptions13 $instance */
285
- if ( is_null($instance) ){
286
- //Instantiate the latest version of the wsScreenOptions class
287
- uksort($ws_screen_options_versions, 'version_compare');
288
- $className = end($ws_screen_options_versions);
289
- $instance = new $className;
290
- $instance->init();
291
- }
292
-
293
- $instance->add_screen_options_panel($id, $title, $callback, $page, $save_callback, $autosave);
294
- }
295
-
296
- }
297
-
1
+ <?php
2
+
3
+ if ( !class_exists('wsScreenOptions12') ):
4
+
5
+ /**
6
+ * Class for adding new panels to the "Screen Options" box.
7
+ *
8
+ * Do not access this class directly. Instead, use the add_screen_options_panel() function.
9
+ *
10
+ * @author Janis Elsts
11
+ * @copyright 2014
12
+ * @version 1.3
13
+ * @access public
14
+ */
15
+ class wsScreenOptions13 {
16
+ var $registered_panels; //List of custom "Screen Options" panels
17
+ var $page_panels; //Index of panels registered for each page ($page => array of panel ids).
18
+
19
+ /**
20
+ * Class constructor
21
+ *
22
+ * @return void
23
+ */
24
+ function init(){
25
+ $this->registered_panels = array();
26
+ $this->page_panels = array();
27
+
28
+ add_action('current_screen', array($this, 'populate_page_panels'));
29
+ add_filter('screen_settings', array(&$this, 'append_screen_settings'), 10, 2);
30
+ add_action('admin_print_scripts', array(&$this, 'add_autosave_script'));
31
+ }
32
+
33
+ /**
34
+ * Add a new settings panel to the "Screen Options" box.
35
+ *
36
+ * @param string $id String to use in the 'id' attribute of the settings panel. Should be unique.
37
+ * @param string $title Title of the settings panel. Set to an empty string to omit title.
38
+ * @param callback $callback Function that fills the panel with the desired content. Should return its output.
39
+ * @param string|array $page The page(s) on which to show the panel (similar to add_meta_box()).
40
+ * @param callback $save_callback Optional. Function that saves the settings.
41
+ * @param bool $autosave Optional. If set, settings will be automatically saved (via AJAX) when the value of any input element in the panel changes. Defaults to false.
42
+ * @return void
43
+ */
44
+ function add_screen_options_panel($id, $title, $callback, $page, $save_callback = null, $autosave = false){
45
+ if ( !is_array($page) ){
46
+ $page = array($page);
47
+ }
48
+
49
+ $new_panel = array(
50
+ 'title' => $title,
51
+ 'callback' => $callback,
52
+ 'page' => $page,
53
+ 'save_callback' => $save_callback,
54
+ 'autosave' => $autosave,
55
+ );
56
+ $this->registered_panels[$id] = $new_panel;
57
+
58
+ if ( $save_callback ){
59
+ add_action('wp_ajax_save_settings-' . $id, array($this, 'ajax_save_callback'));
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Populate a lookup array for screen -> panels queries.
65
+ *
66
+ * This is a callback for the "current_screen" action. We have to do it in this hook or WordPress will
67
+ * complain about "doing it wrong" and incorrectly suggest using the "add_meta_boxes" action.
68
+ *
69
+ * "add_meta_boxes" doesn't work here because it only gets called on CPT pages and we want the ability
70
+ * to add screen options to any page.
71
+ */
72
+ function populate_page_panels() {
73
+ foreach($this->registered_panels as $id => $panel) {
74
+ $page = $panel['page'];
75
+
76
+ //Convert page hooks/slugs to screen IDs
77
+ $page = array_map(array($this, 'page_to_screen_id'), $page);
78
+ $page = array_unique($page);
79
+
80
+ //Store the panel ID in each relevant page's list
81
+ foreach($page as $page_id){
82
+ if ( !isset($this->page_panels[$page_id]) ){
83
+ $this->page_panels[$page_id] = array();
84
+ }
85
+ $this->page_panels[$page_id][] = $id;
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Convert a page hook name to a screen ID.
92
+ *
93
+ * @uses convert_to_screen()
94
+ * @access private
95
+ *
96
+ * @param string $page
97
+ * @return string
98
+ */
99
+ function page_to_screen_id($page){
100
+ if ( function_exists('convert_to_screen') ){
101
+ $screen = convert_to_screen($page);
102
+ if ( isset($screen->id) ){
103
+ return $screen->id;
104
+ } else {
105
+ return '';
106
+ }
107
+ } else {
108
+ return str_replace( array('.php', '-new', '-add' ), '', $page);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Append custom panel HTML to the "Screen Options" box of the current page.
114
+ * Callback for the 'screen_settings' filter (available in WP 3.0 and up).
115
+ *
116
+ * @access private
117
+ *
118
+ * @param string $current
119
+ * @param string $screen Screen object (undocumented).
120
+ * @return string The HTML code to append to "Screen Options"
121
+ */
122
+ function append_screen_settings($current, $screen){
123
+ global $hook_suffix;
124
+
125
+ //Sanity check
126
+ if ( !isset($screen->id) ) {
127
+ return $current;
128
+ }
129
+
130
+ //Are there any panels that want to appear on this page?
131
+ $panels = $this->get_panels_for_screen($screen->id, $hook_suffix);
132
+ if ( empty($panels) ){
133
+ return $current;
134
+ }
135
+
136
+ //Append all panels registered for this screen
137
+ foreach($panels as $panel_id){
138
+ $panel = $this->registered_panels[$panel_id];
139
+
140
+ //Add panel title
141
+ if ( !empty($panel['title']) ){
142
+ $current .= "\n<h5>".$panel['title']."</h5>\n";
143
+ }
144
+ //Generate panel contents
145
+ if ( is_callable($panel['callback']) ){
146
+ $contents = call_user_func($panel['callback']);
147
+ $classes = array(
148
+ 'custom-options-panel',
149
+ );
150
+ if ( $panel['autosave'] ){
151
+ $classes[] = 'requires-autosave';
152
+ }
153
+
154
+ $contents = sprintf(
155
+ '<div id="%s" class="%s"><input type="hidden" name="_wpnonce-%s" value="%s" />%s</div>',
156
+ esc_attr($panel_id),
157
+ implode(' ',$classes),
158
+ esc_attr($panel_id),
159
+ wp_create_nonce('save_settings-'.$panel_id),
160
+ $contents
161
+ );
162
+
163
+ $current .= $contents;
164
+ }
165
+ }
166
+
167
+ return $current;
168
+ }
169
+
170
+ /**
171
+ * AJAX callback for the "Screen Options" autosave.
172
+ *
173
+ * @access private
174
+ * @return void
175
+ */
176
+ function ajax_save_callback(){
177
+ if ( empty($_POST['action']) ){
178
+ die('0');
179
+ }
180
+
181
+ //The 'action' argument is in the form "save_settings-panel_id"
182
+ $id = end(explode('-', $_POST['action'], 2));
183
+
184
+ //Basic security check.
185
+ check_ajax_referer('save_settings-' . $id, '_wpnonce-' . $id);
186
+
187
+ //Hand the request to the registered callback, if any
188
+ if ( !isset($this->registered_panels[$id]) ){
189
+ exit('0');
190
+ }
191
+ $panel = $this->registered_panels[$id];
192
+ if ( is_callable($panel['save_callback']) ){
193
+ call_user_func($panel['save_callback'], $_POST);
194
+ die('1');
195
+ } else {
196
+ die('0');
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Add/enqueue supporting JavaScript for the autosave function of custom "Screen Options" panels.
202
+ *
203
+ * Checks if the current page is supposed to contain any autosave-enabled
204
+ * panels and adds the script only if that's the case.
205
+ *
206
+ * @return void
207
+ */
208
+ function add_autosave_script(){
209
+ //Get the page id/hook/slug/whatever.
210
+ global $hook_suffix;
211
+
212
+ //Check if we have some panels with autosave registered for this page.
213
+ $panels = $this->get_panels_for_screen('', $hook_suffix);
214
+ if ( empty($panels) ){
215
+ return;
216
+ }
217
+
218
+ $got_autosave = false;
219
+ foreach($panels as $panel_id){
220
+ if ( $this->registered_panels[$panel_id]['autosave'] ){
221
+ $got_autosave = true;
222
+ break;
223
+ }
224
+ }
225
+
226
+ if ( $got_autosave ){
227
+ //Enqueue the script itself
228
+ $url = plugins_url('screen-options.js', __FILE__);
229
+ wp_enqueue_script('screen-options-custom-autosave', $url, array('jquery'));
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get custom panels registered for a particular screen and/or page.
235
+ *
236
+ * @param string $screen_id Screen ID.
237
+ * @param string $page Optional. Page filename or hook name.
238
+ * @return array Array of custom panels.
239
+ */
240
+ function get_panels_for_screen($screen_id, $page = ''){
241
+ if ( isset($this->page_panels[$screen_id]) && !empty($this->page_panels[$screen_id]) ){
242
+ $panels = $this->page_panels[$screen_id];
243
+ } else {
244
+ $panels = array();
245
+ }
246
+ if ( !empty($page) ){
247
+ $page_as_screen = $this->page_to_screen_id($page);
248
+ if ( isset($this->page_panels[$page_as_screen]) && !empty($this->page_panels[$page_as_screen]) ){
249
+ $panels = array_merge($panels, $this->page_panels[$page_as_screen]);
250
+ }
251
+ }
252
+ return array_unique($panels);
253
+ }
254
+ }
255
+
256
+ //All versions of the class are stored in a global array
257
+ //and only the latest version is actually used.
258
+ global $ws_screen_options_versions;
259
+ if ( !isset($ws_screen_options_versions) ){
260
+ $ws_screen_options_versions = array();
261
+ }
262
+ $ws_screen_options_versions['1.3'] = 'wsScreenOptions13';
263
+
264
+ endif;
265
+
266
+ if ( !function_exists('add_screen_options_panel') ){
267
+
268
+ /**
269
+ * Add a new settings panel to the "Screen Options" box.
270
+ *
271
+ * @see wsScreenOptions10::add_screen_options_panel()
272
+ *
273
+ * @param string $id String to use in the 'id' attribute of the settings panel. Should be unique.
274
+ * @param string $title Title of the settings panel. Set to an empty string to omit title.
275
+ * @param callback $callback Function that fills the panel with the desired content. Should return its output.
276
+ * @param string|array $page The page(s) on which to show the panel (similar to add_meta_box()).
277
+ * @param callback $save_callback Optional. Function that saves the settings contained in the panel.
278
+ * @param bool $autosave Optional. If set, settings will be automatically saved (via AJAX) when the value of any input element in the panel changes. Defaults to false.
279
+ * @return void
280
+ */
281
+ function add_screen_options_panel($id, $title, $callback, $page, $save_callback = null, $autosave = false){
282
+ global $ws_screen_options_versions;
283
+
284
+ static $instance = null; /** @var wsScreenOptions13 $instance */
285
+ if ( is_null($instance) ){
286
+ //Instantiate the latest version of the wsScreenOptions class
287
+ uksort($ws_screen_options_versions, 'version_compare');
288
+ $className = end($ws_screen_options_versions);
289
+ $instance = new $className;
290
+ $instance->init();
291
+ }
292
+
293
+ $instance->add_screen_options_panel($id, $title, $callback, $page, $save_callback, $autosave);
294
+ }
295
+
296
+ }
297
+
includes/token-bucket.php CHANGED
@@ -1,121 +1,121 @@
1
- <?php
2
-
3
- /**
4
- * This class implements a variant of the popular token bucket algorithm.
5
- */
6
- class blcTokenBucketList {
7
- const MICROSECONDS_PER_SECOND = 1000000;
8
-
9
- /** @var float How many tokens each bucket can hold. */
10
- private $capacity;
11
-
12
- /** @var float How long it takes to completely fill a bucket (in seconds). */
13
- private $fillTime;
14
-
15
- /** @var float Minimum interval between taking tokens from a bucket (in seconds). */
16
- private $minTakeInterval;
17
-
18
- /** @var int How many buckets we can manage, in total. */
19
- private $maxBuckets = 200;
20
-
21
- private $buckets = array();
22
-
23
- public function __construct($capacity, $fillTime, $minInterval = 0) {
24
- $this->capacity = $capacity;
25
- $this->fillTime = $fillTime;
26
- $this->minTakeInterval = $minInterval;
27
- }
28
-
29
- /**
30
- * Take one token from a bucket.
31
- * This method will block until a token becomes available.
32
- *
33
- * @param string $bucketName
34
- */
35
- public function takeToken($bucketName) {
36
- $this->createIfNotExists($bucketName);
37
- $this->waitForToken($bucketName);
38
-
39
- $this->buckets[$bucketName]['tokens']--;
40
- $this->buckets[$bucketName]['lastTokenTakenAt'] = microtime(true);
41
- }
42
-
43
- /**
44
- * Wait until at a token is available.
45
- *
46
- * @param string $name Bucket name.
47
- */
48
- private function waitForToken($name) {
49
- $now = microtime(true);
50
-
51
- $timeSinceLastToken = $now - $this->buckets[$name]['lastTokenTakenAt'];
52
- $intervalWait = max($this->minTakeInterval - $timeSinceLastToken, 0);
53
-
54
- $requiredTokens = max(1 - $this->buckets[$name]['tokens'], 0);
55
- $refillWait = $requiredTokens / $this->getFillRate();
56
-
57
- $totalWait = max($intervalWait, $refillWait);
58
- if ($totalWait > 0) {
59
- usleep($totalWait * self::MICROSECONDS_PER_SECOND);
60
- }
61
-
62
- $this->refillBucket($name);
63
- return;
64
- }
65
-
66
- /**
67
- * Create a bucket if it doesn't exist yet.
68
- *
69
- * @param $name
70
- */
71
- private function createIfNotExists($name) {
72
- if ( !isset($this->buckets[$name]) ) {
73
- $this->buckets[$name] = array(
74
- 'tokens' => $this->capacity,
75
- 'lastRefill' => microtime(true),
76
- 'lastTokenTakenAt' => 0
77
- );
78
- }
79
- //Make sure we don't exceed $maxBuckets.
80
- $this->cleanup();
81
- }
82
-
83
- /**
84
- * Calculate how quickly each bucket should be refilled.
85
- *
86
- * @return float Fill rate in tokens per second.
87
- */
88
- private function getFillRate() {
89
- return $this->capacity / $this->fillTime;
90
- }
91
-
92
- /**
93
- * Refill a bucket with fresh tokens.
94
- *
95
- * @param $name
96
- */
97
- private function refillBucket($name) {
98
- $now = microtime(true);
99
-
100
- $timeSinceRefill = $now - $this->buckets[$name]['lastRefill'];
101
- $this->buckets[$name]['tokens'] += $timeSinceRefill * $this->getFillRate();
102
-
103
- if ($this->buckets[$name]['tokens'] > $this->capacity) {
104
- $this->buckets[$name]['tokens'] = $this->capacity;
105
- }
106
-
107
- $this->buckets[$name]['lastRefill'] = $now;
108
- }
109
-
110
- /**
111
- * Keep the number of active buckets within the $this->maxBuckets limit.
112
- */
113
- private function cleanup() {
114
- if ($this->maxBuckets > 0) {
115
- //Very simplistic implementation - just discard the oldest buckets.
116
- while(count($this->buckets) > $this->maxBuckets) {
117
- array_shift($this->buckets);
118
- }
119
- }
120
- }
121
  }
1
+ <?php
2
+
3
+ /**
4
+ * This class implements a variant of the popular token bucket algorithm.
5
+ */
6
+ class blcTokenBucketList {
7
+ const MICROSECONDS_PER_SECOND = 1000000;
8
+
9
+ /** @var float How many tokens each bucket can hold. */
10
+ private $capacity;
11
+
12
+ /** @var float How long it takes to completely fill a bucket (in seconds). */
13
+ private $fillTime;
14
+
15
+ /** @var float Minimum interval between taking tokens from a bucket (in seconds). */
16
+ private $minTakeInterval;
17
+
18
+ /** @var int How many buckets we can manage, in total. */
19
+ private $maxBuckets = 200;
20
+
21
+ private $buckets = array();
22
+
23
+ public function __construct($capacity, $fillTime, $minInterval = 0) {
24
+ $this->capacity = $capacity;
25
+ $this->fillTime = $fillTime;
26
+ $this->minTakeInterval = $minInterval;
27
+ }
28
+
29
+ /**
30
+ * Take one token from a bucket.
31
+ * This method will block until a token becomes available.
32
+ *
33
+ * @param string $bucketName
34
+ */
35
+ public function takeToken($bucketName) {
36
+ $this->createIfNotExists($bucketName);
37
+ $this->waitForToken($bucketName);
38
+
39
+ $this->buckets[$bucketName]['tokens']--;
40
+ $this->buckets[$bucketName]['lastTokenTakenAt'] = microtime(true);
41
+ }
42
+
43
+ /**
44
+ * Wait until at a token is available.
45
+ *
46
+ * @param string $name Bucket name.
47
+ */
48
+ private function waitForToken($name) {
49
+ $now = microtime(true);
50
+
51
+ $timeSinceLastToken = $now - $this->buckets[$name]['lastTokenTakenAt'];
52
+ $intervalWait = max($this->minTakeInterval - $timeSinceLastToken, 0);
53
+
54
+ $requiredTokens = max(1 - $this->buckets[$name]['tokens'], 0);
55
+ $refillWait = $requiredTokens / $this->getFillRate();
56
+
57
+ $totalWait = max($intervalWait, $refillWait);
58
+ if ($totalWait > 0) {
59
+ usleep($totalWait * self::MICROSECONDS_PER_SECOND);
60
+ }
61
+
62
+ $this->refillBucket($name);
63
+ return;
64
+ }
65
+
66
+ /**
67
+ * Create a bucket if it doesn't exist yet.
68
+ *
69
+ * @param $name
70
+ */
71
+ private function createIfNotExists($name) {
72
+ if ( !isset($this->buckets[$name]) ) {
73
+ $this->buckets[$name] = array(
74
+ 'tokens' => $this->capacity,
75
+ 'lastRefill' => microtime(true),
76
+ 'lastTokenTakenAt' => 0
77
+ );
78
+ }
79
+ //Make sure we don't exceed $maxBuckets.
80
+ $this->cleanup();
81
+ }
82
+
83
+ /**
84
+ * Calculate how quickly each bucket should be refilled.
85
+ *
86
+ * @return float Fill rate in tokens per second.
87
+ */
88
+ private function getFillRate() {
89
+ return $this->capacity / $this->fillTime;
90
+ }
91
+
92
+ /**
93
+ * Refill a bucket with fresh tokens.
94
+ *
95
+ * @param $name
96
+ */
97
+ private function refillBucket($name) {
98
+ $now = microtime(true);
99
+
100
+ $timeSinceRefill = $now - $this->buckets[$name]['lastRefill'];
101
+ $this->buckets[$name]['tokens'] += $timeSinceRefill * $this->getFillRate();
102
+
103
+ if ($this->buckets[$name]['tokens'] > $this->capacity) {
104
+ $this->buckets[$name]['tokens'] = $this->capacity;
105
+ }
106
+
107
+ $this->buckets[$name]['lastRefill'] = $now;
108
+ }
109
+
110
+ /**
111
+ * Keep the number of active buckets within the $this->maxBuckets limit.
112
+ */
113
+ private function cleanup() {
114
+ if ($this->maxBuckets > 0) {
115
+ //Very simplistic implementation - just discard the oldest buckets.
116
+ while(count($this->buckets) > $this->maxBuckets) {
117
+ array_shift($this->buckets);
118
+ }
119
+ }
120
+ }
121
  }
includes/transactions-manager.php CHANGED
@@ -18,14 +18,18 @@ class TransactionManager
18
  public function commit()
19
  {
20
  global $wpdb;
 
 
21
 
22
  $this->start();
23
 
24
  try {
25
  $wpdb->query('COMMIT');
 
26
  $this->isTransactionStarted = false;
27
  } catch (Exception $e) {
28
  $wpdb->query('ROLLBACK');
 
29
  $this->isTransactionStarted = false;
30
  }
31
  }
18
  public function commit()
19
  {
20
  global $wpdb;
21
+ global $blclog;
22
+ $blclog->debug('Starting DB commit.');
23
 
24
  $this->start();
25
 
26
  try {
27
  $wpdb->query('COMMIT');
28
+ $blclog->debug('Commit executed.');
29
  $this->isTransactionStarted = false;
30
  } catch (Exception $e) {
31
  $wpdb->query('ROLLBACK');
32
+ $blclog->debug('Commit failed; rollback.');
33
  $this->isTransactionStarted = false;
34
  }
35
  }
includes/utility-class.php CHANGED
@@ -1,417 +1,417 @@
1
- <?php
2
-
3
- /**
4
- * @author W-Shadow
5
- * @copyright 2010
6
- */
7
-
8
- if ( ! function_exists( 'sys_get_temp_dir' ) ) {
9
- function sys_get_temp_dir() {
10
- if ( ! empty( $_ENV['TMP'] ) ) { return realpath( $_ENV['TMP'] ); }
11
- if ( ! empty( $_ENV['TMPDIR'] ) ) { return realpath( $_ENV['TMPDIR'] ); }
12
- if ( ! empty( $_ENV['TEMP'] ) ) { return realpath( $_ENV['TEMP'] ); }
13
- $tempfile = tempnam( uniqid( rand(),TRUE ),'' );
14
- if ( @file_exists( $tempfile ) ) {
15
- unlink( $tempfile );
16
- return realpath( dirname( $tempfile ) );
17
- }
18
- return '';
19
- }
20
- }
21
-
22
- //Include the internationalized domain name converter (requires PHP 5)
23
- if ( version_compare( phpversion(), '5.0.0', '>=' ) && ! class_exists( 'idna_convert' ) ) {
24
- include BLC_DIRECTORY . '/idn/idna_convert.class.php';
25
- if ( ! function_exists( 'encode_utf8' ) ) {
26
- include BLC_DIRECTORY . '/idn/transcode_wrapper.php';
27
- }
28
- }
29
-
30
-
31
- if ( ! class_exists( 'blcUtility' ) ) {
32
- class blcUtility {
33
- /**
34
- * Checks if PHP is running in safe mode
35
- * blcUtility::is_safe_mode()
36
- *
37
- * @return bool
38
- */
39
- static function is_safe_mode() {
40
- // Check php.ini safe_mode only if PHP version is lower than 5.3.0, else set to false.
41
- if ( version_compare( phpversion(), '5.3.0', '<' ) ) {
42
- $safe_mode = ini_get( 'safe_mode' );
43
- } else {
44
- $safe_mode = false;
45
- }
46
-
47
- // Null, 0, '', '0' and so on count as false.
48
- if ( ! $safe_mode ) {
49
- return false;
50
- }
51
- // Test for some textual true/false variations.
52
- switch ( strtolower( $safe_mode ) ) {
53
- case 'on':
54
- case 'true':
55
- case 'yes':
56
- return true;
57
-
58
- case 'off':
59
- case 'false':
60
- case 'no':
61
- return false;
62
-
63
- default: // Let PHP handle anything else.
64
- return (bool) (int) $safe_mode;
65
- }
66
- }
67
-
68
- /**
69
- * blcUtility::is_open_basedir()
70
- * Checks if open_basedir is enabled
71
- *
72
- * @return bool
73
- */
74
- static function is_open_basedir(){
75
- $open_basedir = ini_get( 'open_basedir' );
76
- return $open_basedir && ( strtolower( $open_basedir ) != 'none' );
77
- }
78
-
79
- /**
80
- * Truncate a string on a specified boundary character.
81
- *
82
- * @param string $text The text to truncate.
83
- * @param integer $max_characters Return no more than $max_characters
84
- * @param string $break Break on this character. Defaults to space.
85
- * @param string $pad Pad the truncated string with this string. Defaults to an HTML ellipsis.
86
- * @return string
87
- */
88
- static function truncate( $text, $max_characters = 0, $break = ' ', $pad = '&hellip;' ) {
89
- if ( strlen( $text ) <= $max_characters ) {
90
- return $text;
91
- }
92
-
93
- $text = substr( $text, 0, $max_characters );
94
- $break_pos = strrpos( $text, $break );
95
- if ( false !== $break_pos ) {
96
- $text = substr( $text, 0, $break_pos );
97
- }
98
-
99
- return $text.$pad;
100
- }
101
-
102
- /**
103
- * extract_tags()
104
- * Extract specific HTML tags and their attributes from a string.
105
- *
106
- * You can either specify one tag, an array of tag names, or a regular expression that matches the tag name(s).
107
- * If multiple tags are specified you must also set the $selfclosing parameter and it must be the same for
108
- * all specified tags (so you can't extract both normal and self-closing tags in one go).
109
- *
110
- * The function returns a numerically indexed array of extracted tags. Each entry is an associative array
111
- * with these keys :
112
- * tag_name - the name of the extracted tag, e.g. "a" or "img".
113
- * offset - the numberic offset of the first character of the tag within the HTML source.
114
- * contents - the inner HTML of the tag. This is always empty for self-closing tags.
115
- * attributes - a name -> value array of the tag's attributes, or an empty array if the tag has none.
116
- * full_tag - the entire matched tag, e.g. '<a href="http://example.com">example.com</a>'. This key
117
- * will only be present if you set $return_the_entire_tag to true.
118
- *
119
- * @param string $html The HTML code to search for tags.
120
- * @param string|array $tag The tag(s) to extract.
121
- * @param bool $selfclosing Whether the tag is self-closing or not. Setting it to null will force the script to try and make an educated guess.
122
- * @param bool $return_the_entire_tag Return the entire matched tag in 'full_tag' key of the results array.
123
- * @param string $charset The character set of the HTML code. Defaults to ISO-8859-1.
124
- *
125
- * @return array An array of extracted tags, or an empty array if no matching tags were found.
126
- */
127
- static function extract_tags( $html, $tag, $selfclosing = null, $return_the_entire_tag = false, $charset = 'ISO-8859-1' ) {
128
-
129
- if ( is_array( $tag ) ) {
130
- $tag = implode( '|', $tag );
131
- }
132
-
133
- //If the user didn't specify if $tag is a self-closing tag we try to auto-detect it
134
- //by checking against a list of known self-closing tags.
135
- $selfclosing_tags = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'col', 'param' );
136
- if ( is_null( $selfclosing ) ) {
137
- $selfclosing = in_array( $tag, $selfclosing_tags );
138
- }
139
-
140
- //The regexp is different for normal and self-closing tags because I can't figure out
141
- //how to make a sufficiently robust unified one.
142
- if ( $selfclosing ) {
143
- $tag_pattern =
144
- '@<(?P<tag>' . $tag . ') # <tag
145
- (?P<attributes>\s[^>]+)? # attributes, if any
146
- \s*/?> # /> or just >, being lenient here
147
- @xsi';
148
- } else {
149
- $tag_pattern =
150
- '@<(?P<tag>' . $tag . ') # <tag
151
- (?P<attributes>\s[^>]+)? # attributes, if any
152
- \s*> # >
153
- (?P<contents>.*?) # tag contents
154
- </(?P=tag)> # the closing </tag>
155
- @xsi';
156
- }
157
-
158
- $attribute_pattern =
159
- '@
160
- (?P<name>\w+) # attribute name
161
- \s*=\s*
162
- (
163
- (?P<quote>[\"\'])(?P<value_quoted>.*?)(?P=quote) # a quoted value
164
- | # or
165
- (?P<value_unquoted>[^\s"\']+?)(?:\s+|$) # an unquoted value (terminated by whitespace or EOF)
166
- )
167
- @xsi';
168
-
169
- //Find all tags
170
- if ( ! preg_match_all( $tag_pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ) {
171
- //Return an empty array if we didn't find anything
172
- return array();
173
- }
174
-
175
- $tags = array();
176
- foreach ( $matches as $match ) {
177
-
178
- // Parse tag attributes, if any.
179
- $attributes = array();
180
- if ( ! empty( $match['attributes'][0] ) ) {
181
-
182
- if ( preg_match_all( $attribute_pattern, $match['attributes'][0], $attribute_data, PREG_SET_ORDER ) ) {
183
- //Turn the attribute data into a name->value array
184
- foreach ( $attribute_data as $attr ) {
185
- if( ! empty( $attr['value_quoted'] ) ) {
186
- $value = $attr['value_quoted'];
187
- } else if( ! empty( $attr['value_unquoted'] ) ) {
188
- $value = $attr['value_unquoted'];
189
- } else {
190
- $value = '';
191
- }
192
-
193
- // Passing the value through html_entity_decode is handy when you want
194
- // to extract link URLs or something like that. You might want to remove
195
- // or modify this call if it doesn't fit your situation.
196
- $value = html_entity_decode( $value, ENT_QUOTES, $charset );
197
-
198
- $attributes[ $attr['name'] ] = $value;
199
- }
200
- }
201
-
202
- }
203
-
204
- $tag = array(
205
- 'tag_name' => $match['tag'][0],
206
- 'offset' => $match[0][1],
207
- 'contents' => ! empty( $match['contents'] ) ? $match['contents'][0] : '', // Empty for self-closing tags.
208
- 'attributes' => $attributes,
209
- );
210
- if ( $return_the_entire_tag ) {
211
- $tag['full_tag'] = $match[0][0];
212
- }
213
-
214
- $tags[] = $tag;
215
- }
216
-
217
- return $tags;
218
- }
219
-
220
- /**
221
- * Get the value of a cookie.
222
- *
223
- * @param string $cookie_name The name of the cookie to return.
224
- * @param string $default_value Optional. If the cookie is not set, this value will be returned instead. Defaults to an empty string.
225
- * @return mixed Either the value of the requested cookie, or $default_value.
226
- */
227
- static function get_cookie( $cookie_name, $default_value = '' ) {
228
- if ( isset( $_COOKIE[$cookie_name] ) ) {
229
- return $_COOKIE[$cookie_name];
230
- } else {
231
- return $default_value;
232
- }
233
- }
234
-
235
- /**
236
- * Format a time delta using a fuzzy format, e.g. '2 minutes ago', '2 days', etc.
237
- *
238
- * @param int $delta Time period in seconds.
239
- * @param string $type Optional. The output template to use.
240
- * @return string
241
- */
242
- static function fuzzy_delta( $delta, $template = 'default' ) {
243
-
244
- $templates = array(
245
- 'seconds' => array(
246
- 'default' => _n_noop('%d second', '%d seconds'),
247
- 'ago' => _n_noop('%d second ago', '%d seconds ago'),
248
- ),
249
- 'minutes' => array(
250
- 'default' => _n_noop('%d minute', '%d minutes'),
251
- 'ago' => _n_noop('%d minute ago', '%d minutes ago'),
252
- ),
253
- 'hours' => array(
254
- 'default' => _n_noop('%d hour', '%d hours'),
255
- 'ago' => _n_noop('%d hour ago', '%d hours ago'),
256
- ),
257
- 'days' => array(
258
- 'default' => _n_noop('%d day', '%d days'),
259
- 'ago' => _n_noop('%d day ago', '%d days ago'),
260
- ),
261
- 'months' => array(
262
- 'default' => _n_noop('%d month', '%d months'),
263
- 'ago' => _n_noop('%d month ago', '%d months ago'),
264
- ),
265
- );
266
-
267
- if ( $delta < 1 ) {
268
- $delta = 1;
269
- }
270
-
271
- if ( $delta < MINUTE_IN_SECONDS ) {
272
- $units = 'seconds';
273
- } elseif ( $delta < HOUR_IN_SECONDS ) {
274
- $delta = intval( $delta / MINUTE_IN_SECONDS );
275
- $units = 'minutes';
276
- } elseif ( $delta < DAY_IN_SECONDS ) {
277
- $delta = intval( $delta / HOUR_IN_SECONDS );
278
- $units = 'hours';
279
- } elseif ( $delta < MONTH_IN_SECONDS ) {
280
- $delta = intval( $delta / DAY_IN_SECONDS );
281
- $units = 'days';
282
- } else {
283
- $delta = intval( $delta / MONTH_IN_SECONDS );
284
- $units = 'months';
285
- }
286
-
287
- return sprintf(
288
- _n(
289
- $templates[$units][$template][0],
290
- $templates[$units][$template][1],
291
- $delta,
292
- 'broken-link-checker'
293
- ),
294
- $delta
295
- );
296
- }
297
-
298
- /**
299
- * Optimize the plugin's tables
300
- *
301
- * @return void
302
- */
303
- static function optimize_database(){
304
- global $wpdb; /** @var wpdb $wpdb */
305
-
306
- $wpdb->query( "OPTIMIZE TABLE {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_synch" );
307
- }
308
-
309
- /**
310
- * Get the server's load averages.
311
- *
312
- * Returns an array with three samples - the 1 minute avg, the 5 minute avg, and the 15 minute avg.
313
- *
314
- * @param integer $cache How long the load averages may be cached, in seconds. Set to 0 to get maximally up-to-date data.
315
- * @return array|null Array, or NULL if retrieving load data is impossible (e.g. when running on a Windows box).
316
- */
317
- static function get_server_load( $cache = 5 ) {
318
- static $cached_load = null;
319
- static $cached_when = 0;
320
-
321
- if ( ! empty( $cache ) && ((time() - $cached_when) <= $cache) ) {
322
- return $cached_load;
323
- }
324
-
325
- $load = null;
326
-
327
- if ( function_exists( 'sys_getloadavg' ) ) {
328
- $load = sys_getloadavg();
329
- } else {
330
- $loadavg_file = '/proc/loadavg';
331
- if ( @is_readable( $loadavg_file ) ) {
332
- $load = explode( ' ', file_get_contents( $loadavg_file ) );
333
- $load = array_map( 'floatval', $load );
334
- }
335
- }
336
-
337
- $cached_load = $load;
338
- $cached_when = time();
339
- return $load;
340
- }
341
-
342
- /**
343
- * Convert an internationalized domain name or URL to ASCII-compatible encoding.
344
- *
345
- * @param string $url Either a domain name or a complete URL.
346
- * @param string $charset The character encoding of the $url parameter. Defaults to the encoding set in Settings -> Reading.
347
- * @return string
348
- */
349
- static function idn_to_ascii( $url, $charset = '' ) {
350
- $idn = blcUtility::get_idna_converter();
351
- if ( $idn != null ) {
352
- if ( empty( $charset ) ) {
353
- $charset = get_bloginfo( 'charset' );
354
- }
355
-
356
- // Encode only the host.
357
- if ( preg_match( '@(\w+:/*)?([^/:]+)(.*$)?@s', $url, $matches ) ) {
358
- $host = $matches[2];
359
- if ( ( strtoupper( $charset ) != 'UTF-8') && ( strtoupper( $charset ) != 'UTF8') ) {
360
- $host = encode_utf8( $host, $charset, true );
361
- }
362
- $host = $idn->encode( $host );
363
- $url = $matches[1] . $host . $matches[3];
364
- }
365
- }
366
-
367
- return $url;
368
- }
369
-
370
- /**
371
- * Convert an internationalized domain name (or URL) from ASCII-compatible encoding to UTF8.
372
- *
373
- * @param string $url
374
- * @return string
375
- */
376
- static function idn_to_utf8( $url ) {
377
- $idn = blcUtility::get_idna_converter();
378
- if ( null !== $idn ) {
379
- $url = $idn->decode( $url );
380
- }
381
-
382
- return $url;
383
- }
384
-
385
- /**
386
- * Get an instance of idna_converter
387
- *
388
- * @return idna_convert|null Either an instance of IDNA converter, or NULL if the converter class is not available
389
- */
390
- static function get_idna_converter() {
391
- static $idn = null;
392
- if ( ( null === $idn ) && class_exists( 'idna_convert' ) ) {
393
- $idn = new idna_convert();
394
- }
395
- return $idn;
396
- }
397
-
398
- /**
399
- * Generate a numeric hash from a string. The result will be constrained to the specified interval.
400
- *
401
- * @static
402
- * @param string $input
403
- * @param int $min
404
- * @param int $max
405
- * @return float
406
- */
407
- public static function constrained_hash( $input, $min = 0, $max = 1 ) {
408
- $bytes_to_use = 3;
409
- $md5_char_count = 32;
410
- $hash = substr( md5( $input ), $md5_char_count - $bytes_to_use * 2 );
411
- $hash = intval( hexdec( $hash ) );
412
- return $min + ( ( $max - $min ) * ( $hash / ( pow( 2, $bytes_to_use * 8 ) - 1) ) );
413
- }
414
-
415
- }//class
416
-
417
- }//class_exists
1
+ <?php
2
+
3
+ /**
4
+ * @author W-Shadow
5
+ * @copyright 2010
6
+ */
7
+
8
+ if ( ! function_exists( 'sys_get_temp_dir' ) ) {
9
+ function sys_get_temp_dir() {
10
+ if ( ! empty( $_ENV['TMP'] ) ) { return realpath( $_ENV['TMP'] ); }
11
+ if ( ! empty( $_ENV['TMPDIR'] ) ) { return realpath( $_ENV['TMPDIR'] ); }
12
+ if ( ! empty( $_ENV['TEMP'] ) ) { return realpath( $_ENV['TEMP'] ); }
13
+ $tempfile = tempnam( uniqid( rand(),TRUE ),'' );
14
+ if ( @file_exists( $tempfile ) ) {
15
+ unlink( $tempfile );
16
+ return realpath( dirname( $tempfile ) );
17
+ }
18
+ return '';
19
+ }
20
+ }
21
+
22
+ //Include the internationalized domain name converter (requires PHP 5)
23
+ if ( version_compare( phpversion(), '5.0.0', '>=' ) && ! class_exists( 'idna_convert' ) ) {
24
+ include BLC_DIRECTORY . '/idn/idna_convert.class.php';
25
+ if ( ! function_exists( 'encode_utf8' ) ) {
26
+ include BLC_DIRECTORY . '/idn/transcode_wrapper.php';
27
+ }
28
+ }
29
+
30
+
31
+ if ( ! class_exists( 'blcUtility' ) ) {
32
+ class blcUtility {
33
+ /**
34
+ * Checks if PHP is running in safe mode
35
+ * blcUtility::is_safe_mode()
36
+ *
37
+ * @return bool
38
+ */
39
+ static function is_safe_mode() {
40
+ // Check php.ini safe_mode only if PHP version is lower than 5.3.0, else set to false.
41
+ if ( version_compare( phpversion(), '5.3.0', '<' ) ) {
42
+ $safe_mode = ini_get( 'safe_mode' );
43
+ } else {
44
+ $safe_mode = false;
45
+ }
46
+
47
+ // Null, 0, '', '0' and so on count as false.
48
+ if ( ! $safe_mode ) {
49
+ return false;
50
+ }
51
+ // Test for some textual true/false variations.
52
+ switch ( strtolower( $safe_mode ) ) {
53
+ case 'on':
54
+ case 'true':
55
+ case 'yes':
56
+ return true;
57
+
58
+ case 'off':
59
+ case 'false':
60
+ case 'no':
61
+ return false;
62
+
63
+ default: // Let PHP handle anything else.
64
+ return (bool) (int) $safe_mode;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * blcUtility::is_open_basedir()
70
+ * Checks if open_basedir is enabled
71
+ *
72
+ * @return bool
73
+ */
74
+ static function is_open_basedir(){
75
+ $open_basedir = ini_get( 'open_basedir' );
76
+ return $open_basedir && ( strtolower( $open_basedir ) != 'none' );
77
+ }
78
+
79
+ /**
80
+ * Truncate a string on a specified boundary character.
81
+ *
82
+ * @param string $text The text to truncate.
83
+ * @param integer $max_characters Return no more than $max_characters
84
+ * @param string $break Break on this character. Defaults to space.
85
+ * @param string $pad Pad the truncated string with this string. Defaults to an HTML ellipsis.
86
+ * @return string
87
+ */
88
+ static function truncate( $text, $max_characters = 0, $break = ' ', $pad = '&hellip;' ) {
89
+ if ( strlen( $text ) <= $max_characters ) {
90
+ return $text;
91
+ }
92
+
93
+ $text = substr( $text, 0, $max_characters );
94
+ $break_pos = strrpos( $text, $break );
95
+ if ( false !== $break_pos ) {
96
+ $text = substr( $text, 0, $break_pos );
97
+ }
98
+
99
+ return $text.$pad;
100
+ }
101
+
102
+ /**
103
+ * extract_tags()
104
+ * Extract specific HTML tags and their attributes from a string.
105
+ *
106
+ * You can either specify one tag, an array of tag names, or a regular expression that matches the tag name(s).
107
+ * If multiple tags are specified you must also set the $selfclosing parameter and it must be the same for
108
+ * all specified tags (so you can't extract both normal and self-closing tags in one go).
109
+ *
110
+ * The function returns a numerically indexed array of extracted tags. Each entry is an associative array
111
+ * with these keys :
112
+ * tag_name - the name of the extracted tag, e.g. "a" or "img".
113
+ * offset - the numberic offset of the first character of the tag within the HTML source.
114
+ * contents - the inner HTML of the tag. This is always empty for self-closing tags.
115
+ * attributes - a name -> value array of the tag's attributes, or an empty array if the tag has none.
116
+ * full_tag - the entire matched tag, e.g. '<a href="http://example.com">example.com</a>'. This key
117
+ * will only be present if you set $return_the_entire_tag to true.
118
+ *
119
+ * @param string $html The HTML code to search for tags.
120
+ * @param string|array $tag The tag(s) to extract.
121
+ * @param bool $selfclosing Whether the tag is self-closing or not. Setting it to null will force the script to try and make an educated guess.
122
+ * @param bool $return_the_entire_tag Return the entire matched tag in 'full_tag' key of the results array.
123
+ * @param string $charset The character set of the HTML code. Defaults to ISO-8859-1.
124
+ *
125
+ * @return array An array of extracted tags, or an empty array if no matching tags were found.
126
+ */
127
+ static function extract_tags( $html, $tag, $selfclosing = null, $return_the_entire_tag = false, $charset = 'ISO-8859-1' ) {
128
+
129
+ if ( is_array( $tag ) ) {
130
+ $tag = implode( '|', $tag );
131
+ }
132
+
133
+ //If the user didn't specify if $tag is a self-closing tag we try to auto-detect it
134
+ //by checking against a list of known self-closing tags.
135
+ $selfclosing_tags = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'col', 'param' );
136
+ if ( is_null( $selfclosing ) ) {
137
+ $selfclosing = in_array( $tag, $selfclosing_tags );
138
+ }
139
+
140
+ //The regexp is different for normal and self-closing tags because I can't figure out
141
+ //how to make a sufficiently robust unified one.
142
+ if ( $selfclosing ) {
143
+ $tag_pattern =
144
+ '@<(?P<tag>' . $tag . ') # <tag
145
+ (?P<attributes>\s[^>]+)? # attributes, if any
146
+ \s*/?> # /> or just >, being lenient here
147
+ @xsi';
148
+ } else {
149
+ $tag_pattern =
150
+ '@<(?P<tag>' . $tag . ') # <tag
151
+ (?P<attributes>\s[^>]+)? # attributes, if any
152
+ \s*> # >
153
+ (?P<contents>.*?) # tag contents
154
+ </(?P=tag)> # the closing </tag>
155
+ @xsi';
156
+ }
157
+
158
+ $attribute_pattern =
159
+ '@
160
+ (?P<name>\w+) # attribute name
161
+ \s*=\s*
162
+ (
163
+ (?P<quote>[\"\'])(?P<value_quoted>.*?)(?P=quote) # a quoted value
164
+ | # or
165
+ (?P<value_unquoted>[^\s"\']+?)(?:\s+|$) # an unquoted value (terminated by whitespace or EOF)
166
+ )
167
+ @xsi';
168
+
169
+ //Find all tags
170
+ if ( ! preg_match_all( $tag_pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ) {
171
+ //Return an empty array if we didn't find anything
172
+ return array();
173
+ }
174
+
175
+ $tags = array();
176
+ foreach ( $matches as $match ) {
177
+
178
+ // Parse tag attributes, if any.
179
+ $attributes = array();
180
+ if ( ! empty( $match['attributes'][0] ) ) {
181
+
182
+ if ( preg_match_all( $attribute_pattern, $match['attributes'][0], $attribute_data, PREG_SET_ORDER ) ) {
183
+ //Turn the attribute data into a name->value array
184
+ foreach ( $attribute_data as $attr ) {
185
+ if( ! empty( $attr['value_quoted'] ) ) {
186
+ $value = $attr['value_quoted'];
187
+ } else if( ! empty( $attr['value_unquoted'] ) ) {
188
+ $value = $attr['value_unquoted'];
189
+ } else {
190
+ $value = '';
191
+ }
192
+
193
+ // Passing the value through html_entity_decode is handy when you want
194
+ // to extract link URLs or something like that. You might want to remove
195
+ // or modify this call if it doesn't fit your situation.
196
+ $value = html_entity_decode( $value, ENT_QUOTES, $charset );
197
+
198
+ $attributes[ $attr['name'] ] = $value;
199
+ }
200
+ }
201
+
202
+ }
203
+
204
+ $tag = array(
205
+ 'tag_name' => $match['tag'][0],
206
+ 'offset' => $match[0][1],
207
+ 'contents' => ! empty( $match['contents'] ) ? $match['contents'][0] : '', // Empty for self-closing tags.
208
+ 'attributes' => $attributes,
209
+ );
210
+ if ( $return_the_entire_tag ) {
211
+ $tag['full_tag'] = $match[0][0];
212
+ }
213
+
214
+ $tags[] = $tag;
215
+ }
216
+
217
+ return $tags;
218
+ }
219
+
220
+ /**
221
+ * Get the value of a cookie.
222
+ *
223
+ * @param string $cookie_name The name of the cookie to return.
224
+ * @param string $default_value Optional. If the cookie is not set, this value will be returned instead. Defaults to an empty string.
225
+ * @return mixed Either the value of the requested cookie, or $default_value.
226
+ */
227
+ static function get_cookie( $cookie_name, $default_value = '' ) {
228
+ if ( isset( $_COOKIE[$cookie_name] ) ) {
229
+ return $_COOKIE[$cookie_name];
230
+ } else {
231
+ return $default_value;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Format a time delta using a fuzzy format, e.g. '2 minutes ago', '2 days', etc.
237
+ *
238
+ * @param int $delta Time period in seconds.
239
+ * @param string $type Optional. The output template to use.
240
+ * @return string
241
+ */
242
+ static function fuzzy_delta( $delta, $template = 'default' ) {
243
+
244
+ $templates = array(
245
+ 'seconds' => array(
246
+ 'default' => _n_noop('%d second', '%d seconds'),
247
+ 'ago' => _n_noop('%d second ago', '%d seconds ago'),
248
+ ),
249
+ 'minutes' => array(
250
+ 'default' => _n_noop('%d minute', '%d minutes'),
251
+ 'ago' => _n_noop('%d minute ago', '%d minutes ago'),
252
+ ),
253
+ 'hours' => array(
254
+ 'default' => _n_noop('%d hour', '%d hours'),
255
+ 'ago' => _n_noop('%d hour ago', '%d hours ago'),
256
+ ),
257
+ 'days' => array(
258
+ 'default' => _n_noop('%d day', '%d days'),
259
+ 'ago' => _n_noop('%d day ago', '%d days ago'),
260
+ ),
261
+ 'months' => array(
262
+ 'default' => _n_noop('%d month', '%d months'),
263
+ 'ago' => _n_noop('%d month ago', '%d months ago'),
264
+ ),
265
+ );
266
+
267
+ if ( $delta < 1 ) {
268
+ $delta = 1;
269
+ }
270
+
271
+ if ( $delta < MINUTE_IN_SECONDS ) {
272
+ $units = 'seconds';
273
+ } elseif ( $delta < HOUR_IN_SECONDS ) {
274
+ $delta = intval( $delta / MINUTE_IN_SECONDS );
275
+ $units = 'minutes';
276
+ } elseif ( $delta < DAY_IN_SECONDS ) {
277
+ $delta = intval( $delta / HOUR_IN_SECONDS );
278
+ $units = 'hours';
279
+ } elseif ( $delta < MONTH_IN_SECONDS ) {
280
+ $delta = intval( $delta / DAY_IN_SECONDS );
281
+ $units = 'days';
282
+ } else {
283
+ $delta = intval( $delta / MONTH_IN_SECONDS );
284
+ $units = 'months';
285
+ }
286
+
287
+ return sprintf(
288
+ _n(
289
+ $templates[$units][$template][0],
290
+ $templates[$units][$template][1],
291
+ $delta,
292
+ 'broken-link-checker'
293
+ ),
294
+ $delta
295
+ );
296
+ }
297
+
298
+ /**
299
+ * Optimize the plugin's tables
300
+ *
301
+ * @return void
302
+ */
303
+ static function optimize_database(){
304
+ global $wpdb; /** @var wpdb $wpdb */
305
+
306
+ $wpdb->query( "OPTIMIZE TABLE {$wpdb->prefix}blc_links, {$wpdb->prefix}blc_instances, {$wpdb->prefix}blc_synch" );
307
+ }
308
+
309
+ /**
310
+ * Get the server's load averages.
311
+ *
312
+ * Returns an array with three samples - the 1 minute avg, the 5 minute avg, and the 15 minute avg.
313
+ *
314
+ * @param integer $cache How long the load averages may be cached, in seconds. Set to 0 to get maximally up-to-date data.
315
+ * @return array|null Array, or NULL if retrieving load data is impossible (e.g. when running on a Windows box).
316
+ */
317
+ static function get_server_load( $cache = 5 ) {
318
+ static $cached_load = null;
319
+ static $cached_when = 0;
320
+
321
+ if ( ! empty( $cache ) && ((time() - $cached_when) <= $cache) ) {
322
+ return $cached_load;
323
+ }
324
+
325
+ $load = null;
326
+
327
+ if ( function_exists( 'sys_getloadavg' ) ) {
328
+ $load = sys_getloadavg();
329
+ } else {
330
+ $loadavg_file = '/proc/loadavg';
331
+ if ( @is_readable( $loadavg_file ) ) {
332
+ $load = explode( ' ', file_get_contents( $loadavg_file ) );
333
+ $load = array_map( 'floatval', $load );
334
+ }
335
+ }
336
+
337
+ $cached_load = $load;
338
+ $cached_when = time();
339
+ return $load;
340
+ }
341
+
342
+ /**
343
+ * Convert an internationalized domain name or URL to ASCII-compatible encoding.
344
+ *
345
+ * @param string $url Either a domain name or a complete URL.
346
+ * @param string $charset The character encoding of the $url parameter. Defaults to the encoding set in Settings -> Reading.
347
+ * @return string
348
+ */
349
+ static function idn_to_ascii( $url, $charset = '' ) {
350
+ $idn = blcUtility::get_idna_converter();
351
+ if ( $idn != null ) {
352
+ if ( empty( $charset ) ) {
353
+ $charset = get_bloginfo( 'charset' );
354
+ }
355
+
356
+ // Encode only the host.
357
+ if ( preg_match( '@(\w+:/*)?([^/:]+)(.*$)?@s', $url, $matches ) ) {
358
+ $host = $matches[2];
359
+ if ( ( strtoupper( $charset ) != 'UTF-8') && ( strtoupper( $charset ) != 'UTF8') ) {
360
+ $host = encode_utf8( $host, $charset, true );
361
+ }
362
+ $host = $idn->encode( $host );
363
+ $url = $matches[1] . $host . $matches[3];
364
+ }
365
+ }
366
+
367
+ return $url;
368
+ }
369
+
370
+ /**
371
+ * Convert an internationalized domain name (or URL) from ASCII-compatible encoding to UTF8.
372
+ *
373
+ * @param string $url
374
+ * @return string
375
+ */
376
+ static function idn_to_utf8( $url ) {
377
+ $idn = blcUtility::get_idna_converter();
378
+ if ( null !== $idn ) {
379
+ $url = $idn->decode( $url );
380
+ }
381
+
382
+ return $url;
383
+ }
384
+
385
+ /**
386
+ * Get an instance of idna_converter
387
+ *
388
+ * @return idna_convert|null Either an instance of IDNA converter, or NULL if the converter class is not available
389
+ */
390
+ static function get_idna_converter() {
391
+ static $idn = null;
392
+ if ( ( null === $idn ) && class_exists( 'idna_convert' ) ) {
393
+ $idn = new idna_convert();
394
+ }
395
+ return $idn;
396
+ }
397
+
398
+ /**
399
+ * Generate a numeric hash from a string. The result will be constrained to the specified interval.
400
+ *
401
+ * @static
402
+ * @param string $input
403
+ * @param int $min
404
+ * @param int $max
405
+ * @return float
406
+ */
407
+ public static function constrained_hash( $input, $min = 0, $max = 1 ) {
408
+ $bytes_to_use = 3;
409
+ $md5_char_count = 32;
410
+ $hash = substr( md5( $input ), $md5_char_count - $bytes_to_use * 2 );
411
+ $hash = intval( hexdec( $hash ) );
412
+ return $min + ( ( $max - $min ) * ( $hash / ( pow( 2, $bytes_to_use * 8 ) - 1) ) );
413
+ }
414
+
415
+ }//class
416
+
417
+ }//class_exists
includes/wp-mutex.php CHANGED
@@ -1,57 +1,57 @@
1
- <?php
2
-
3
- if ( !class_exists('WPMutex') ):
4
-
5
- class WPMutex {
6
- /**
7
- * Get an exclusive named lock.
8
- *
9
- * @param string $name
10
- * @param integer $timeout
11
- * @param bool $network_wide
12
- * @return bool
13
- */
14
- static function acquire($name, $timeout = 0, $network_wide = false){
15
- global $wpdb; /* @var wpdb $wpdb */
16
- if ( !$network_wide ){
17
- $name = WPMutex::_get_private_name($name);
18
- }
19
- $state = $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, %d)', $name, $timeout));
20
- return $state == 1;
21
- }
22
-
23
- /**
24
- * Release a named lock.
25
- *
26
- * @param string $name
27
- * @param bool $network_wide
28
- * @return bool
29
- */
30
- static function release($name, $network_wide = false){
31
- global $wpdb; /* @var wpdb $wpdb */
32
- if ( !$network_wide ){
33
- $name = WPMutex::_get_private_name($name);
34
- }
35
- $released = $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $name));
36
- return $released == 1;
37
- }
38
-
39
- /**
40
- * Given a generic lock name, create a new one that's unique to the current blog.
41
- *
42
- * @access private
43
- *
44
- * @param string $name
45
- * @return string
46
- */
47
- static function _get_private_name($name){
48
- global $current_blog;
49
- if ( function_exists('is_multisite') && is_multisite() && isset($current_blog->blog_id) ){
50
- $name .= '-blog-' . $current_blog->blog_id;
51
- }
52
- return $name;
53
- }
54
- }
55
-
56
- endif;
57
-
1
+ <?php
2
+
3
+ if ( !class_exists('WPMutex') ):
4
+
5
+ class WPMutex {
6
+ /**
7
+ * Get an exclusive named lock.
8
+ *
9
+ * @param string $name
10
+ * @param integer $timeout
11
+ * @param bool $network_wide
12
+ * @return bool
13
+ */
14
+ static function acquire($name, $timeout = 0, $network_wide = false){
15
+ global $wpdb; /* @var wpdb $wpdb */
16
+ if ( !$network_wide ){
17
+ $name = WPMutex::_get_private_name($name);
18
+ }
19
+ $state = $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, %d)', $name, $timeout));
20
+ return $state == 1;
21
+ }
22
+
23
+ /**
24
+ * Release a named lock.
25
+ *
26
+ * @param string $name
27
+ * @param bool $network_wide
28
+ * @return bool
29
+ */
30
+ static function release($name, $network_wide = false){
31
+ global $wpdb; /* @var wpdb $wpdb */
32
+ if ( !$network_wide ){
33
+ $name = WPMutex::_get_private_name($name);
34
+ }
35
+ $released = $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $name));
36
+ return $released == 1;
37
+ }
38
+
39
+ /**
40
+ * Given a generic lock name, create a new one that's unique to the current blog.
41
+ *
42
+ * @access private
43
+ *
44
+ * @param string $name
45
+ * @return string
46
+ */
47
+ static function _get_private_name($name){
48
+ global $current_blog;
49
+ if ( function_exists('is_multisite') && is_multisite() && isset($current_blog->blog_id) ){
50
+ $name .= '-blog-' . $current_blog->blog_id;
51
+ }
52
+ return $name;
53
+ }
54
+ }
55
+
56
+ endif;
57
+
js/jquery.cookie.js CHANGED
File without changes
js/sprintf.js CHANGED
@@ -1,55 +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
- }
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/alternative.broken-link-checker-it_IT.mo DELETED
Binary file
languages/alternative.broken-link-checker-it_IT.po DELETED
@@ -1,692 +0,0 @@
1
- # Broken Link Checker POT file
2
- # Copyright (C) 2009 Janis Elsts
3
- # This file is distributed under the same license as the BLC package.
4
- # Janis Elsts <whiteshadow@w-shadow.com>, 2009.
5
- #
6
- msgid ""
7
- msgstr ""
8
- "Project-Id-Version: broken-link-checker in italiano\n"
9
- "Report-Msgid-Bugs-To: whiteshadow@w-shadow.com\n"
10
- "POT-Creation-Date: 2009-10-28 22:36+0000\n"
11
- "PO-Revision-Date: 2009-11-04 14:55+0200\n"
12
- "Last-Translator: Gaianorm <gaianorm@gmail.com>\n"
13
- "Language-Team: Gaianorm <gaianorm@gmail.com>\n"
14
- "Language-Team: Gaianorm | http://www.luxemozione.com/ <gaianorm@gmail.com>\n"
15
- "MIME-Version: 1.0\n"
16
- "Content-Type: text/plain; charset=utf-8\n"
17
- "Content-Transfer-Encoding: 8bit\n"
18
- "Plural-Forms: nplurals=2; plural=n != 1;\n"
19
- "X-Poedit-Language: Italian\n"
20
- "X-Poedit-Country: ITALY\n"
21
- "X-Poedit-SourceCharset: utf-8\n"
22
-
23
- #: core.php:132
24
- #: core.php:1284
25
- msgid "Loading..."
26
- msgstr "Caricamento..."
27
-
28
- #: core.php:155
29
- #: core.php:569
30
- msgid "[ Network error ]"
31
- msgstr "[ Errore di rete ]"
32
-
33
- #: core.php:180
34
- msgid "Automatically expand the widget if broken links have been detected"
35
- msgstr "Espandi automaticamenti il widget se vengono rilevati link non validi"
36
-
37
- #: core.php:359
38
- #: core.php:368
39
- #: core.php:386
40
- #: core.php:400
41
- #: core.php:907
42
- #, php-format
43
- msgid "Database error : %s"
44
- msgstr "Errore nel database: %s"
45
-
46
- #: core.php:423
47
- msgid "Link Checker Settings"
48
- msgstr "Link Checker Impostazioni"
49
-
50
- #: core.php:424
51
- msgid "Link Checker"
52
- msgstr ""
53
-
54
- #: core.php:435
55
- msgid "View Broken Links"
56
- msgstr "Mostra i link non validi"
57
-
58
- #: core.php:436
59
- #: core.php:833
60
- msgid "Broken Links"
61
- msgstr ""
62
-
63
- #: core.php:453
64
- msgid "Settings"
65
- msgstr "Impostazioni"
66
-
67
- #: core.php:533
68
- msgid "Broken Link Checker Options"
69
- msgstr "Opzioni per i link non validi"
70
-
71
- #: core.php:546
72
- msgid "Status"
73
- msgstr "Stato"
74
-
75
- #: core.php:548
76
- #: core.php:771
77
- msgid "Show debug info"
78
- msgstr "Mostra le info di debug"
79
-
80
- #: core.php:582
81
- msgid "Re-check all pages"
82
- msgstr "Ricontrolla tutte le pagine"
83
-
84
- #: core.php:606
85
- msgid "Check each link"
86
- msgstr "Controlla i link "
87
-
88
- #: core.php:611
89
- #, php-format
90
- msgid "Every %s hours"
91
- msgstr "Ogni %s ore"
92
-
93
- #: core.php:620
94
- msgid "Existing links will be checked this often. New links will usually be checked ASAP."
95
- msgstr "I link esistenti saranno ricontrollati nell'intervallo di tempo previsto. I nuovi link saranno controllati al più presto"
96
-
97
- #: core.php:627
98
- msgid "Broken link CSS"
99
- msgstr "CSS dei link non validi"
100
-
101
- #: core.php:632
102
- msgid "Apply <em>class=\"broken_link\"</em> to broken links"
103
- msgstr "Applica <em>class=\"broken_link\"</em> ai link non validi"
104
-
105
- #: core.php:644
106
- msgid "Exclusion list"
107
- msgstr "Lista esclusioni"
108
-
109
- #: core.php:645
110
- msgid "Don't check links where the URL contains any of these words (one per line) :"
111
- msgstr "Non controllare i link quando gli URL contengono una delle seguenti parole (una per riga)"
112
-
113
- #: core.php:655
114
- msgid "Custom fields"
115
- msgstr "Area personalizzata"
116
-
117
- #: core.php:656
118
- msgid "Check URLs entered in these custom fields (one per line) :"
119
- msgstr "Controlla gli url inseriti nell'area qua sotto (uno per riga)"
120
-
121
- #: core.php:667
122
- msgid "Advanced"
123
- msgstr "Avanzate"
124
-
125
- #: core.php:673
126
- msgid "Timeout"
127
- msgstr ""
128
-
129
- #: core.php:679
130
- #: core.php:735
131
- #, php-format
132
- msgid "%s seconds"
133
- msgstr "%s secondi"
134
-
135
- #: core.php:688
136
- msgid "Links that take longer than this to load will be marked as broken."
137
- msgstr "I link che verranno caricati in un tempo superiore a quello indicato verranno segnalati come non validi"
138
-
139
- #: core.php:697
140
- msgid "Custom temporary directory"
141
- msgstr "Directory temporanea personalizzata"
142
-
143
- #: core.php:706
144
- #: core.php:2047
145
- #: core.php:2088
146
- msgid "OK"
147
- msgstr ""
148
-
149
- #: core.php:709
150
- msgid "Error : This directory isn't writable by PHP."
151
- msgstr "Errore: la directory non può essere modifcata da PHP"
152
-
153
- #: core.php:714
154
- msgid "Error : This directory doesn't exist."
155
- msgstr "Errore: La directory non esiste"
156
-
157
- #: core.php:722
158
- msgid "Set this field if you want the plugin to use a custom directory for its lockfiles. Otherwise, leave it blank."
159
- msgstr "Inserisci un valore in questo spazio se desideri utilizzare un percorso personalizzato per i lockfiles. Altrimenti lascialo in bianco"
160
-
161
- #: core.php:729
162
- msgid "Max. execution time"
163
- msgstr "Tempo massimo di esecuzione"
164
-
165
- #: core.php:746
166
- 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."
167
- msgstr "Il plugin analizzerà periodicamente in bakgroud i post alla ricerca di link non validi, controllare gli URL e altre operazioni che potrebbero consumare risorse. Qua sopra puoi indicare il periodo massimo in cui le operazioni in background potranno essere eseguite prima di essere terminate."
168
-
169
- #: core.php:756
170
- msgid "Save Changes"
171
- msgstr "Salva le modifiche"
172
-
173
- #: core.php:769
174
- msgid "Hide debug info"
175
- msgstr "Nascondi le info di Debug"
176
-
177
- #: core.php:832
178
- msgid "Broken"
179
- msgstr "Non validi"
180
-
181
- #: core.php:834
182
- msgid "No broken links found"
183
- msgstr "Nessun link non valido trovato"
184
-
185
- #: core.php:838
186
- msgid "Redirects"
187
- msgstr "Redirects"
188
-
189
- #: core.php:839
190
- msgid "Redirected Links"
191
- msgstr "Link reindirizzati"
192
-
193
- #: core.php:840
194
- msgid "No redirects found"
195
- msgstr "Nessun reindirizzamento trovato"
196
-
197
- #: core.php:845
198
- msgid "All"
199
- msgstr "Tutti"
200
-
201
- #: core.php:846
202
- msgid "Detected Links"
203
- msgstr "Link trovati"
204
-
205
- #: core.php:847
206
- msgid "No links found (yet)"
207
- msgstr "Nessun link trovato (per ora)"
208
-
209
- #: core.php:966
210
- msgid "&laquo;"
211
- msgstr ""
212
-
213
- #: core.php:967
214
- msgid "&raquo;"
215
- msgstr ""
216
-
217
- #: core.php:974
218
- #: core.php:1149
219
- #, php-format
220
- msgid "Displaying %s&#8211;%s of <span class=\"current-link-count\">%s</span>"
221
- msgstr "Mostra %s&#8211;%s di <span class=\"current-link-count\">%s</span>"
222
-
223
- #: core.php:1025
224
- #: core.php:1031
225
- msgid "Edit this post"
226
- msgstr "Modifica il post"
227
-
228
- #: core.php:1031
229
- #: core.php:1046
230
- msgid "Edit"
231
- msgstr "Modifica"
232
-
233
- #: core.php:1032
234
- msgid "Delete this post"
235
- msgstr "Cancella il post"
236
-
237
- #: core.php:1032
238
- #, php-format
239
- msgid ""
240
- "You are about to delete this post '%s'\n"
241
- " 'Cancel' to stop, 'OK' to delete."
242
- msgstr ""
243
- "Si sta per cancellare il post '%s'\n"
244
- " premi 'Cancelal' per interrompere, 'OK' per cancellare."
245
-
246
- #: core.php:1032
247
- #: core.php:1047
248
- msgid "Delete"
249
- msgstr "Cancella"
250
-
251
- #: core.php:1034
252
- #, php-format
253
- msgid "View \"%s\""
254
- msgstr "Mostra \"%s\""
255
-
256
- #: core.php:1034
257
- msgid "View"
258
- msgstr "Mostra"
259
-
260
- #: core.php:1041
261
- #: core.php:1046
262
- msgid "Edit this bookmark"
263
- msgstr "Modifica il segnalibro"
264
-
265
- #: core.php:1047
266
- #, php-format
267
- msgid ""
268
- "You are about to delete this link '%s'\n"
269
- " 'Cancel' to stop, 'OK' to delete."
270
- msgstr ""
271
- "Si sta per cancellare questo link '%s'\n"
272
- " 'Cancella' per fermarsi, 'OK' per cancellare."
273
-
274
- #: core.php:1056
275
- msgid "[An orphaned link! This is a bug.]"
276
- msgstr "[An orphaned link! This is a bug.]"
277
-
278
- #: core.php:1070
279
- msgid "Image"
280
- msgstr "Immagine"
281
-
282
- #: core.php:1081
283
- msgid "Custom field"
284
- msgstr "Area personalizzata"
285
-
286
- #: core.php:1089
287
- msgid "Bookmark"
288
- msgstr "Segnalibro"
289
-
290
- #: core.php:1104
291
- msgid "Show more info about this link"
292
- msgstr "Mosta maggiori info su questo link"
293
-
294
- #: core.php:1104
295
- #: core.php:2381
296
- msgid "Details"
297
- msgstr "Dettagli"
298
-
299
- #: core.php:1106
300
- msgid "Remove this link from all posts"
301
- msgstr "Rimuovi questo link da tutti i post"
302
-
303
- #: core.php:1107
304
- #: core.php:1371
305
- msgid "Unlink"
306
- msgstr ""
307
-
308
- #: core.php:1110
309
- #: core.php:1401
310
- #: core.php:1412
311
- msgid "Excluded"
312
- msgstr "Escludi"
313
-
314
- #: core.php:1112
315
- msgid "Add this URL to the exclusion list"
316
- msgstr "Aggiungi questo url alla lista delle esclusioni"
317
-
318
- #: core.php:1113
319
- #: core.php:1415
320
- msgid "Exclude"
321
- msgstr "Escludi"
322
-
323
- #: core.php:1116
324
- msgid "Edit link URL"
325
- msgstr "Modifica il link "
326
-
327
- #: core.php:1116
328
- #: core.php:1340
329
- msgid "Edit URL"
330
- msgstr "Modifica l'URL"
331
-
332
- #: core.php:1122
333
- msgid "Cancel URL editing"
334
- msgstr "Cancella la modifica dell'URL"
335
-
336
- #: core.php:1122
337
- msgid "Cancel"
338
- msgstr "Elimina"
339
-
340
- #: core.php:1133
341
- msgid "Remove this link from the list of broken links and mark it as valid"
342
- msgstr "Rimuovi questo link dalla lista dei non validi e segnalalo come valido"
343
-
344
- #: core.php:1135
345
- #: core.php:1204
346
- msgid "Discard"
347
- msgstr "Scarta"
348
-
349
- #: core.php:1180
350
- #: core.php:1347
351
- #: core.php:1384
352
- msgid "Wait..."
353
- msgstr "Attendi..."
354
-
355
- #: core.php:1238
356
- msgid "Save URL"
357
- msgstr "Salva l'URL"
358
-
359
- #: core.php:1248
360
- msgid "Saving changes..."
361
- msgstr "Salva le modifiche..."
362
-
363
- #: core.php:1437
364
- msgid "Log"
365
- msgstr ""
366
-
367
- #: core.php:1445
368
- msgid "Post published on"
369
- msgstr "Post pubblicato il"
370
-
371
- #: core.php:1450
372
- msgid "Link last checked"
373
- msgstr "Ultimo controllo dei link"
374
-
375
- #: core.php:1454
376
- msgid "Never"
377
- msgstr "Mai"
378
-
379
- #: core.php:1460
380
- msgid "HTTP code"
381
- msgstr "codice HTTP"
382
-
383
- #: core.php:1465
384
- msgid "Response time"
385
- msgstr "Tempo di risposta"
386
-
387
- #: core.php:1467
388
- #, php-format
389
- msgid "%2.3f seconds"
390
- msgstr "%2.3f secondi"
391
-
392
- #: core.php:1470
393
- msgid "Final URL"
394
- msgstr "URL di arrivo"
395
-
396
- #: core.php:1475
397
- msgid "Redirect count"
398
- msgstr "Conteggio dei redirect"
399
-
400
- #: core.php:1480
401
- msgid "Instance count"
402
- msgstr "Conteggio Istance"
403
-
404
- #: core.php:1489
405
- #, php-format
406
- msgid "This link has failed %d time."
407
- msgid_plural "This link has failed %d times."
408
- msgstr[0] "Il link ha fallito %d volta."
409
- msgstr[1] "Il link ha fallito %d volte."
410
-
411
- #: core.php:1879
412
- #: core.php:2206
413
- msgid "This link wasn't checked because a matching keyword was found on your exclusion list."
414
- msgstr "Questo link non è stato controllato perchè contenente una parola inserita nella lista delle esclusioni"
415
-
416
- #: core.php:1922
417
- #, php-format
418
- msgid "Found %d broken link"
419
- msgid_plural "Found %d broken links"
420
- msgstr[0] "Trovato %d link non valido"
421
- msgstr[1] "Trovati %d link non validi"
422
-
423
- #: core.php:1928
424
- msgid "No broken links found."
425
- msgstr "Nessun link non valido trovato"
426
-
427
- #: core.php:1935
428
- #, php-format
429
- msgid "%d URL in the work queue"
430
- msgid_plural "%d URLs in the work queue"
431
- msgstr[0] "%d URL nella coda attiva"
432
- msgstr[1] "%d URL nella coda attiva"
433
-
434
- #: core.php:1938
435
- msgid "No URLs in the work queue."
436
- msgstr "Nessun URL nella coda attiva"
437
-
438
- #: core.php:1944
439
- #, php-format
440
- msgid "Detected %d unique URL"
441
- msgid_plural "Detected %d unique URLs"
442
- msgstr[0] "Rilevato %d URL unico"
443
- msgstr[1] "Rilevati %d URL unici"
444
-
445
- #: core.php:1945
446
- #, php-format
447
- msgid "in %d link"
448
- msgid_plural "in %d links"
449
- msgstr[0] "in %d link"
450
- msgstr[1] "in %d link"
451
-
452
- #: core.php:1950
453
- msgid "and still searching..."
454
- msgstr "e continua a cercare..."
455
-
456
- #: core.php:1956
457
- msgid "Searching your blog for links..."
458
- msgstr "Ricerca nel blog dei link..."
459
-
460
- #: core.php:1958
461
- msgid "No links detected."
462
- msgstr "Nessun link rilevato"
463
-
464
- #: core.php:2027
465
- #: core.php:2059
466
- #: core.php:2102
467
- #: core.php:2183
468
- msgid "You're not allowed to do that!"
469
- msgstr "Non sei abilitato ad eseguire questa operazione"
470
-
471
- #: core.php:2035
472
- #: core.php:2069
473
- #: core.php:2112
474
- #: core.php:2193
475
- #, php-format
476
- msgid "Oops, I can't find the link %d"
477
- msgstr "Oops, non posso trovare il link %d"
478
-
479
- #: core.php:2043
480
- msgid "This link was manually marked as working by the user."
481
- msgstr "Questo link è stato segnalato come funzionante dall'utente"
482
-
483
- #: core.php:2049
484
- msgid "Oops, couldn't modify the link!"
485
- msgstr "Oops, impossibile modificare il link!"
486
-
487
- #: core.php:2052
488
- #: core.php:2129
489
- msgid "Error : link_id not specified"
490
- msgstr "Error : link_id non specificato"
491
-
492
- #: core.php:2076
493
- msgid "Oops, the new URL is invalid!"
494
- msgstr "Oops, il nuovo URL non è valido!"
495
-
496
- #: core.php:2085
497
- msgid "An unexpected error occured!"
498
- msgstr "Si è manifestato un errore inatteso"
499
-
500
- #: core.php:2094
501
- msgid "Error : link_id or new_url not specified"
502
- msgstr "Errore: link_id o new_url non specificati"
503
-
504
- #: core.php:2119
505
- #, php-format
506
- msgid "URL %s was removed."
507
- msgstr "URL %s è stato rimosso"
508
-
509
- #: core.php:2123
510
- msgid "The plugin failed to remove the link."
511
- msgstr "Il plugin non è riuscito a rimuovere il link."
512
-
513
- #: core.php:2138
514
- msgid "You don't have sufficient privileges to access this information!"
515
- msgstr "Non hai abbastanza privilegi per poter accedere a questa informazione!"
516
-
517
- #: core.php:2151
518
- msgid "Error : link ID not specified"
519
- msgstr "Errore: link ID non specificato"
520
-
521
- #: core.php:2175
522
- #, php-format
523
- msgid "Failed to load link details (%s)"
524
- msgstr "Impossibile caricare i dettagli del link (%s)"
525
-
526
- #: core.php:2213
527
- #, php-format
528
- msgid "URL %s added to the exclusion list"
529
- msgstr "URL %s aggiunto alla lista delle esclusioni"
530
-
531
- #: core.php:2217
532
- msgid "Link ID not specified"
533
- msgstr "Link ID non specificato"
534
-
535
- #: core.php:2367
536
- #, php-format
537
- msgid "The current temporary directory is not accessible; please <a href=\"%s\">set a different one</a>."
538
- msgstr "La directory temporanea attualmente selezionata non è accessibile <a href=\"%s\">selezionane una differente</a>."
539
-
540
- #: core.php:2372
541
- #, php-format
542
- msgid "Please make the directory <code>%1$s</code> writable by plugins or <a href=\"%2$s\">set a custom temporary directory</a>."
543
- msgstr "Rendi la directory <code>%1$s</code> modificabile dal plugin o <a href=\"%2$s\">seleziona una directory temporanea</a>."
544
-
545
- #: core.php:2379
546
- msgid "Broken Link Checker can't create a lockfile."
547
- msgstr "Broken Link Checker non riesce a creare il lockfile."
548
-
549
- #: core.php:2384
550
- msgid ""
551
- "The plugin uses a file-based locking mechanism to ensure that only one instance of the\r\n"
552
- "\t\t\t\tresource-heavy link checking algorithm is running at any given time. Unfortunately, \r\n"
553
- "\t\t\t\tBLC can't find a writable directory where it could store the lockfile - it failed to \r\n"
554
- "\t\t\t\tdetect the location of your server's temporary directory, and the plugin's own directory\r\n"
555
- "\t\t\t\tisn't writable by PHP. To fix this problem, please make the plugin's directory writable\r\n"
556
- "\t\t\t\tor enter a specify a custom temporary directory in the plugin's settings."
557
- msgstr ""
558
- "Il plugin usa un file basato sul meccanismo di locking per assicurarsi che ogni istanza dello\r\n"
559
- "\t\t\t\algoritmo di controllo tresource-heavy link funzioni ad ogni richiesta . Sfortunamente, \r\n"
560
- "\t\t\t\tBLC non riesce a trovare una directory in cui inserire il lockfile o è fallito \r\n"
561
- "\t\t\t\til rilevamento della dyrectory temporanea sul tuo server o la directory del plugin \r\n"
562
- "\t\t\t\tnon è modificabile da PHP. Per risolvere il problemadevi rendere la directory modificabile\r\n"
563
- "\t\t\t\to inserire una directory personalizzata nelle impostazioni del plugin."
564
-
565
- #: core.php:2409
566
- msgid "PHP version"
567
- msgstr "Versione PHP"
568
-
569
- #: core.php:2415
570
- msgid "MySQL version"
571
- msgstr "Versione MySQL"
572
-
573
- #: core.php:2428
574
- msgid "You have an old version of CURL. Redirect detection may not work properly."
575
- msgstr "Se hai una vecchia versione di CURL il rilevamento dei redirect potrebbe non funzionare correttamente."
576
-
577
- #: core.php:2440
578
- #: core.php:2456
579
- #: core.php:2461
580
- msgid "Not installed"
581
- msgstr "Non installato"
582
-
583
- #: core.php:2443
584
- msgid "CURL version"
585
- msgstr "Versione CURL"
586
-
587
- #: core.php:2449
588
- msgid "Installed"
589
- msgstr "Installato"
590
-
591
- #: core.php:2462
592
- msgid "You must have either CURL or Snoopy installed for the plugin to work!"
593
- msgstr "Devi avere sia CURL che Snoopy installati per il corretto funzionamento del plugin!"
594
-
595
- #: core.php:2473
596
- msgid "On"
597
- msgstr ""
598
-
599
- #: core.php:2474
600
- msgid "Redirects may be detected as broken links when safe_mode is on."
601
- msgstr "Potrebbero essere rilevati dei reindirizzamenti come link non validi se safe_mode è on"
602
-
603
- #: core.php:2479
604
- #: core.php:2493
605
- msgid "Off"
606
- msgstr ""
607
-
608
- #: core.php:2487
609
- #, php-format
610
- msgid "On ( %s )"
611
- msgstr ""
612
-
613
- #: core.php:2488
614
- msgid "Redirects may be detected as broken links when open_basedir is on."
615
- msgstr "I reindirizzamenti potrebbero essere rilevati come link non validi se open_basedir è on."
616
-
617
- #: core.php:2507
618
- msgid "Can't create a lockfile. Please specify a custom temporary directory."
619
- msgstr "Impossibile creare il lockfile. Seleziona una directory temporanea personalizzata."
620
-
621
- #: link-classes.php:212
622
- msgid "First try : 0 (No response)"
623
- msgstr "Primo tentativo : 0 (Nessua risposta)"
624
-
625
- #: link-classes.php:214
626
- #, php-format
627
- msgid "First try : %d"
628
- msgstr "Primo tentativo : %d"
629
-
630
- #: link-classes.php:222
631
- msgid "Trying a second time with different settings..."
632
- msgstr "Prova una seconda volta con impostazioni differenti..."
633
-
634
- #: link-classes.php:237
635
- msgid "Second try : 0 (No response)"
636
- msgstr "Secondo tentativo : 0 (Nessuna risposta)"
637
-
638
- #: link-classes.php:239
639
- #, php-format
640
- msgid "Second try : %d"
641
- msgstr "Secondo tentativo : %d"
642
-
643
- #: link-classes.php:265
644
- msgid "Using Snoopy"
645
- msgstr "Usando Snoopy"
646
-
647
- #: link-classes.php:285
648
- msgid "Request timed out."
649
- msgstr "Richiesta scaduta"
650
-
651
- #: link-classes.php:304
652
- msgid "Link is valid."
653
- msgstr "Il link è valido"
654
-
655
- #: link-classes.php:309
656
- msgid "Link is broken."
657
- msgstr "Il link non è valido"
658
-
659
- #: link-classes.php:313
660
- msgid "Most likely the connection timed out or the domain doesn't exist."
661
- msgstr "Molto probabilmente non possibile stabilire una connessione o il dominio non esiste."
662
-
663
- #: link-classes.php:354
664
- #, php-format
665
- msgid "Error adding link %s : %s"
666
- msgstr "Errore aggiungendo il link %s : %s"
667
-
668
- #: link-classes.php:374
669
- #, php-format
670
- msgid "Error updating link %d : %s"
671
- msgstr "Errore di aggiornamento del link %d : %s"
672
-
673
- #. Plugin Name of an extension
674
- msgid "Broken Link Checker"
675
- msgstr ""
676
-
677
- #. Plugin URI of an extension
678
- msgid "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
679
- msgstr ""
680
-
681
- #. Description of an extension
682
- msgid "Checks your posts for broken links and missing images and notifies you on the dashboard if any are found."
683
- msgstr "Controlla i post alla ricerca di link non validi e immagini mancanti e notifica nella bacheca se vengono trovati."
684
-
685
- #. Author of an extension
686
- msgid "Janis Elsts"
687
- msgstr ""
688
-
689
- #. Author URI of an extension
690
- msgid "http://w-shadow.com/blog/"
691
- msgstr ""
692
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/broken-link-checker-ar_AR.mo DELETED
Binary file
languages/broken-link-checker-ar_AR.po DELETED
@@ -1,1668 +0,0 @@
1
- # Copyright (C) 2012 Broken Link Checker
2
- # This file is distributed under the same license as the Broken Link Checker package.
3
- msgid ""
4
- msgstr ""
5
- "Project-Id-Version: Broken Link Checker 1.6.2\n"
6
- "Report-Msgid-Bugs-To: http://wordpress.org/tag/broken-link-checker\n"
7
- "POT-Creation-Date: 2012-07-11 15:57:29+00:00\n"
8
- "MIME-Version: 1.0\n"
9
- "Content-Type: text/plain; charset=UTF-8\n"
10
- "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2013-07-09 12:53+0200\n"
12
- "Last-Translator: Janis Elsts <whiteshadow@w-shadow.com>\n"
13
- "Language-Team: Yaser Maadan\n"
14
- "X-Generator: Poedit 1.5.5\n"
15
- "Language: Arabic\n"
16
- "Plural-Forms: nplurals=2; plural=n != 1;\n"
17
-
18
- # Done By Yaser Maadan
19
- # Friday, 21 Dec, 2012
20
- #: core/core.php:151 includes/admin/links-page-js.php:37
21
- msgid "Loading..."
22
- msgstr "تحميل..."
23
-
24
- #: core/core.php:175 includes/admin/options-page-js.php:18
25
- msgid "[ Network error ]"
26
- msgstr "[ خطأ في الشبكة ]"
27
-
28
- #: core/core.php:202
29
- msgid "Automatically expand the widget if broken links have been detected"
30
- msgstr "توسيع المربع تلقائياً في حال استكشاف روابط معطوبة"
31
-
32
- #: core/core.php:292
33
- msgid "Link Checker Settings"
34
- msgstr "إعدادات الإضافة"
35
-
36
- #: core/core.php:293
37
- msgid "Link Checker"
38
- msgstr "فاحص الروابط"
39
-
40
- #: core/core.php:298 includes/link-query.php:27
41
- msgid "Broken Links"
42
- msgstr "اللنكات المعطوبة"
43
-
44
- #: core/core.php:314
45
- msgid "View Broken Links"
46
- msgstr "عرض اللنكات المعطوبة"
47
-
48
- #: core/core.php:329
49
- msgid "Feedback"
50
- msgstr "التغذية الراجعة"
51
-
52
- #: core/core.php:368
53
- msgid "Go to Broken Links"
54
- msgstr "الذهاب الى الروابط المعطوبة"
55
-
56
- #: core/core.php:397
57
- msgid "Settings"
58
- msgstr "الإعدادات"
59
-
60
- #: core/core.php:554
61
- msgid "Settings saved."
62
- msgstr "تم حفظ الإعدادت"
63
-
64
- #: core/core.php:560
65
- msgid "Thank you for your donation!"
66
- msgstr "شكراً لدعمك!"
67
-
68
- #: core/core.php:568
69
- msgid "Complete site recheck started."
70
- msgstr "الفحص الكامل للموقع بدأ."
71
-
72
- #: core/core.php:577
73
- msgid "Details"
74
- msgstr "التفاصيل"
75
-
76
- #: core/core.php:591
77
- msgid "General"
78
- msgstr "عام"
79
-
80
- #: core/core.php:592
81
- msgid "Look For Links In"
82
- msgstr "فحص الروابط في"
83
-
84
- #: core/core.php:593
85
- msgid "Which Links To Check"
86
- msgstr "أيّ الروابط المُراد فحصها"
87
-
88
- #: core/core.php:594
89
- msgid "Protocols & APIs"
90
- msgstr "البروتوكولات والآيه بي آي"
91
-
92
- #: core/core.php:595
93
- msgid "Advanced"
94
- msgstr "متقدم"
95
-
96
- #: core/core.php:610
97
- msgid "Broken Link Checker Options"
98
- msgstr "إعدادات فاحص الروابط المعطوبة"
99
-
100
- #: core/core.php:652 includes/admin/table-printer.php:197
101
- msgid "Status"
102
- msgstr "الحالة"
103
-
104
- #: core/core.php:654 includes/admin/options-page-js.php:56
105
- msgid "Show debug info"
106
- msgstr "عرض معلومات المطورين"
107
-
108
- #: core/core.php:682
109
- msgid "Check each link"
110
- msgstr "فحص كل رابط"
111
-
112
- #: core/core.php:687
113
- msgid "Every %s hours"
114
- msgstr "كل %s ساعة"
115
-
116
- #: core/core.php:696
117
- msgid ""
118
- "Existing links will be checked this often. New links will usually be checked "
119
- "ASAP."
120
- msgstr ""
121
- "الروابط الحالية سيتم فحصها الآن. الروابط الجديدة سيتم فحصها بأقرب فرصة ممكنة."
122
-
123
- #: core/core.php:703
124
- msgid "E-mail notifications"
125
- msgstr "التنبيهات على البريد الإلكتروني"
126
-
127
- #: core/core.php:709
128
- msgid "Send me e-mail notifications about newly detected broken links"
129
- msgstr ""
130
- "أرسل لي بريد إلكتروني تنبيهي عن الروابط المعطوبة الجديدة التي يتم اكتشافها"
131
-
132
- #: core/core.php:717
133
- msgid "Send authors e-mail notifications about broken links in their posts"
134
- msgstr "أرسل لكتّاب التدوينات والمُحررين تنبيه بريدي بالروابط الجديدة المعطوبة"
135
-
136
- #: core/core.php:724
137
- msgid "Link tweaks"
138
- msgstr "إعدادات الروابط"
139
-
140
- #: core/core.php:730
141
- msgid "Apply custom formatting to broken links"
142
- msgstr "تطبيق التهيئة الخاصة للروابط المعطوبة"
143
-
144
- #: core/core.php:734 core/core.php:765
145
- msgid "Edit CSS"
146
- msgstr "تحرير CSS"
147
-
148
- #: core/core.php:750
149
- msgid "Example : Lorem ipsum <a %s>broken link</a>, dolor sit amet."
150
- msgstr "Example : Lorem ipsum <a %s>broken link</a>, dolor sit amet."
151
-
152
- #: core/core.php:753 core/core.php:784
153
- msgid "Click \"Save Changes\" to update example output."
154
- msgstr "اضغط \"حفظ التغييرات\" لتحديث المثال."
155
-
156
- #: core/core.php:761
157
- msgid "Apply custom formatting to removed links"
158
- msgstr "طبّق التهيئة المُخصصة لإزالة الروابط"
159
-
160
- #: core/core.php:781
161
- msgid "Example : Lorem ipsum <span %s>removed link</span>, dolor sit amet."
162
- msgstr "مثال: Lorem ipsum <span %s>removed link</span>, dolor sit amet."
163
-
164
- #: core/core.php:794
165
- msgid "Stop search engines from following broken links"
166
- msgstr "إيقاف محركات البحث من تتبع الروابط المعطوبة"
167
-
168
- #: core/core.php:811
169
- msgid "Look for links in"
170
- msgstr "العثور على روابط في"
171
-
172
- #: core/core.php:822
173
- msgid "Post statuses"
174
- msgstr "حالات التدوينة"
175
-
176
- #: core/core.php:855
177
- msgid "Link types"
178
- msgstr "أنواع الروابط"
179
-
180
- #: core/core.php:861
181
- msgid "Error : All link parsers missing!"
182
- msgstr "خطأ : كل عبارات الرابط مفقودة!"
183
-
184
- #: core/core.php:868
185
- msgid "Exclusion list"
186
- msgstr "قائمة الإستبعاد"
187
-
188
- #: core/core.php:869
189
- msgid ""
190
- "Don't check links where the URL contains any of these words (one per line) :"
191
- msgstr ""
192
- "لا تفحص الروابط عندما تحتوي تلك الروابط على إحدى الكلمات التالية (كلمة في كل "
193
- "سطر) :"
194
-
195
- #: core/core.php:887
196
- msgid "Check links using"
197
- msgstr "فحص الروابط بإستخدام"
198
-
199
- #: core/core.php:906 includes/links.php:856
200
- msgid "Timeout"
201
- msgstr "المهلة الزمنيّة"
202
-
203
- #: core/core.php:912 core/core.php:958 core/core.php:2801
204
- msgid "%s seconds"
205
- msgstr "%s ثانية"
206
-
207
- #: core/core.php:921
208
- msgid "Links that take longer than this to load will be marked as broken."
209
- msgstr "الروابط التي تأخذ زمن طويل للتحميل سوف تُعتبر معطوبة."
210
-
211
- #: core/core.php:928
212
- msgid "Link monitor"
213
- msgstr "مراقبة رابط"
214
-
215
- #: core/core.php:936
216
- msgid "Run continuously while the Dashboard is open"
217
- msgstr "تشغيل بشكل مستمر عند فتح اللوحة الرئيسية للتحكم."
218
-
219
- #: core/core.php:944
220
- msgid "Run hourly in the background"
221
- msgstr "تشغيله كل ساعة بدون أن تشعر - في الخلفية"
222
-
223
- #: core/core.php:952
224
- msgid "Max. execution time"
225
- msgstr "أقصى زمن للتنفيذ"
226
-
227
- #: core/core.php:969
228
- msgid ""
229
- "The plugin works by periodically launching a background job that parses your "
230
- "posts for links, checks the discovered URLs, and performs other time-"
231
- "consuming tasks. Here you can set for how long, at most, the link monitor "
232
- "may run each time before stopping."
233
- msgstr ""
234
- "الإضافة تعمل على فترات في الخلفية لتقوم بفحص تدويناتك جيداً للبحث عن أي "
235
- "روابط، ثم تقوم بإستكشاف الروابط المعثور عليها . هُنا تستطيع تحديد الفترة "
236
- "المُراد جعل الإضافة تعمل في إطارها قبل التوقف."
237
-
238
- #: core/core.php:978
239
- msgid "Server load limit"
240
- msgstr "حدود تحمّل السيرفر"
241
-
242
- #: core/core.php:993
243
- msgid "Current load : %s"
244
- msgstr "الحمل الحالي : %s"
245
-
246
- #: core/core.php:998
247
- msgid ""
248
- "Link checking will be suspended if the average <a href=\"%s\">server load</"
249
- "a> rises above this number. Leave this field blank to disable load limiting."
250
- msgstr ""
251
- "فحص الروابط سوف يتوقف إذا معدل <a href=\"%s\">تحمّل السيرفر</a> فاق هذا "
252
- "الرقم. أترك هذا الحقل فارغاً لتعطيل حدود التحمّل."
253
-
254
- #: core/core.php:1007
255
- msgid "Not available"
256
- msgstr "غير متوفر"
257
-
258
- #: core/core.php:1009
259
- msgid ""
260
- "Load limiting only works on Linux-like systems where <code>/proc/loadavg</"
261
- "code> is present and accessible."
262
- msgstr ""
263
- "خاصيّة حدود التحمّل تعمل فقط في أنظمة لينوكس عندما يكون المسار <code>/proc/"
264
- "loadavg</code> موجود ومُتاح."
265
-
266
- #: core/core.php:1017
267
- msgid "Forced recheck"
268
- msgstr "إعادة فحص من البداية"
269
-
270
- #: core/core.php:1020
271
- msgid "Re-check all pages"
272
- msgstr "اعادة فحص كل الصفحات"
273
-
274
- #: core/core.php:1024
275
- msgid ""
276
- "The \"Nuclear Option\". Click this button to make the plugin empty its link "
277
- "database and recheck the entire site from scratch."
278
- msgstr ""
279
- "إنه \"الخيار النووي\". قم بالضغط على هذا الزر لجعل الإضافة تقوم بتفريغ كل "
280
- "الروابط المسجلة في قاعدة البيانات وتعيد فحص الموقع بالكامل من الصفر."
281
-
282
- #: core/core.php:1035
283
- msgid "Save Changes"
284
- msgstr "حفظ التعديلات"
285
-
286
- #: core/core.php:1086
287
- msgid "Configure"
288
- msgstr "إعداد"
289
-
290
- #: core/core.php:1168
291
- msgid "Check URLs entered in these custom fields (one per line) :"
292
- msgstr "افحص الروابط المُدخلة في هذا الحقل المُخصص (رابط في كل سطر) :"
293
-
294
- #: core/core.php:1296 core/core.php:1378 core/core.php:1410
295
- msgid "Database error : %s"
296
- msgstr "خطأ في قاعدة البيانات: %s"
297
-
298
- #: core/core.php:1360
299
- msgid "You must enter a filter name!"
300
- msgstr "يجب إدخال اسم الفلتر!"
301
-
302
- #: core/core.php:1364
303
- msgid "Invalid search query."
304
- msgstr "إستعلام خاطئ للبحث."
305
-
306
- #: core/core.php:1373
307
- msgid "Filter \"%s\" created"
308
- msgstr "الفلتر \"%s\" تم إنشاءه"
309
-
310
- #: core/core.php:1400
311
- msgid "Filter ID not specified."
312
- msgstr "رقم الفلتر لم يحدد"
313
-
314
- #: core/core.php:1407
315
- msgid "Filter deleted"
316
- msgstr "تم مسح الفلتر"
317
-
318
- #: core/core.php:1454
319
- msgid "Replaced %d redirect with a direct link"
320
- msgid_plural "Replaced %d redirects with direct links"
321
- msgstr[0] "تم استبدال %d إحالة بروابط مباشرة"
322
- msgstr[1] "تم استبدال %d إحالة بروابط مباشرة"
323
-
324
- #: core/core.php:1465
325
- msgid "Failed to fix %d redirect"
326
- msgid_plural "Failed to fix %d redirects"
327
- msgstr[0] "فشل في إصلاح %d إحالة"
328
- msgstr[1] "فشل في إصلاح %d إحالة"
329
-
330
- #: core/core.php:1476
331
- msgid "None of the selected links are redirects!"
332
- msgstr "لا توجد روابط مُحولة من الذين تم اختيارهم!"
333
-
334
- #: core/core.php:1554
335
- msgid "%d link updated."
336
- msgid_plural "%d links updated."
337
- msgstr[0] "%d رابط تم تحديثه."
338
- msgstr[1] "%d رابط تم تحديثه."
339
-
340
- #: core/core.php:1565
341
- msgid "Failed to update %d link."
342
- msgid_plural "Failed to update %d links."
343
- msgstr[0] "فشل في تحديث %d رابط."
344
- msgstr[1] "فشل في تحديث %d رابط."
345
-
346
- #: core/core.php:1619
347
- msgid "%d link removed"
348
- msgid_plural "%d links removed"
349
- msgstr[0] "%d رابط تمت ازالته"
350
- msgstr[1] "%d رابط تمت ازالته"
351
-
352
- #: core/core.php:1630
353
- msgid "Failed to remove %d link"
354
- msgid_plural "Failed to remove %d links"
355
- msgstr[0] "فشل في إزالة %d رابط"
356
- msgstr[1] "فشل في إزالة %d رابط"
357
-
358
- #: core/core.php:1739
359
- msgid ""
360
- "%d item was skipped because it can't be moved to the Trash. You need to "
361
- "delete it manually."
362
- msgid_plural ""
363
- "%d items were skipped because they can't be moved to the Trash. You need to "
364
- "delete them manually."
365
- msgstr[0] ""
366
- "%d كائن تم تجاوزهم لأن لا يمكن نقلهم إلى سلة المهملات. يجب أن تقوم بحذفهم "
367
- "يدوياً."
368
- msgstr[1] ""
369
- "%d كائن تم تجاوزهم لأن لا يمكن نقلهم إلى سلة المهملات. يجب أن تقوم بحذفهم "
370
- "يدوياً."
371
-
372
- #: core/core.php:1761
373
- msgid "Didn't find anything to delete!"
374
- msgstr "لا يعثر على أي شيء للحذف!"
375
-
376
- #: core/core.php:1789
377
- msgid "%d link scheduled for rechecking"
378
- msgid_plural "%d links scheduled for rechecking"
379
- msgstr[0] "%d رابط تم جدولته للفحص"
380
- msgstr[1] "%d رابط تم جدولته للفحص"
381
-
382
- #: core/core.php:1835 core/core.php:2444
383
- msgid "This link was manually marked as working by the user."
384
- msgstr "هذا الرابط حُدد يدوياً للعمل من قبل المستخدم."
385
-
386
- #: core/core.php:1842
387
- msgid "Couldn't modify link %d"
388
- msgstr "لا يمكن تعديل الرابط %d"
389
-
390
- #: core/core.php:1852
391
- msgid "%d link marked as not broken"
392
- msgid_plural "%d links marked as not broken"
393
- msgstr[0] "%d رابط مُحدد على أنه غير معطوب"
394
- msgstr[1] "%d رابط مُحدد على أنه غير معطوب"
395
-
396
- #: core/core.php:1892
397
- msgid "Table columns"
398
- msgstr "أعمدة الجدول"
399
-
400
- #: core/core.php:1911
401
- msgid "Show on screen"
402
- msgstr "عرض في الشاشة"
403
-
404
- #: core/core.php:1918
405
- msgid "links"
406
- msgstr "روباط"
407
-
408
- #: core/core.php:1919 includes/admin/table-printer.php:165
409
- msgid "Apply"
410
- msgstr "تطبيق"
411
-
412
- #: core/core.php:1923
413
- msgid "Misc"
414
- msgstr "منوعات"
415
-
416
- #: core/core.php:1938
417
- msgid "Highlight links broken for at least %s days"
418
- msgstr "تحديد الروابط المعطوبة على الأقل لـ %s يوم"
419
-
420
- #: core/core.php:1947
421
- msgid "Color-code status codes"
422
- msgstr "الكود اللوني للحالات"
423
-
424
- #: core/core.php:1964 core/core.php:2429 core/core.php:2469 core/core.php:2502
425
- #: core/core.php:2565
426
- msgid "You're not allowed to do that!"
427
- msgstr "غير مصّرح لك بعمل هذا!"
428
-
429
- #: core/core.php:2299
430
- msgid "View broken links"
431
- msgstr "عرض الروابط المعطوبة"
432
-
433
- #: core/core.php:2300
434
- msgid "Found %d broken link"
435
- msgid_plural "Found %d broken links"
436
- msgstr[0] "عُثر على %d رابط معطوب"
437
- msgstr[1] "عُثر على %d رابط معطوب"
438
-
439
- #: core/core.php:2306
440
- msgid "No broken links found."
441
- msgstr "لا توجد روابط معطوبة"
442
-
443
- #: core/core.php:2313
444
- msgid "%d URL in the work queue"
445
- msgid_plural "%d URLs in the work queue"
446
- msgstr[0] "%d رابط في صف الإنتظار"
447
- msgstr[1] "%d رابط في صف الإنتظار"
448
-
449
- #: core/core.php:2316
450
- msgid "No URLs in the work queue."
451
- msgstr "لا روابط في صف الإنتظار."
452
-
453
- #: core/core.php:2322
454
- msgctxt "for the \"Detected X unique URLs in Y links\" message"
455
- msgid "%d unique URL"
456
- msgid_plural "%d unique URLs"
457
- msgstr[0] "%d رابط مميز"
458
- msgstr[1] "%d روابط مميزة"
459
-
460
- #: core/core.php:2326
461
- msgctxt "for the \"Detected X unique URLs in Y links\" message"
462
- msgid "%d link"
463
- msgid_plural "%d links"
464
- msgstr[0] "%d رابط"
465
- msgstr[1] "%d رابط"
466
-
467
- #: core/core.php:2332
468
- msgid "Detected %1$s in %2$s and still searching..."
469
- msgstr "تم رصد %1$s في %2$s و لازال البحث جاري..."
470
-
471
- #: core/core.php:2338
472
- msgid "Detected %1$s in %2$s."
473
- msgstr "تم رصد %1$s في %2$s."
474
-
475
- #: core/core.php:2345
476
- msgid "Searching your blog for links..."
477
- msgstr "البحث في مدونتك على روابط..."
478
-
479
- #: core/core.php:2347
480
- msgid "No links detected."
481
- msgstr "لا توجد روابط."
482
-
483
- #: core/core.php:2373
484
- msgctxt "current load"
485
- msgid "Unknown"
486
- msgstr "غير معروف"
487
-
488
- #: core/core.php:2437 core/core.php:2477 core/core.php:2512 core/core.php:2575
489
- msgid "Oops, I can't find the link %d"
490
- msgstr "عفواً، لا أستطيع العثور على الرابط %d"
491
-
492
- #: core/core.php:2450 core/core.php:2487
493
- msgid "Oops, couldn't modify the link!"
494
- msgstr "عفواً، لا أستطيع تعديل الرابط!"
495
-
496
- #: core/core.php:2453 core/core.php:2490 core/core.php:2601
497
- msgid "Error : link_id not specified"
498
- msgstr "خطأ : link_id لم يحدد"
499
-
500
- #: core/core.php:2522
501
- msgid "Oops, the new URL is invalid!"
502
- msgstr "عفواً"
503
-
504
- #: core/core.php:2533 core/core.php:2584
505
- msgid "An unexpected error occured!"
506
- msgstr "حدث خطأ غير متوقع!"
507
-
508
- #: core/core.php:2551
509
- msgid "Error : link_id or new_url not specified"
510
- msgstr "خطأ : link_id أو new_url لم يُحدد"
511
-
512
- #: core/core.php:2610
513
- msgid "You don't have sufficient privileges to access this information!"
514
- msgstr "لا تملك الصلاحيات الكافية للوصول الى هذه المعلومات!"
515
-
516
- #: core/core.php:2623
517
- msgid "Error : link ID not specified"
518
- msgstr "خطأ : رقم الرابط لم يحُدد"
519
-
520
- #: core/core.php:2637
521
- msgid "Failed to load link details (%s)"
522
- msgstr "فشل في تحميل تفاصيل الرابط (%s)"
523
-
524
- #. #-#-#-#-# plugin.pot (Broken Link Checker 1.6.2) #-#-#-#-#
525
- #. Plugin Name of the plugin/theme
526
- #: core/core.php:2690
527
- msgid "Broken Link Checker"
528
- msgstr "فاحص الرابط المعطوب"
529
-
530
- #: core/core.php:2710
531
- msgid "PHP version"
532
- msgstr "اصدار بي اتش بي"
533
-
534
- #: core/core.php:2716
535
- msgid "MySQL version"
536
- msgstr "اصدار MySQL"
537
-
538
- #: core/core.php:2729
539
- msgid ""
540
- "You have an old version of CURL. Redirect detection may not work properly."
541
- msgstr "لديك نسخة CURL قديمة. راصد الإحالات قد لا يعمل بشكل صحيح."
542
-
543
- #: core/core.php:2741 core/core.php:2757 core/core.php:2762
544
- msgid "Not installed"
545
- msgstr "لم ينصّب."
546
-
547
- #: core/core.php:2744
548
- msgid "CURL version"
549
- msgstr "CURL اصدار"
550
-
551
- #: core/core.php:2750
552
- msgid "Installed"
553
- msgstr "منصّب"
554
-
555
- #: core/core.php:2763
556
- msgid "You must have either CURL or Snoopy installed for the plugin to work!"
557
- msgstr "يجب أن يكون لديك CURL او Snoopy مسبق التنصيب، لتعمل لديك الإضافة."
558
-
559
- #: core/core.php:2774
560
- msgid "On"
561
- msgstr "تشغيل"
562
-
563
- #: core/core.php:2775
564
- msgid "Redirects may be detected as broken links when safe_mode is on."
565
- msgstr "الإحالات قد تُرصد كروابط معطوبة إذا كان الوضع الآمن مفعلاً."
566
-
567
- #: core/core.php:2780 core/core.php:2794
568
- msgid "Off"
569
- msgstr "تعطيل"
570
-
571
- #: core/core.php:2788
572
- msgid "On ( %s )"
573
- msgstr "تشغيل ( %s )"
574
-
575
- #: core/core.php:2789
576
- msgid "Redirects may be detected as broken links when open_basedir is on."
577
- msgstr "الإحالات قد تُرصد كروابط معطوبة عندما تكون الدالة open_basedir مفعلة."
578
-
579
- #: core/core.php:2818
580
- msgid ""
581
- "If this value is zero even after several page reloads you have probably "
582
- "encountered a bug."
583
- msgstr ""
584
- "اذا كانت هذه القيمة صفر، بعد عدّة مرات من اعادة تحميل الصفحة من المحتمل أن "
585
- "تواجه مشكلة."
586
-
587
- #: core/core.php:2909 core/core.php:3007
588
- msgid "[%s] Broken links detected"
589
- msgstr "[%s] رابط معطوب تم رصده."
590
-
591
- #: core/core.php:2914
592
- msgid "Broken Link Checker has detected %d new broken link on your site."
593
- msgid_plural ""
594
- "Broken Link Checker has detected %d new broken links on your site."
595
- msgstr[0] "فاحص الروابط المعطوبة رصد %d رابط جديد ومعطوب في موقعك."
596
- msgstr[1] "فاحص الروابط المعطوبة رصد %d رابط جديد ومعطوب في موقعك."
597
-
598
- #: core/core.php:2937
599
- msgid "Here's a list of the first %d broken links:"
600
- msgid_plural "Here's a list of the first %d broken links:"
601
- msgstr[0] "هُنا قائمة تحتوي على أول %d رابط معطوب:"
602
- msgstr[1] "هُنا قائمة تحتوي على أول %d رابط معطوب:"
603
-
604
- #: core/core.php:2946
605
- msgid "Here's a list of the new broken links: "
606
- msgstr "هُنا قائمة بالروابط المعطوبة الجديدة:"
607
-
608
- #: core/core.php:2955
609
- msgid "Link text : %s"
610
- msgstr "نص الرابط : %s"
611
-
612
- #: core/core.php:2956
613
- msgid "Link URL : <a href=\"%s\">%s</a>"
614
- msgstr "رابط الوصلة: <a href=\"%s\">%s</a>"
615
-
616
- #: core/core.php:2957
617
- msgid "Source : %s"
618
- msgstr "المصدر : %s"
619
-
620
- #: core/core.php:2970
621
- msgid "You can see all broken links here:"
622
- msgstr "تستطيع رؤية كل الروابط المعطوبة هنا:"
623
-
624
- #: core/core.php:3012
625
- msgid "Broken Link Checker has detected %d new broken link in your posts."
626
- msgid_plural ""
627
- "Broken Link Checker has detected %d new broken links in your posts."
628
- msgstr[0] " %d رابط جديد معطوب في تدويناتك."
629
- msgstr[1] " %d رابط جديد معطوب في تدويناتك."
630
-
631
- #: core/init.php:235
632
- msgid "Once Weekly"
633
- msgstr "مرة أسبوعياً"
634
-
635
- #: core/init.php:241
636
- msgid "Twice a Month"
637
- msgstr "مرتين شهرياً"
638
-
639
- #: core/init.php:317
640
- msgid ""
641
- "Broken Link Checker installation failed. Try deactivating and then "
642
- "reactivating the plugin."
643
- msgstr "فشل في تنصيب الإضافة. حاول تعطيلها وإعادة تفعيلها."
644
-
645
- #: includes/admin/db-upgrade.php:95
646
- msgid "Failed to delete old DB tables. Database error : %s"
647
- msgstr "فشل في حذف جداول قاعدة البيانات القديمة . خطأ في قاعدة البيانات: %s"
648
-
649
- #: includes/admin/links-page-js.php:55 includes/admin/links-page-js.php:403
650
- msgid "Wait..."
651
- msgstr "لحظة ..."
652
-
653
- #: includes/admin/links-page-js.php:100 includes/admin/table-printer.php:630
654
- msgid "Not broken"
655
- msgstr "غير معطوب"
656
-
657
- #: includes/admin/links-page-js.php:315
658
- msgid "%d instances of the link were successfully modified."
659
- msgstr "%d طلب مربوط تم تعديله بنجاح."
660
-
661
- #: includes/admin/links-page-js.php:321
662
- msgid ""
663
- "However, %d instances couldn't be edited and still point to the old URL."
664
- msgstr "على كل حال، %d طلب لم يتم تعديله ويظل يشير الى الرابط القديم."
665
-
666
- #: includes/admin/links-page-js.php:327
667
- msgid "The link could not be modified."
668
- msgstr "هذا الرابط قد لم يتم تعديله."
669
-
670
- #: includes/admin/links-page-js.php:330 includes/admin/links-page-js.php:455
671
- msgid "The following error(s) occured :"
672
- msgstr "الأخطاء التالية حصلت: "
673
-
674
- #: includes/admin/links-page-js.php:441
675
- msgid "%d instances of the link were successfully unlinked."
676
- msgstr "%d طلب مربوط تم تفكيك ربطه بنجاح."
677
-
678
- #: includes/admin/links-page-js.php:447
679
- msgid "However, %d instances couldn't be removed."
680
- msgstr "عموماً، %d طلب لم يزالوا."
681
-
682
- #: includes/admin/links-page-js.php:452
683
- msgid "The plugin failed to remove the link."
684
- msgstr "الإضافة فشلت في إزالة الرابط."
685
-
686
- #: includes/admin/links-page-js.php:463 includes/admin/table-printer.php:273
687
- #: includes/admin/table-printer.php:624
688
- msgid "Unlink"
689
- msgstr "إلغاء ربط"
690
-
691
- #: includes/admin/links-page-js.php:507
692
- msgid "Enter a name for the new custom filter"
693
- msgstr "أدخل اسم الفلتر المخصص الجديد"
694
-
695
- #: includes/admin/links-page-js.php:518
696
- msgid ""
697
- "You are about to delete the current filter.\n"
698
- "'Cancel' to stop, 'OK' to delete"
699
- msgstr ""
700
- "أنت على وشك حذف الفلتر الحالي.\n"
701
- "\v\"إلغاء\" لإيقاف العملية, \"موافق\" للحذف"
702
-
703
- #: includes/admin/links-page-js.php:538
704
- msgid ""
705
- "Are you sure you want to delete all posts, bookmarks or other items that "
706
- "contain any of the selected links? This action can't be undone.\n"
707
- "'Cancel' to stop, 'OK' to delete"
708
- msgstr ""
709
- "هل أنت متأكد من رغبتك في إزالة كل التدوينات، المفضلات أو أي كائنات أخرى "
710
- "تحتوي على إحدى الروابط المخُتارة؟ هذا الخيار نهائي ولا يمكن التراجع عنه.\n"
711
- "\v\"إلغاء\" للإيقاف، \"موفق\" للحذف"
712
-
713
- #: includes/admin/links-page-js.php:548
714
- msgid ""
715
- "Are you sure you want to remove the selected links? This action can't be "
716
- "undone.\n"
717
- "'Cancel' to stop, 'OK' to remove"
718
- msgstr ""
719
- "هل أنت متأكد من رغبتك في إزالة الروابط المُحددة؟ لا يمكنك التراجع عن هذه "
720
- "الخطوة لاحقاً.\n"
721
- "\"إلغاء\" لإيقاف العملية، \"موافق\" للإزالة"
722
-
723
- #: includes/admin/links-page-js.php:657
724
- msgid "Enter a search string first."
725
- msgstr "أدخل عبارة البحث أولاً."
726
-
727
- #: includes/admin/links-page-js.php:664
728
- msgid "Select one or more links to edit."
729
- msgstr "اختر رابط أو اكثر للتحرير."
730
-
731
- #: includes/admin/options-page-js.php:54
732
- msgid "Hide debug info"
733
- msgstr "إخفاء معلومات المطورين"
734
-
735
- #: includes/admin/search-form.php:16
736
- msgid "Save This Search As a Filter"
737
- msgstr "حفظ هذا البحث كفلتر"
738
-
739
- #: includes/admin/search-form.php:26
740
- msgid "Delete This Filter"
741
- msgstr "حذف هذا الفلتر"
742
-
743
- #: includes/admin/search-form.php:32 includes/link-query.php:65
744
- msgid "Search"
745
- msgstr "بحث"
746
-
747
- #: includes/admin/search-form.php:42
748
- msgid "Link text"
749
- msgstr "نص الرابط"
750
-
751
- #: includes/admin/search-form.php:45 includes/admin/table-printer.php:202
752
- msgid "URL"
753
- msgstr "الرابط"
754
-
755
- #: includes/admin/search-form.php:48 includes/admin/table-printer.php:491
756
- msgid "HTTP code"
757
- msgstr "كود HTTP"
758
-
759
- #: includes/admin/search-form.php:51
760
- msgid "Link status"
761
- msgstr "حالة الرابط"
762
-
763
- #: includes/admin/search-form.php:68 includes/admin/search-form.php:85
764
- msgid "Link type"
765
- msgstr "نوع الرابط"
766
-
767
- #: includes/admin/search-form.php:70
768
- msgid "Any"
769
- msgstr "أي"
770
-
771
- #: includes/admin/search-form.php:74
772
- msgid "Links used in"
773
- msgstr "الروابط مستخدمة في"
774
-
775
- #: includes/admin/search-form.php:112
776
- msgid "Search Links"
777
- msgstr "البحث عن الروابط"
778
-
779
- #: includes/admin/search-form.php:113 includes/admin/table-printer.php:349
780
- #: includes/admin/table-printer.php:652 includes/admin/table-printer.php:658
781
- msgid "Cancel"
782
- msgstr "إلغاء"
783
-
784
- #: includes/admin/sidebar.php:28 includes/admin/sidebar.php:68
785
- msgid "More plugins by Janis Elsts"
786
- msgstr "المزيد من الإضافات مقدمة من Janis Elsts"
787
-
788
- #: includes/admin/sidebar.php:85
789
- msgid "Donate $10, $20 or $50!"
790
- msgstr "تبرّغ 10$, 20$, او 50$!"
791
-
792
- #: includes/admin/sidebar.php:88
793
- msgid ""
794
- "If you like this plugin, please donate to support development and "
795
- "maintenance!"
796
- msgstr "اذا أحببت هذه الإضافة، الرجاء تبرّع للمساهمة في تطويرها وصيانتها!"
797
-
798
- #: includes/admin/sidebar.php:106
799
- msgid "Return to WordPress Dashboard"
800
- msgstr "العودة للوحة الرئيسية لووردبريس"
801
-
802
- #: includes/admin/table-printer.php:179
803
- msgid "Compact View"
804
- msgstr "العرض المدمّج"
805
-
806
- #: includes/admin/table-printer.php:180
807
- msgid "Detailed View"
808
- msgstr "العرض المفصّل"
809
-
810
- #: includes/admin/table-printer.php:209
811
- msgid "Source"
812
- msgstr "مصدر"
813
-
814
- #: includes/admin/table-printer.php:215
815
- msgid "Link Text"
816
- msgstr "نص الرابط"
817
-
818
- #: includes/admin/table-printer.php:220
819
- msgid "Redirect URL"
820
- msgstr "إحالة الرابط"
821
-
822
- #: includes/admin/table-printer.php:268
823
- msgid "Bulk Actions"
824
- msgstr "إجراء بالجملة"
825
-
826
- #: includes/admin/table-printer.php:269 includes/admin/table-printer.php:621
827
- msgid "Edit URL"
828
- msgstr "تحرير الرابط"
829
-
830
- #: includes/admin/table-printer.php:270
831
- msgid "Recheck"
832
- msgstr "إعادة فحص"
833
-
834
- #: includes/admin/table-printer.php:271
835
- msgid "Fix redirects"
836
- msgstr "إصلاح الإحالات"
837
-
838
- #: includes/admin/table-printer.php:272
839
- msgid "Mark as not broken"
840
- msgstr "إجعله كـغير معطوب"
841
-
842
- #: includes/admin/table-printer.php:276
843
- msgid "Move sources to Trash"
844
- msgstr "نقل المصادر الى سلة المهملات"
845
-
846
- #: includes/admin/table-printer.php:278
847
- msgid "Delete sources"
848
- msgstr "حذف المصادر"
849
-
850
- #: includes/admin/table-printer.php:293
851
- msgid "&laquo;"
852
- msgstr "&laquo;"
853
-
854
- #: includes/admin/table-printer.php:294
855
- msgid "&raquo;"
856
- msgstr "&raquo;"
857
-
858
- #: includes/admin/table-printer.php:302
859
- msgid "Displaying %s&#8211;%s of <span class=\"current-link-count\">%s</span>"
860
- msgstr "عرض %s&#8211;%s من <span class=\"current-link-count\">%s</span>"
861
-
862
- #: includes/admin/table-printer.php:325
863
- msgid "Bulk Edit URLs"
864
- msgstr "تحرير الروابط بالجملة"
865
-
866
- #: includes/admin/table-printer.php:327
867
- msgid "Find"
868
- msgstr "أعثر"
869
-
870
- #: includes/admin/table-printer.php:331
871
- msgid "Replace with"
872
- msgstr "استبدل بـ"
873
-
874
- #: includes/admin/table-printer.php:339
875
- msgid "Case sensitive"
876
- msgstr "متحسس لحالة الأحرف"
877
-
878
- #: includes/admin/table-printer.php:343
879
- msgid "Regular expression"
880
- msgstr "التعبير العادي"
881
-
882
- #: includes/admin/table-printer.php:351
883
- msgid "Update"
884
- msgstr "تحديث"
885
-
886
- #: includes/admin/table-printer.php:476
887
- msgid "Post published on"
888
- msgstr "نشر الموضوع في"
889
-
890
- #: includes/admin/table-printer.php:481
891
- msgid "Link last checked"
892
- msgstr "آخر فحص للروابط"
893
-
894
- #: includes/admin/table-printer.php:485
895
- msgid "Never"
896
- msgstr "أبداً"
897
-
898
- #: includes/admin/table-printer.php:496
899
- msgid "Response time"
900
- msgstr "زمن الإستجابة"
901
-
902
- #: includes/admin/table-printer.php:498
903
- msgid "%2.3f seconds"
904
- msgstr "%2.3f ثانية"
905
-
906
- #: includes/admin/table-printer.php:501
907
- msgid "Final URL"
908
- msgstr "الرابط النهائي"
909
-
910
- #: includes/admin/table-printer.php:506
911
- msgid "Redirect count"
912
- msgstr "عدّاد الإحالة"
913
-
914
- #: includes/admin/table-printer.php:511
915
- msgid "Instance count"
916
- msgstr "عدّاد الطلب"
917
-
918
- #: includes/admin/table-printer.php:520
919
- msgid "This link has failed %d time."
920
- msgid_plural "This link has failed %d times."
921
- msgstr[0] "هذا الرابط فشل %d مرّة."
922
- msgstr[1] "هذا الروابط فشلت %d مرّة."
923
-
924
- #: includes/admin/table-printer.php:528
925
- msgid "This link has been broken for %s."
926
- msgstr "هذا الرابط قد عطب لـ %s."
927
-
928
- #: includes/admin/table-printer.php:539
929
- msgid "Log"
930
- msgstr "سجل"
931
-
932
- #: includes/admin/table-printer.php:564
933
- msgid "Show more info about this link"
934
- msgstr "عرض المزيد من المعلومات عن الرابط"
935
-
936
- #: includes/admin/table-printer.php:582
937
- msgctxt "checked how long ago"
938
- msgid "Checked"
939
- msgstr "مفحوص"
940
-
941
- #: includes/admin/table-printer.php:598
942
- msgid "Broken for"
943
- msgstr "معطوب لـ"
944
-
945
- #: includes/admin/table-printer.php:621
946
- msgid "Edit link URL"
947
- msgstr "تحرير الرابط"
948
-
949
- #: includes/admin/table-printer.php:623
950
- msgid "Remove this link from all posts"
951
- msgstr "إزالة هذا الرابط من كل التدوينات"
952
-
953
- #: includes/admin/table-printer.php:629
954
- msgid "Remove this link from the list of broken links and mark it as valid"
955
- msgstr "إزالة هذا الرابط من قائمة الروابط المعطوبة وتحديده كرابط صالح"
956
-
957
- #: includes/admin/table-printer.php:637
958
- msgid "Hide this link and do not report it again unless its status changes"
959
- msgstr "إخفاء هذا الرابط و عدم إظهاره مُجددا إلا في حالة تغير حالته"
960
-
961
- #: includes/admin/table-printer.php:638
962
- msgid "Dismiss"
963
- msgstr "صرف النظر"
964
-
965
- #: includes/admin/table-printer.php:643
966
- msgid "Undismiss this link"
967
- msgstr "عدم صرف النظر لهذا الرابط"
968
-
969
- #: includes/admin/table-printer.php:644
970
- msgid "Undismiss"
971
- msgstr "عدم صرف النظر"
972
-
973
- #: includes/admin/table-printer.php:652
974
- msgid "Cancel URL editing"
975
- msgstr "إلغاء تحرير الرابط"
976
-
977
- #: includes/admin/table-printer.php:659
978
- msgid "Update URL"
979
- msgstr "تحديث الرابط"
980
-
981
- #: includes/admin/table-printer.php:686
982
- msgid "[An orphaned link! This is a bug.]"
983
- msgstr "[رابط يتيم! هذه مشكلة.]"
984
-
985
- #: includes/any-post.php:397 modules/containers/blogroll.php:46
986
- #: modules/containers/comment.php:153 modules/containers/custom_field.php:197
987
- msgid "Edit"
988
- msgstr "تحرير"
989
-
990
- #: includes/any-post.php:405 modules/containers/custom_field.php:203
991
- msgid "Move this item to the Trash"
992
- msgstr "نقل هذا الكائن الى سلة المهملات"
993
-
994
- #: includes/any-post.php:407 modules/containers/custom_field.php:205
995
- msgid "Trash"
996
- msgstr "سلة المهملات"
997
-
998
- #: includes/any-post.php:412 modules/containers/custom_field.php:210
999
- msgid "Delete this item permanently"
1000
- msgstr "حذف هذا الكائن نهائياً"
1001
-
1002
- #: includes/any-post.php:414 modules/containers/blogroll.php:47
1003
- #: modules/containers/custom_field.php:212
1004
- msgid "Delete"
1005
- msgstr "حذف"
1006
-
1007
- #: includes/any-post.php:427
1008
- msgid "Preview &#8220;%s&#8221;"
1009
- msgstr "عرض &#8220;%s&#8221;"
1010
-
1011
- #: includes/any-post.php:428
1012
- msgid "Preview"
1013
- msgstr "عرض"
1014
-
1015
- #: includes/any-post.php:435
1016
- msgid "View &#8220;%s&#8221;"
1017
- msgstr "مشاهدة &#8220;%s&#8221;"
1018
-
1019
- #: includes/any-post.php:436 modules/containers/comment.php:166
1020
- #: modules/containers/custom_field.php:217
1021
- msgid "View"
1022
- msgstr "مشاهدة"
1023
-
1024
- #: includes/any-post.php:455 modules/containers/custom_field.php:197
1025
- msgid "Edit this item"
1026
- msgstr "تحرير هذا الكائن"
1027
-
1028
- #: includes/any-post.php:519 modules/containers/blogroll.php:83
1029
- #: modules/containers/comment.php:43
1030
- msgid "Nothing to update"
1031
- msgstr "لا شيء للتحديث"
1032
-
1033
- #: includes/any-post.php:529
1034
- msgid "Updating post %d failed"
1035
- msgstr "فشل في تحديث التدوينة %d"
1036
-
1037
- #: includes/any-post.php:564 modules/containers/custom_field.php:284
1038
- msgid "Failed to delete post \"%s\" (%d)"
1039
- msgstr "فشل في حذف التدوينة \"%s\" (%d)"
1040
-
1041
- #: includes/any-post.php:583 modules/containers/custom_field.php:303
1042
- msgid ""
1043
- "Can't move post \"%s\" (%d) to the trash because the trash feature is "
1044
- "disabled"
1045
- msgstr ""
1046
- "لا يمكن نقل التدوينة \"%s\" (%d) الى سلة المهملات بسبب أنّ خاصية سلة "
1047
- "المهملات معطلة"
1048
-
1049
- #: includes/any-post.php:603 modules/containers/custom_field.php:322
1050
- msgid "Failed to move post \"%s\" (%d) to the trash"
1051
- msgstr "فشل في نقل التدوينة الى \"%s\" (%d) الى سلة المهملات"
1052
-
1053
- #: includes/any-post.php:711
1054
- msgid "%d post deleted."
1055
- msgid_plural "%d posts deleted."
1056
- msgstr[0] "%d تدوينة محذوفة."
1057
- msgstr[1] "%d تدوينة محذوفة."
1058
-
1059
- #: includes/any-post.php:713
1060
- msgid "%d page deleted."
1061
- msgid_plural "%d pages deleted."
1062
- msgstr[0] "%d صفحة محذوفة."
1063
- msgstr[1] "%d صفحة محذوفة."
1064
-
1065
- #: includes/any-post.php:715
1066
- msgid "%d \"%s\" deleted."
1067
- msgid_plural "%d \"%s\" deleted."
1068
- msgstr[0] "%d \"%s\" محذوفة."
1069
- msgstr[1] "%d \"%s\" محذوفة."
1070
-
1071
- #: includes/any-post.php:734
1072
- msgid "%d post moved to the Trash."
1073
- msgid_plural "%d posts moved to the Trash."
1074
- msgstr[0] "%d تدوينة نقلت الى سلة المهملات."
1075
- msgstr[1] "%d تدوينة نقلت الى سلة المهملات."
1076
-
1077
- #: includes/any-post.php:736
1078
- msgid "%d page moved to the Trash."
1079
- msgid_plural "%d pages moved to the Trash."
1080
- msgstr[0] "%d صفحة نقلت الى سلة المهملات."
1081
- msgstr[1] "%d صفحة نقلت الى سلة المهملات."
1082
-
1083
- #: includes/any-post.php:738
1084
- msgid "%d \"%s\" moved to the Trash."
1085
- msgid_plural "%d \"%s\" moved to the Trash."
1086
- msgstr[0] "%d \"%s\" نقلت الى سلة المهملات."
1087
- msgstr[1] "%d \"%s\" نقلت الى سلة المهملات."
1088
-
1089
- #: includes/containers.php:122
1090
- msgid "%d '%s' has been deleted"
1091
- msgid_plural "%d '%s' have been deleted"
1092
- msgstr[0] "%d '%s' تم حذفها"
1093
- msgstr[1] "%d '%s' تم حذفها"
1094
-
1095
- #: includes/containers.php:873 includes/containers.php:891
1096
- msgid "Container type '%s' not recognized"
1097
- msgstr "نوع الحاوي '%s' غير معترف بها."
1098
-
1099
- #: includes/extra-strings.php:2
1100
- msgctxt "module name"
1101
- msgid "Basic HTTP"
1102
- msgstr "HTTP أساسي"
1103
-
1104
- #: includes/extra-strings.php:3
1105
- msgctxt "module name"
1106
- msgid "Blogroll items"
1107
- msgstr "روابط المواقع الصديقة"
1108
-
1109
- #: includes/extra-strings.php:4
1110
- msgctxt "module name"
1111
- msgid "Comments"
1112
- msgstr "التعليقات"
1113
-
1114
- #: includes/extra-strings.php:5
1115
- msgctxt "module name"
1116
- msgid "Custom fields"
1117
- msgstr "الحقول المُخصصة"
1118
-
1119
- #: includes/extra-strings.php:6
1120
- msgctxt "module name"
1121
- msgid "Embedded DailyMotion videos"
1122
- msgstr "متضمنة بفيديوهات دايلي موشن"
1123
-
1124
- #: includes/extra-strings.php:7
1125
- msgctxt "module name"
1126
- msgid "Embedded GoogleVideo videos"
1127
- msgstr "متضمنة بفيديوهات جوجل فيديو"
1128
-
1129
- #: includes/extra-strings.php:8
1130
- msgctxt "module name"
1131
- msgid "Embedded Megavideo videos"
1132
- msgstr "متضمنة بفيديوهات ميجا فيديو"
1133
-
1134
- #: includes/extra-strings.php:9
1135
- msgctxt "module name"
1136
- msgid "Embedded Vimeo videos"
1137
- msgstr "متضمنة بفيديوهات فيميو"
1138
-
1139
- #: includes/extra-strings.php:10
1140
- msgctxt "module name"
1141
- msgid "Embedded YouTube videos"
1142
- msgstr "متضمنة بفيديوهات يوتيوب"
1143
-
1144
- #: includes/extra-strings.php:11
1145
- msgctxt "module name"
1146
- msgid "Embedded YouTube videos (old embed code)"
1147
- msgstr "متضمنة بفيديوهات يوتيوب (الكود القديم)"
1148
-
1149
- #: includes/extra-strings.php:12
1150
- msgctxt "module name"
1151
- msgid "FileServe API"
1152
- msgstr "FileServe API"
1153
-
1154
- #: includes/extra-strings.php:13
1155
- msgctxt "module name"
1156
- msgid "HTML images"
1157
- msgstr "HTML صور"
1158
-
1159
- #: includes/extra-strings.php:14
1160
- msgctxt "module name"
1161
- msgid "HTML links"
1162
- msgstr "HTML روابط"
1163
-
1164
- #: includes/extra-strings.php:15
1165
- msgctxt "module name"
1166
- msgid "MediaFire API"
1167
- msgstr "MediaFire API"
1168
-
1169
- #: includes/extra-strings.php:16
1170
- msgctxt "module name"
1171
- msgid "MegaUpload API"
1172
- msgstr "MegaUpload API"
1173
-
1174
- #: includes/extra-strings.php:17
1175
- msgctxt "module name"
1176
- msgid "Plaintext URLs"
1177
- msgstr " نصّية روابط"
1178
-
1179
- #: includes/extra-strings.php:18
1180
- msgctxt "module name"
1181
- msgid "RapidShare API"
1182
- msgstr "RapidShare API"
1183
-
1184
- #: includes/extra-strings.php:19
1185
- msgctxt "module name"
1186
- msgid "YouTube API"
1187
- msgstr "YouTube API"
1188
-
1189
- #: includes/extra-strings.php:20
1190
- msgctxt "module name"
1191
- msgid "Posts"
1192
- msgstr "تدوينات"
1193
-
1194
- #: includes/extra-strings.php:21
1195
- msgctxt "module name"
1196
- msgid "Pages"
1197
- msgstr "صفحات"
1198
-
1199
- #: includes/instances.php:102 includes/instances.php:158
1200
- msgid "Container %s[%d] not found"
1201
- msgstr "الحاوي %s[%d] لم يتم العثور عليه"
1202
-
1203
- #: includes/instances.php:111 includes/instances.php:167
1204
- msgid "Parser '%s' not found."
1205
- msgstr "المحلل '%s' لم يتم العثور عليه."
1206
-
1207
- #: includes/link-query.php:26
1208
- msgid "Broken"
1209
- msgstr "معطوب"
1210
-
1211
- #: includes/link-query.php:28
1212
- msgid "No broken links found"
1213
- msgstr "لا توجد روابط معطوبة"
1214
-
1215
- #: includes/link-query.php:36
1216
- msgid "Redirects"
1217
- msgstr "إحالات"
1218
-
1219
- #: includes/link-query.php:37
1220
- msgid "Redirected Links"
1221
- msgstr "الروابط المُحالة"
1222
-
1223
- #: includes/link-query.php:38
1224
- msgid "No redirects found"
1225
- msgstr "لا روابط مُحالة"
1226
-
1227
- #: includes/link-query.php:46
1228
- msgid "Dismissed"
1229
- msgstr "تم صرف النظر عنه"
1230
-
1231
- #: includes/link-query.php:47
1232
- msgid "Dismissed Links"
1233
- msgstr "روابط تم صرف النظر عنها"
1234
-
1235
- #: includes/link-query.php:48
1236
- msgid "No dismissed links found"
1237
- msgstr "لا روابط مصروف النظر عنها"
1238
-
1239
- #: includes/link-query.php:56
1240
- msgid "All"
1241
- msgstr "الكل"
1242
-
1243
- #: includes/link-query.php:57
1244
- msgid "Detected Links"
1245
- msgstr "الروابط المرصودة"
1246
-
1247
- #: includes/link-query.php:58
1248
- msgid "No links found (yet)"
1249
- msgstr "لا روابط معثورٌ عليها حتى الآن"
1250
-
1251
- #: includes/link-query.php:66
1252
- msgid "Search Results"
1253
- msgstr "نتيجة البحث"
1254
-
1255
- #: includes/link-query.php:67 includes/link-query.php:114
1256
- msgid "No links found for your query"
1257
- msgstr "لا توجد روابط تم العثور عليها من خلال استعلامك"
1258
-
1259
- #: includes/links.php:218
1260
- msgid "The plugin script was terminated while trying to check the link."
1261
- msgstr "سكربت الإضافة تم إنهاءهُ بينما كان يحاول فحص الرابط."
1262
-
1263
- #: includes/links.php:264
1264
- msgid "The plugin doesn't know how to check this type of link."
1265
- msgstr "الإضافة لم تستطيع معرفة كيف تقوم بفحص هذا النوع من الروبط."
1266
-
1267
- #: includes/links.php:357
1268
- msgid "Link is valid."
1269
- msgstr "الرابط صالح."
1270
-
1271
- #: includes/links.php:359
1272
- msgid "Link is broken."
1273
- msgstr "الرابط معطوب."
1274
-
1275
- #: includes/links.php:571 includes/links.php:673 includes/links.php:700
1276
- msgid "Link is not valid"
1277
- msgstr "الرابط غير صالح"
1278
-
1279
- #: includes/links.php:588
1280
- msgid ""
1281
- "This link can not be edited because it is not used anywhere on this site."
1282
- msgstr "هذا الرابط لا يمكن تحريره لأنه غير مستخدم في أي مكان بهذا الموقع."
1283
-
1284
- #: includes/links.php:614
1285
- msgid "Failed to create a DB entry for the new URL."
1286
- msgstr "فشل في إنشاء مدخل جديد في قاعدة البيانات للرابط الجديد."
1287
-
1288
- #: includes/links.php:680
1289
- msgid "This link is not a redirect"
1290
- msgstr "هذا الرابط ليس مُحال."
1291
-
1292
- #: includes/links.php:727 includes/links.php:764
1293
- msgid "Couldn't delete the link's database record"
1294
- msgstr "لا يمكن حذف سجلات الرابط من قاعدة البيانات"
1295
-
1296
- #: includes/links.php:838
1297
- msgctxt "link status"
1298
- msgid "Unknown"
1299
- msgstr "غير معروف"
1300
-
1301
- #: includes/links.php:852 modules/checkers/http.php:264
1302
- #: modules/extras/mediafire.php:101
1303
- msgid "Unknown Error"
1304
- msgstr "خطأ مجهول"
1305
-
1306
- #: includes/links.php:876
1307
- msgid "Not checked"
1308
- msgstr "لم يفحص"
1309
-
1310
- #: includes/links.php:879
1311
- msgid "False positive"
1312
- msgstr "تبليغ خاطئ"
1313
-
1314
- #: includes/links.php:882 modules/extras/fileserve.php:121
1315
- #: modules/extras/megaupload.php:115 modules/extras/rapidshare.php:145
1316
- #: modules/extras/rapidshare.php:151 modules/extras/rapidshare.php:178
1317
- msgctxt "link status"
1318
- msgid "OK"
1319
- msgstr "موافق"
1320
-
1321
- #: includes/parsers.php:109
1322
- msgid "Editing is not implemented in the '%s' parser"
1323
- msgstr "التحرير لا ينطبق في '%s' محلل"
1324
-
1325
- #: includes/parsers.php:124
1326
- msgid "Unlinking is not implemented in the '%s' parser"
1327
- msgstr "لم يتم تنفيذ إلغاء الرابط في '%s' محلل"
1328
-
1329
- #: includes/utility-class.php:245
1330
- msgid "%d second"
1331
- msgid_plural "%d seconds"
1332
- msgstr[0] "%d ثانية"
1333
- msgstr[1] "%d ثانية"
1334
-
1335
- #: includes/utility-class.php:246
1336
- msgid "%d second ago"
1337
- msgid_plural "%d seconds ago"
1338
- msgstr[0] "%d ثانية مضت"
1339
- msgstr[1] "%d ثانية مضت"
1340
-
1341
- #: includes/utility-class.php:249
1342
- msgid "%d minute"
1343
- msgid_plural "%d minutes"
1344
- msgstr[0] "%d دقيقة"
1345
- msgstr[1] "%d دقيقة"
1346
-
1347
- #: includes/utility-class.php:250
1348
- msgid "%d minute ago"
1349
- msgid_plural "%d minutes ago"
1350
- msgstr[0] "%d دقيقة مضت"
1351
- msgstr[1] "%d دقيقة مضت"
1352
-
1353
- #: includes/utility-class.php:253
1354
- msgid "%d hour"
1355
- msgid_plural "%d hours"
1356
- msgstr[0] "%d ساعة"
1357
- msgstr[1] "%d ساعة"
1358
-
1359
- #: includes/utility-class.php:254
1360
- msgid "%d hour ago"
1361
- msgid_plural "%d hours ago"
1362
- msgstr[0] "%d ساعة مضت"
1363
- msgstr[1] "%d ساعة مضت"
1364
-
1365
- #: includes/utility-class.php:257
1366
- msgid "%d day"
1367
- msgid_plural "%d days"
1368
- msgstr[0] "%d يوم"
1369
- msgstr[1] "%d يوم"
1370
-
1371
- #: includes/utility-class.php:258
1372
- msgid "%d day ago"
1373
- msgid_plural "%d days ago"
1374
- msgstr[0] "%d يوم مضى"
1375
- msgstr[1] "%d يوم مضى"
1376
-
1377
- #: includes/utility-class.php:261
1378
- msgid "%d month"
1379
- msgid_plural "%d months"
1380
- msgstr[0] "%d شهر"
1381
- msgstr[1] "%d شهر"
1382
-
1383
- #: includes/utility-class.php:262
1384
- msgid "%d month ago"
1385
- msgid_plural "%d months ago"
1386
- msgstr[0] "%d شهر مضى"
1387
- msgstr[1] "%d شهر مضى"
1388
-
1389
- #: modules/checkers/http.php:243
1390
- msgid "Server Not Found"
1391
- msgstr "لم يتم العثور على السيرفر"
1392
-
1393
- #: modules/checkers/http.php:258
1394
- msgid "Connection Failed"
1395
- msgstr "فشل في الإتصال"
1396
-
1397
- #: modules/checkers/http.php:293 modules/checkers/http.php:363
1398
- msgid "HTTP code : %d"
1399
- msgstr "كود الـ HTTP: %d"
1400
-
1401
- #: modules/checkers/http.php:295 modules/checkers/http.php:365
1402
- msgid "(No response)"
1403
- msgstr "(لا استجابة)"
1404
-
1405
- #: modules/checkers/http.php:301
1406
- msgid "Most likely the connection timed out or the domain doesn't exist."
1407
- msgstr "من المحتمل أن الإتصال قد انتتهت مهلته أو النطاق غير موجود أصلاً."
1408
-
1409
- #: modules/checkers/http.php:372
1410
- msgid "Request timed out."
1411
- msgstr "انتهت مهلة انتظار الطلب."
1412
-
1413
- #: modules/checkers/http.php:390
1414
- msgid "Using Snoopy"
1415
- msgstr "استخدام سنووبي"
1416
-
1417
- #: modules/containers/blogroll.php:21
1418
- msgid "Bookmark"
1419
- msgstr "المفضلة"
1420
-
1421
- #: modules/containers/blogroll.php:27 modules/containers/blogroll.php:46
1422
- msgid "Edit this bookmark"
1423
- msgstr "تحرير هذه المفضلة"
1424
-
1425
- #: modules/containers/blogroll.php:47
1426
- msgid ""
1427
- "You are about to delete this link '%s'\n"
1428
- " 'Cancel' to stop, 'OK' to delete."
1429
- msgstr ""
1430
- "أنت على وشك أن تحذف هذا الرابط '%s'\n"
1431
- "\v \"إلغاء\" لإيقاف هذه العملية, \"موافق\" للحذف."
1432
-
1433
- #: modules/containers/blogroll.php:97
1434
- msgid "Updating bookmark %d failed"
1435
- msgstr "تحديث المفضلة %d لم يكتمل"
1436
-
1437
- #: modules/containers/blogroll.php:128
1438
- msgid "Failed to delete blogroll link \"%s\" (%d)"
1439
- msgstr "فشل في حذف رابط المواقع الصديقة \"%s\" (%d)"
1440
-
1441
- #: modules/containers/blogroll.php:298
1442
- msgid "%d blogroll link deleted."
1443
- msgid_plural "%d blogroll links deleted."
1444
- msgstr[0] "%d رابط صديق حذف"
1445
- msgstr[1] "%d روابط صديقة حذفت"
1446
-
1447
- #: modules/containers/comment.php:53
1448
- msgid "Updating comment %d failed"
1449
- msgstr "تحديث التعليق %d لم يكتمل"
1450
-
1451
- #: modules/containers/comment.php:74
1452
- msgid "Failed to delete comment %d"
1453
- msgstr "فشل في حذف التعليق %d"
1454
-
1455
- #: modules/containers/comment.php:95
1456
- msgid "Can't move comment %d to the trash"
1457
- msgstr "لا يمكن نقل التعليق %d الى سلة المهملات"
1458
-
1459
- #: modules/containers/comment.php:153 modules/containers/comment.php:195
1460
- msgid "Edit comment"
1461
- msgstr "تحرير التعليق"
1462
-
1463
- #: modules/containers/comment.php:160
1464
- msgid "Delete Permanently"
1465
- msgstr "حذف نهائي"
1466
-
1467
- #: modules/containers/comment.php:162
1468
- msgid "Move this comment to the trash"
1469
- msgstr "انقل هذا التعليق الى سلة المهملات"
1470
-
1471
- #: modules/containers/comment.php:162
1472
- msgctxt "verb"
1473
- msgid "Trash"
1474
- msgstr "إرمٍ"
1475
-
1476
- #: modules/containers/comment.php:166
1477
- msgid "View comment"
1478
- msgstr "عرض التعليق"
1479
-
1480
- #: modules/containers/comment.php:183
1481
- msgid "Comment"
1482
- msgstr "تعليق"
1483
-
1484
- #: modules/containers/comment.php:355
1485
- msgid "%d comment has been deleted."
1486
- msgid_plural "%d comments have been deleted."
1487
- msgstr[0] "%d تعليق قد حذف"
1488
- msgstr[1] "%d تعليقات قد حذفت"
1489
-
1490
- #: modules/containers/comment.php:374
1491
- msgid "%d comment moved to the Trash."
1492
- msgid_plural "%d comments moved to the Trash."
1493
- msgstr[0] "%d تعليق نقل الى سلة المهملات."
1494
- msgstr[1] "%d تعليق نقل الى سلة المهملات."
1495
-
1496
- #: modules/containers/custom_field.php:84
1497
- msgid "Failed to update the meta field '%s' on %s [%d]"
1498
- msgstr "فشل في تحديث حقل الميتا '%s' في %s [%d]"
1499
-
1500
- #: modules/containers/custom_field.php:110
1501
- msgid "Failed to delete the meta field '%s' on %s [%d]"
1502
- msgstr "فشل في حذف حقل الميتا '%s' في %s [%d]"
1503
-
1504
- #: modules/containers/custom_field.php:187
1505
- msgid "Edit this post"
1506
- msgstr "تحرير هذه التدوينة"
1507
-
1508
- #: modules/containers/custom_field.php:217
1509
- msgid "View \"%s\""
1510
- msgstr "عرض \"%s\""
1511
-
1512
- #: modules/containers/dummy.php:34 modules/containers/dummy.php:45
1513
- msgid "I don't know how to edit a '%s' [%d]."
1514
- msgstr "لا أعرف كيف أحرر '%s' [%d]."
1515
-
1516
- #: modules/extras/dailymotion-embed.php:23
1517
- msgid "DailyMotion Video"
1518
- msgstr "دايلي موشن فيديو"
1519
-
1520
- #: modules/extras/dailymotion-embed.php:24
1521
- msgid "Embedded DailyMotion video"
1522
- msgstr "تضمين فيديوهات دايلي موشن"
1523
-
1524
- #: modules/extras/embed-parser-base.php:196
1525
- msgid ""
1526
- "Embedded videos can't be edited using Broken Link Checker. Please edit or "
1527
- "replace the video in question manually."
1528
- msgstr ""
1529
- "الفيديوهات المضمنة لا يمكن أن تحرر بإستخدام هذه الإضافة. الرجاء تحرير أو "
1530
- "استبدال الفيديو يدوياً."
1531
-
1532
- #: modules/extras/fileserve.php:55
1533
- msgid "Using FileServe API"
1534
- msgstr "استخدام FileServe API"
1535
-
1536
- #: modules/extras/fileserve.php:112 modules/extras/mediafire.php:91
1537
- #: modules/extras/mediafire.php:96 modules/extras/megaupload.php:81
1538
- #: modules/extras/megaupload.php:123 modules/extras/rapidshare.php:139
1539
- msgid "Not Found"
1540
- msgstr "لم يعثر عليه"
1541
-
1542
- #: modules/extras/fileserve.php:115
1543
- msgid "FileServe : %d %s"
1544
- msgstr "FileServe : %d %s"
1545
-
1546
- #: modules/extras/googlevideo-embed.php:24
1547
- msgid "GoogleVideo Video"
1548
- msgstr "فيديوهات جوجل فيديو"
1549
-
1550
- #: modules/extras/googlevideo-embed.php:25
1551
- msgid "Embedded GoogleVideo video"
1552
- msgstr "تضمين فيديوهات جوجل فيديو"
1553
-
1554
- #: modules/extras/megaupload.php:130
1555
- msgid "File Temporarily Unavailable"
1556
- msgstr "الملف غير متوفر مؤقتاً"
1557
-
1558
- #: modules/extras/megaupload.php:136
1559
- msgid "API Error"
1560
- msgstr "خطأ API "
1561
-
1562
- #: modules/extras/megavideo-embed.php:24
1563
- msgid "Megavideo Video"
1564
- msgstr "فيديوهات ميجا فيديو"
1565
-
1566
- #: modules/extras/megavideo-embed.php:25
1567
- msgid "Embedded Megavideo video"
1568
- msgstr "تضمين فيديوهات ميجا فيديو"
1569
-
1570
- #: modules/extras/rapidshare.php:51
1571
- msgid "Using RapidShare API"
1572
- msgstr "استخدام RapidShare API"
1573
-
1574
- #: modules/extras/rapidshare.php:158
1575
- msgid "RS Server Down"
1576
- msgstr "سيرفر RS لا يعمل بكفاءة"
1577
-
1578
- #: modules/extras/rapidshare.php:165
1579
- msgid "File Blocked"
1580
- msgstr "ملف محظور"
1581
-
1582
- #: modules/extras/rapidshare.php:172
1583
- msgid "File Locked"
1584
- msgstr "ملف مقفول"
1585
-
1586
- #: modules/extras/rapidshare.php:183
1587
- msgid "RapidShare : %s"
1588
- msgstr "RapidShare : %s"
1589
-
1590
- #: modules/extras/rapidshare.php:189
1591
- msgid "RapidShare API error: %s"
1592
- msgstr "خطأ RapidShare API: %s"
1593
-
1594
- #: modules/extras/vimeo-embed.php:24
1595
- msgid "Vimeo Video"
1596
- msgstr "فيديوهات فيميو"
1597
-
1598
- #: modules/extras/vimeo-embed.php:25
1599
- msgid "Embedded Vimeo video"
1600
- msgstr "تضمين فيديوهات فيميو"
1601
-
1602
- #: modules/extras/youtube-embed.php:24 modules/extras/youtube-iframe.php:25
1603
- msgid "YouTube Video"
1604
- msgstr "فيديوهات يوتيوب"
1605
-
1606
- #: modules/extras/youtube-embed.php:25 modules/extras/youtube-iframe.php:26
1607
- msgid "Embedded YouTube video"
1608
- msgstr "تضمين فيديوهات يوتيوب"
1609
-
1610
- #: modules/extras/youtube.php:73 modules/extras/youtube.php:76
1611
- msgid "Video Not Found"
1612
- msgstr "الفيديم لم يتم العثور عليه"
1613
-
1614
- #: modules/extras/youtube.php:84
1615
- msgid "Video Removed"
1616
- msgstr "تم ازالة الفيديو"
1617
-
1618
- #: modules/extras/youtube.php:92
1619
- msgid "Invalid Video ID"
1620
- msgstr "رقم الآي دي الخاص بالفيديو غير صالح"
1621
-
1622
- #: modules/extras/youtube.php:104
1623
- msgid "Video OK"
1624
- msgstr "الفيديو يبدو جيداً!"
1625
-
1626
- #: modules/extras/youtube.php:105 modules/extras/youtube.php:132
1627
- msgid "OK"
1628
- msgstr "موافق"
1629
-
1630
- #: modules/extras/youtube.php:118
1631
- msgid "Video status : %s%s"
1632
- msgstr "حالة الفيديو: %s%s"
1633
-
1634
- #: modules/extras/youtube.php:137
1635
- msgid "Video Restricted"
1636
- msgstr "الفيديو محظور"
1637
-
1638
- #: modules/extras/youtube.php:154
1639
- msgid "Unknown YouTube API response received."
1640
- msgstr "رد غير معروف تم استلامه من YouTube API "
1641
-
1642
- #: modules/parsers/image.php:159
1643
- msgid "Image"
1644
- msgstr "صورة"
1645
-
1646
- #: modules/parsers/metadata.php:117
1647
- msgid "Custom field"
1648
- msgstr "حقل مُخصص"
1649
-
1650
- #. Plugin URI of the plugin/theme
1651
- msgid "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
1652
- msgstr "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
1653
-
1654
- #. Description of the plugin/theme
1655
- msgid ""
1656
- "Checks your blog for broken links and missing images and notifies you on the "
1657
- "dashboard if any are found."
1658
- msgstr ""
1659
- "فحص شامل لمدونتك عن الروابط والصور المفقودة والمعطوبة، وتنبيهك في اللوحة "
1660
- "الرئيسية عن أي روابط يتم العثور عليها."
1661
-
1662
- #. Author of the plugin/theme
1663
- msgid "Janis Elsts"
1664
- msgstr "Janis Elsts"
1665
-
1666
- #. Author URI of the plugin/theme
1667
- msgid "http://w-shadow.com/blog/"
1668
- msgstr "http://w-shadow.com/blog/"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/broken-link-checker-be_BY.mo DELETED
Binary file
languages/broken-link-checker-be_BY.po DELETED
@@ -1,700 +0,0 @@
1
- # Broken Link Checker POT file
2
- # Copyright (C) 2009 Janis Elsts
3
- # This file is distributed under the same license as the BLC package.
4
- # Janis Elsts <whiteshadow@w-shadow.com>, 2009.
5
- #
6
- msgid ""
7
- msgstr ""
8
- "Project-Id-Version: Broken Link Checker\n"
9
- "Report-Msgid-Bugs-To: \n"
10
- "POT-Creation-Date: 2009-11-10 14:44+0300\n"
11
- "PO-Revision-Date: 2009-11-23 00:30+0200\n"
12
- "Last-Translator: Fat Cow <zhr@tut.by>\n"
13
- "Language-Team: FatCow <zhr@tut.by>\n"
14
- "MIME-Version: 1.0\n"
15
- "Content-Type: text/plain; charset=UTF-8\n"
16
- "Content-Transfer-Encoding: 8bit\n"
17
- "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11) ? 0 : ((n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20)) ? 1 : 2);\n"
18
- "X-Poedit-Language: Belarusian\n"
19
- "X-Poedit-Country: BELARUS\n"
20
- "X-Poedit-KeywordsList: __;_c;_e;_n:1,2;_nc:1,2;_n_noop:1,2;__ngettext:1,2;__ngettext_noop:1,2\n"
21
- "X-Poedit-Basepath: .\n"
22
- "X-Poedit-SourceCharset: utf-8\n"
23
- "X-Poedit-SearchPath-0: D:\\Projects\\l10n-ru\\trunk\\wp-plugins\\broken-link-checker\n"
24
-
25
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:132
26
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1284
27
- msgid "Loading..."
28
- msgstr "Загрузка..."
29
-
30
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:155
31
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:569
32
- msgid "[ Network error ]"
33
- msgstr "[ Памылка сеткі ]"
34
-
35
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:180
36
- msgid "Automatically expand the widget if broken links have been detected"
37
- msgstr "Аўтаматычна расчыніць виджет, калі выяўлены няправільныя спасылкі"
38
-
39
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:359
40
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:368
41
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:386
42
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:400
43
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:907
44
- #, php-format
45
- msgid "Database error : %s"
46
- msgstr "Памылка базы дадзеных: %s"
47
-
48
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:423
49
- msgid "Link Checker Settings"
50
- msgstr "Налада праверкі спасылак"
51
-
52
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:424
53
- msgid "Link Checker"
54
- msgstr "Праверка спасылак"
55
-
56
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:435
57
- msgid "View Broken Links"
58
- msgstr "Прагледзець няправільныя спасылкі"
59
-
60
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:436
61
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:833
62
- msgid "Broken Links"
63
- msgstr "Няправільныя спасылкі"
64
-
65
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:453
66
- msgid "Settings"
67
- msgstr "Налады"
68
-
69
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:533
70
- msgid "Broken Link Checker Options"
71
- msgstr "Налада праверкі спасылак"
72
-
73
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:546
74
- msgid "Status"
75
- msgstr "Стан"
76
-
77
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:548
78
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:771
79
- msgid "Show debug info"
80
- msgstr "Паказаць адладкавую інфармацыю"
81
-
82
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:582
83
- msgid "Re-check all pages"
84
- msgstr "Пераправерыць усе старонкі"
85
-
86
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:606
87
- msgid "Check each link"
88
- msgstr "Перыядычнасць праверак"
89
-
90
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:611
91
- #, php-format
92
- msgid "Every %s hours"
93
- msgstr "Кожныя %s ч."
94
-
95
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:620
96
- msgid "Existing links will be checked this often. New links will usually be checked ASAP."
97
- msgstr "Спасылкі ў старых запісах будуць пераправярацца з паказаным інтэрвалам. Новыя запісы звычайна правяраюцца па меры іх з'яўлення."
98
-
99
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:627
100
- msgid "Broken link CSS"
101
- msgstr "Стыль няправільных спасылак"
102
-
103
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:632
104
- msgid "Apply <em>class=\"broken_link\"</em> to broken links"
105
- msgstr "Дастасаваць да няправільным спасылкам <em>class=&quot;broken_link&quot;</em>"
106
-
107
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:644
108
- msgid "Exclusion list"
109
- msgstr "Спіс выключэнняў"
110
-
111
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:645
112
- msgid "Don't check links where the URL contains any of these words (one per line) :"
113
- msgstr "Не правяраць спасылкі, якія ўтрымоўваюць любое з гэтых слоў (па адным на радок):"
114
-
115
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:655
116
- msgid "Custom fields"
117
- msgstr "Адвольныя палі"
118
-
119
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:656
120
- msgid "Check URLs entered in these custom fields (one per line) :"
121
- msgstr "Правяраць URL у гэтых адвольных палях (па адным на радок):"
122
-
123
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:667
124
- msgid "Advanced"
125
- msgstr "Дадаткова"
126
-
127
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:673
128
- msgid "Timeout"
129
- msgstr "Час чакання загрузкі"
130
-
131
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:679
132
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:735
133
- #, php-format
134
- msgid "%s seconds"
135
- msgstr "%s сек."
136
-
137
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:688
138
- msgid "Links that take longer than this to load will be marked as broken."
139
- msgstr "Спасылкі, час загрузкі якіх больш, чым паказанае, будуць пазначаны як няправільныя."
140
-
141
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:697
142
- msgid "Custom temporary directory"
143
- msgstr "Адвольная часавая дырэкторыя"
144
-
145
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:706
146
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2047
147
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2088
148
- msgid "OK"
149
- msgstr "ОК"
150
-
151
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:709
152
- msgid "Error : This directory isn't writable by PHP."
153
- msgstr "Памылка : Гэта дырэкторыя недаступная для запісу пасродкам PHP."
154
-
155
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:714
156
- msgid "Error : This directory doesn't exist."
157
- msgstr "Памылка: Гэта дырэкторыя не існуе."
158
-
159
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:722
160
- msgid "Set this field if you want the plugin to use a custom directory for its lockfiles. Otherwise, leave it blank."
161
- msgstr "Тут вы можаце паказаць адвольную дырэкторыю для захоўвання часавых файлаў убудовы. Пакіньце гэта поле пустым, каб выкарыстоўваць стандартнае значэнне."
162
-
163
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:729
164
- msgid "Max. execution time"
165
- msgstr "Максімальны час пошуку"
166
-
167
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:746
168
- 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."
169
- msgstr "Убудова стварае фонавы працэс, які шукае гіперспасылкі ў вашым часопісе і правярае іх працаздольнасць. Гэты параметр вызначае як доўга можа працаваць такі працэс."
170
-
171
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:756
172
- msgid "Save Changes"
173
- msgstr "Захаваць змены"
174
-
175
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:769
176
- msgid "Hide debug info"
177
- msgstr "Схаваць адладкавую інфармацыю"
178
-
179
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:832
180
- msgid "Broken"
181
- msgstr "Няправільныя"
182
-
183
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:834
184
- msgid "No broken links found"
185
- msgstr "Няправільных спасылак няма"
186
-
187
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:838
188
- msgid "Redirects"
189
- msgstr "Перанакіраваныя"
190
-
191
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:839
192
- msgid "Redirected Links"
193
- msgstr "Перанакіраваныя спасылкі"
194
-
195
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:840
196
- msgid "No redirects found"
197
- msgstr "Перанакіраваных спасылак няма"
198
-
199
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:845
200
- msgid "All"
201
- msgstr "Усё"
202
-
203
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:846
204
- msgid "Detected Links"
205
- msgstr "Усе спасылкі"
206
-
207
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:847
208
- msgid "No links found (yet)"
209
- msgstr "Спасылак няма (пакуль)"
210
-
211
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:966
212
- msgid "&laquo;"
213
- msgstr "&laquo;"
214
-
215
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:967
216
- msgid "&raquo;"
217
- msgstr "&raquo;"
218
-
219
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:974
220
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1149
221
- #, php-format
222
- msgid "Displaying %s&#8211;%s of <span class=\"current-link-count\">%s</span>"
223
- msgstr "Паказана %s&#8211;%s з <span class=\"current-link-count\">%s</span>"
224
-
225
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:996
226
- msgid "Source"
227
- msgstr "Крыніца"
228
-
229
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:998
230
- msgid "Link Text"
231
- msgstr "Тып&nbsp;/&nbsp;Тэкст"
232
-
233
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:999
234
- msgid "URL"
235
- msgstr "URL"
236
-
237
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1025
238
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1031
239
- msgid "Edit this post"
240
- msgstr "Рэдагаваць гэты запіс"
241
-
242
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1031
243
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1046
244
- msgid "Edit"
245
- msgstr "Рэдагаваць"
246
-
247
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1032
248
- msgid "Delete this post"
249
- msgstr "Выдаліць гэты запіс"
250
-
251
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1032
252
- #, php-format
253
- msgid ""
254
- "You are about to delete this post '%s'\n"
255
- " 'Cancel' to stop, 'OK' to delete."
256
- msgstr ""
257
- "Вы збіраецеся выдаліць запіс '%s'.\n"
258
- "Націсніце 'Адмена', каб спыніць, ці 'OK' для выдалення."
259
-
260
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1032
261
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1047
262
- msgid "Delete"
263
- msgstr "Выдаліць"
264
-
265
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1034
266
- #, php-format
267
- msgid "View \"%s\""
268
- msgstr "Паглядзець &laquo;%s&raquo;"
269
-
270
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1034
271
- msgid "View"
272
- msgstr "Паглядзець"
273
-
274
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1041
275
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1046
276
- msgid "Edit this bookmark"
277
- msgstr "Рэдагаваць гэту закладку"
278
-
279
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1047
280
- #, php-format
281
- msgid ""
282
- "You are about to delete this link '%s'\n"
283
- " 'Cancel' to stop, 'OK' to delete."
284
- msgstr ""
285
- "Вы збіраецеся выдаліць гэту спасылку: '%s'\n"
286
- "Націсніце 'Адмена', каб спыніць, ці 'OK' для выдалення."
287
-
288
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1056
289
- msgid "[An orphaned link! This is a bug.]"
290
- msgstr "[Спасылка-сірата! Гэта памылка.]"
291
-
292
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1070
293
- msgid "Image"
294
- msgstr "Малюнак"
295
-
296
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1081
297
- msgid "Custom field"
298
- msgstr "Адвольнае поле"
299
-
300
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1089
301
- msgid "Bookmark"
302
- msgstr "Закладка"
303
-
304
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1104
305
- msgid "Show more info about this link"
306
- msgstr "Паказаць падрабязную справаздачу пра гэту спасылку"
307
-
308
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1104
309
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2381
310
- msgid "Details"
311
- msgstr "Паглядзець справаздачу"
312
-
313
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1106
314
- msgid "Remove this link from all posts"
315
- msgstr "Выдаліць гэту спасылку ва ўсіх запісах"
316
-
317
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1107
318
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1371
319
- msgid "Unlink"
320
- msgstr "Выдаліць спасылку"
321
-
322
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1110
323
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1401
324
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1412
325
- msgid "Excluded"
326
- msgstr "Выключана"
327
-
328
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1112
329
- msgid "Add this URL to the exclusion list"
330
- msgstr "Дадаць гэты URL у спіс выключэнняў"
331
-
332
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1113
333
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1415
334
- msgid "Exclude"
335
- msgstr "Выключыць"
336
-
337
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1116
338
- msgid "Edit link URL"
339
- msgstr "Рэдагаваць URL спасылкі"
340
-
341
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1116
342
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1340
343
- msgid "Edit URL"
344
- msgstr "Рэдагаваць URL"
345
-
346
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1122
347
- msgid "Cancel URL editing"
348
- msgstr "Адмяніць рэдагаванне URL"
349
-
350
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1122
351
- msgid "Cancel"
352
- msgstr "Адмена"
353
-
354
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1133
355
- msgid "Remove this link from the list of broken links and mark it as valid"
356
- msgstr "Выдаліць гэта паведамленне і пазначыць спасылку як правільную"
357
-
358
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1135
359
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1204
360
- msgid "Discard"
361
- msgstr "Скінуць"
362
-
363
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1180
364
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1347
365
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1384
366
- msgid "Wait..."
367
- msgstr "Пачакайце..."
368
-
369
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1238
370
- msgid "Save URL"
371
- msgstr "Захаваць URL"
372
-
373
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1248
374
- msgid "Saving changes..."
375
- msgstr "Захаванне змен..."
376
-
377
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1437
378
- msgid "Log"
379
- msgstr "Справаздача пра праверку"
380
-
381
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1445
382
- msgid "Post published on"
383
- msgstr "Дата публікацыі"
384
-
385
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1450
386
- msgid "Link last checked"
387
- msgstr "Дата апошняй праверкі"
388
-
389
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1454
390
- msgid "Never"
391
- msgstr "Ніколі"
392
-
393
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1460
394
- msgid "HTTP code"
395
- msgstr "Код HTTP"
396
-
397
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1465
398
- msgid "Response time"
399
- msgstr "Час водгуку"
400
-
401
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1467
402
- #, php-format
403
- msgid "%2.3f seconds"
404
- msgstr "%2.3f сек."
405
-
406
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1470
407
- msgid "Final URL"
408
- msgstr "Канчатковы URL"
409
-
410
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1475
411
- msgid "Redirect count"
412
- msgstr "Кол-у перанакіраванняў"
413
-
414
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1480
415
- msgid "Instance count"
416
- msgstr "Кол-у асобнікаў"
417
-
418
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1489
419
- #, php-format
420
- msgid "This link has failed %d time."
421
- msgid_plural "This link has failed %d times."
422
- msgstr[0] "Гэта спасылка не прайшла праверку %d раз."
423
- msgstr[1] "Гэта спасылка не прайшла праверку %d разу."
424
- msgstr[2] "Гэта спасылка не прайшла праверку %d раз."
425
-
426
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1879
427
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2206
428
- msgid "This link wasn't checked because a matching keyword was found on your exclusion list."
429
- msgstr "Гэта спасылка не была праверана, паколькі знаходзіцца ў спісе выключэнняў."
430
-
431
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1921
432
- msgid "View broken links"
433
- msgstr "Паказаць няправільныя спасылкі"
434
-
435
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1922
436
- #, php-format
437
- msgid "Found %d broken link"
438
- msgid_plural "Found %d broken links"
439
- msgstr[0] "Знойдзена %d няправільная спасылка"
440
- msgstr[1] "Знойдзены %d няправільныя спасылкі"
441
- msgstr[2] "Знойдзена %d няправільных спасылак"
442
-
443
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1928
444
- msgid "No broken links found."
445
- msgstr "Цяпер не знойдзена ніводнай няправільнай спасылкі."
446
-
447
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1935
448
- #, php-format
449
- msgid "%d URL in the work queue"
450
- msgid_plural "%d URLs in the work queue"
451
- msgstr[0] "Цяпер у чэргі на праверку знаходзіцца %d URL."
452
- msgstr[1] "Цяпер у чэргі на праверку знаходзяцца %d URL."
453
- msgstr[2] "Цяпер у чэргі на праверку знаходзяцца %d URL."
454
-
455
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1938
456
- msgid "No URLs in the work queue."
457
- msgstr "Цяпер у чэргі на праверку няма ніводнага URL."
458
-
459
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1944
460
- #, php-format
461
- msgid "Detected %d unique URL"
462
- msgid_plural "Detected %d unique URLs"
463
- msgstr[0] "Выяўлены %d унікальны URL"
464
- msgstr[1] "Выяўлены %d унікальных URL"
465
- msgstr[2] "Выяўлена %d унікальных URL"
466
-
467
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1945
468
- #, php-format
469
- msgid "in %d link"
470
- msgid_plural "in %d links"
471
- msgstr[0] "у %d спасылцы"
472
- msgstr[1] "у %d спасылках"
473
- msgstr[2] "у %d спасылках"
474
-
475
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1950
476
- msgid "and still searching..."
477
- msgstr ". Пошук працягваецца..."
478
-
479
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1956
480
- msgid "Searching your blog for links..."
481
- msgstr "Пошук спасылак у вашым часопісе..."
482
-
483
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:1958
484
- msgid "No links detected."
485
- msgstr "Не выяўлена ніводнай спасылкі."
486
-
487
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2027
488
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2059
489
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2102
490
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2183
491
- msgid "You're not allowed to do that!"
492
- msgstr "Вам забаронена рабіць гэта!"
493
-
494
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2035
495
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2069
496
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2112
497
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2193
498
- #, php-format
499
- msgid "Oops, I can't find the link %d"
500
- msgstr "Выбачыце, але спасылку %d немагчыма знайсці"
501
-
502
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2043
503
- msgid "This link was manually marked as working by the user."
504
- msgstr "Гэта спасылка была ўручную пазначана карыстачом як працоўная."
505
-
506
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2049
507
- msgid "Oops, couldn't modify the link!"
508
- msgstr "Выбачыце, але спасылку немагчыма адрэдагаваць!"
509
-
510
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2052
511
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2129
512
- msgid "Error : link_id not specified"
513
- msgstr "Памылка: не паказаны ID спасылкі"
514
-
515
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2076
516
- msgid "Oops, the new URL is invalid!"
517
- msgstr "Выбачыце, але гэты новы URL таксама няправільны!"
518
-
519
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2085
520
- msgid "An unexpected error occured!"
521
- msgstr "Адбылася нечаканая памылка!"
522
-
523
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2094
524
- msgid "Error : link_id or new_url not specified"
525
- msgstr "Памылка: не паказаны ID спасылкі ці яе новы URL"
526
-
527
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2119
528
- #, php-format
529
- msgid "URL %s was removed."
530
- msgstr "URL %s выдалены."
531
-
532
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2123
533
- msgid "The plugin failed to remove the link."
534
- msgstr "Гэту спасылку не атрымалася выдаліць."
535
-
536
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2138
537
- msgid "You don't have sufficient privileges to access this information!"
538
- msgstr "Вам забаронены доступ да гэтай інфармацыі!"
539
-
540
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2151
541
- msgid "Error : link ID not specified"
542
- msgstr "Памылка: не паказаны ID спасылкі"
543
-
544
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2175
545
- #, php-format
546
- msgid "Failed to load link details (%s)"
547
- msgstr "Не атрымалася загрузіць падрабязнасці пра спасылку (%s)"
548
-
549
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2213
550
- #, php-format
551
- msgid "URL %s added to the exclusion list"
552
- msgstr "URL %s дададзены ў спіс выключэнняў"
553
-
554
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2217
555
- msgid "Link ID not specified"
556
- msgstr "Не паказаны ID спасылкі"
557
-
558
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2367
559
- #, php-format
560
- msgid "The current temporary directory is not accessible; please <a href=\"%s\">set a different one</a>."
561
- msgstr "Бягучая дырэкторыя для захоўвання часавых файлаў недаступная; паспрабуйце <a href=\"%s\">абраць іншую</a>."
562
-
563
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2372
564
- #, php-format
565
- msgid "Please make the directory <code>%1$s</code> writable by plugins or <a href=\"%2$s\">set a custom temporary directory</a>."
566
- msgstr "Калі ласка, зрабіце дырэкторыю <code>%1$s</code> даступнай для запісу ці <a href=\"%2$s\">абярыце іншую</a>."
567
-
568
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2379
569
- msgid "Broken Link Checker can't create a lockfile."
570
- msgstr "Убудова не можа стварыць лок-файл."
571
-
572
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2384
573
- 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."
574
- msgstr "Працэдура праверкі спасылак даволі рэсурсаёмістая, і для таго, каб яна запускалася толькі ў адным асобніку на адзінку часу, Broken Link Checker выкарыстоўвае адмысловы механізм блакавання, дадзеныя пра стан якога захоўваюцца ў адмысловых лок-файлах. Нажаль, убудова не змагла выявіць падыходную дырэкторыю для месцавання гэтых дадзеных &mdash; знайсці стандартную tmp-дырэкторыю вашага сервера не атрымалася, а ўласная дырэкторыя ўбудовы не даступная для запісу пасродкам PHP. Каб вырашыць гэту праблему, калі ласка, зрабіце дырэкторыю ўбудовы даступнай для запісу ці пакажыце ў частцы налад адвольную дырэкторыю для захоўвання часавых файлаў."
575
-
576
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2404
577
- msgid "PHP version"
578
- msgstr "Версія PHP"
579
-
580
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2410
581
- msgid "MySQL version"
582
- msgstr "Версія MySQL"
583
-
584
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2423
585
- msgid "You have an old version of CURL. Redirect detection may not work properly."
586
- msgstr "Вы выкарыстоўваеце старую версію CURL. Выяўленне перанакіраванняў можа працаваць некарэктна."
587
-
588
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2435
589
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2451
590
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2456
591
- msgid "Not installed"
592
- msgstr "Не ўсталявана"
593
-
594
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2438
595
- msgid "CURL version"
596
- msgstr "Версія CURL"
597
-
598
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2444
599
- msgid "Installed"
600
- msgstr "Усталявана"
601
-
602
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2457
603
- msgid "You must have either CURL or Snoopy installed for the plugin to work!"
604
- msgstr "Для таго, каб гэта ўбудова магла працаваць, у вас павінны быць усталяваны або CURL, або Snoopy!"
605
-
606
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2468
607
- msgid "On"
608
- msgstr "Вкл."
609
-
610
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2469
611
- msgid "Redirects may be detected as broken links when safe_mode is on."
612
- msgstr "Калі параметр safe mode уключаны, то перанакіраванні могуць быць успрыняты як няправільныя спасылкі."
613
-
614
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2474
615
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2488
616
- msgid "Off"
617
- msgstr "Выкл."
618
-
619
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2482
620
- #, php-format
621
- msgid "On ( %s )"
622
- msgstr "Вкл. ( %s )"
623
-
624
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2483
625
- msgid "Redirects may be detected as broken links when open_basedir is on."
626
- msgstr "Калі параметр open_basedir уключаны, то перанакіраванні могуць быць успрыняты як няправільныя спасылкі."
627
-
628
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/core.php:2502
629
- msgid "Can't create a lockfile. Please specify a custom temporary directory."
630
- msgstr "Немагчыма стварыць лок-файл. Калі ласка, абярыце іншую дырэкторыю для захоўвання часавых файлаў."
631
-
632
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:212
633
- #, php-format
634
- msgid "First try : %d"
635
- msgstr "Першая спроба: %d"
636
-
637
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:214
638
- msgid "First try : 0 (No response)"
639
- msgstr "Першая спроба: 0 (Няма адказу)"
640
-
641
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:222
642
- msgid "Trying a second time with different settings..."
643
- msgstr "Які спрабуецца яшчэ раз, з іншымі наладамі..."
644
-
645
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:237
646
- #, php-format
647
- msgid "Second try : %d"
648
- msgstr "Другая спроба: %d"
649
-
650
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:239
651
- msgid "Second try : 0 (No response)"
652
- msgstr "Другая спроба: 0 (Няма адказу)"
653
-
654
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:265
655
- msgid "Using Snoopy"
656
- msgstr "Выкарыстоўваны Snoopy"
657
-
658
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:285
659
- msgid "Request timed out."
660
- msgstr "Час чакання адказу на запыт мінула."
661
-
662
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:304
663
- msgid "Link is valid."
664
- msgstr "Спасылка працуе."
665
-
666
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:309
667
- msgid "Link is broken."
668
- msgstr "Спасылка не працуе."
669
-
670
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:313
671
- msgid "Most likely the connection timed out or the domain doesn't exist."
672
- msgstr "Мяркуючы па ўсім, злучэнне разарвана ці гэты дамен не існуе"
673
-
674
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:354
675
- #, php-format
676
- msgid "Error adding link %s : %s"
677
- msgstr "Памылка пры даданні спасылкі %s: %s"
678
-
679
- #: D:\Projects\l10n-ru\trunk\wp-plugins\broken-link-checker/link-classes.php:374
680
- #, php-format
681
- msgid "Error updating link %d : %s"
682
- msgstr "Памылка пры абнаўленні спасылкі %d: %s"
683
-
684
- #~ msgid "Broken Link Checker"
685
- #~ msgstr "Праверка спасылак"
686
- #~ msgid ""
687
- #~ "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
688
- #~ msgstr ""
689
- #~ "http://w-shadow.com/blog/2007/08/05/broken-link-checker-for-wordpress/"
690
- #~ msgid ""
691
- #~ "Checks your posts for broken links and missing images and notifies you on "
692
- #~ "the dashboard if any are found."
693
- #~ msgstr ""
694
- #~ "Правярае ваш часопіс на няправільныя спасылкі на сайты ці малюнкі і "
695
- #~ "выводзіць паведамленне ў кансоль, калі яны ёсць."
696
- #~ msgid "Janis Elsts"
697
- #~ msgstr "Janis Elsts"
698
- #~ msgid "http://w-shadow.com/blog/"
699
- #~ msgstr "http://w-shadow.com/blog/"
700
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/broken-link-checker-cs_CZ.mo DELETED
Binary file
languages/broken-link-checker-cs_CZ.po DELETED
@@ -1,872 +0,0 @@
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ě