PageRenderTime 75ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/broken-link-checker/core/core.php

https://bitbucket.org/lgorence/quickpress
PHP | 3100 lines | 2270 code | 374 blank | 456 comment | 238 complexity | 41a237c91dd4ed7ae616689aa36ee5cb MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, AGPL-1.0
  1. <?php
  2. /**
  3. * Simple function to replicate PHP 5 behaviour
  4. */
  5. if ( !function_exists( 'microtime_float' ) ) {
  6. function microtime_float()
  7. {
  8. list($usec, $sec) = explode(" ", microtime());
  9. return ((float)$usec + (float)$sec);
  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. if (!class_exists('wsBrokenLinkChecker')) {
  16. class wsBrokenLinkChecker {
  17. var $conf;
  18. var $loader;
  19. var $my_basename = '';
  20. var $db_version; //The required version of the plugin's DB schema.
  21. var $execution_start_time; //Used for a simple internal execution timer in start_timer()/execution_time()
  22. /**
  23. * wsBrokenLinkChecker::wsBrokenLinkChecker()
  24. * Class constructor
  25. *
  26. * @param string $loader The fully qualified filename of the loader script that WP identifies as the "main" plugin file.
  27. * @param blcConfigurationManager $conf An instance of the configuration manager
  28. * @return void
  29. */
  30. function wsBrokenLinkChecker ( $loader, &$conf ) {
  31. global $wpdb;
  32. $this->db_version = BLC_DATABASE_VERSION;
  33. $this->conf = &$conf;
  34. $this->loader = $loader;
  35. $this->my_basename = plugin_basename( $this->loader );
  36. $this->load_language();
  37. //Unlike the activation hook, the deactivation callback *can* be registered in this file
  38. //because deactivation happens after this class has already been instantiated (durinng the
  39. //'init' action).
  40. register_deactivation_hook($loader, array(&$this, 'deactivation'));
  41. add_action('admin_menu', array(&$this,'admin_menu'));
  42. //Load jQuery on Dashboard pages (probably redundant as WP already does that)
  43. add_action('admin_print_scripts', array(&$this,'admin_print_scripts'));
  44. //The dashboard widget
  45. add_action('wp_dashboard_setup', array(&$this, 'hook_wp_dashboard_setup'));
  46. //AJAXy hooks
  47. add_action( 'wp_ajax_blc_full_status', array(&$this,'ajax_full_status') );
  48. add_action( 'wp_ajax_blc_dashboard_status', array(&$this,'ajax_dashboard_status') );
  49. add_action( 'wp_ajax_blc_work', array(&$this,'ajax_work') );
  50. add_action( 'wp_ajax_blc_discard', array(&$this,'ajax_discard') );
  51. add_action( 'wp_ajax_blc_edit', array(&$this,'ajax_edit') );
  52. add_action( 'wp_ajax_blc_link_details', array(&$this,'ajax_link_details') );
  53. add_action( 'wp_ajax_blc_unlink', array(&$this,'ajax_unlink') );
  54. add_action( 'wp_ajax_blc_current_load', array(&$this,'ajax_current_load') );
  55. add_action( 'wp_ajax_blc_dismiss', array($this, 'ajax_dismiss') );
  56. add_action( 'wp_ajax_blc_undismiss', array($this, 'ajax_undismiss') );
  57. //Add/remove Cron events
  58. $this->setup_cron_events();
  59. //Set hooks that listen for our Cron actions
  60. add_action('blc_cron_email_notifications', array( &$this, 'maybe_send_email_notifications' ));
  61. add_action('blc_cron_check_links', array(&$this, 'cron_check_links'));
  62. add_action('blc_cron_database_maintenance', array(&$this, 'database_maintenance'));
  63. add_action('blc_cron_check_news', array(&$this, 'check_news'));
  64. //Set the footer hook that will call the worker function via AJAX.
  65. add_action('admin_footer', array(&$this,'admin_footer'));
  66. //Add a "Screen Options" panel to the "Broken Links" page
  67. add_screen_options_panel(
  68. 'blc-screen-options',
  69. '',
  70. array(&$this, 'screen_options_html'),
  71. 'tools_page_view-broken-links',
  72. array(&$this, 'ajax_save_screen_options'),
  73. true
  74. );
  75. }
  76. /**
  77. * Output the script that runs the link monitor while the Dashboard is open.
  78. *
  79. * @return void
  80. */
  81. function admin_footer(){
  82. if ( !$this->conf->options['run_in_dashboard'] ){
  83. return;
  84. }
  85. ?>
  86. <!-- wsblc admin footer -->
  87. <script type='text/javascript'>
  88. (function($){
  89. //(Re)starts the background worker thread
  90. function blcDoWork(){
  91. $.post(
  92. "<?php echo admin_url('admin-ajax.php'); ?>",
  93. {
  94. 'action' : 'blc_work'
  95. }
  96. );
  97. }
  98. //Call it the first time
  99. blcDoWork();
  100. //Then call it periodically every X seconds
  101. setInterval(blcDoWork, <?php echo (intval($this->conf->options['max_execution_time']) + 1 )*1000; ?>);
  102. })(jQuery);
  103. </script>
  104. <!-- /wsblc admin footer -->
  105. <?php
  106. }
  107. /**
  108. * Check if an URL matches the exclusion list.
  109. *
  110. * @param string $url
  111. * @return bool
  112. */
  113. function is_excluded($url){
  114. if (!is_array($this->conf->options['exclusion_list'])) return false;
  115. foreach($this->conf->options['exclusion_list'] as $excluded_word){
  116. if (stristr($url, $excluded_word)){
  117. return true;
  118. }
  119. }
  120. return false;
  121. }
  122. function dashboard_widget(){
  123. ?>
  124. <p id='wsblc_activity_box'><?php _e('Loading...', 'broken-link-checker'); ?></p>
  125. <script type='text/javascript'>
  126. jQuery( function($){
  127. var blc_was_autoexpanded = false;
  128. function blcDashboardStatus(){
  129. $.getJSON(
  130. "<?php echo admin_url('admin-ajax.php'); ?>",
  131. {
  132. 'action' : 'blc_dashboard_status',
  133. 'random' : Math.random()
  134. },
  135. function (data, textStatus){
  136. if ( data && ( typeof(data.text) != 'undefined' ) ) {
  137. $('#wsblc_activity_box').html(data.text);
  138. <?php if ( $this->conf->options['autoexpand_widget'] ) { ?>
  139. //Expand the widget if there are broken links.
  140. //Do this only once per pageload so as not to annoy the user.
  141. if ( !blc_was_autoexpanded && ( data.status.broken_links > 0 ) ){
  142. $('#blc_dashboard_widget.postbox').removeClass('closed');
  143. blc_was_autoexpanded = true;
  144. }
  145. <?php } ?>
  146. } else {
  147. $('#wsblc_activity_box').html('<?php _e('[ Network error ]', 'broken-link-checker'); ?>');
  148. }
  149. setTimeout( blcDashboardStatus, 120*1000 ); //...update every two minutes
  150. }
  151. );
  152. }
  153. blcDashboardStatus();//Call it the first time
  154. } );
  155. </script>
  156. <?php
  157. }
  158. function dashboard_widget_control(
  159. /** @noinspection PhpUnusedParameterInspection */ $widget_id, $form_inputs = array()
  160. ){
  161. if ( 'POST' == $_SERVER['REQUEST_METHOD'] && 'blc_dashboard_widget' == $_POST['widget_id'] ) {
  162. //It appears $form_inputs isn't used in the current WP version, so lets just use $_POST
  163. $this->conf->options['autoexpand_widget'] = !empty($_POST['blc-autoexpand']);
  164. $this->conf->save_options();
  165. }
  166. ?>
  167. <p><label for="blc-autoexpand">
  168. <input id="blc-autoexpand" name="blc-autoexpand" type="checkbox" value="1" <?php if ( $this->conf->options['autoexpand_widget'] ) echo 'checked="checked"'; ?> />
  169. <?php _e('Automatically expand the widget if broken links have been detected', 'broken-link-checker'); ?>
  170. </label></p>
  171. <?php
  172. }
  173. function admin_print_scripts(){
  174. //jQuery is used for triggering the link monitor via AJAX when any admin page is open.
  175. wp_enqueue_script('jquery');
  176. }
  177. function enqueue_settings_scripts(){
  178. //jQuery UI is used on the settings page
  179. wp_enqueue_script('jquery-ui-core'); //Used for background color animation
  180. wp_enqueue_script('jquery-ui-dialog');
  181. wp_enqueue_script('jquery-ui-tabs');
  182. wp_enqueue_script('jquery-cookie', plugins_url('js/jquery.cookie.js', BLC_PLUGIN_FILE)); //Used for storing last widget states, etc
  183. }
  184. function enqueue_link_page_scripts(){
  185. wp_enqueue_script('jquery-ui-core'); //Used for background color animation
  186. wp_enqueue_script('jquery-ui-dialog'); //Used for the search form
  187. wp_enqueue_script('sprintf', plugins_url('js/sprintf.js', BLC_PLUGIN_FILE)); //Used in error messages
  188. }
  189. /**
  190. * Initiate a full recheck - reparse everything and check all links anew.
  191. *
  192. * @return void
  193. */
  194. function initiate_recheck(){
  195. global $wpdb; /** @var wpdb $wpdb */
  196. //Delete all discovered instances
  197. $wpdb->query("TRUNCATE {$wpdb->prefix}blc_instances");
  198. //Delete all discovered links
  199. $wpdb->query("TRUNCATE {$wpdb->prefix}blc_links");
  200. //Mark all posts, custom fields and bookmarks for processing.
  201. blc_resynch(true);
  202. }
  203. /**
  204. * A hook executed when the plugin is deactivated.
  205. *
  206. * @return void
  207. */
  208. function deactivation(){
  209. //Remove our Cron events
  210. wp_clear_scheduled_hook('blc_cron_check_links');
  211. wp_clear_scheduled_hook('blc_cron_email_notifications');
  212. wp_clear_scheduled_hook('blc_cron_database_maintenance');
  213. wp_clear_scheduled_hook('blc_cron_check_news');
  214. //Note the deactivation time for each module. This will help them
  215. //synch up propely if/when the plugin is reactivated.
  216. $moduleManager = blcModuleManager::getInstance();
  217. $the_time = current_time('timestamp');
  218. foreach($moduleManager->get_active_modules() as $module_id => $module){
  219. $this->conf->options['module_deactivated_when'][$module_id] = $the_time;
  220. }
  221. $this->conf->save_options();
  222. }
  223. /**
  224. * Perform various database maintenance tasks on the plugin's tables.
  225. *
  226. * Removes records that reference disabled containers and parsers,
  227. * deletes invalid instances and links, optimizes tables, etc.
  228. *
  229. * @return void
  230. */
  231. function database_maintenance(){
  232. blcContainerHelper::cleanup_containers();
  233. blc_cleanup_instances();
  234. blc_cleanup_links();
  235. blcUtility::optimize_database();
  236. }
  237. /**
  238. * Create the plugin's menu items and enqueue their scripts and CSS.
  239. * Callback for the 'admin_menu' action.
  240. *
  241. * @return void
  242. */
  243. function admin_menu(){
  244. if (current_user_can('manage_options'))
  245. add_filter('plugin_action_links', array(&$this, 'plugin_action_links'), 10, 2);
  246. $options_page_hook = add_options_page(
  247. __('Link Checker Settings', 'broken-link-checker'),
  248. __('Link Checker', 'broken-link-checker'),
  249. 'manage_options',
  250. 'link-checker-settings',array(&$this, 'options_page')
  251. );
  252. $menu_title = __('Broken Links', 'broken-link-checker');
  253. if ( $this->conf->options['show_link_count_bubble'] ){
  254. //To make it easier to notice when broken links appear, display the current number of
  255. //broken links in a little bubble notification in the "Broken Links" menu.
  256. //(Similar to how the number of plugin updates and unmoderated comments is displayed).
  257. $blc_link_query = blcLinkQuery::getInstance();
  258. $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
  259. if ( $broken_links > 0 ){
  260. //TODO: Appropriating existing CSS classes for my own purposes is hacky. Fix eventually.
  261. $menu_title .= sprintf(
  262. ' <span class="update-plugins"><span class="update-count blc-menu-bubble">%d</span></span>',
  263. $broken_links
  264. );
  265. }
  266. }
  267. $links_page_hook = add_management_page(
  268. __('View Broken Links', 'broken-link-checker'),
  269. $menu_title,
  270. 'edit_others_posts',
  271. 'view-broken-links',array(&$this, 'links_page')
  272. );
  273. //Add plugin-specific scripts and CSS only to the it's own pages
  274. add_action( 'admin_print_styles-' . $options_page_hook, array(&$this, 'options_page_css') );
  275. add_action( 'admin_print_styles-' . $links_page_hook, array(&$this, 'links_page_css') );
  276. add_action( 'admin_print_scripts-' . $options_page_hook, array(&$this, 'enqueue_settings_scripts') );
  277. add_action( 'admin_print_scripts-' . $links_page_hook, array(&$this, 'enqueue_link_page_scripts') );
  278. //Add a "Feedback" button that links to the plugin's UserVoice forum
  279. add_screen_meta_link(
  280. 'blc-feedback-widget',
  281. __('Feedback', 'broken-link-checker'),
  282. 'http://whiteshadow.uservoice.com/forums/58400-broken-link-checker',
  283. array($options_page_hook, $links_page_hook)
  284. );
  285. //Add a link to the Admin Menu Editor site to the "Broken Links" page.
  286. if ( !$this->conf->get('user_has_donated') ) {
  287. //Choose anchor text randomly.
  288. $possible_anchor_texts = array(
  289. 'Organize WordPress admin menu',
  290. 'Simplify WordPress Admin Menu',
  291. 'Customize WP Admin Menu',
  292. 'Organize WP Admin: use Admin Menu Editor',
  293. 'Web Developer? Check out Admin Menu Editor',
  294. 'Admin Menu Editor for WP',
  295. 'Organize, Hide And Customize Admin Menus',
  296. );
  297. $index = $this->conf->get('view-broken-links-meta-ad', null);
  298. if ( $index === null ) {
  299. $index = rand(0, count($possible_anchor_texts) - 1);
  300. $this->conf->set('view-broken-links-meta-ad', $index);
  301. $this->conf->save_options();
  302. }
  303. add_screen_meta_link(
  304. 'blc-more-plugins-link',
  305. $possible_anchor_texts[$index],
  306. sprintf(
  307. 'http://w-shadow.com/admin-menu-editor-pro/?utm_source=broken_link_checker&utm_medium=Broken_Links_meta_link&utm_campaign=Plugins&utm_content=copy-a%s',
  308. urlencode($index)
  309. ),
  310. $links_page_hook,
  311. array('style' => 'font-weight: bold;')
  312. );
  313. }
  314. //Make the Settings page link to the link list
  315. add_screen_meta_link(
  316. 'blc-links-page-link',
  317. __('Go to Broken Links', 'broken-link-checker'),
  318. admin_url('tools.php?page=view-broken-links'),
  319. $options_page_hook,
  320. array('style' => 'font-weight: bold;')
  321. );
  322. //Add a link to the latest blog post/whatever about this plugin, if any.
  323. if ( isset($this->conf->options['plugin_news']) && !empty($this->conf->options['plugin_news']) ){
  324. $news = $this->conf->options['plugin_news'];
  325. add_screen_meta_link(
  326. 'blc-plugin-news-link',
  327. $news[0],
  328. $news[1],
  329. array($options_page_hook, $links_page_hook)
  330. );
  331. }
  332. }
  333. /**
  334. * plugin_action_links()
  335. * Handler for the 'plugin_action_links' hook. Adds a "Settings" link to this plugin's entry
  336. * on the plugin list.
  337. *
  338. * @param array $links
  339. * @param string $file
  340. * @return array
  341. */
  342. function plugin_action_links($links, $file) {
  343. if ($file == $this->my_basename)
  344. $links[] = "<a href='options-general.php?page=link-checker-settings'>" . __('Settings') . "</a>";
  345. return $links;
  346. }
  347. function options_page(){
  348. global $blclog;
  349. $moduleManager = blcModuleManager::getInstance();
  350. //Prior to 1.5.2 (released 2012-05-27), there was a bug that would cause the donation flag to be
  351. //set incorrectly. So we'll unset the flag in that case.
  352. $reset_donation_flag =
  353. ($this->conf->get('first_installation_timestamp', 0) < strtotime('2012-05-27 00:00')) &&
  354. !$this->conf->get('donation_flag_fixed', false);
  355. if ( $reset_donation_flag) {
  356. $this->conf->set('user_has_donated', false);
  357. $this->conf->set('donation_flag_fixed', true);
  358. $this->conf->save_options();
  359. }
  360. if (isset($_POST['recheck']) && !empty($_POST['recheck']) ){
  361. $this->initiate_recheck();
  362. //Redirect back to the settings page
  363. $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
  364. wp_redirect( add_query_arg( array( 'recheck-initiated' => true), $base_url ) );
  365. die();
  366. }
  367. if(isset($_POST['submit'])) {
  368. check_admin_referer('link-checker-options');
  369. //Activate/deactivate modules
  370. if ( !empty($_POST['module']) ){
  371. $active = array_keys($_POST['module']);
  372. $moduleManager->set_active_modules($active);
  373. }
  374. //Only post statuses that actually exist can be selected
  375. if ( isset($_POST['enabled_post_statuses']) && is_array($_POST['enabled_post_statuses']) ){
  376. $available_statuses = get_post_stati();
  377. $enabled_post_statuses = array_intersect($_POST['enabled_post_statuses'], $available_statuses);
  378. } else {
  379. $enabled_post_statuses = array();
  380. }
  381. //At least one status must be enabled; defaults to "Published".
  382. if ( empty($enabled_post_statuses) ){
  383. $enabled_post_statuses = array('publish');
  384. }
  385. $this->conf->options['enabled_post_statuses'] = $enabled_post_statuses;
  386. //The execution time limit must be above zero
  387. $new_execution_time = intval($_POST['max_execution_time']);
  388. if( $new_execution_time > 0 ){
  389. $this->conf->options['max_execution_time'] = $new_execution_time;
  390. }
  391. //The check threshold also must be > 0
  392. $new_check_threshold=intval($_POST['check_threshold']);
  393. if( $new_check_threshold > 0 ){
  394. $this->conf->options['check_threshold'] = $new_check_threshold;
  395. }
  396. $this->conf->options['mark_broken_links'] = !empty($_POST['mark_broken_links']);
  397. $new_broken_link_css = trim($_POST['broken_link_css']);
  398. $this->conf->options['broken_link_css'] = $new_broken_link_css;
  399. $this->conf->options['mark_removed_links'] = !empty($_POST['mark_removed_links']);
  400. $new_removed_link_css = trim($_POST['removed_link_css']);
  401. $this->conf->options['removed_link_css'] = $new_removed_link_css;
  402. $this->conf->options['nofollow_broken_links'] = !empty($_POST['nofollow_broken_links']);
  403. $this->conf->options['exclusion_list'] = array_filter(
  404. preg_split(
  405. '/[\s\r\n]+/', //split on newlines and whitespace
  406. $_POST['exclusion_list'],
  407. -1,
  408. PREG_SPLIT_NO_EMPTY //skip empty values
  409. )
  410. );
  411. //Parse the custom field list
  412. $new_custom_fields = array_filter(
  413. preg_split( '/[\r\n]+/', $_POST['blc_custom_fields'], -1, PREG_SPLIT_NO_EMPTY )
  414. );
  415. //Calculate the difference between the old custom field list and the new one (used later)
  416. $diff1 = array_diff( $new_custom_fields, $this->conf->options['custom_fields'] );
  417. $diff2 = array_diff( $this->conf->options['custom_fields'], $new_custom_fields );
  418. $this->conf->options['custom_fields'] = $new_custom_fields;
  419. //HTTP timeout
  420. $new_timeout = intval($_POST['timeout']);
  421. if( $new_timeout > 0 ){
  422. $this->conf->options['timeout'] = $new_timeout ;
  423. }
  424. //Server load limit
  425. if ( isset($_POST['server_load_limit']) ){
  426. $this->conf->options['server_load_limit'] = floatval($_POST['server_load_limit']);
  427. if ( $this->conf->options['server_load_limit'] < 0 ){
  428. $this->conf->options['server_load_limit'] = 0;
  429. }
  430. $this->conf->options['enable_load_limit'] = $this->conf->options['server_load_limit'] > 0;
  431. }
  432. //When to run the checker
  433. $this->conf->options['run_in_dashboard'] = !empty($_POST['run_in_dashboard']);
  434. $this->conf->options['run_via_cron'] = !empty($_POST['run_via_cron']);
  435. //Email notifications on/off
  436. $email_notifications = !empty($_POST['send_email_notifications']);
  437. $send_authors_email_notifications = !empty($_POST['send_authors_email_notifications']);
  438. if (
  439. ($email_notifications && !$this->conf->options['send_email_notifications'])
  440. || ($send_authors_email_notifications && !$this->conf->options['send_authors_email_notifications'])
  441. ){
  442. /*
  443. The plugin should only send notifications about links that have become broken
  444. since the time when email notifications were turned on. If we don't do this,
  445. the first email notification will be sent nigh-immediately and list *all* broken
  446. links that the plugin currently knows about.
  447. */
  448. $this->conf->options['last_notification_sent'] = time();
  449. }
  450. $this->conf->options['send_email_notifications'] = $email_notifications;
  451. $this->conf->options['send_authors_email_notifications'] = $send_authors_email_notifications;
  452. //Make settings that affect our Cron events take effect immediately
  453. $this->setup_cron_events();
  454. $this->conf->save_options();
  455. /*
  456. If the list of custom fields was modified then we MUST resynchronize or
  457. custom fields linked with existing posts may not be detected. This is somewhat
  458. inefficient.
  459. */
  460. if ( ( count($diff1) > 0 ) || ( count($diff2) > 0 ) ){
  461. $manager = blcContainerHelper::get_manager('custom_field');
  462. if ( !is_null($manager) ){
  463. $manager->resynch();
  464. blc_got_unsynched_items();
  465. }
  466. }
  467. //Redirect back to the settings page
  468. $base_url = remove_query_arg( array('_wpnonce', 'noheader', 'updated', 'error', 'action', 'message') );
  469. wp_redirect( add_query_arg( array( 'settings-updated' => true), $base_url ) );
  470. }
  471. //Show a confirmation message when settings are saved.
  472. if ( !empty($_GET['settings-updated']) ){
  473. echo '<div id="message" class="updated fade"><p><strong>',__('Settings saved.', 'broken-link-checker'), '</strong></p></div>';
  474. }
  475. //Show a thank-you message when a donation is made.
  476. if ( !empty($_GET['donated']) ){
  477. echo '<div id="message" class="updated fade"><p><strong>',__('Thank you for your donation!', 'broken-link-checker'), '</strong></p></div>';
  478. $this->conf->set('user_has_donated', true);
  479. $this->conf->save_options();
  480. }
  481. //Show one when recheck is started, too.
  482. if ( !empty($_GET['recheck-initiated']) ){
  483. echo '<div id="message" class="updated fade"><p><strong>',
  484. __('Complete site recheck started.', 'broken-link-checker'), // -- Yoda
  485. '</strong></p></div>';
  486. }
  487. //Cull invalid and missing modules
  488. $moduleManager->validate_active_modules();
  489. $debug = $this->get_debug_info();
  490. $details_text = __('Details', 'broken-link-checker');
  491. add_filter('blc-module-settings-custom_field', array(&$this, 'make_custom_field_input'), 10, 2);
  492. //Translate and markup-ify module headers for display
  493. $modules = $moduleManager->get_modules_by_category('', true, true);
  494. //Output the custom broken link/removed link styles for example links
  495. printf(
  496. '<style type="text/css">%s %s</style>',
  497. $this->conf->options['broken_link_css'],
  498. $this->conf->options['removed_link_css']
  499. );
  500. $section_names = array(
  501. 'general' => __('General', 'broken-link-checker'),
  502. 'where' => __('Look For Links In', 'broken-link-checker'),
  503. 'which' => __('Which Links To Check', 'broken-link-checker'),
  504. 'how' => __('Protocols & APIs', 'broken-link-checker'),
  505. 'advanced' => __('Advanced', 'broken-link-checker'),
  506. );
  507. ?>
  508. <!--[if lte IE 7]>
  509. <style type="text/css">
  510. /* Simulate inline-block in IE7 */
  511. ul.ui-tabs-nav li {
  512. display: inline;
  513. zoom: 1;
  514. }
  515. </style>
  516. <![endif]-->
  517. <div class="wrap" id="blc-settings-wrap">
  518. <?php screen_icon(); ?><h2><?php _e('Broken Link Checker Options', 'broken-link-checker'); ?></h2>
  519. <div id="blc-sidebar">
  520. <div class="metabox-holder">
  521. <?php include BLC_DIRECTORY . '/includes/admin/sidebar.php'; ?>
  522. </div>
  523. </div>
  524. <div id="blc-admin-content">
  525. <form name="link_checker_options" id="link_checker_options" method="post" action="<?php
  526. echo admin_url('options-general.php?page=link-checker-settings&noheader=1');
  527. ?>">
  528. <?php
  529. wp_nonce_field('link-checker-options');
  530. ?>
  531. <div id="blc-tabs">
  532. <ul class="hide-if-no-js">
  533. <?php
  534. foreach($section_names as $section_id => $section_name){
  535. printf(
  536. '<li id="tab-button-%s"><a href="#section-%s" title="%s">%s</a></li>',
  537. esc_attr($section_id),
  538. esc_attr($section_id),
  539. esc_attr($section_name),
  540. $section_name
  541. );
  542. }
  543. ?>
  544. </ul>
  545. <div id="section-general" class="blc-section">
  546. <h3 class="hide-if-js"><?php echo $section_names['general']; ?></h3>
  547. <table class="form-table">
  548. <tr valign="top">
  549. <th scope="row">
  550. <?php _e('Status','broken-link-checker'); ?>
  551. <br>
  552. <a href="javascript:void(0)" id="blc-debug-info-toggle"><?php _e('Show debug info', 'broken-link-checker'); ?></a>
  553. </th>
  554. <td>
  555. <div id='wsblc_full_status'>
  556. <br/><br/><br/>
  557. </div>
  558. <table id="blc-debug-info">
  559. <?php
  560. //Output the debug info in a table
  561. foreach( $debug as $key => $value ){
  562. printf (
  563. '<tr valign="top" class="blc-debug-item-%s"><th scope="row">%s</th><td>%s<div class="blc-debug-message">%s</div></td></tr>',
  564. $value['state'],
  565. $key,
  566. $value['value'],
  567. ( array_key_exists('message', $value)?$value['message']:'')
  568. );
  569. }
  570. ?>
  571. </table>
  572. </td>
  573. </tr>
  574. <tr valign="top">
  575. <th scope="row"><?php _e('Check each link','broken-link-checker'); ?></th>
  576. <td>
  577. <?php
  578. printf(
  579. __('Every %s hours','broken-link-checker'),
  580. sprintf(
  581. '<input type="text" name="check_threshold" id="check_threshold" value="%d" size="5" maxlength="5" />',
  582. $this->conf->options['check_threshold']
  583. )
  584. );
  585. ?>
  586. <br/>
  587. <span class="description">
  588. <?php _e('Existing links will be checked this often. New links will usually be checked ASAP.', 'broken-link-checker'); ?>
  589. </span>
  590. </td>
  591. </tr>
  592. <tr valign="top">
  593. <th scope="row"><?php _e('E-mail notifications', 'broken-link-checker'); ?></th>
  594. <td>
  595. <p style="margin-top: 0;">
  596. <label for='send_email_notifications'>
  597. <input type="checkbox" name="send_email_notifications" id="send_email_notifications"
  598. <?php if ($this->conf->options['send_email_notifications']) echo ' checked="checked"'; ?>/>
  599. <?php _e('Send me e-mail notifications about newly detected broken links', 'broken-link-checker'); ?>
  600. </label><br />
  601. </p>
  602. <p>
  603. <label for='send_authors_email_notifications'>
  604. <input type="checkbox" name="send_authors_email_notifications" id="send_authors_email_notifications"
  605. <?php if ($this->conf->options['send_authors_email_notifications']) echo ' checked="checked"'; ?>/>
  606. <?php _e('Send authors e-mail notifications about broken links in their posts', 'broken-link-checker'); ?>
  607. </label><br />
  608. </p>
  609. </td>
  610. </tr>
  611. <tr valign="top">
  612. <th scope="row"><?php _e('Link tweaks','broken-link-checker'); ?></th>
  613. <td>
  614. <p style="margin-top: 0; margin-bottom: 0.5em;">
  615. <label for='mark_broken_links'>
  616. <input type="checkbox" name="mark_broken_links" id="mark_broken_links"
  617. <?php if ($this->conf->options['mark_broken_links']) echo ' checked="checked"'; ?>/>
  618. <?php _e('Apply custom formatting to broken links', 'broken-link-checker'); ?>
  619. </label>
  620. |
  621. <a id="toggle-broken-link-css-editor" href="#" class="blc-toggle-link"><?php
  622. _e('Edit CSS', 'broken-link-checker');
  623. ?></a>
  624. </p>
  625. <div id="broken-link-css-wrap"<?php
  626. if ( !blcUtility::get_cookie('broken-link-css-wrap', false) ){
  627. echo ' class="hidden"';
  628. }
  629. ?>>
  630. <textarea name="broken_link_css" id="broken_link_css" cols='45' rows='4'/><?php
  631. if( isset($this->conf->options['broken_link_css']) ) {
  632. echo $this->conf->options['broken_link_css'];
  633. }
  634. ?></textarea>
  635. <p class="description"><?php
  636. printf(
  637. __('Example : Lorem ipsum <a %s>broken link</a>, dolor sit amet.', 'broken-link-checker'),
  638. ' href="#" class="broken_link" onclick="return false;"'
  639. );
  640. echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
  641. ?></p>
  642. </div>
  643. <p style="margin-bottom: 0.5em;">
  644. <label for='mark_removed_links'>
  645. <input type="checkbox" name="mark_removed_links" id="mark_removed_links"
  646. <?php if ($this->conf->options['mark_removed_links']) echo ' checked="checked"'; ?>/>
  647. <?php _e('Apply custom formatting to removed links', 'broken-link-checker'); ?>
  648. </label>
  649. |
  650. <a id="toggle-removed-link-css-editor" href="#" class="blc-toggle-link"><?php
  651. _e('Edit CSS', 'broken-link-checker');
  652. ?></a>
  653. </p>
  654. <div id="removed-link-css-wrap" <?php
  655. if ( !blcUtility::get_cookie('removed-link-css-wrap', false) ){
  656. echo ' class="hidden"';
  657. }
  658. ?>>
  659. <textarea name="removed_link_css" id="removed_link_css" cols='45' rows='4'/><?php
  660. if( isset($this->conf->options['removed_link_css']) )
  661. echo $this->conf->options['removed_link_css'];
  662. ?></textarea>
  663. <p class="description"><?php
  664. printf(
  665. __('Example : Lorem ipsum <span %s>removed link</span>, dolor sit amet.', 'broken-link-checker'),
  666. ' class="removed_link"'
  667. );
  668. echo ' ', __('Click "Save Changes" to update example output.', 'broken-link-checker');
  669. ?>
  670. </p>
  671. </div>
  672. <p>
  673. <label for='nofollow_broken_links'>
  674. <input type="checkbox" name="nofollow_broken_links" id="nofollow_broken_links"
  675. <?php if ($this->conf->options['nofollow_broken_links']) echo ' checked="checked"'; ?>/>
  676. <?php _e('Stop search engines from following broken links', 'broken-link-checker'); ?>
  677. </label>
  678. </p>
  679. </td>
  680. </tr>
  681. </table>
  682. </div>
  683. <div id="section-where" class="blc-section">
  684. <h3 class="hide-if-js"><?php echo $section_names['where']; ?></h3>
  685. <table class="form-table">
  686. <tr valign="top">
  687. <th scope="row"><?php _e('Look for links in', 'broken-link-checker'); ?></th>
  688. <td>
  689. <?php
  690. if ( !empty($modules['container']) ){
  691. uasort($modules['container'], create_function('$a, $b', 'return strcasecmp($a["Name"], $b["Name"]);'));
  692. $this->print_module_list($modules['container'], $this->conf->options);
  693. }
  694. ?>
  695. </td></tr>
  696. <tr valign="top">
  697. <th scope="row"><?php _e('Post statuses', 'broken-link-checker'); ?></th>
  698. <td>
  699. <?php
  700. $available_statuses = get_post_stati(array('internal' => false), 'objects');
  701. if ( isset($this->conf->options['enabled_post_statuses']) ){
  702. $enabled_post_statuses = $this->conf->options['enabled_post_statuses'];
  703. } else {
  704. $enabled_post_statuses = array();
  705. }
  706. foreach($available_statuses as $status => $status_object){
  707. printf(
  708. '<p><label><input type="checkbox" name="enabled_post_statuses[]" value="%s"%s> %s</label></p>',
  709. esc_attr($status),
  710. in_array($status, $enabled_post_statuses)?' checked="checked"':'',
  711. $status_object->label
  712. );
  713. }
  714. ?>
  715. </td></tr>
  716. </table>
  717. </div>
  718. <div id="section-which" class="blc-section">
  719. <h3 class="hide-if-js"><?php echo $section_names['which']; ?></h3>
  720. <table class="form-table">
  721. <tr valign="top">
  722. <th scope="row"><?php _e('Link types', 'broken-link-checker'); ?></th>
  723. <td>
  724. <?php
  725. if ( !empty($modules['parser']) ){
  726. $this->print_module_list($modules['parser'], $this->conf->options);
  727. } else {
  728. echo __('Error : All link parsers missing!', 'broken-link-checker');
  729. }
  730. ?>
  731. </td>
  732. </tr>
  733. <tr valign="top">
  734. <th scope="row"><?php _e('Exclusion list', 'broken-link-checker'); ?></th>
  735. <td><?php _e("Don't check links where the URL contains any of these words (one per line) :", 'broken-link-checker'); ?><br/>
  736. <textarea name="exclusion_list" id="exclusion_list" cols='45' rows='4' wrap='off'/><?php
  737. if( isset($this->conf->options['exclusion_list']) )
  738. echo implode("\n", $this->conf->options['exclusion_list']);
  739. ?></textarea>
  740. </td>
  741. </tr>
  742. </table>
  743. </div>
  744. <div id="section-how" class="blc-section">
  745. <h3 class="hide-if-js"><?php echo $section_names['how']; ?></h3>
  746. <table class="form-table">
  747. <tr valign="top">
  748. <th scope="row"><?php _e('Check links using', 'broken-link-checker'); ?></th>
  749. <td>
  750. <?php
  751. if ( !empty($modules['checker']) ){
  752. $modules['checker'] = array_reverse($modules['checker']);
  753. $this->print_module_list($modules['checker'], $this->conf->options);
  754. }
  755. ?>
  756. </td></tr>
  757. </table>
  758. </div>
  759. <div id="section-advanced" class="blc-section">
  760. <h3 class="hide-if-js"><?php echo $section_names['advanced']; ?></h3>
  761. <table class="form-table">
  762. <tr valign="top">
  763. <th scope="row"><?php _e('Timeout', 'broken-link-checker'); ?></th>
  764. <td>
  765. <?php
  766. printf(
  767. __('%s seconds', 'broken-link-checker'),
  768. sprintf(
  769. '<input type="text" name="timeout" id="blc_timeout" value="%d" size="5" maxlength="3" />',
  770. $this->conf->options['timeout']
  771. )
  772. );
  773. ?>
  774. <br/><span class="description">
  775. <?php _e('Links that take longer than this to load will be marked as broken.','broken-link-checker'); ?>
  776. </span>
  777. </td>
  778. </tr>
  779. <tr valign="top">
  780. <th scope="row"><?php _e('Link monitor', 'broken-link-checker'); ?></th>
  781. <td>
  782. <p>
  783. <label for='run_in_dashboard'>
  784. <input type="checkbox" name="run_in_dashboard" id="run_in_dashboard"
  785. <?php if ($this->conf->options['run_in_dashboard']) echo ' checked="checked"'; ?>/>
  786. <?php _e('Run continuously while the Dashboard is open', 'broken-link-checker'); ?>
  787. </label>
  788. </p>
  789. <p>
  790. <label for='run_via_cron'>
  791. <input type="checkbox" name="run_via_cron" id="run_via_cron"
  792. <?php if ($this->conf->options['run_via_cron']) echo ' checked="checked"'; ?>/>
  793. <?php _e('Run hourly in the background', 'broken-link-checker'); ?>
  794. </label>
  795. </p>
  796. </td>
  797. </tr>
  798. <tr valign="top">
  799. <th scope="row"><?php _e('Max. execution time', 'broken-link-checker'); ?></th>
  800. <td>
  801. <?php
  802. printf(
  803. __('%s seconds', 'broken-link-checker'),
  804. sprintf(
  805. '<input type="text" name="max_execution_time" id="max_execution_time" value="%d" size="5" maxlength="5" />',
  806. $this->conf->options['max_execution_time']
  807. )
  808. );
  809. ?>
  810. <br/><span class="description">
  811. <?php
  812. _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');
  813. ?>
  814. </span>
  815. </td>
  816. </tr>
  817. <tr valign="top">
  818. <th scope="row"><?php _e('Server load limit', 'broken-link-checker'); ?></th>
  819. <td>
  820. <?php
  821. $load = blcUtility::get_server_load();
  822. $available = !empty($load);
  823. if ( $available ){
  824. $value = !empty($this->conf->options['server_load_limit'])?sprintf('%.2f', $this->conf->options['server_load_limit']):'';
  825. printf(
  826. '<input type="text" name="server_load_limit" id="server_load_limit" value="%s" size="5" maxlength="5"/> ',
  827. $value
  828. );
  829. printf(
  830. __('Current load : %s', 'broken-link-checker'),
  831. '<span id="wsblc_current_load">...</span>'
  832. );
  833. echo '<br/><span class="description">';
  834. printf(
  835. __(
  836. '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.',
  837. 'broken-link-checker'
  838. ),
  839. 'http://en.wikipedia.org/wiki/Load_(computing)'
  840. );
  841. echo '</span>';
  842. } else {
  843. echo '<input type="text" disabled="disabled" value="', esc_attr(__('Not available', 'broken-link-checker')), '" size="13"/><br>';
  844. echo '<span class="description">';
  845. _e('Load limiting only works on Linux-like systems where <code>/proc/loadavg</code> is present and accessible.', 'broken-link-checker');
  846. echo '</span>';
  847. }
  848. ?>
  849. </td>
  850. </tr>
  851. <tr valign="top">
  852. <th scope="row"><?php _e('Forced recheck', 'broken-link-checker'); ?></th>
  853. <td>
  854. <input class="button" type="button" name="start-recheck" id="start-recheck"
  855. value="<?php _e('Re-check all pages', 'broken-link-checker'); ?>" />
  856. <input type="hidden" name="recheck" value="" id="recheck" />
  857. <br />
  858. <span class="description"><?php
  859. _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');
  860. ?></span>
  861. </td>
  862. </tr>
  863. </table>
  864. </div>
  865. </div>
  866. <p class="submit"><input type="submit" name="submit" class='button-primary' value="<?php _e('Save Changes') ?>" /></p>
  867. </form>
  868. </div> <!-- First postbox-container -->
  869. </div>
  870. <?php
  871. //The various JS for this page is stored in a separate file for the purposes readability.
  872. include dirname($this->loader) . '/includes/admin/options-page-js.php';
  873. }
  874. /**
  875. * Output a list of modules and their settings.
  876. *
  877. * Each list entry will contain a checkbox that is checked if the module is
  878. * currently active.
  879. *
  880. * @param array $modules Array of modules to display
  881. * @param array $current_settings
  882. * @return void
  883. */
  884. function print_module_list($modules, $current_settings){
  885. $moduleManager = blcModuleManager::getInstance();
  886. foreach($modules as $module_id => $module_data){
  887. $module_id = $module_data['ModuleID'];
  888. $style = $module_data['ModuleHidden']?' style="display:none;"':'';
  889. printf(
  890. '<div class="module-container" id="module-container-%s"%s>',
  891. $module_id,
  892. $style
  893. );
  894. $this->print_module_checkbox($module_id, $module_data, $moduleManager->is_active($module_id));
  895. $extra_settings = apply_filters(
  896. 'blc-module-settings-'.$module_id,
  897. '',
  898. $current_settings
  899. );
  900. if ( !empty($extra_settings) ){
  901. printf(
  902. ' | <a class="blc-toggle-link toggle-module-settings" id="toggle-module-settings-%s" href="#">%s</a>',
  903. $module_id,
  904. __('Configure', 'broken-link-checker')
  905. );
  906. //The plugin remembers the last open/closed state of module configuration boxes
  907. $box_id = 'module-extra-settings-' . $module_id;
  908. $show = blcUtility::get_cookie(
  909. $box_id,
  910. $moduleManager->is_active($module_id)
  911. );
  912. printf(
  913. '<div class="module-extra-settings%s" id="%s">%s</div>',
  914. $show?'':' hidden',
  915. $box_id,
  916. $extra_settings
  917. );
  918. }
  919. echo '</div>';
  920. }
  921. }
  922. /**
  923. * Output a checkbox for a module.
  924. *
  925. * Generates a simple checkbox that can be used to mark a module as active/inactive.
  926. * If the specified module can't be deactivated (ModuleAlwaysActive = true), the checkbox
  927. * will be displayed in a disabled state and a hidden field will be created to make
  928. * form submissions work correctly.
  929. *
  930. * @param string $module_id Module ID.
  931. * @param array $module_data Associative array of module data.
  932. * @param bool $active If true, the newly created checkbox will start out checked.
  933. * @return void
  934. */
  935. function print_module_checkbox($module_id, $module_data, $active = false){
  936. $disabled = false;
  937. $name_prefix = 'module';
  938. $label_class = '';
  939. $active = $active || $module_data['ModuleAlwaysActive'];
  940. if ( $module_data['ModuleAlwaysActive'] ){
  941. $disabled = true;
  942. $name_prefix = 'module-always-active';
  943. }
  944. $checked = $active ? ' checked="checked"':'';
  945. if ( $disabled ){
  946. $checked .= ' disabled="disabled"';
  947. }
  948. printf(
  949. '<label class="%s">
  950. <input type="checkbox" name="%s[%s]" id="module-checkbox-%s"%s /> %s
  951. </label>',
  952. esc_attr($label_class),
  953. $name_prefix,
  954. esc_attr($module_id),
  955. esc_attr($module_id),
  956. $checked,
  957. $module_data['Name']
  958. );
  959. if ( $module_data['ModuleAlwaysActive'] ){
  960. printf(
  961. '<input type="hidden" name="module[%s]" value="on">',
  962. esc_attr($module_id)
  963. );
  964. }
  965. }
  966. /**
  967. * Add extra settings to the "Custom fields" entry on the plugin's config. page.
  968. *
  969. * Callback for the 'blc-module-settings-custom_field' filter.
  970. *
  971. * @param string $html Current extra HTML
  972. * @param array $current_settings The current plugin configuration.
  973. * @return string New extra HTML.
  974. */
  975. function make_custom_field_input($html, $current_settings){
  976. $html .= '<span class="description">' .
  977. __('Check URLs entered in these custom fields (one per line) :', 'broken-link-checker') .
  978. '</span>';
  979. $html .= '<br><textarea name="blc_custom_fields" id="blc_custom_fields" cols="45" rows="4" />';
  980. if( isset($current_settings['custom_fields']) )
  981. $html .= implode("\n", $current_settings['custom_fields']);
  982. $html .= '</textarea>';
  983. return $html;
  984. }
  985. /**
  986. * Enqueue CSS file for the plugin's Settings page.
  987. *
  988. * @return void
  989. */
  990. function options_page_css(){
  991. wp_enqueue_style('blc-options-page', plugins_url('css/options-page.css', BLC_PLUGIN_FILE), array(), '20120527' );
  992. wp_enqueue_style('dashboard');
  993. }
  994. /**
  995. * Display the "Broken Links" page, listing links detected by the plugin and their status.
  996. *
  997. * @return void
  998. */
  999. function links_page(){
  1000. global $wpdb, $blclog; /* @var wpdb $wpdb */
  1001. $blc_link_query = blcLinkQuery::getInstance();
  1002. //Cull invalid and missing modules so that we don't get dummy links/instances showing up.
  1003. $moduleManager = blcModuleManager::getInstance();
  1004. $moduleManager->validate_active_modules();
  1005. if ( defined('BLC_DEBUG') && constant('BLC_DEBUG') ){
  1006. //Make module headers translatable. They need to be formatted corrrectly and
  1007. //placed in a .php file to be visible to the script(s) that generate .pot files.
  1008. $code = $moduleManager->_build_header_translation_code();
  1009. file_put_contents( dirname($this->loader) . '/includes/extra-strings.php', $code );
  1010. }
  1011. $action = !empty($_POST['action'])?$_POST['action']:'';
  1012. if ( intval($action) == -1 ){
  1013. //Try the second bulk actions box
  1014. $action = !empty($_POST['action2'])?$_POST['action2']:'';
  1015. }
  1016. //Get the list of link IDs selected via checkboxes
  1017. $selected_links = array();
  1018. if ( isset($_POST['selected_links']) && is_array($_POST['selected_links']) ){
  1019. //Convert all link IDs to integers (non-numeric entries are converted to zero)
  1020. $selected_links = array_map('intval', $_POST['selected_links']);
  1021. //Remove all zeroes
  1022. $selected_links = array_filter($selected_links);
  1023. }
  1024. $message = '';
  1025. $msg_class = 'updated';
  1026. //Run the selected bulk action, if any
  1027. $force_delete = false;
  1028. switch ( $action ){
  1029. case 'create-custom-filter':
  1030. list($message, $msg_class) = $this->do_create_custom_filter();
  1031. break;
  1032. case 'delete-custom-filter':
  1033. list($message, $msg_class) = $this->do_delete_custom_filter();
  1034. break;
  1035. /** @noinspection PhpMissingBreakStatementInspection Deliberate fall-through. */
  1036. case 'bulk-delete-sources':
  1037. $force_delete = true;
  1038. case 'bulk-trash-sources':
  1039. list($message, $msg_class) = $this->do_bulk_delete_sources($selected_links, $force_delete);
  1040. break;
  1041. case 'bulk-unlink':
  1042. list($message, $msg_class) = $this->do_bulk_unlink($selected_links);
  1043. break;
  1044. case 'bulk-deredirect':
  1045. list($message, $msg_class) = $this->do_bulk_deredirect($selected_links);
  1046. break;
  1047. case 'bulk-recheck':
  1048. list($message, $msg_class) = $this->do_bulk_recheck($selected_links);
  1049. break;
  1050. case 'bulk-not-broken':
  1051. list($message, $msg_class) = $this->do_bulk_discard($selected_links);
  1052. break;
  1053. case 'bulk-edit':
  1054. list($message, $msg_class) = $this->do_bulk_edit($selected_links);
  1055. break;
  1056. }
  1057. if ( !empty($message) ){
  1058. echo '<div id="message" class="'.$msg_class.' fade"><p>'.$message.'</p></div>';
  1059. }
  1060. $start_time = microtime_float();
  1061. //Load custom filters, if any
  1062. $blc_link_query->load_custom_filters();
  1063. //Calculate the number of links matching each filter
  1064. $blc_link_query->count_filter_results();
  1065. //Run the selected filter (defaults to displaying broken links)
  1066. $selected_filter_id = isset($_GET['filter_id'])?$_GET['filter_id']:'broken';
  1067. $current_filter = $blc_link_query->exec_filter(
  1068. $selected_filter_id,
  1069. isset($_GET['paged']) ? intval($_GET['paged']) : 1,
  1070. $this->conf->options['table_links_per_page'],
  1071. 'broken',
  1072. isset($_GET['orderby']) ? $_GET['orderby'] : '',
  1073. isset($_GET['order']) ? $_GET['order'] : ''
  1074. );
  1075. //exec_filter() returns an array with filter data, including the actual filter ID that was used.
  1076. $filter_id = $current_filter['filter_id'];
  1077. //Error?
  1078. if ( empty($current_filter['links']) && !empty($wpdb->last_error) ){
  1079. printf( __('Database error : %s', 'broken-link-checker'), $wpdb->last_error);
  1080. }
  1081. ?>
  1082. <script type='text/javascript'>
  1083. var blc_current_filter = '<?php echo $filter_id; ?>';
  1084. var blc_is_broken_filter = <?php echo $current_filter['is_broken_filter'] ? 'true' : 'false'; ?>;
  1085. var blc_current_base_filter = '<?php echo esc_js($current_filter['base_filter']); ?>';
  1086. </script>
  1087. <div class="wrap"><?php screen_icon(); ?>
  1088. <?php
  1089. $blc_link_query->print_filter_heading($current_filter);
  1090. $blc_link_query->print_filter_menu($filter_id);
  1091. //Display the "Search" form and associated buttons.
  1092. //The form requires the $filter_id and $current_filter variables to be set.
  1093. include dirname($this->loader) . '/includes/admin/search-form.php';
  1094. //If the user has decided to switch the table to a different mode (compact/full),
  1095. //save the new setting.
  1096. if ( isset($_GET['compact']) ){
  1097. $this->conf->options['table_compact'] = (bool)$_GET['compact'];
  1098. $this->conf->save_options();
  1099. }
  1100. //Display the links, if any
  1101. if( $current_filter['links'] && ( count($current_filter['links']) > 0 ) ) {
  1102. include dirname($this->loader) . '/includes/admin/table-printer.php';
  1103. $table = new blcTablePrinter($this);
  1104. $table->print_table(
  1105. $current_filter,
  1106. $this->conf->options['table_layout'],
  1107. $this->conf->options['table_visible_columns'],
  1108. $this->conf->options['table_compact']
  1109. );
  1110. };
  1111. printf('<!-- Total elapsed : %.4f seconds -->', microtime_float() - $start_time);
  1112. //Load assorted JS event handlers and other shinies
  1113. include dirname($this->loader) . '/includes/admin/links-page-js.php';
  1114. ?></div><?php
  1115. }
  1116. /**
  1117. * Create a custom link filter using params passed in $_POST.
  1118. *
  1119. * @uses $_POST
  1120. * @uses $_GET to replace the current filter ID (if any) with that of the newly created filter.
  1121. *
  1122. * @return array Message and the CSS class to apply to the message.
  1123. */
  1124. function do_create_custom_filter(){
  1125. global $wpdb;
  1126. //Create a custom filter!
  1127. check_admin_referer( 'create-custom-filter' );
  1128. $msg_class = 'updated';
  1129. //Filter name must be set
  1130. if ( empty($_POST['name']) ){
  1131. $message = __("You must enter a filter name!", 'broken-link-checker');
  1132. $msg_class = 'error';
  1133. //Filter parameters (a search query) must also be set
  1134. } elseif ( empty($_POST['params']) ){
  1135. $message = __("Invalid search query.", 'broken-link-checker');
  1136. $msg_class = 'error';
  1137. } else {
  1138. //Save the new filter
  1139. $blc_link_query = blcLinkQuery::getInstance();
  1140. $filter_id = $blc_link_query->create_custom_filter($_POST['name'], $_POST['params']);
  1141. if ( $filter_id ){
  1142. //Saved
  1143. $message = sprintf( __('Filter "%s" created', 'broken-link-checker'), $_POST['name']);
  1144. //A little hack to make the filter active immediately
  1145. $_GET['filter_id'] = $filter_id;
  1146. } else {
  1147. //Error
  1148. $message = sprintf( __("Database error : %s", 'broken-link-checker'), $wpdb->last_error);
  1149. $msg_class = 'error';
  1150. }
  1151. }
  1152. return array($message, $msg_class);
  1153. }
  1154. /**
  1155. * Delete a custom link filter.
  1156. *
  1157. * @uses $_POST
  1158. *
  1159. * @return array Message and a CSS class to apply to the message.
  1160. */
  1161. function do_delete_custom_filter(){
  1162. //Delete an existing custom filter!
  1163. check_admin_referer( 'delete-custom-filter' );
  1164. $msg_class = 'updated';
  1165. //Filter ID must be set
  1166. if ( empty($_POST['filter_id']) ){
  1167. $message = __("Filter ID not specified.", 'broken-link-checker');
  1168. $msg_class = 'error';
  1169. } else {
  1170. //Try to delete the filter
  1171. $blc_link_query = blcLinkQuery::getInstance();
  1172. if ( $blc_link_query->delete_custom_filter($_POST['filter_id']) ){
  1173. //Success
  1174. $message = __('Filter deleted', 'broken-link-checker');
  1175. } else {
  1176. //Either the ID is wrong or there was some other error
  1177. $message = __('Database error : %s', 'broken-link-checker');
  1178. $msg_class = 'error';
  1179. }
  1180. }
  1181. return array($message, $msg_class);
  1182. }
  1183. /**
  1184. * Modify multiple links to point to their target URLs.
  1185. *
  1186. * @param array $selected_links
  1187. * @return array The message to display and its CSS class.
  1188. */
  1189. function do_bulk_deredirect($selected_links){
  1190. //For all selected links, replace the URL with the final URL that it redirects to.
  1191. $message = '';
  1192. $msg_class = 'updated';
  1193. check_admin_referer( 'bulk-action' );
  1194. if ( count($selected_links) > 0 ) {
  1195. //Fetch all the selected links
  1196. $links = blc_get_links(array(
  1197. 'link_ids' => $selected_links,
  1198. 'purpose' => BLC_FOR_EDITING,
  1199. ));
  1200. if ( count($links) > 0 ) {
  1201. $processed_links = 0;
  1202. $failed_links = 0;
  1203. //Deredirect all selected links
  1204. foreach($links as $link){
  1205. $rez = $link->deredirect();
  1206. if ( !is_wp_error($rez) && empty($rez['errors'] )){
  1207. $processed_links++;
  1208. } else {
  1209. $failed_links++;
  1210. }
  1211. }
  1212. $message = sprintf(
  1213. _n(
  1214. 'Replaced %d redirect with a direct link',
  1215. 'Replaced %d redirects with direct links',
  1216. $processed_links,
  1217. 'broken-link-checker'
  1218. ),
  1219. $processed_links
  1220. );
  1221. if ( $failed_links > 0 ) {
  1222. $message .= '<br>' . sprintf(
  1223. _n(
  1224. 'Failed to fix %d redirect',
  1225. 'Failed to fix %d redirects',
  1226. $failed_links,
  1227. 'broken-link-checker'
  1228. ),
  1229. $failed_links
  1230. );
  1231. $msg_class = 'error';
  1232. }
  1233. } else {
  1234. $message = __('None of the selected links are redirects!', 'broken-link-checker');
  1235. }
  1236. }
  1237. return array($message, $msg_class);
  1238. }
  1239. /**
  1240. * Edit multiple links in one go.
  1241. *
  1242. * @param array $selected_links
  1243. * @return array The message to display and its CSS class.
  1244. */
  1245. function do_bulk_edit($selected_links){
  1246. $message = '';
  1247. $msg_class = 'updated';
  1248. check_admin_referer( 'bulk-action' );
  1249. $post = $_POST;
  1250. if ( function_exists('wp_magic_quotes') ){
  1251. $post = stripslashes_deep($post); //Ceterum censeo, WP shouldn't mangle superglobals.
  1252. }
  1253. $search = isset($post['search']) ? $post['search'] : '';
  1254. $replace = isset($post['replace']) ? $post['replace'] : '';
  1255. $use_regex = !empty($post['regex']);
  1256. $case_sensitive = !empty($post['case_sensitive']);
  1257. $delimiter = '`'; //Pick a char that's uncommon in URLs so that escaping won't usually be a problem
  1258. if ( $use_regex ){
  1259. $search = $delimiter . str_replace($delimiter, '\\' . $delimiter, $search) . $delimiter;
  1260. if ( !$case_sensitive ){
  1261. $search .= 'i';
  1262. }
  1263. } elseif ( !$case_sensitive ) {
  1264. //str_ireplace() would be more appropriate for case-insensitive, non-regexp replacement,
  1265. //but that's only available in PHP5.
  1266. $search = $delimiter . str_replace($delimiter, '\\' . $delimiter, preg_quote($search)) . $delimiter . 'i';
  1267. $use_regex = true;
  1268. }
  1269. if ( count($selected_links) > 0 ) {
  1270. set_time_limit(300); //In case the user decides to edit hundreds of links at once
  1271. //Fetch all the selected links
  1272. $links = blc_get_links(array(
  1273. 'link_ids' => $selected_links,
  1274. 'purpose' => BLC_FOR_EDITING,
  1275. ));
  1276. if ( count($links) > 0 ) {
  1277. $processed_links = 0;
  1278. $failed_links = 0;
  1279. $skipped_links = 0;
  1280. //Edit the links
  1281. foreach($links as $link){
  1282. if ( $use_regex ){
  1283. $new_url = preg_replace($search, $replace, $link->url);
  1284. } else {
  1285. $new_url = str_replace($search, $replace, $link->url);
  1286. }
  1287. if ( $new_url == $link->url ){
  1288. $skipped_links++;
  1289. continue;
  1290. }
  1291. $rez = $link->edit($new_url);
  1292. if ( !is_wp_error($rez) && empty($rez['errors'] )){
  1293. $processed_links++;
  1294. } else {
  1295. $failed_links++;
  1296. }
  1297. }
  1298. $message .= sprintf(
  1299. _n(
  1300. '%d link updated.',
  1301. '%d links updated.',
  1302. $processed_links,
  1303. 'broken-link-checker'
  1304. ),
  1305. $processed_links
  1306. );
  1307. if ( $failed_links > 0 ) {
  1308. $message .= '<br>' . sprintf(
  1309. _n(
  1310. 'Failed to update %d link.',
  1311. 'Failed to update %d links.',
  1312. $failed_links,
  1313. 'broken-link-checker'
  1314. ),
  1315. $failed_links
  1316. );
  1317. $msg_class = 'error';
  1318. }
  1319. }
  1320. }
  1321. return array($message, $msg_class);
  1322. }
  1323. /**
  1324. * Unlink multiple links.
  1325. *
  1326. * @param array $selected_links
  1327. * @return array Message and a CSS classname.
  1328. */
  1329. function do_bulk_unlink($selected_links){
  1330. //Unlink all selected links.
  1331. $message = '';
  1332. $msg_class = 'updated';
  1333. check_admin_referer( 'bulk-action' );
  1334. if ( count($selected_links) > 0 ) {
  1335. //Fetch all the selected links
  1336. $links = blc_get_links(array(
  1337. 'link_ids' => $selected_links,
  1338. 'purpose' => BLC_FOR_EDITING,
  1339. ));
  1340. if ( count($links) > 0 ) {
  1341. $processed_links = 0;
  1342. $failed_links = 0;
  1343. //Unlink (delete) each one
  1344. foreach($links as $link){
  1345. $rez = $link->unlink();
  1346. if ( ($rez == false) || is_wp_error($rez) ){
  1347. $failed_links++;
  1348. } else {
  1349. $processed_links++;
  1350. }
  1351. }
  1352. //This message is slightly misleading - it doesn't account for the fact that
  1353. //a link can be present in more than one post.
  1354. $message = sprintf(
  1355. _n(
  1356. '%d link removed',
  1357. '%d links removed',
  1358. $processed_links,
  1359. 'broken-link-checker'
  1360. ),
  1361. $processed_links
  1362. );
  1363. if ( $failed_links > 0 ) {
  1364. $message .= '<br>' . sprintf(
  1365. _n(
  1366. 'Failed to remove %d link',
  1367. 'Failed to remove %d links',
  1368. $failed_links,
  1369. 'broken-link-checker'
  1370. ),
  1371. $failed_links
  1372. );
  1373. $msg_class = 'error';
  1374. }
  1375. }
  1376. }
  1377. return array($message, $msg_class);
  1378. }
  1379. /**
  1380. * Delete or trash posts, bookmarks and other items that contain any of the specified links.
  1381. *
  1382. * Will prefer moving stuff to trash to permanent deletion. If it encounters an item that
  1383. * can't be moved to the trash, it will skip that item by default.
  1384. *
  1385. * @param array $selected_links An array of link IDs
  1386. * @param bool $force_delete Whether to bypass trash and force deletion. Defaults to false.
  1387. * @return array Confirmation message and its CSS class.
  1388. */
  1389. function do_bulk_delete_sources($selected_links, $force_delete = false){
  1390. $message = '';
  1391. $msg_class = 'updated';
  1392. //Delete posts, blogroll entries and any other link containers that contain any of the selected links.
  1393. //
  1394. //Note that once all containers containing a particular link have been deleted,
  1395. //there is no need to explicitly delete the link record itself. The hooks attached to
  1396. //the actions that execute when something is deleted (e.g. "post_deleted") will
  1397. //take care of that.
  1398. check_admin_referer( 'bulk-action' );
  1399. if ( count($selected_links) > 0 ) {
  1400. $messages = array();
  1401. //Fetch all the selected links
  1402. $links = blc_get_links(array(
  1403. 'link_ids' => $selected_links,
  1404. 'load_instances' => true,
  1405. ));
  1406. //Make a list of all containers associated with these links, with each container
  1407. //listed only once.
  1408. $containers = array();
  1409. foreach($links as $link){ /* @var blcLink $link */
  1410. $instances = $link->get_instances();
  1411. foreach($instances as $instance){ /* @var blcLinkInstance $instance */
  1412. $key = $instance->container_type . '|' . $instance->container_id;
  1413. $containers[$key] = array($instance->container_type, $instance->container_id);
  1414. }
  1415. }
  1416. //Instantiate the containers
  1417. $containers = blcContainerHelper::get_containers($containers);
  1418. //Delete/trash their associated entities
  1419. $deleted = array();
  1420. $skipped = array();
  1421. foreach($containers as $container){ /* @var blcContainer $container */
  1422. if ( !$container->current_user_can_delete() ){
  1423. continue;
  1424. }
  1425. if ( $force_delete ){
  1426. $rez = $container->delete_wrapped_object();
  1427. } else {
  1428. if ( $container->can_be_trashed() ){
  1429. $rez = $container->trash_wrapped_object();
  1430. } else {
  1431. $skipped[] = $container;
  1432. continue;
  1433. }
  1434. }
  1435. if ( is_wp_error($rez) ){ /* @var WP_Error $rez */
  1436. //Record error messages for later display
  1437. $messages[] = $rez->get_error_message();
  1438. $msg_class = 'error';
  1439. } else {
  1440. //Keep track of how many of each type were deleted.
  1441. $container_type = $container->container_type;
  1442. if ( isset($deleted[$container_type]) ){
  1443. $deleted[$container_type]++;
  1444. } else {
  1445. $deleted[$container_type] = 1;
  1446. }
  1447. }
  1448. }
  1449. //Generate delete confirmation messages
  1450. foreach($deleted as $container_type => $number){
  1451. if ( $force_delete ){
  1452. $messages[] = blcContainerHelper::ui_bulk_delete_message($container_type, $number);
  1453. } else {
  1454. $messages[] = blcContainerHelper::ui_bulk_trash_message($container_type, $number);
  1455. }
  1456. }
  1457. //If some items couldn't be trashed, let the user know
  1458. if ( count($skipped) > 0 ){
  1459. $message = sprintf(
  1460. _n(
  1461. "%d item was skipped because it can't be moved to the Trash. You need to delete it manually.",
  1462. "%d items were skipped because they can't be moved to the Trash. You need to delete them manually.",
  1463. count($skipped)
  1464. ),
  1465. count($skipped)
  1466. );
  1467. $message .= '<br><ul>';
  1468. foreach($skipped as $container){
  1469. $message .= sprintf(
  1470. '<li>%s</li>',
  1471. $container->ui_get_source('')
  1472. );
  1473. }
  1474. $message .= '</ul>';
  1475. $messages[] = $message;
  1476. }
  1477. if ( count($messages) > 0 ){
  1478. $message = implode('<p>', $messages);
  1479. } else {
  1480. $message = __("Didn't find anything to delete!", 'broken-link-checker');
  1481. $msg_class = 'error';
  1482. }
  1483. }
  1484. return array($message, $msg_class);
  1485. }
  1486. /**
  1487. * Mark multiple links as unchecked.
  1488. *
  1489. * @param array $selected_links An array of link IDs
  1490. * @return array Confirmation nessage and the CSS class to use with that message.
  1491. */
  1492. function do_bulk_recheck($selected_links){
  1493. /** @var wpdb $wpdb */
  1494. global $wpdb;
  1495. $message = '';
  1496. $msg_class = 'updated';
  1497. if ( count($selected_links) > 0 ){
  1498. $q = "UPDATE {$wpdb->prefix}blc_links
  1499. SET last_check_attempt = '0000-00-00 00:00:00'
  1500. WHERE link_id IN (".implode(', ', $selected_links).")";
  1501. $changes = $wpdb->query($q);
  1502. $message = sprintf(
  1503. _n(
  1504. "%d link scheduled for rechecking",
  1505. "%d links scheduled for rechecking",
  1506. $changes,
  1507. 'broken-link-checker'
  1508. ),
  1509. $changes
  1510. );
  1511. }
  1512. return array($message, $msg_class);
  1513. }
  1514. /**
  1515. * Mark multiple links as not broken.
  1516. *
  1517. * @param array $selected_links An array of link IDs
  1518. * @return array Confirmation nessage and the CSS class to use with that message.
  1519. */
  1520. function do_bulk_discard($selected_links){
  1521. check_admin_referer( 'bulk-action' );
  1522. $messages = array();
  1523. $msg_class = 'updated';
  1524. $processed_links = 0;
  1525. if ( count($selected_links) > 0 ){
  1526. foreach($selected_links as $link_id){
  1527. //Load the link
  1528. $link = new blcLink( intval($link_id) );
  1529. //Skip links that don't actually exist
  1530. if ( !$link->valid() ){
  1531. continue;
  1532. }
  1533. //Skip links that weren't actually detected as broken
  1534. if ( !$link->broken ){
  1535. continue;
  1536. }
  1537. //Make it appear "not broken"
  1538. $link->broken = false;
  1539. $link->false_positive = true;
  1540. $link->last_check_attempt = time();
  1541. $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
  1542. //Save the changes
  1543. if ( $link->save() ){
  1544. $processed_links++;
  1545. } else {
  1546. $messages[] = sprintf(
  1547. __("Couldn't modify link %d", 'broken-link-checker'),
  1548. $link_id
  1549. );
  1550. $msg_class = 'error';
  1551. }
  1552. }
  1553. }
  1554. if ( $processed_links > 0 ){
  1555. $messages[] = sprintf(
  1556. _n(
  1557. '%d link marked as not broken',
  1558. '%d links marked as not broken',
  1559. $processed_links,
  1560. 'broken-link-checker'
  1561. ),
  1562. $processed_links
  1563. );
  1564. }
  1565. return array(implode('<br>', $messages), $msg_class);
  1566. }
  1567. /**
  1568. * Enqueue CSS files for the "Broken Links" page
  1569. *
  1570. * @return void
  1571. */
  1572. function links_page_css(){
  1573. wp_enqueue_style('blc-links-page', plugins_url('css/links-page.css', $this->loader), array(), '20120702');
  1574. }
  1575. /**
  1576. * Generate the HTML for the plugin's Screen Options panel.
  1577. *
  1578. * @return string
  1579. */
  1580. function screen_options_html(){
  1581. //Update the links-per-page setting when "Apply" is clicked
  1582. if ( isset($_POST['per_page']) && is_numeric($_POST['per_page']) ) {
  1583. check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
  1584. $per_page = intval($_POST['per_page']);
  1585. if ( ($per_page >= 1) && ($per_page <= 500) ){
  1586. $this->conf->options['table_links_per_page'] = $per_page;
  1587. $this->conf->save_options();
  1588. }
  1589. }
  1590. //Let the user show/hide individual table columns
  1591. $html = '<h5>' . __('Table columns', 'broken-link-checker') . '</h5>';
  1592. include dirname($this->loader) . '/includes/admin/table-printer.php';
  1593. $table = new blcTablePrinter($this);
  1594. $available_columns = $table->get_layout_columns($this->conf->options['table_layout']);
  1595. $html .= '<div id="blc-column-selector" class="metabox-prefs">';
  1596. foreach( $available_columns as $column_id => $data ){
  1597. $html .= sprintf(
  1598. '<label><input type="checkbox" name="visible_columns[%s]"%s>%s</label>',
  1599. esc_attr($column_id),
  1600. in_array($column_id, $this->conf->options['table_visible_columns']) ? ' checked="checked"' : '',
  1601. $data['heading']
  1602. );
  1603. }
  1604. $html .= '</div>';
  1605. $html .= '<h5>' . __('Show on screen') . '</h5>';
  1606. $html .= '<div class="screen-options">';
  1607. $html .= sprintf(
  1608. '<input type="text" name="per_page" maxlength="3" value="%d" class="screen-per-page" id="blc_links_per_page" />
  1609. <label for="blc_links_per_page">%s</label>
  1610. <input type="button" class="button" value="%s" id="blc-per-page-apply-button" /><br />',
  1611. $this->conf->options['table_links_per_page'],
  1612. __('links', 'broken-link-checker'),
  1613. __('Apply')
  1614. );
  1615. $html .= '</div>';
  1616. $html .= '<h5>' . __('Misc', 'broken-link-checker') . '</h5>';
  1617. $html .= '<div class="screen-options">';
  1618. /*
  1619. Display a checkbox in "Screen Options" that lets the user highlight links that
  1620. have been broken for at least X days.
  1621. */
  1622. $html .= sprintf(
  1623. '<label><input type="checkbox" id="highlight_permanent_failures" name="highlight_permanent_failures"%s> ',
  1624. $this->conf->options['highlight_permanent_failures'] ? ' checked="checked"' : ''
  1625. );
  1626. $input_box = sprintf(
  1627. '</label><input type="text" name="failure_duration_threshold" id="failure_duration_threshold" value="%d" size="2"><label for="highlight_permanent_failures">',
  1628. $this->conf->options['failure_duration_threshold']
  1629. );
  1630. $html .= sprintf(
  1631. __('Highlight links broken for at least %s days', 'broken-link-checker'),
  1632. $input_box
  1633. );
  1634. $html .= '</label>';
  1635. //Display a checkbox for turning colourful link status messages on/off
  1636. $html .= sprintf(
  1637. '<br/><label><input type="checkbox" id="table_color_code_status" name="table_color_code_status"%s> %s</label>',
  1638. $this->conf->options['table_color_code_status'] ? ' checked="checked"' : '',
  1639. __('Color-code status codes', 'broken-link-checker')
  1640. );
  1641. $html .= '</div>';
  1642. return $html;
  1643. }
  1644. /**
  1645. * AJAX callback for saving the "Screen Options" panel settings
  1646. *
  1647. * @param array $form
  1648. * @return void
  1649. */
  1650. function ajax_save_screen_options($form){
  1651. if ( !current_user_can('edit_others_posts') ){
  1652. die( json_encode( array(
  1653. 'error' => __("You're not allowed to do that!", 'broken-link-checker')
  1654. )));
  1655. }
  1656. $this->conf->options['highlight_permanent_failures'] = !empty($form['highlight_permanent_failures']);
  1657. $this->conf->options['table_color_code_status'] = !empty($form['table_color_code_status']);
  1658. $failure_duration_threshold = intval($form['failure_duration_threshold']);
  1659. if ( $failure_duration_threshold >=1 ){
  1660. $this->conf->options['failure_duration_threshold'] = $failure_duration_threshold;
  1661. }
  1662. if ( isset($form['visible_columns']) && is_array($form['visible_columns']) ){
  1663. $this->conf->options['table_visible_columns'] = array_keys($form['visible_columns']);
  1664. }
  1665. $this->conf->save_options();
  1666. die('1');
  1667. }
  1668. function start_timer(){
  1669. $this->execution_start_time = microtime_float();
  1670. }
  1671. function execution_time(){
  1672. return microtime_float() - $this->execution_start_time;
  1673. }
  1674. /**
  1675. * The main worker function that does all kinds of things.
  1676. *
  1677. * @return void
  1678. */
  1679. function work(){
  1680. global $wpdb;
  1681. if ( !$this->acquire_lock() ){
  1682. //FB::warn("Another instance of BLC is already working. Stop.");
  1683. return;
  1684. }
  1685. if ( $this->server_too_busy() ){
  1686. //FB::warn("Server is too busy. Stop.");
  1687. return;
  1688. }
  1689. $this->start_timer();
  1690. $max_execution_time = $this->conf->options['max_execution_time'];
  1691. /*****************************************
  1692. Preparation
  1693. ******************************************/
  1694. // Check for safe mode
  1695. if( blcUtility::is_safe_mode() ){
  1696. // Do it the safe mode way - obey the existing max_execution_time setting
  1697. $t = ini_get('max_execution_time');
  1698. if ($t && ($t < $max_execution_time))
  1699. $max_execution_time = $t-1;
  1700. } else {
  1701. // Do it the regular way
  1702. @set_time_limit( $max_execution_time * 2 ); //x2 should be plenty, running any longer would mean a glitch.
  1703. }
  1704. //Don't stop the script when the connection is closed
  1705. ignore_user_abort( true );
  1706. //Close the connection as per http://www.php.net/manual/en/features.connection-handling.php#71172
  1707. //This reduces resource usage and may solve the mysterious slowdowns certain users have
  1708. //encountered when activating the plugin.
  1709. //(Disable when debugging or you won't get the FirePHP output)
  1710. if ( !headers_sent() && (!defined('BLC_DEBUG') || !constant('BLC_DEBUG')) ){
  1711. @ob_end_clean(); //Discard the existing buffer, if any
  1712. header("Connection: close");
  1713. ob_start();
  1714. echo ('Connection closed'); //This could be anything
  1715. $size = ob_get_length();
  1716. header("Content-Length: $size");
  1717. ob_end_flush(); // Strange behaviour, will not work
  1718. flush(); // Unless both are called !
  1719. }
  1720. //Load modules for this context
  1721. $moduleManager = blcModuleManager::getInstance();
  1722. $moduleManager->load_modules('work');
  1723. /*****************************************
  1724. Parse posts and bookmarks
  1725. ******************************************/
  1726. $orphans_possible = false;
  1727. $still_need_resynch = $this->conf->options['need_resynch'];
  1728. if ( $still_need_resynch ) {
  1729. //FB::log("Looking for containers that need parsing...");
  1730. while( $containers = blcContainerHelper::get_unsynched_containers(50) ){
  1731. //FB::log($containers, 'Found containers');
  1732. foreach($containers as $container){
  1733. //FB::log($container, "Parsing container");
  1734. $container->synch();
  1735. //Check if we still have some execution time left
  1736. if( $this->execution_time() > $max_execution_time ){
  1737. //FB::log('The alloted execution time has run out');
  1738. blc_cleanup_links();
  1739. $this->release_lock();
  1740. return;
  1741. }
  1742. //Check if the server isn't overloaded
  1743. if ( $this->server_too_busy() ){
  1744. //FB::log('Server overloaded, bailing out.');
  1745. blc_cleanup_links();
  1746. $this->release_lock();
  1747. return;
  1748. }
  1749. }
  1750. $orphans_possible = true;
  1751. }
  1752. //FB::log('No unparsed items found.');
  1753. $still_need_resynch = false;
  1754. } else {
  1755. //FB::log('Resynch not required.');
  1756. }
  1757. /******************************************
  1758. Resynch done?
  1759. *******************************************/
  1760. if ( $this->conf->options['need_resynch'] && !$still_need_resynch ){
  1761. $this->conf->options['need_resynch'] = $still_need_resynch;
  1762. $this->conf->save_options();
  1763. }
  1764. /******************************************
  1765. Remove orphaned links
  1766. *******************************************/
  1767. if ( $orphans_possible ) {
  1768. //FB::log('Cleaning up the link table.');
  1769. blc_cleanup_links();
  1770. }
  1771. //Check if we still have some execution time left
  1772. if( $this->execution_time() > $max_execution_time ){
  1773. //FB::log('The alloted execution time has run out');
  1774. $this->release_lock();
  1775. return;
  1776. }
  1777. if ( $this->server_too_busy() ){
  1778. //FB::log('Server overloaded, bailing out.');
  1779. $this->release_lock();
  1780. return;
  1781. }
  1782. /*****************************************
  1783. Check links
  1784. ******************************************/
  1785. while ( $links = $this->get_links_to_check(30) ){
  1786. //Some unchecked links found
  1787. //FB::log("Checking ".count($links)." link(s)");
  1788. foreach ($links as $link) {
  1789. //Does this link need to be checked? Excluded links aren't checked, but their URLs are still
  1790. //tested periodically to see if they're still on the exlusion list.
  1791. if ( !$this->is_excluded( $link->url ) ) {
  1792. //Check the link.
  1793. //FB::log($link->url, "Checking link {$link->link_id}");
  1794. $link->check( true );
  1795. } else {
  1796. //FB::info("The URL {$link->url} is excluded, skipping link {$link->link_id}.");
  1797. $link->last_check_attempt = time();
  1798. $link->save();
  1799. }
  1800. //Check if we still have some execution time left
  1801. if( $this->execution_time() > $max_execution_time ){
  1802. //FB::log('The alloted execution time has run out');
  1803. $this->release_lock();
  1804. return;
  1805. }
  1806. //Check if the server isn't overloaded
  1807. if ( $this->server_too_busy() ){
  1808. //FB::log('Server overloaded, bailing out.');
  1809. $this->release_lock();
  1810. return;
  1811. }
  1812. }
  1813. }
  1814. //FB::log('No links need to be checked right now.');
  1815. $this->release_lock();
  1816. //FB::log('All done.');
  1817. }
  1818. /**
  1819. * This function is called when the plugin's cron hook executes.
  1820. * Its only purpose is to invoke the worker function.
  1821. *
  1822. * @uses wsBrokenLinkChecker::work()
  1823. *
  1824. * @return void
  1825. */
  1826. function cron_check_links(){
  1827. $this->work();
  1828. }
  1829. /**
  1830. * Retrieve links that need to be checked or re-checked.
  1831. *
  1832. * @param integer $max_results The maximum number of links to return. Defaults to 0 = no limit.
  1833. * @param bool $count_only If true, only the number of found links will be returned, not the links themselves.
  1834. * @return int|blcLink[]
  1835. */
  1836. function get_links_to_check($max_results = 0, $count_only = false){
  1837. global $wpdb; /* @var wpdb $wpdb */
  1838. $check_threshold = date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
  1839. $recheck_threshold = date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
  1840. //FB::log('Looking for links to check (threshold : '.$check_threshold.', recheck_threshold : '.$recheck_threshold.')...');
  1841. //Select some links that haven't been checked for a long time or
  1842. //that are broken and need to be re-checked again. Links that are
  1843. //marked as "being checked" and have been that way for several minutes
  1844. //can also be considered broken/buggy, so those will be selected
  1845. //as well.
  1846. //Only check links that have at least one valid instance (i.e. an instance exists and
  1847. //it corresponds to one of the currently loaded container/parser types).
  1848. $manager = blcModuleManager::getInstance();
  1849. $loaded_containers = $manager->get_escaped_ids('container');
  1850. $loaded_parsers = $manager->get_escaped_ids('parser');
  1851. //Note : This is a slow query, but AFAIK there is no way to speed it up.
  1852. //I could put an index on last_check_attempt, but that value is almost
  1853. //certainly unique for each row so it wouldn't be much better than a full table scan.
  1854. if ( $count_only ){
  1855. $q = "SELECT COUNT(links.link_id)\n";
  1856. } else {
  1857. $q = "SELECT links.*\n";
  1858. }
  1859. $q .= "FROM {$wpdb->prefix}blc_links AS links
  1860. WHERE
  1861. (
  1862. ( last_check_attempt < %s )
  1863. OR
  1864. (
  1865. (broken = 1 OR being_checked = 1)
  1866. AND may_recheck = 1
  1867. AND check_count < %d
  1868. AND last_check_attempt < %s
  1869. )
  1870. )
  1871. AND EXISTS (
  1872. SELECT 1 FROM {$wpdb->prefix}blc_instances AS instances
  1873. WHERE
  1874. instances.link_id = links.link_id
  1875. AND ( instances.container_type IN ({$loaded_containers}) )
  1876. AND ( instances.parser_type IN ({$loaded_parsers}) )
  1877. )
  1878. ";
  1879. if ( !$count_only ){
  1880. $q .= "\nORDER BY last_check_attempt ASC\n";
  1881. if ( !empty($max_results) ){
  1882. $q .= "LIMIT " . intval($max_results);
  1883. }
  1884. }
  1885. $link_q = $wpdb->prepare(
  1886. $q,
  1887. $check_threshold,
  1888. $this->conf->options['recheck_count'],
  1889. $recheck_threshold
  1890. );
  1891. //FB::log($link_q, "Find links to check");
  1892. //If we just need the number of links, retrieve it and return
  1893. if ( $count_only ){
  1894. return $wpdb->get_var($link_q);
  1895. }
  1896. //Fetch the link data
  1897. $link_data = $wpdb->get_results($link_q, ARRAY_A);
  1898. if ( empty($link_data) ){
  1899. return array();
  1900. }
  1901. //Instantiate blcLink objects for all fetched links
  1902. $links = array();
  1903. foreach($link_data as $data){
  1904. $links[] = new blcLink($data);
  1905. }
  1906. return $links;
  1907. }
  1908. /**
  1909. * Output the current link checker status in JSON format.
  1910. * Ajax hook for the 'blc_full_status' action.
  1911. *
  1912. * @return void
  1913. */
  1914. function ajax_full_status( ){
  1915. $status = $this->get_status();
  1916. $text = $this->status_text( $status );
  1917. echo json_encode( array(
  1918. 'text' => $text,
  1919. 'status' => $status,
  1920. ) );
  1921. die();
  1922. }
  1923. /**
  1924. * Generates a status message based on the status info in $status
  1925. *
  1926. * @param array $status
  1927. * @return string
  1928. */
  1929. function status_text( $status ){
  1930. $text = '';
  1931. if( $status['broken_links'] > 0 ){
  1932. $text .= sprintf(
  1933. "<a href='%s' title='" . __('View broken links', 'broken-link-checker') . "'><strong>".
  1934. _n('Found %d broken link', 'Found %d broken links', $status['broken_links'], 'broken-link-checker') .
  1935. "</strong></a>",
  1936. admin_url('tools.php?page=view-broken-links'),
  1937. $status['broken_links']
  1938. );
  1939. } else {
  1940. $text .= __("No broken links found.", 'broken-link-checker');
  1941. }
  1942. $text .= "<br/>";
  1943. if( $status['unchecked_links'] > 0) {
  1944. $text .= sprintf(
  1945. _n('%d URL in the work queue', '%d URLs in the work queue', $status['unchecked_links'], 'broken-link-checker'),
  1946. $status['unchecked_links'] );
  1947. } else {
  1948. $text .= __("No URLs in the work queue.", 'broken-link-checker');
  1949. }
  1950. $text .= "<br/>";
  1951. if ( $status['known_links'] > 0 ){
  1952. $text .= sprintf(
  1953. _n('Detected %d unique URL', 'Detected %d unique URLs', $status['known_links'], 'broken-link-checker') .
  1954. ' ' . _n('in %d link', 'in %d links', $status['known_instances'], 'broken-link-checker'),
  1955. $status['known_links'],
  1956. $status['known_instances']
  1957. );
  1958. if ($this->conf->options['need_resynch']){
  1959. $text .= ' ' . __('and still searching...', 'broken-link-checker');
  1960. } else {
  1961. $text .= '.';
  1962. }
  1963. } else {
  1964. if ($this->conf->options['need_resynch']){
  1965. $text .= __('Searching your blog for links...', 'broken-link-checker');
  1966. } else {
  1967. $text .= __('No links detected.', 'broken-link-checker');
  1968. }
  1969. }
  1970. return $text;
  1971. }
  1972. /**
  1973. * @uses wsBrokenLinkChecker::ajax_full_status()
  1974. *
  1975. * @return void
  1976. */
  1977. function ajax_dashboard_status(){
  1978. //Just display the full status.
  1979. $this->ajax_full_status();
  1980. }
  1981. /**
  1982. * Output the current average server load (over the last one-minute period).
  1983. * Called via AJAX.
  1984. *
  1985. * @return void
  1986. */
  1987. function ajax_current_load(){
  1988. $load = blcUtility::get_server_load();
  1989. if ( empty($load) ){
  1990. die( _x('Unknown', 'current load', 'broken-link-checker') );
  1991. }
  1992. $one_minute = reset($load);
  1993. printf('%.2f', $one_minute);
  1994. die();
  1995. }
  1996. /**
  1997. * Returns an array with various status information about the plugin. Array key reference:
  1998. * check_threshold - date/time; links checked before this threshold should be checked again.
  1999. * recheck_threshold - date/time; broken links checked before this threshold should be re-checked.
  2000. * known_links - the number of detected unique URLs (a misleading name, yes).
  2001. * known_instances - the number of detected link instances, i.e. actual link elements in posts and other places.
  2002. * broken_links - the number of detected broken links.
  2003. * unchecked_links - the number of URLs that need to be checked ASAP; based on check_threshold and recheck_threshold.
  2004. *
  2005. * @return array
  2006. */
  2007. function get_status(){
  2008. global $wpdb;
  2009. $blc_link_query = blcLinkQuery::getInstance();
  2010. $check_threshold=date('Y-m-d H:i:s', strtotime('-'.$this->conf->options['check_threshold'].' hours'));
  2011. $recheck_threshold=date('Y-m-d H:i:s', time() - $this->conf->options['recheck_threshold']);
  2012. $known_links = blc_get_links(array('count_only' => true));
  2013. $known_instances = blc_get_usable_instance_count();
  2014. $broken_links = $blc_link_query->get_filter_links('broken', array('count_only' => true));
  2015. $unchecked_links = $this->get_links_to_check(0, true);
  2016. return array(
  2017. 'check_threshold' => $check_threshold,
  2018. 'recheck_threshold' => $recheck_threshold,
  2019. 'known_links' => $known_links,
  2020. 'known_instances' => $known_instances,
  2021. 'broken_links' => $broken_links,
  2022. 'unchecked_links' => $unchecked_links,
  2023. );
  2024. }
  2025. function ajax_work(){
  2026. //Run the worker function
  2027. $this->work();
  2028. die();
  2029. }
  2030. /**
  2031. * AJAX hook for the "Not broken" button. Marks a link as broken and as a likely false positive.
  2032. *
  2033. * @return void
  2034. */
  2035. function ajax_discard(){
  2036. if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_discard', false, false)){
  2037. die( __("You're not allowed to do that!", 'broken-link-checker') );
  2038. }
  2039. if ( isset($_POST['link_id']) ){
  2040. //Load the link
  2041. $link = new blcLink( intval($_POST['link_id']) );
  2042. if ( !$link->valid() ){
  2043. printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
  2044. die();
  2045. }
  2046. //Make it appear "not broken"
  2047. $link->broken = false;
  2048. $link->false_positive = true;
  2049. $link->last_check_attempt = time();
  2050. $link->log = __("This link was manually marked as working by the user.", 'broken-link-checker');
  2051. //Save the changes
  2052. if ( $link->save() ){
  2053. die( "OK" );
  2054. } else {
  2055. die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
  2056. }
  2057. } else {
  2058. die( __("Error : link_id not specified", 'broken-link-checker') );
  2059. }
  2060. }
  2061. public function ajax_dismiss(){
  2062. $this->ajax_set_link_dismissed(true);
  2063. }
  2064. public function ajax_undismiss(){
  2065. $this->ajax_set_link_dismissed(false);
  2066. }
  2067. private function ajax_set_link_dismissed($dismiss){
  2068. $action = $dismiss ? 'blc_dismiss' : 'blc_undismiss';
  2069. if (!current_user_can('edit_others_posts') || !check_ajax_referer($action, false, false)){
  2070. die( __("You're not allowed to do that!", 'broken-link-checker') );
  2071. }
  2072. if ( isset($_POST['link_id']) ){
  2073. //Load the link
  2074. $link = new blcLink( intval($_POST['link_id']) );
  2075. if ( !$link->valid() ){
  2076. printf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) );
  2077. die();
  2078. }
  2079. $link->dismissed = $dismiss;
  2080. //Save the changes
  2081. if ( $link->save() ){
  2082. die( "OK" );
  2083. } else {
  2084. die( __("Oops, couldn't modify the link!", 'broken-link-checker') ) ;
  2085. }
  2086. } else {
  2087. die( __("Error : link_id not specified", 'broken-link-checker') );
  2088. }
  2089. }
  2090. /**
  2091. * AJAX hook for the inline link editor on Tools -> Broken Links.
  2092. *
  2093. * @return void
  2094. */
  2095. function ajax_edit(){
  2096. if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_edit', false, false)){
  2097. die( json_encode( array(
  2098. 'error' => __("You're not allowed to do that!", 'broken-link-checker')
  2099. )));
  2100. }
  2101. if ( isset($_GET['link_id']) && !empty($_GET['new_url']) ){
  2102. //Load the link
  2103. $link = new blcLink( intval($_GET['link_id']) );
  2104. if ( !$link->valid() ){
  2105. die( json_encode( array(
  2106. 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_GET['link_id']) )
  2107. )));
  2108. }
  2109. $new_url = $_GET['new_url'];
  2110. $new_url = stripslashes($new_url);
  2111. $parsed = @parse_url($new_url);
  2112. if ( !$parsed ){
  2113. die( json_encode( array(
  2114. 'error' => __("Oops, the new URL is invalid!", 'broken-link-checker')
  2115. )));
  2116. }
  2117. //Try and edit the link
  2118. //FB::log($new_url, "Ajax edit");
  2119. //FB::log($_GET, "Ajax edit");
  2120. $rez = $link->edit($new_url);
  2121. if ( $rez === false ){
  2122. die( json_encode( array(
  2123. 'error' => __("An unexpected error occured!", 'broken-link-checker')
  2124. )));
  2125. } else {
  2126. $response = array(
  2127. 'new_link_id' => $rez['new_link_id'],
  2128. 'cnt_okay' => $rez['cnt_okay'],
  2129. 'cnt_error' => $rez['cnt_error'],
  2130. 'errors' => array(),
  2131. );
  2132. foreach($rez['errors'] as $error){ /** @var $error WP_Error */
  2133. array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
  2134. }
  2135. die( json_encode($response) );
  2136. }
  2137. } else {
  2138. die( json_encode( array(
  2139. 'error' => __("Error : link_id or new_url not specified", 'broken-link-checker')
  2140. )));
  2141. }
  2142. }
  2143. /**
  2144. * AJAX hook for the "Unlink" action links in Tools -> Broken Links.
  2145. * Removes the specified link from all posts and other supported items.
  2146. *
  2147. * @return void
  2148. */
  2149. function ajax_unlink(){
  2150. if (!current_user_can('edit_others_posts') || !check_ajax_referer('blc_unlink', false, false)){
  2151. die( json_encode( array(
  2152. 'error' => __("You're not allowed to do that!", 'broken-link-checker')
  2153. )));
  2154. }
  2155. if ( isset($_POST['link_id']) ){
  2156. //Load the link
  2157. $link = new blcLink( intval($_POST['link_id']) );
  2158. if ( !$link->valid() ){
  2159. die( json_encode( array(
  2160. 'error' => sprintf( __("Oops, I can't find the link %d", 'broken-link-checker'), intval($_POST['link_id']) )
  2161. )));
  2162. }
  2163. //Try and unlink it
  2164. $rez = $link->unlink();
  2165. if ( $rez === false ){
  2166. die( json_encode( array(
  2167. 'error' => __("An unexpected error occured!", 'broken-link-checker')
  2168. )));
  2169. } else {
  2170. $response = array(
  2171. 'cnt_okay' => $rez['cnt_okay'],
  2172. 'cnt_error' => $rez['cnt_error'],
  2173. 'errors' => array(),
  2174. );
  2175. foreach($rez['errors'] as $error){ /** @var WP_Error $error */
  2176. array_push( $response['errors'], implode(', ', $error->get_error_messages()) );
  2177. }
  2178. die( json_encode($response) );
  2179. }
  2180. } else {
  2181. die( json_encode( array(
  2182. 'error' => __("Error : link_id not specified", 'broken-link-checker')
  2183. )));
  2184. }
  2185. }
  2186. function ajax_link_details(){
  2187. global $wpdb; /* @var wpdb $wpdb */
  2188. if (!current_user_can('edit_others_posts')){
  2189. die( __("You don't have sufficient privileges to access this information!", 'broken-link-checker') );
  2190. }
  2191. //FB::log("Loading link details via AJAX");
  2192. if ( isset($_GET['link_id']) ){
  2193. //FB::info("Link ID found in GET");
  2194. $link_id = intval($_GET['link_id']);
  2195. } else if ( isset($_POST['link_id']) ){
  2196. //FB::info("Link ID found in POST");
  2197. $link_id = intval($_POST['link_id']);
  2198. } else {
  2199. //FB::error('Link ID not specified, you hacking bastard.');
  2200. die( __('Error : link ID not specified', 'broken-link-checker') );
  2201. }
  2202. //Load the link.
  2203. $link = new blcLink($link_id);
  2204. if ( !$link->is_new ){
  2205. //FB::info($link, 'Link loaded');
  2206. if ( !class_exists('blcTablePrinter') ){
  2207. require dirname($this->loader) . '/includes/admin/table-printer.php';
  2208. }
  2209. blcTablePrinter::details_row_contents($link);
  2210. die();
  2211. } else {
  2212. printf( __('Failed to load link details (%s)', 'broken-link-checker'), $wpdb->last_error );
  2213. die();
  2214. }
  2215. }
  2216. /**
  2217. * Acquire an exclusive lock.
  2218. * If we already hold a lock, it will be released and a new one will be acquired.
  2219. *
  2220. * @return bool
  2221. */
  2222. function acquire_lock(){
  2223. return WPMutex::acquire('blc_lock');
  2224. }
  2225. /**
  2226. * Relese our exclusive lock.
  2227. * Does nothing if the lock has already been released.
  2228. *
  2229. * @return bool
  2230. */
  2231. function release_lock(){
  2232. return WPMutex::release('blc_lock');
  2233. }
  2234. /**
  2235. * Check if server is currently too overloaded to run the link checker.
  2236. *
  2237. * @return bool
  2238. */
  2239. function server_too_busy(){
  2240. if ( !$this->conf->options['enable_load_limit'] ){
  2241. return false;
  2242. }
  2243. $loads = blcUtility::get_server_load();
  2244. if ( empty($loads) ){
  2245. return false;
  2246. }
  2247. $one_minute = floatval(reset($loads));
  2248. return $one_minute > $this->conf->options['server_load_limit'];
  2249. }
  2250. /**
  2251. * Register BLC's Dashboard widget
  2252. *
  2253. * @return void
  2254. */
  2255. function hook_wp_dashboard_setup(){
  2256. if ( function_exists( 'wp_add_dashboard_widget' ) && current_user_can('edit_others_posts') ) {
  2257. wp_add_dashboard_widget(
  2258. 'blc_dashboard_widget',
  2259. __('Broken Link Checker', 'broken-link-checker'),
  2260. array( &$this, 'dashboard_widget' ),
  2261. array( &$this, 'dashboard_widget_control' )
  2262. );
  2263. }
  2264. }
  2265. /**
  2266. * Collect various debugging information and return it in an associative array
  2267. *
  2268. * @return array
  2269. */
  2270. function get_debug_info(){
  2271. /** @var wpdb $wpdb */
  2272. global $wpdb;
  2273. //Collect some information that's useful for debugging
  2274. $debug = array();
  2275. //PHP version. Any one is fine as long as WP supports it.
  2276. $debug[ __('PHP version', 'broken-link-checker') ] = array(
  2277. 'state' => 'ok',
  2278. 'value' => phpversion(),
  2279. );
  2280. //MySQL version
  2281. $debug[ __('MySQL version', 'broken-link-checker') ] = array(
  2282. 'state' => 'ok',
  2283. 'value' => $wpdb->db_version(),
  2284. );
  2285. //CURL presence and version
  2286. if ( function_exists('curl_version') ){
  2287. $version = curl_version();
  2288. if ( version_compare( $version['version'], '7.16.0', '<=' ) ){
  2289. $data = array(
  2290. 'state' => 'warning',
  2291. 'value' => $version['version'],
  2292. 'message' => __('You have an old version of CURL. Redirect detection may not work properly.', 'broken-link-checker'),
  2293. );
  2294. } else {
  2295. $data = array(
  2296. 'state' => 'ok',
  2297. 'value' => $version['version'],
  2298. );
  2299. }
  2300. } else {
  2301. $data = array(
  2302. 'state' => 'warning',
  2303. 'value' => __('Not installed', 'broken-link-checker'),
  2304. );
  2305. }
  2306. $debug[ __('CURL version', 'broken-link-checker') ] = $data;
  2307. //Snoopy presence
  2308. if ( class_exists('Snoopy') || file_exists(ABSPATH. WPINC . '/class-snoopy.php') ){
  2309. $data = array(
  2310. 'state' => 'ok',
  2311. 'value' => __('Installed', 'broken-link-checker'),
  2312. );
  2313. } else {
  2314. //No Snoopy? This should never happen, but if it does we *must* have CURL.
  2315. if ( function_exists('curl_init') ){
  2316. $data = array(
  2317. 'state' => 'ok',
  2318. 'value' => __('Not installed', 'broken-link-checker'),
  2319. );
  2320. } else {
  2321. $data = array(
  2322. 'state' => 'error',
  2323. 'value' => __('Not installed', 'broken-link-checker'),
  2324. 'message' => __('You must have either CURL or Snoopy installed for the plugin to work!', 'broken-link-checker'),
  2325. );
  2326. }
  2327. }
  2328. $debug['Snoopy'] = $data;
  2329. //Safe_mode status
  2330. if ( blcUtility::is_safe_mode() ){
  2331. $debug['Safe mode'] = array(
  2332. 'state' => 'warning',
  2333. 'value' => __('On', 'broken-link-checker'),
  2334. 'message' => __('Redirects may be detected as broken links when safe_mode is on.', 'broken-link-checker'),
  2335. );
  2336. } else {
  2337. $debug['Safe mode'] = array(
  2338. 'state' => 'ok',
  2339. 'value' => __('Off', 'broken-link-checker'),
  2340. );
  2341. }
  2342. //Open_basedir status
  2343. if ( blcUtility::is_open_basedir() ){
  2344. $debug['open_basedir'] = array(
  2345. 'state' => 'warning',
  2346. 'value' => sprintf( __('On ( %s )', 'broken-link-checker'), ini_get('open_basedir') ),
  2347. 'message' => __('Redirects may be detected as broken links when open_basedir is on.', 'broken-link-checker'),
  2348. );
  2349. } else {
  2350. $debug['open_basedir'] = array(
  2351. 'state' => 'ok',
  2352. 'value' => __('Off', 'broken-link-checker'),
  2353. );
  2354. }
  2355. //Default PHP execution time limit
  2356. $debug['Default PHP execution time limit'] = array(
  2357. 'state' => 'ok',
  2358. 'value' => sprintf(__('%s seconds'), ini_get('max_execution_time')),
  2359. );
  2360. //Resynch flag.
  2361. $debug['Resynch. flag'] = array(
  2362. 'state' => 'ok',
  2363. 'value' => sprintf('%d', $this->conf->options['need_resynch'] ? '1 (resynch. required)' : '0 (resynch. not required)'),
  2364. );
  2365. //Synch records
  2366. $synch_records = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch"));
  2367. $data = array(
  2368. 'state' => 'ok',
  2369. 'value' => sprintf('%d', $synch_records),
  2370. );
  2371. if ( $synch_records == 0 ){
  2372. $data['state'] = 'warning';
  2373. $data['message'] = __('If this value is zero even after several page reloads you have probably encountered a bug.', 'broken-link-checker');
  2374. }
  2375. $debug['Synch. records'] = $data;
  2376. //Total links and instances (including invalid ones)
  2377. $all_links = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_links"));
  2378. $all_instances = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_instances"));
  2379. //Show the number of unparsed containers. Useful for debugging. For performance,
  2380. //this is only shown when we have no links/instances yet.
  2381. if( ($all_links == 0) && ($all_instances == 0) ){
  2382. $unparsed_items = intval($wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}blc_synch WHERE synched=0"));
  2383. $debug['Unparsed items'] = array(
  2384. 'state' => 'warning',
  2385. 'value' => $unparsed_items,
  2386. );
  2387. }
  2388. //Links & instances
  2389. if ( ($all_links > 0) && ($all_instances > 0) ){
  2390. $debug['Link records'] = array(
  2391. 'state' => 'ok',
  2392. 'value' => sprintf('%d (%d)', $all_links, $all_instances),
  2393. );
  2394. } else {
  2395. $debug['Link records'] = array(
  2396. 'state' => 'warning',
  2397. 'value' => sprintf('%d (%d)', $all_links, $all_instances),
  2398. );
  2399. }
  2400. //Installation log
  2401. $logger = new blcCachedOptionLogger('blc_installation_log');
  2402. $installation_log = $logger->get_messages();
  2403. if ( !empty($installation_log) ){
  2404. $debug['Installation log'] = array(
  2405. 'state' => $this->conf->options['installation_complete'] ? 'ok' : 'error',
  2406. 'value' => implode("<br>\n", $installation_log),
  2407. );
  2408. } else {
  2409. $debug['Installation log'] = array(
  2410. 'state' => 'warning',
  2411. 'value' => 'No installation log found found.',
  2412. );
  2413. }
  2414. return $debug;
  2415. }
  2416. function maybe_send_email_notifications() {
  2417. global $wpdb; /** @var wpdb $wpdb */
  2418. if ( !($this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications']) ){
  2419. return;
  2420. }
  2421. //Find links that have been detected as broken since the last sent notification.
  2422. $last_notification = date('Y-m-d H:i:s', $this->conf->options['last_notification_sent']);
  2423. $where = $wpdb->prepare('( first_failure >= %s )', $last_notification);
  2424. $links = blc_get_links(array(
  2425. 's_filter' => 'broken',
  2426. 'where_expr' => $where,
  2427. 'load_instances' => true,
  2428. 'load_containers' => true,
  2429. 'load_wrapped_objects' => $this->conf->options['send_authors_email_notifications'],
  2430. 'max_results' => 0,
  2431. ));
  2432. if ( empty($links) ){
  2433. return;
  2434. }
  2435. //Send the admin notification
  2436. $admin_email = get_option('admin_email');
  2437. if ( $this->conf->options['send_email_notifications'] && !empty($admin_email) ) {
  2438. $this->send_admin_notification($links, $admin_email);
  2439. }
  2440. //Send notifications to post authors
  2441. if ( $this->conf->options['send_authors_email_notifications'] ) {
  2442. $this->send_authors_notifications($links);
  2443. }
  2444. $this->conf->options['last_notification_sent'] = time();
  2445. $this->conf->save_options();
  2446. }
  2447. function send_admin_notification($links, $email) {
  2448. //Prepare email message
  2449. $subject = sprintf(
  2450. __("[%s] Broken links detected", 'broken-link-checker'),
  2451. html_entity_decode(get_option('blogname'), ENT_QUOTES)
  2452. );
  2453. $body = sprintf(
  2454. _n(
  2455. "Broken Link Checker has detected %d new broken link on your site.",
  2456. "Broken Link Checker has detected %d new broken links on your site.",
  2457. count($links),
  2458. 'broken-link-checker'
  2459. ),
  2460. count($links)
  2461. );
  2462. $body .= "<br>";
  2463. $instances = array();
  2464. foreach($links as $link) { /* @var blcLink $link */
  2465. $instances = array_merge($instances, $link->get_instances());
  2466. }
  2467. $body .= $this->build_instance_list_for_email($instances);
  2468. $this->send_html_email($email, $subject, $body);
  2469. }
  2470. function build_instance_list_for_email($instances, $max_displayed_links = 5){
  2471. $result = '';
  2472. if ( count($instances) > $max_displayed_links ){
  2473. $line = sprintf(
  2474. _n(
  2475. "Here's a list of the first %d broken links:",
  2476. "Here's a list of the first %d broken links:",
  2477. $max_displayed_links,
  2478. 'broken-link-checker'
  2479. ),
  2480. $max_displayed_links
  2481. );
  2482. } else {
  2483. $line = __("Here's a list of the new broken links: ", 'broken-link-checker');
  2484. }
  2485. $result .= "<p>$line</p>";
  2486. //Show up to $max_displayed_links broken link instances right in the email.
  2487. $displayed = 0;
  2488. foreach($instances as $instance){ /* @var blcLinkInstance $instance */
  2489. $pieces = array(
  2490. sprintf( __('Link text : %s', 'broken-link-checker'), $instance->ui_get_link_text('email') ),
  2491. sprintf( __('Link URL : <a href="%s">%s</a>', 'broken-link-checker'), htmlentities($instance->get_url()), blcUtility::truncate($instance->get_url(), 70, '') ),
  2492. sprintf( __('Source : %s', 'broken-link-checker'), $instance->ui_get_source('email') ),
  2493. );
  2494. $link_entry = implode("<br>", $pieces);
  2495. $result .= "$link_entry<br><br>";
  2496. $displayed++;
  2497. if ( $displayed >= $max_displayed_links ){
  2498. break;
  2499. }
  2500. }
  2501. //Add a link to the "Broken Links" tab.
  2502. $result .= __("You can see all broken links here:", 'broken-link-checker') . "<br>";
  2503. $result .= sprintf('<a href="%1$s">%1$s</a>', admin_url('tools.php?page=view-broken-links'));
  2504. return $result;
  2505. }
  2506. function send_html_email($email_address, $subject, $body) {
  2507. //Need to override the default 'text/plain' content type to send a HTML email.
  2508. add_filter('wp_mail_content_type', array(&$this, 'override_mail_content_type'));
  2509. $success = wp_mail($email_address, $subject, $body);
  2510. //Remove the override so that it doesn't interfere with other plugins that might
  2511. //want to send normal plaintext emails.
  2512. remove_filter('wp_mail_content_type', array(&$this, 'override_mail_content_type'));
  2513. return $success;
  2514. }
  2515. function send_authors_notifications($links) {
  2516. $authorInstances = array();
  2517. foreach($links as $link){ /* @var blcLink $link */
  2518. foreach($link->get_instances() as $instance){ /* @var blcLinkInstance $instance */
  2519. $container = $instance->get_container(); /** @var blcContainer $container */
  2520. if ( empty($container) || !($container instanceof blcAnyPostContainer) ) {
  2521. continue;
  2522. }
  2523. $post = $container->get_wrapped_object(); /** @var StdClass $post */
  2524. if ( !array_key_exists($post->post_author, $authorInstances) ) {
  2525. $authorInstances[$post->post_author] = array();
  2526. }
  2527. $authorInstances[$post->post_author][] = $instance;
  2528. }
  2529. }
  2530. foreach($authorInstances as $author_id => $instances) {
  2531. $subject = sprintf(
  2532. __("[%s] Broken links detected", 'broken-link-checker'),
  2533. html_entity_decode(get_option('blogname'), ENT_QUOTES)
  2534. );
  2535. $body = sprintf(
  2536. _n(
  2537. "Broken Link Checker has detected %d new broken link in your posts.",
  2538. "Broken Link Checker has detected %d new broken links in your posts.",
  2539. count($instances),
  2540. 'broken-link-checker'
  2541. ),
  2542. count($instances)
  2543. );
  2544. $body .= "<br>";
  2545. $body .= $this->build_instance_list_for_email($instances);
  2546. $author = get_user_by('id', $author_id); /** @var WP_User $author */
  2547. $this->send_html_email($author->user_email, $subject, $body);
  2548. }
  2549. }
  2550. function override_mail_content_type($content_type){
  2551. return 'text/html';
  2552. }
  2553. /**
  2554. * Install or uninstall the plugin's Cron events based on current settings.
  2555. *
  2556. * @uses wsBrokenLinkChecker::$conf Uses $conf->options to determine if events need to be (un)installed.
  2557. *
  2558. * @return void
  2559. */
  2560. function setup_cron_events(){
  2561. //Link monitor
  2562. if ( $this->conf->options['run_via_cron'] ){
  2563. if (!wp_next_scheduled('blc_cron_check_links')) {
  2564. wp_schedule_event( time(), 'hourly', 'blc_cron_check_links' );
  2565. }
  2566. } else {
  2567. wp_clear_scheduled_hook('blc_cron_check_links');
  2568. }
  2569. //Email notifications about broken links
  2570. if ( $this->conf->options['send_email_notifications'] || $this->conf->options['send_authors_email_notifications'] ){
  2571. if ( !wp_next_scheduled('blc_cron_email_notifications') ){
  2572. wp_schedule_event(time(), $this->conf->options['notification_schedule'], 'blc_cron_email_notifications');
  2573. }
  2574. } else {
  2575. wp_clear_scheduled_hook('blc_cron_email_notifications');
  2576. }
  2577. //Run database maintenance every two weeks or so
  2578. if ( !wp_next_scheduled('blc_cron_database_maintenance') ){
  2579. wp_schedule_event(time(), 'bimonthly', 'blc_cron_database_maintenance');
  2580. }
  2581. //Check for news notices related to this plugin
  2582. if ( !wp_next_scheduled('blc_cron_check_news') ){
  2583. wp_schedule_event(time(), 'daily', 'blc_cron_check_news');
  2584. }
  2585. }
  2586. /**
  2587. * Load the plugin's textdomain
  2588. *
  2589. * @return void
  2590. */
  2591. function load_language(){
  2592. load_plugin_textdomain( 'broken-link-checker', false, basename(dirname($this->loader)) . '/languages' );
  2593. }
  2594. /**
  2595. * Check if there's a "news" link to display on the plugin's pages.
  2596. *
  2597. * @return void
  2598. */
  2599. function check_news(){
  2600. $url = 'http://w-shadow.com/plugin-news/broken-link-checker-news.txt';
  2601. //Retrieve the appropriate "news" file
  2602. $res = wp_remote_get($url);
  2603. if ( is_wp_error($res) ){
  2604. return;
  2605. }
  2606. //Anything there?
  2607. if ( isset($res['response']['code']) && ($res['response']['code'] == 200) && isset($res['body']) ) {
  2608. //The file should contain two lines - a title and an URL
  2609. $news = explode("\n", trim($res['body']));
  2610. if ( count($news) == 2 ){
  2611. //Save for later.
  2612. $this->conf->options['plugin_news'] = $news;
  2613. } else {
  2614. $this->conf->options['plugin_news'] = null;
  2615. }
  2616. $this->conf->save_options();
  2617. }
  2618. }
  2619. }//class ends here
  2620. } // if class_exists...
  2621. ?>