Changeset 3475895
- Timestamp:
- 03/05/2026 08:03:50 PM (3 weeks ago)
- Location:
- broken-link-notifier
- Files:
-
- 58 added
- 14 deleted
- 10 edited
-
tags/1.2.0 (deleted)
-
tags/1.2.1 (deleted)
-
tags/1.2.2 (deleted)
-
tags/1.2.3 (deleted)
-
tags/1.2.4 (deleted)
-
tags/1.2.4.1 (deleted)
-
tags/1.2.4.2 (deleted)
-
tags/1.2.5 (deleted)
-
tags/1.2.5.1 (deleted)
-
tags/1.2.5.2 (deleted)
-
tags/1.2.5.3 (deleted)
-
tags/1.2.6 (deleted)
-
tags/1.2.6.1 (deleted)
-
tags/1.3.1 (deleted)
-
tags/1.3.7 (added)
-
tags/1.3.7/broken-link-notifier.php (added)
-
tags/1.3.7/includes (added)
-
tags/1.3.7/includes/cache.php (added)
-
tags/1.3.7/includes/css (added)
-
tags/1.3.7/includes/css/results-front.css (added)
-
tags/1.3.7/includes/css/results-front.min.css (added)
-
tags/1.3.7/includes/discord.php (added)
-
tags/1.3.7/includes/example-hook.php (added)
-
tags/1.3.7/includes/export.php (added)
-
tags/1.3.7/includes/helpers.php (added)
-
tags/1.3.7/includes/img (added)
-
tags/1.3.7/includes/img/admin-help-docs.png (added)
-
tags/1.3.7/includes/img/clear-cache-everywhere.png (added)
-
tags/1.3.7/includes/img/dev-debug-tools.png (added)
-
tags/1.3.7/includes/img/devconsole.gif (added)
-
tags/1.3.7/includes/img/discord.png (added)
-
tags/1.3.7/includes/img/eri-file-library.png (added)
-
tags/1.3.7/includes/img/gf-discord.png (added)
-
tags/1.3.7/includes/img/gf-msteams.png (added)
-
tags/1.3.7/includes/img/gf-tools.png (added)
-
tags/1.3.7/includes/img/gravity-zwr.png (added)
-
tags/1.3.7/includes/img/logo-teal.png (added)
-
tags/1.3.7/includes/img/logo-transparent.png (added)
-
tags/1.3.7/includes/img/screen_options.png (added)
-
tags/1.3.7/includes/index.php (added)
-
tags/1.3.7/includes/integrations.php (added)
-
tags/1.3.7/includes/js (added)
-
tags/1.3.7/includes/js/index.php (added)
-
tags/1.3.7/includes/js/omits.js (added)
-
tags/1.3.7/includes/js/results-back.js (added)
-
tags/1.3.7/includes/js/results-front.js (added)
-
tags/1.3.7/includes/js/results-front.min.js (added)
-
tags/1.3.7/includes/js/scan-multi.js (added)
-
tags/1.3.7/includes/js/scan-single.js (added)
-
tags/1.3.7/includes/js/settings.js (added)
-
tags/1.3.7/includes/loader.php (added)
-
tags/1.3.7/includes/menu.php (added)
-
tags/1.3.7/includes/msteams.php (added)
-
tags/1.3.7/includes/omits.php (added)
-
tags/1.3.7/includes/page-export.php (added)
-
tags/1.3.7/includes/page-help.php (added)
-
tags/1.3.7/includes/page-link-search.php (added)
-
tags/1.3.7/includes/page-results.php (added)
-
tags/1.3.7/includes/page-scan-multi.php (added)
-
tags/1.3.7/includes/page-scan-single.php (added)
-
tags/1.3.7/includes/page-settings.php (added)
-
tags/1.3.7/includes/page.php (added)
-
tags/1.3.7/includes/results.php (added)
-
tags/1.3.7/includes/scan-multi.php (added)
-
tags/1.3.7/includes/scan.php (added)
-
tags/1.3.7/includes/test_files (added)
-
tags/1.3.7/includes/test_files/Jubiläum-A-und-B.pdf (added)
-
tags/1.3.7/index.php (added)
-
tags/1.3.7/readme.txt (added)
-
tags/1.3.7/uninstall.php (added)
-
trunk/broken-link-notifier.php (modified) (1 diff)
-
trunk/includes/cache.php (modified) (1 diff)
-
trunk/includes/example-hook.php (modified) (1 diff)
-
trunk/includes/helpers.php (modified) (5 diffs)
-
trunk/includes/js/omits.js (modified) (1 diff)
-
trunk/includes/js/results-back.js (modified) (21 diffs)
-
trunk/includes/menu.php (modified) (4 diffs)
-
trunk/includes/omits.php (modified) (3 diffs)
-
trunk/includes/page-results.php (added)
-
trunk/includes/results.php (modified) (24 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/uninstall.php (added)
Legend:
- Unmodified
- Added
- Removed
-
broken-link-notifier/trunk/broken-link-notifier.php
r3471187 r3475895 4 4 * Plugin URI: https://pluginrx.com/plugin/broken-link-notifier/ 5 5 * Description: Get notified when someone loads a page with a broken link 6 * Version: 1.3. 66 * Version: 1.3.7 7 7 * Requires at least: 5.9 8 8 * Tested up to: 6.9 -
broken-link-notifier/trunk/includes/cache.php
r3287974 r3475895 18 18 19 19 /** 20 * Main pluginclass.20 * Cache class. 21 21 */ 22 22 class BLNOTIFIER_CACHE { -
broken-link-notifier/trunk/includes/example-hook.php
r3067171 r3475895 3 3 4 4 /** 5 * An example of how to filter a link before it's checked for validity5 * Skip links containing a specific query string before link checks. 6 6 * 7 * @param string $link8 * @return string| false7 * @param string|array $link The original link to be checked. 8 * @return string|array Modified link or structured status array. 9 9 */ 10 function blnotifier_link( $link ) { 11 // Filter ajax calls 12 if ( strpos( $link, '/admin-ajax.php' ) !== false ) { 13 14 // Parse the url (ie. https://mydomain.com/wp-admin/admin-ajax.php?action=my_action&post_id=12345&nonce=1234567890) 15 $url_components = wp_parse_url( $link ); 16 $query_strings = explode( '&', $url_components[ 'query' ] ); 17 $action = false; 18 $post_id = false; 19 foreach ( $query_strings as $query_string ) { 20 $query_string = str_replace( 'amp;', '', $query_string ); 21 if ( substr( $query_string, 0, strlen( 'action=' ) ) == 'action=' ) { 22 $action = substr( $query_string, strlen( 'action=' ) ); 23 } 24 if ( substr( $query_string, 0, strlen( 'post_id=' ) ) == 'post_id=' ) { 25 $post_id = substr( $query_string, strlen( 'post_id=' ) ); 26 } 27 } 28 29 // Check the action and make sure we have a post id 30 if ( $action == 'my_action' && $post_id ) { 31 32 // Check the post it's associated with 33 $meta_value = get_post_meta( $post_id, 'meta_key', true ); 34 35 // New url to pass through all the checks 36 if ( $meta_value && $meta_value == 'some keyword' ) { 37 $link = 'https://somewebsite.com/'.$meta_value; 38 } 39 40 // Or simply return a status 41 } elseif ( $action == 'my_other_action' ) { 42 43 // Condition 44 if ( get_post( $post_id ) ) { 45 $status = [ 46 'type' => 'good', 47 'code' => 200, 48 'text' => 'Post exists: '.get_the_title( $post_id ), 49 'link' => $link 50 ]; 51 } else { 52 $status = [ 53 'type' => 'broken', 54 'code' => 666, // We use 666 as an alternative to 0 in case warnings are disabled 55 'text' => 'Post does not exist', 56 'link' => $link 57 ]; 58 } 59 return $status; 60 } 10 add_filter( 'blnotifier_link_before_prechecks', function( $link ) { 11 // Skip UTM tracking links 12 if ( is_string( $link ) && strpos( $link, 'utm_' ) !== false ) { 13 return [ 14 'type' => 'good', 15 'code' => 200, 16 'text' => 'Skipped due to tracking params', 17 'link' => $link 18 ]; 61 19 } 62 20 63 // Always return link64 21 return $link; 65 } // End blnotifier_link() 66 67 add_filter( 'blnotifier_link_before_prechecks', 'blnotifier_link' ); 22 }, 10 ); -
broken-link-notifier/trunk/includes/helpers.php
r3471187 r3475895 200 200 return []; 201 201 } 202 203 unset( $post_types[ ( new BLNOTIFIER_RESULTS )->post_type ] );204 202 205 203 if ( isset( $post_types[ 'help-docs' ] ) ) { … … 405 403 */ 406 404 public function count_broken_links() { 407 $broken_links = get_posts( [ 408 'posts_per_page' => -1, 409 'post_status' => 'publish', 410 'post_type' => 'blnotifier-results', 411 'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 412 [ 413 'key' => 'type', 414 'value' => 'broken', 415 ] 416 ], 417 'fields' => 'ids' 418 ] ); 419 return count( $broken_links ); 405 global $wpdb; 406 $table = $wpdb->prefix . 'blnotifier_results'; 407 408 $count = $wpdb->get_var( 409 $wpdb->prepare( 410 "SELECT COUNT(*) FROM {$table} WHERE type = %s", 411 'broken' 412 ) 413 ); 414 415 return absint( $count ); 420 416 } // End count_broken_links() 421 417 … … 1563 1559 1564 1560 // String replace 1565 $link = $this->str_replace_on_link( $link ); 1561 if ( is_array( $link ) && isset( $link[ 'link' ] ) ) { 1562 $link[ 'link' ] = $this->str_replace_on_link( $link[ 'link' ] ); 1563 } elseif ( is_string( $link ) ) { 1564 $link = $this->str_replace_on_link( $link ); 1565 } 1566 1566 1567 1567 // Check for cached link only if the link is not an array … … 1800 1800 */ 1801 1801 public function get_broken_links() { 1802 global $wpdb; 1803 $table = $wpdb->prefix . 'blnotifier_results'; 1804 1805 $rows = $wpdb->get_results( 1806 $wpdb->prepare( 1807 "SELECT link, text, type, code, source, location, method, author_id, created_at FROM {$table} WHERE type = %s ORDER BY created_at DESC", 1808 'broken' 1809 ), 1810 ARRAY_A 1811 ); 1812 1802 1813 $links = []; 1803 1804 $results = new BLNOTIFIER_RESULTS(); 1805 $post_type = $results->post_type; 1806 1807 $posts = get_posts( [ 1808 'post_type' => $post_type, 1809 'posts_per_page' => -1, 1810 'post_status' => 'publish', 1811 'orderby' => 'date', 1812 'order' => 'DESC', 1813 ] ); 1814 $source_cache = []; // stores [ 'post_id' => ..., 'title' => ... ] for each source URL 1814 1815 1815 1816 $methods = [ 1816 'visit' => __( 'Front-End Visit', 'broken-link-notifier' ),1817 'multi' => __( 'Multi-Scan', 'broken-link-notifier' ),1818 'single' => __( 'Page Scan', 'broken-link-notifier' ),1817 'visit' => __( 'Front-End Visit', 'broken-link-notifier' ), 1818 'multi' => __( 'Multi-Scan', 'broken-link-notifier' ), 1819 'single' => __( 'Page Scan', 'broken-link-notifier' ), 1819 1820 ]; 1820 1821 1821 foreach ( $posts as $post ) { 1822 $source_link = sanitize_url( get_post_meta( $post->ID, 'source', true ) ); 1823 $location = sanitize_key( get_post_meta( $post->ID, 'location', true ) ); 1824 $type_meta = sanitize_key( get_post_meta( $post->ID, 'type', true ) ); 1825 $code = absint( get_post_meta( $post->ID, 'code', true ) ); 1826 $user_id = $post->post_author; 1827 $user = $user_id ? get_userdata( $user_id ) : null; 1828 $method = sanitize_key( get_post_meta( $post->ID, 'method', true ) ); 1822 foreach ( $rows as $row ) { 1823 1824 $source_url = $row[ 'source' ]; 1825 1826 if ( isset( $source_cache[ $source_url ] ) ) { 1827 $source_post_id = $source_cache[ $source_url ][ 'post_id' ]; 1828 $source_title = $source_cache[ $source_url ][ 'title' ]; 1829 } else { 1830 $source_post_id = url_to_postid( $source_url ); 1831 $source_title = $source_post_id ? get_the_title( $source_post_id ) : __( 'Unknown', 'broken-link-notifier' ); 1832 $source_cache[ $source_url ] = [ 1833 'post_id' => $source_post_id, 1834 'title' => $source_title, 1835 ]; 1836 } 1837 1838 $user = $row[ 'author_id' ] ? get_userdata( $row[ 'author_id' ] ) : null; 1829 1839 1830 1840 $links[] = [ 1831 'date' => gmdate( 'Y-m-d H:i:s', strtotime( $post->post_date )),1832 'type' => ucwords( $type_meta),1833 'code' => $code,1834 'message' => $post->post_content,1835 'link' => $post->post_title,1836 'source_name' => $source_ link ? get_the_title( url_to_postid( $source_link ) ) : 'Unknown',1837 'source_link' => $source_link,1838 'location' => ucwords( $location),1839 'user' => $user ? $user->display_name : 'Guest',1840 'method' => isset( $methods[ $ method ] ) ? $methods[ $method] : __( 'Unknown', 'broken-link-notifier' ),1841 'date' => mysql2date( 'Y-m-d H:i:s', $row[ 'created_at' ] ), 1842 'type' => ucwords( sanitize_text_field( $row[ 'type' ] ) ), 1843 'code' => absint( $row[ 'code' ] ), 1844 'message' => sanitize_text_field( $row[ 'text' ] ), 1845 'link' => esc_url( $row[ 'link' ] ), 1846 'source_name' => $source_title, 1847 'source_link' => esc_url( $source_url ), 1848 'location' => ucwords( sanitize_key( $row[ 'location' ] ) ), 1849 'user' => $user ? $user->display_name : __( 'Guest', 'broken-link-notifier' ), 1850 'method' => isset( $methods[ $row[ 'method' ] ] ) ? $methods[ $row[ 'method' ] ] : __( 'Unknown', 'broken-link-notifier' ), 1841 1851 ]; 1842 1852 } … … 1844 1854 usort( $links, function( $a, $b ) { 1845 1855 $date_compare = strtotime( $b[ 'date' ] ) <=> strtotime( $a[ 'date' ] ); 1846 1847 1856 if ( $date_compare === 0 ) { 1848 1857 return strcasecmp( $a[ 'link' ], $b[ 'link' ] ); 1849 1858 } 1850 1851 1859 return $date_compare; 1852 } );1860 }); 1853 1861 1854 1862 return $links; -
broken-link-notifier/trunk/includes/js/omits.js
r3067171 r3475895 7 7 // Scan type 8 8 const scanType = blnotifier_omit.scan_type; 9 const omitEl = scanType == 'scan-results' ? '.omit-link a' : '.omit-link'; 9 10 10 11 // Listen for omitting links 11 $( '.omit-link').on( 'click', function( e ) {12 $( omitEl ).on( 'click', function( e ) { 12 13 e.preventDefault(); 13 14 var row; 14 15 var link; 15 16 if ( scanType == 'scan-results' ) { 16 const postID = $( this ).data( 'post-id' );17 $( `# post-${postID}` ).addClass( 'omitted' );17 const linkID = $( this ).closest( 'tr' ).data( 'link-id' ); 18 $( `#link-${linkID}` ).addClass( 'omitted' ); 18 19 $( this ).replaceWith( 'Omitted' ); 19 20 link = $( this ).data( 'link' ); -
broken-link-notifier/trunk/includes/js/results-back.js
r3258065 r3475895 1 1 jQuery( $ => { 2 2 // console.log( 'Broken Link Notifier JS Loaded...' ); 3 4 // Add target _blank to title links5 $( 'a.row-title' ).attr( 'target', '_blank' );6 7 // Clear filters8 $( '#link-type-filter' ).on( 'change', function( e ) {9 $( '#code-filter' ).val( '' );10 } );11 $( '#code-filter' ).on( 'change', function( e ) {12 $( '#link-type-filter' ).val( '' );13 } );14 3 15 4 // Nonces … … 24 13 25 14 // Scan an individual link 26 const scanLink = async ( link, postID, code, type, sourceID, method ) => {15 const scanLink = async ( link, linkID, code, type, sourceID, method ) => { 27 16 console.log( `Scanning link (${link})...` ); 28 17 29 18 // Say it started 30 var span = $( `#bln-verify-${ postID}` );19 var span = $( `#bln-verify-${linkID}` ); 31 20 span.addClass( 'scanning' ).html( `<em>Verifying</em>` ); 32 21 … … 40 29 nonce: nonceRescan, 41 30 link: link, 42 postID: postID,31 linkID: linkID, 43 32 code: code, 44 33 type: type, … … 58 47 for ( const linkSpan of linkSpans ) { 59 48 const link = linkSpan.dataset.link; 60 const postID = linkSpan.dataset.postId;49 const linkID = linkSpan.dataset.linkId; 61 50 const code = linkSpan.dataset.code; 62 51 const type = linkSpan.dataset.type; … … 65 54 66 55 // Scan it 67 const data = await scanLink( link, postID, code, type, sourceID, method );56 const data = await scanLink( link, linkID, code, type, sourceID, method ); 68 57 console.log( data ); 69 58 … … 91 80 } 92 81 93 $( `# post-${postID}` ).addClass( 'omitted' );94 $( `# post-${postID} .bln-type` ).addClass( statusType ).text( statusType );95 $( `# post-${postID} .bln_type code` ).html( 'Code: ' + statusCode );96 $( `# post-${postID} .bln_type .message` ).text( statusText );97 $( `# post-${postID} .title.row-actions` ).remove();98 $( `# post-${postID} .bln_source .row-actions` ).remove();82 $( `#link-${linkID}` ).addClass( 'omitted' ); 83 $( `#link-${linkID} .bln-type` ).addClass( statusType ).text( statusType ); 84 $( `#link-${linkID} .bln_type code` ).html( 'Code: ' + statusCode ); 85 $( `#link-${linkID} .bln_type .message` ).text( statusText ); 86 $( `#link-${linkID} .link .row-actions` ).remove(); 87 $( `#link-${linkID} .source .row-actions` ).remove(); 99 88 100 89 // Also reduce count in admin bar 101 reduce AdminBarCount();90 reduceCount(); 102 91 103 92 } else if ( code != statusCode || type != statusType ) { … … 109 98 text = `Link is still bad, but showing a different type. Old type was ${type}; new type is ${statusType}.`; 110 99 } 111 $( `# post-${postID} .bln-type` ).attr( 'class', `bln-type ${statusType}`).text( statusType );100 $( `#link-${linkID} .bln-type` ).attr( 'class', `bln-type ${statusType}`).text( statusType ); 112 101 var codeLink = 'Code: ' + statusCode; 113 102 if ( statusCode != 0 && statusCode != 666 ) { 114 103 codeLink = `<a href="https://http.dev/${statusCode}" target="_blank">Code: ${statusCode}</a>`; 115 104 } 116 $( `# post-${postID} .bln_type code` ).html( codeLink );117 $( `# post-${postID} .bln_typemessage` ).text( statusText );105 $( `#link-${linkID} .bln_type code` ).html( codeLink ); 106 $( `#link-${linkID} .bln_type .message` ).text( statusText ); 118 107 } else { 119 108 text = `Still showing ${statusType}.`; … … 121 110 122 111 // Update the page 123 $( `#bln-verify-${ postID}` ).removeClass( 'scanning' ).addClass( statusType ).html( text );112 $( `#bln-verify-${linkID}` ).removeClass( 'scanning' ).addClass( statusType ).html( text ); 124 113 } 125 114 … … 137 126 */ 138 127 139 $( document ).on( 'click', '.replace-link ', function ( e ) {128 $( document ).on( 'click', '.replace-link a', function ( e ) { 140 129 e.preventDefault(); 141 130 142 let linkElement = $( this ).closest( 'td' ).find( '. row-title' );143 let oldLink = linkElement.attr( 'href' );144 let postID = $( this ).data( 'post-id' );145 let sourceID = $( this ). data( 'source-id' );131 let linkElement = $( this ).closest( 'td' ).find( '.link-url' ); 132 let oldLink = $( this ).data( 'link' ); 133 let linkID = $( this ).closest( 'tr' ).data( 'link-id' ); 134 let sourceID = $( this ).closest( 'tr' ).find( '.source' ).data( 'source-id' ); 146 135 147 136 // Get the current link text … … 159 148 // Handle input field blur (when user clicks outside) 160 149 inputField.on( 'blur', function () { 161 saveLink( $(this), oldLink, sourceID, postID );150 saveLink( $(this), oldLink, sourceID, linkID ); 162 151 } ); 163 152 … … 171 160 172 161 // Function to save the link and replace the input field with a new link 173 function saveLink( inputField, oldLink, sourceID, postID ) {162 function saveLink( inputField, oldLink, sourceID, linkID ) { 174 163 let newLink = inputField.val().trim(); 175 164 176 165 // If the new link is empty, revert to the original 177 166 if ( newLink === '' || newLink === oldLink || !sourceID ) { 178 inputField.replaceWith( `<a class="row-title" href="${oldLink}" target="_blank">${oldLink}</a>` );167 inputField.replaceWith( `<a href="${oldLink}" class="link-url" target="_blank" rel="noopener">${oldLink}</a>` ); 179 168 return; 180 169 } 181 console.log( oldLink, newLink );182 170 183 171 // Create a new link element with the updated text 184 let newLinkElement = $( `<a class="row-title" href="${newLink}" target="_blank">${newLink}</a>` );172 let newLinkElement = $( `<a href="${newLink}" class="link-url" target="_blank" rel="noopener">${newLink}</a>` ); 185 173 186 174 // Replace the input field with the new link … … 195 183 action: 'blnotifier_replace_link', 196 184 nonce: nonceReplace, 197 resultID: postID,198 185 oldLink: oldLink, 199 186 newLink: newLink, … … 204 191 console.log( 'Link updated successfully.' ); 205 192 193 // Replace the old data-link in the Replace Link attribute 194 $( `#link-${linkID} .link .row-actions .replace-link a` ).data( 'link', newLink ).attr( 'data-link', newLink ); 195 206 196 // Replace the source blink 207 let viewPageLink = $( `tr# post-${postID} .column-bln_source .row-actions .view a` );197 let viewPageLink = $( `tr#link-${linkID} .source .row-actions .view a` ); 208 198 let currentHref = viewPageLink.attr( 'href' ); 209 199 let newBlinkUrl = encodeURIComponent( newLink ); … … 212 202 213 203 // Update the type 214 $( `# post-${postID} .bln-type` ).addClass( 'fixed' ).text( 'Replaced' );215 $( `# post-${postID} .bln_type code` ).remove();216 $( `# post-${postID} .bln_type .message` ).html( `The old link has been replaced. Result will be removed.<br>Old link: ${oldLink}` );204 $( `#link-${linkID} .bln-type` ).addClass( 'fixed' ).text( 'Replaced' ); 205 $( `#link-${linkID} .bln_type code` ).remove(); 206 $( `#link-${linkID} .bln_type .message` ).html( `The old link has been replaced. Result will be removed.<br>Old link: ${oldLink}` ); 217 207 218 208 // Remove omit link action 219 $( `# post-${postID} .column-title .row-actions .clear` ).remove();220 $( `# post-${postID} .column-title .row-actions .omit` ).remove();209 $( `#link-${linkID} .link .row-actions .clear-result` ).remove(); 210 $( `#link-${linkID} .link .row-actions .omit-link` ).remove(); 221 211 222 212 // Update the Verify column 223 $( `#bln-verify-${ postID}` ).text( `N/A` );224 225 let rowActions = $( `# post-${postID} .column-title.row-actions` );213 $( `#bln-verify-${linkID}` ).text( `N/A` ); 214 215 let rowActions = $( `#link-${linkID} .link .row-actions` ); 226 216 if ( rowActions.children().length === 1 ) { 227 217 rowActions.html( rowActions.html().replace(' | ', '' ) ); … … 229 219 230 220 // Reduce admin bar count 231 reduce AdminBarCount();221 reduceCount(); 232 222 233 223 } else { … … 246 236 */ 247 237 248 $( document ).on( 'click', '.clear-result ', function ( e ) {238 $( document ).on( 'click', '.clear-result a', function ( e ) { 249 239 e.preventDefault(); 250 240 251 241 let button = $( this ); 252 let postID = button.data( 'post-id' );242 let link = button.data( 'link' ); 253 243 254 244 $.ajax( { … … 259 249 action: 'blnotifier_delete_result', 260 250 nonce: nonceDelete, 261 postID: postID251 link: link 262 252 }, 263 253 success: function ( response ) { … … 265 255 button.closest( 'tr' ).fadeOut( 'fast', function () { 266 256 $( this ).remove(); 257 reduceCount(); 267 258 } ); 268 269 // Reduce admin bar count270 reduceAdminBarCount();271 259 272 260 } else { … … 290 278 let button = $( this ); 291 279 let sourceID = button.data( 'source-id' ); 292 console.log( sourceID );293 280 let postTitle = button.data( 'source-title' ); 294 281 … … 311 298 $( 'tr' ).each( function () { 312 299 let row = $( this ); 313 let rowSourceID = row.find( '. row-actions[data-source-id]' ).attr( 'data-source-id' );300 let rowSourceID = row.find( '.source[data-source-id]' ).attr( 'data-source-id' ); 314 301 315 302 if ( rowSourceID == sourceID ) { 316 303 row.fadeOut( 'fast', function () { 317 304 $( this ).remove(); 305 reduceCount(); 318 306 } ); 319 320 // Reduce admin bar count321 reduceAdminBarCount();322 307 } 323 308 } ); … … 337 322 */ 338 323 339 function reduceAdminBarCount() { 324 function reduceCount() { 325 // Count broken links currently on the page 326 var countBroken = $( 'tr .bln-type.broken' ).length; 327 var countAll = $( 'tr .bln-type' ).length; 328 329 // Update admin bar 340 330 var adminBarEl = $( '#wp-admin-bar-blnotifier-notify' ); 341 331 if ( adminBarEl.length ) { 342 var adminBarCountEl = adminBarEl.find( '.awaiting-mod' ); 343 var adminBarCount = parseInt( adminBarCountEl.text(), 10 ); 344 345 if ( !isNaN( adminBarCount ) && adminBarCount > 0 ) { 346 adminBarCountEl.text( adminBarCount - 1 ); 347 } 348 } 349 } 350 332 adminBarEl.find( '.blnotifier-count-indicator' ).text( countBroken ); 333 } 334 335 // Update admin menu 336 var adminMenuEl = $( 'li.toplevel_page_broken-link-notifier' ); 337 if ( adminMenuEl.length ) { 338 adminMenuEl.find( '.awaiting-mod' ).text( countBroken ); 339 } 340 341 // Update page total counter 342 var pageCountEl = $( '#bln-total-broken-links' ); 343 if ( pageCountEl.length ) { 344 pageCountEl.text( countAll ); 345 } 346 } 347 348 349 /** 350 * TRASH SELECTED BUTTON 351 */ 352 function updateDeleteButton() { 353 const checkedCount = $( '.bln-row-checkbox:checked' ).length; 354 $( '#bln-delete-selected' ).prop( 'disabled', checkedCount === 0 ); 355 } 356 357 $( document ).on( 'change', '.bln-row-checkbox', function() { 358 updateDeleteButton(); 359 } ); 360 361 $( '#cb-select-all-1' ).on( 'change', function() { 362 const checked = $( this ).prop( 'checked' ); 363 $( '.bln-row-checkbox' ).prop( 'checked', checked ); 364 updateDeleteButton(); 365 } ); 351 366 } ) -
broken-link-notifier/trunk/includes/menu.php
r3471187 r3475895 56 56 // The menu items 57 57 $this->menu_items = [ 58 'results' => [ __( 'Results', 'broken-link-notifier' ) , 'edit.php?post_type=blnotifier-results'],59 'omit-links' => [ __( 'Omitted Links', 'broken-link-notifier' ), 'edit-tags.php?taxonomy=omit-links &post_type=blnotifier-results' ],60 'omit-pages' => [ __( 'Omitted Pages', 'broken-link-notifier' ), 'edit-tags.php?taxonomy=omit-pages &post_type=blnotifier-results' ],58 'results' => [ __( 'Results', 'broken-link-notifier' ) ], 59 'omit-links' => [ __( 'Omitted Links', 'broken-link-notifier' ), 'edit-tags.php?taxonomy=omit-links' ], 60 'omit-pages' => [ __( 'Omitted Pages', 'broken-link-notifier' ), 'edit-tags.php?taxonomy=omit-pages' ], 61 61 'scan-single' => [ __( 'Page Scan', 'broken-link-notifier' ) ], 62 62 'scan-multi' => [ __( 'Multi-Scan', 'broken-link-notifier' ) ], … … 206 206 // Taxonomies first 207 207 } elseif ( $current_screen->id == 'edit-omit-links' ) { 208 $submenu_file = 'edit-tags.php?taxonomy=omit-links &post_type=blnotifier-results';208 $submenu_file = 'edit-tags.php?taxonomy=omit-links'; 209 209 $parent_file = $this->get_plugin_page_short_path( null ); 210 210 } elseif ( $current_screen->id == 'edit-omit-pages' ) { 211 $submenu_file = 'edit-tags.php?taxonomy=omit-pages &post_type=blnotifier-results';211 $submenu_file = 'edit-tags.php?taxonomy=omit-pages'; 212 212 $parent_file = $this->get_plugin_page_short_path( null ); 213 214 // Post Type215 } elseif ( $current_screen->post_type == 'blnotifier-results' ) {216 $submenu_file = 'edit.php?post_type=blnotifier-results';217 $parent_file = $this->get_plugin_page_short_path();218 213 } 219 214 … … 582 577 ] 583 578 ); 579 580 // Uninstall database cleanup 581 $uninstall_cleanup_option_name = 'blnotifier_uninstall_cleanup'; 582 register_setting( $this->page_slug, $uninstall_cleanup_option_name, [ $this, 'sanitize_checkbox' ] ); 583 add_settings_field( 584 $uninstall_cleanup_option_name, 585 'Remove Data on Uninstall', 586 [ $this, 'field_checkbox' ], 587 $this->page_slug, 588 'general', 589 [ 590 'class' => $uninstall_cleanup_option_name, 591 'name' => $uninstall_cleanup_option_name, 592 'default' => false, 593 'comments' => 'Enable this option to automatically remove the database table that the links are stored in and all options when the plugin is uninstalled.' 594 ] 595 ); 584 596 } // End settings_fields() 585 597 … … 981 993 */ 982 994 public function get_plugin_page( $tab = 'settings' ) { 983 if ( $tab == 'results' ) { 984 return admin_url( 'edit.php?post_type=blnotifier-results' ); 985 } elseif ( $tab == 'omit-links' || $tab == 'omit-pages' ) { 986 return admin_url( 'edit-tags.php?taxonomy='.$tab.'&post_type=blnotifier-results' ); 995 if ( $tab == 'omit-links' || $tab == 'omit-pages' ) { 996 return admin_url( 'edit-tags.php?taxonomy='.$tab ); 987 997 } else { 988 998 return admin_url( 'admin.php?page='.BLNOTIFIER_TEXTDOMAIN ).'&tab='.sanitize_key( $tab ); -
broken-link-notifier/trunk/includes/omits.php
r3471187 r3475895 123 123 124 124 // Register it as a new taxonomy 125 register_taxonomy( $taxonomy, 'blnotifier-results', [125 register_taxonomy( $taxonomy, [], [ 126 126 // 'hierarchical' => false, 127 127 'labels' => $labels, … … 292 292 // Also delete it from results 293 293 if ( $page && $page == 'scan-results' ) { 294 if ( $post_id = post_exists( $link ) ) { 295 if ( get_post_type( $post_id ) == (new BLNOTIFIER_RESULTS())->post_type ) { 296 wp_delete_post( $post_id, true ); 297 } 298 } 294 (new BLNOTIFIER_RESULTS)->remove( $link ); 299 295 } 300 296 return true; … … 419 415 $options_page = 'toplevel_page_'.BLNOTIFIER_TEXTDOMAIN; 420 416 $tab = (new BLNOTIFIER_HELPERS)->get_tab(); 421 $post_type = get_post_type(); 422 if ( ( $screen == $options_page && $tab == 'scan-single' ) || ( $screen == 'edit.php' && ( isset( $_REQUEST[ '_wpnonce' ] ) && wp_verify_nonce( sanitize_text_field( wp_unslash ( $_REQUEST[ '_wpnonce' ] ) ), 'blnotifier_blinks' ) && isset( $_GET[ 'blinks' ] ) && sanitize_key( $_GET[ 'blinks' ] ) == 'true' ) || $post_type == 'blnotifier-results' ) ) { 423 if ( !$tab && $post_type == 'blnotifier-results' ) { 417 418 if ( 419 ( $screen == $options_page && $tab == 'scan-single' ) || 420 ( $screen == $options_page && $tab == 'results' ) || 421 ( $screen == 'edit.php' && 422 ( 423 isset( $_REQUEST[ '_wpnonce' ] ) && 424 wp_verify_nonce( sanitize_text_field( wp_unslash ( $_REQUEST[ '_wpnonce' ] ) ), 'blnotifier_blinks' ) && 425 isset( $_GET[ 'blinks' ] ) && 426 sanitize_key( $_GET[ 'blinks' ] ) == 'true' 427 ) 428 ) 429 ) { 430 431 if ( $tab == 'results' ) { 424 432 $tab = 'scan-results'; 425 433 } elseif ( !$tab ) { -
broken-link-notifier/trunk/includes/results.php
r3471187 r3475895 21 21 22 22 /** 23 * Main pluginclass.23 * Results class. 24 24 */ 25 25 class BLNOTIFIER_RESULTS { … … 30 30 * @var string 31 31 */ 32 public $ post_type = 'blnotifier-results';32 public $table_name = 'blnotifier_results'; 33 33 34 34 … … 62 62 public function init() { 63 63 64 // Register the post type65 $this-> register_post_type();64 // Maybe create the database table 65 $this->maybe_create_db(); 66 66 67 67 // Add the header to the top of the admin list page 68 68 add_action( 'load-edit.php', [ $this, 'add_header' ] ); 69 69 add_action( 'load-edit-tags.php', [ $this, 'add_header' ] ); 70 71 // Add instructions to top of page72 add_action( 'admin_notices', [ $this, 'description_notice' ] );73 74 // Redirect75 add_filter( 'get_edit_post_link', [ $this, 'redirect' ], 10, 3 );76 77 // Remove Edit from Bulk Actions78 add_filter( 'bulk_actions-edit-'.$this->post_type, [ $this, 'remove_from_bulk_actions' ] );79 80 // Remove post states81 add_filter( 'display_post_states', [ $this, 'remove_post_states' ], 999, 2 );82 83 // Remove Edit and Quick Edit links, and add ignore link84 add_action( 'post_row_actions', [ $this, 'row_actions' ], 10, 2 );85 86 // Skip trash and auto delete87 add_action( 'trashed_post', [ $this, 'skip_trash' ] );88 89 // Add a type filter90 add_action( 'restrict_manage_posts', [ $this, 'admin_filters' ], 10, 2 );91 add_action( 'pre_get_posts', [ $this, 'admin_filters_query' ] );92 93 // Add admin columns94 add_filter( 'manage_'.$this->post_type.'_posts_columns', [ $this, 'admin_columns' ] );95 add_action( 'manage_'.$this->post_type.'_posts_custom_column', [ $this, 'admin_column_content' ], 10, 2 );96 97 // Make admin columns sortable98 add_filter( 'manage_edit-'.$this->post_type.'_sortable_columns', [ $this, 'sort_columns' ] );99 add_action( 'pre_get_posts', [ $this, 'sort_columns_query' ] );100 70 101 71 // Add notifications to admin bar … … 118 88 119 89 } // End init() 120 121 122 /**123 * Register the post type124 */125 public function register_post_type() {126 // Set the labels127 $labels = [128 'name' => _x( 'Links', 'Post Type General Name', 'broken-link-notifier' ),129 'singular_name' => _x( 'Link', 'Post Type Singular Name', 'broken-link-notifier' ),130 'menu_name' => __( 'Links', 'broken-link-notifier' ),131 'name_admin_bar' => __( 'Links', 'broken-link-notifier' ),132 'search_items' => __( 'Search links', 'broken-link-notifier' ),133 'not_found' => __( 'Not found', 'broken-link-notifier' ),134 'not_found_in_trash' => __( 'Not found in Trash', 'broken-link-notifier' ),135 'filter_items_list' => __( 'Filter link list', 'broken-link-notifier' ),136 ];137 138 // Set the CPT args139 $args = [140 'label' => __( 'Links', 'broken-link-notifier' ),141 'description' => __( 'Links', 'broken-link-notifier' ),142 'labels' => $labels,143 'supports' => [],144 'taxonomies' => [],145 'public' => false,146 'show_ui' => true,147 'show_in_menu' => false,148 'show_in_admin_bar' => false,149 'show_in_nav_menus' => false,150 'can_export' => true,151 'has_archive' => false,152 'exclude_from_search' => true,153 'publicly_queryable' => false,154 'query_var' => $this->post_type,155 'capability_type' => 'post',156 'capabilities' => [157 'create_posts' => 'do_not_allow',158 ],159 'map_meta_cap' => true,160 'show_in_rest' => true,161 ];162 163 // Register the CPT164 register_post_type( $this->post_type, $args );165 } // End register_post_type()166 90 167 91 … … 235 159 236 160 /** 237 * Add a description notice238 *239 * @return void240 */241 public function description_notice() {242 global $current_screen;243 if ( 'edit-'.$this->post_type === $current_screen->id ) {244 echo '<div class="notice notice-info" >245 <p>' . esc_html__( 'This page shows the results of your scans. It automatically rechecks the links themselves to see if they are still broken, but it does not remove the links from the pages or rescan the pages to see if broken links have been fixed. After fixing a broken link, you will need to clear the result below. Then when you rescan the page it should not show up here again. Note that the plugin will still find broken links if you simply hide them on the page.', 'broken-link-notifier' ) . '</p>246 </div>';247 }248 } // End description_notice()249 250 251 /**252 * Redirect the edit link253 *254 * @param string $url255 * @param int $post_id256 * @param [type] $context257 * @return string258 */259 public function redirect( $url, $post_id, $context ) {260 if ( get_post_type( $post_id ) == $this->post_type ) {261 return sanitize_text_field( get_the_title( $post_id ) );262 } else {263 return $url;264 }265 } // End redirect()266 267 268 /**269 * Remove Edit from Bulk Actions270 *271 * @param array $actions272 * @return array273 */274 public function remove_from_bulk_actions( $actions ) {275 unset( $actions[ 'edit' ] );276 return $actions;277 } // End remove_from_bulk_actions()278 279 280 /**281 * Remove states282 *283 * @param array $states284 * @param WP_Post $post285 * @return void286 */287 public function remove_post_states( $states, $post ) {288 if ( get_post_type( $post ) == $this->post_type ) {289 return false;290 }291 return $states;292 } // End remove_post_states()293 294 295 /**296 * Action links297 *298 * @param array $actions299 * @param object $post300 * @return array301 */302 public function row_actions( $actions, $post ) {303 if ( $this->post_type == $post->post_type ) {304 $actions = [];305 306 $actions[ 'clear' ] = '<a class="clear-result" href="#" data-post-id="' . $post->ID . '">' . __( 'Clear Result', 'broken-link-notifier' ) . '</a></span>';307 308 $permalink = get_the_title( $post );309 if ( !(new BLNOTIFIER_OMITS)->is_omitted( $permalink, 'links' ) ) {310 $actions[ 'omit' ] = '<a class="omit-link" href="#" data-link="' . $permalink . '" data-post-id="' . $post->ID . '">' . __( 'Omit Link', 'broken-link-notifier' ) . '</a>';311 }312 313 $source_url = get_post_meta( $post->ID, 'source', true );314 if ( $source_url ) {315 $source_url = filter_var( $source_url, FILTER_SANITIZE_URL );316 317 if ( $source_id = url_to_postid( $source_url ) ) {318 $actions[ 'replace' ] = '<a class="replace-link" href="#" data-link="' . $permalink . '" data-post-id="' . $post->ID . '" data-source-id="' . $source_id . '">' . __( 'Replace Link', 'broken-link-notifier' ) . '</a></span>';319 }320 }321 }322 return $actions;323 } // End row_actions()324 325 326 /**327 * Skip trash and auto delete328 *329 * @param int $post_id330 * @return void331 */332 public function skip_trash( $post_id ) {333 if ( get_post_type( $post_id ) == $this->post_type ) {334 wp_delete_post( $post_id, true );335 }336 } // End skip_trash()337 338 339 /**340 * Type filter341 *342 * @return void343 */344 public function admin_filters( $post_type, $which ) {345 $screen = get_current_screen();346 if ( $screen && $screen->id == 'edit-'.$this->post_type ) {347 348 // Link type349 if ( isset( $_REQUEST[ 'link-type' ] ) ) { // phpcs:ignore350 $s = sanitize_key( $_REQUEST[ 'link-type' ] ); // phpcs:ignore351 } else {352 $s = '';353 }354 $HELPERS = new BLNOTIFIER_HELPERS;355 echo '<select id="link-type-filter" name="link-type">356 <option value=""'.esc_html( $HELPERS->is_selected( $s, '' ) ).'>All Link Types</option>357 <option value="broken"'.esc_html( $HELPERS->is_selected( $s, 'broken' ) ).'>Broken Links</option>358 <option value="warning"'.esc_html( $HELPERS->is_selected( $s, 'warning' ) ).'>Warning Links</option>359 </select>';360 361 // Status code362 $bad_codes = $HELPERS->get_bad_status_codes();363 $warning_codes = $HELPERS->get_warning_status_codes();364 $all_codes = array_unique( array_merge( $bad_codes, $warning_codes ) );365 sort( $all_codes );366 367 if ( isset( $_GET[ 'code' ] ) && sanitize_text_field( $_GET[ 'code' ] ) != '' ) { // phpcs:ignore368 $s = absint( $_GET[ 'code' ] ); // phpcs:ignore369 } else {370 $s = '';371 }372 $HELPERS = new BLNOTIFIER_HELPERS;373 echo '<select id="code-filter" name="code">374 <option value=""'.esc_html( $HELPERS->is_selected( $s, '' ) ).'>All Status Codes</option>';375 376 foreach ( $all_codes as $code ) {377 echo '<option value="'.esc_attr( $code ).'"'.esc_html( $HELPERS->is_selected( $s, $code ) ).'>'.esc_attr( $code ).'</option>';378 }379 380 echo '</select>';381 }382 } // End admin_filters()383 384 385 /**386 * The type filter query387 *388 * @param object $query389 * @return void390 */391 public function admin_filters_query( $query ) {392 $post_type = $query->get( 'post_type' );393 if ( $post_type == $this->post_type ) {394 395 // Link type396 if ( isset( $_REQUEST[ 'link-type' ] ) && sanitize_text_field( $_REQUEST[ 'link-type' ] ) != '' ) { // phpcs:ignore397 $type = sanitize_key( $_REQUEST[ 'link-type' ] ); // phpcs:ignore398 if ( $type != '' ) {399 $meta_query[] = [400 'key' => 'type',401 'value' => $type,402 ];403 $query->set( 'meta_query', $meta_query );404 }405 406 // Status code407 } elseif ( isset( $_REQUEST[ 'code' ] ) && sanitize_text_field( $_REQUEST[ 'code' ] ) != '' ) { // phpcs:ignore408 $code = absint( $_REQUEST[ 'code' ] ); // phpcs:ignore409 if ( $code != '' ) {410 $meta_query[] = [411 'key' => 'code',412 'value' => $code,413 ];414 $query->set( 'meta_query', $meta_query );415 }416 }417 }418 } // End admin_filters_query()419 420 421 /**422 * Admin columns423 *424 * @param array $columns425 * @return array426 */427 public function admin_columns( $columns ) {428 return [429 'cb' => '<input type="checkbox"/>',430 'bln_type' => __( 'Type', 'broken-link-notifier' ),431 'title' => __( 'Link', 'broken-link-notifier' ),432 'bln_source' => __( 'Source', 'broken-link-notifier' ),433 'bln_source_pt' => __( 'Source Post Type', 'broken-link-notifier' ),434 'bln_date' => __( 'Date', 'broken-link-notifier' ),435 'bln_author' => __( 'User', 'broken-link-notifier' ),436 'bln_method' => __( 'Method', 'broken-link-notifier' ),437 'bln_verify' => __( 'Verify', 'broken-link-notifier' ),438 ];439 } // End admin_columns()440 441 442 /**443 * Admin column content444 *445 * @param string $column446 * @param int $post_id447 * @return void448 */449 public function admin_column_content( $column, $post_id ) {450 // Type451 if ( 'bln_type' === $column ) {452 $post = get_post( $post_id );453 if ( $post->type == 'broken' ) {454 echo '<div class="bln-type broken">' . esc_html( __( 'Broken', 'broken-link-notifier' ) ) . '</div>';455 } elseif ( $post->type == 'warning' ) {456 echo '<div class="bln-type warning">' . esc_html( __( 'Warning', 'broken-link-notifier' ) ) . '</div>';457 } elseif ( $post->type == 'good' ) {458 echo '<div class="bln-type good">' . esc_html( __( 'Good', 'broken-link-notifier' ) ) . '</div>';459 }460 $code = $post->code;461 if ( $code != 0 && $code != 666 ) {462 $code = '<a href="https://http.dev/'.$code.'" target="_blank">'.$code.'</a>';463 }464 if ( $code == 666 ) {465 $incl_title = ' title="' . __( 'A status code of 666 is a code we use to force invalid URL code 0 to be a broken link. It is not an official status code.', 'broken-link-notifier' ) . '"';466 } elseif ( $code == 0 ) {467 $incl_title = ' title="' . __( 'A status code of 0 means there was no response and it can occur for various reasons, like request time outs. It almost always means something is randomly interfering with the user\'s connection, like a proxy server / firewall / load balancer / laggy connection / network congestion, etc.', 'broken-link-notifier' ) . '"';468 } else {469 $incl_title = '';470 }471 echo '<code'.wp_kses_post( $incl_title ).'>Code: '.wp_kses_post( $code ).'</code> <span class="message">'.esc_html( $post->post_content ).'</span>';472 }473 474 // Source475 if ( 'bln_source' === $column ) {476 $source_url = get_post_meta( $post_id, 'source', true );477 if ( $source_url ) {478 $source_url = filter_var( $source_url, FILTER_SANITIZE_URL );479 $source_url = remove_query_arg( (new BLNOTIFIER_HELPERS)->get_qs_to_remove_from_source(), $source_url );480 $actions = [481 '<span class="view"><a href="'.add_query_arg( 'blink', urlencode( strtok( get_the_title( $post_id ), '?' ) ), $source_url ).'" target="_blank">' . __( 'View Page', 'broken-link-notifier' ) . '</a></span>'482 ];483 if ( $source_id = url_to_postid( $source_url ) ) {484 if ( !(new BLNOTIFIER_OMITS)->is_omitted( $source_url, 'pages' ) ) {485 $actions[] = '<span class="omit"><a class="omit-page" href="#" data-link="'.$source_url.'">' . __( 'Omit Page', 'broken-link-notifier' ) . '</a></span>';486 }487 $scan_nonce = wp_create_nonce( 'blnotifier_scan_single' );488 $actions[] = '<span class="scan"><a class="scan-page" href="'.(new BLNOTIFIER_MENU)->get_plugin_page( 'scan-single' ).'&scan='.$source_url.'&_wpnonce='.$scan_nonce.'" target="_blank">Scan Page</a></span>';489 $actions[] = '<span class="edit"><a href="'.admin_url( 'post.php' ).'?post='.$source_id.'&action=edit">' . __( 'Edit Page', 'broken-link-notifier' ) . '</a></span>';490 if ( is_plugin_active( 'cornerstone/cornerstone.php' ) ) {491 $actions[] = '<span class="edit-in-cornerstone"><a href="'.home_url( '/cornerstone/edit/'.$source_id ).'">' . __( 'Edit in Cornerstone', 'broken-link-notifier' ) . '</a></span>';492 }493 494 $title = get_the_title( $source_id );495 $source_url = '<strong><a class="source-url" href="'.$source_url.'">'.$title.'</a></strong>';496 497 if ( get_option( 'blnotifier_enable_delete_source' ) ) {498 $actions[] = '<span class="delete"><a class="delete-source" href="#" data-source-title="' . $title . '" data-source-id="' . $source_id . '">' . __( 'Trash Page', 'broken-link-notifier' ) . '</a></span>';499 }500 }501 502 echo wp_kses_post( $source_url ).'<div class="row-actions" data-source-id="' . esc_attr( $source_id ) . '">'.wp_kses_post( implode( ' | ', $actions ) ).'</div>';503 } else {504 echo esc_html__( 'No source found...', 'broken-link-notifier' );505 }506 }507 508 // Source URL509 if ( 'bln_source_pt' === $column ) {510 $url = get_post_meta( $post_id, 'source', true );511 if ( $url ) {512 $url = filter_var( $url, FILTER_SANITIZE_URL );513 if ( $source_id = url_to_postid( $url ) ) {514 $post_type = get_post_type( $source_id );515 $post_type_name = (new BLNOTIFIER_HELPERS)->get_post_type_name( $post_type, true );516 } else {517 $post_type_name = '--';518 }519 echo esc_html( $post_type_name );520 }521 }522 523 // Date524 if ( 'bln_date' === $column ) {525 $date = get_the_date( 'F j, Y g:i A', $post_id );526 $location = get_post_meta( $post_id, 'location', true );527 echo 'Discovered in '.esc_html( ucwords( $location ) ).'<br>'.esc_html( $date );528 }529 530 // Author531 if ( 'bln_author' === $column ) {532 $post = get_post( $post_id );533 if ( isset( $post->guest ) && $post->guest ) {534 $display_name = 'Guest';535 } elseif ( $author = $post->post_author ) {536 $user = get_user_by( 'ID', $author );537 $display_name = $user->display_name;538 } else {539 $display_name = 'Guest';540 }541 echo esc_html( $display_name );542 }543 544 // Method545 if ( 'bln_method' === $column ) {546 $post = get_post( $post_id );547 if ( isset( $post->method ) && $post->method ) {548 549 switch ( sanitize_key( $post->method ) ) {550 case 'visit':551 $method = __( 'Front-End Visit', 'broken-link-notifier' );552 break;553 case 'multi':554 $method = __( 'Multi-Scan', 'broken-link-notifier' );555 break;556 case 'single':557 $method = __( 'Page Scan', 'broken-link-notifier' );558 break;559 default:560 $method = __( 'Unknown', 'broken-link-notifier' );561 }562 } else {563 $method = __( 'Unknown', 'broken-link-notifier' );564 }565 echo esc_html( $method );566 }567 568 // Verify569 if ( 'bln_verify' === $column ) {570 571 if ( !(new BLNOTIFIER_HELPERS ())->is_results_verification_paused() ) {572 $link = get_the_title( $post_id );573 $post = get_post( $post_id );574 $code = absint( $post->code );575 $type = sanitize_text_field( $post->type );576 $source_url = filter_var( $post->source, FILTER_SANITIZE_URL );577 $source_url = remove_query_arg( (new BLNOTIFIER_HELPERS)->get_qs_to_remove_from_source(), $source_url );578 $source_id = url_to_postid( $source_url );579 $method = $post->method ? sanitize_key( $post->method ) : 'unknown';580 581 echo '<span id="bln-verify-'.esc_attr( $post_id ).'" class="bln-verify" data-type="' . esc_attr( $type ) . '" data-post-id="'.esc_attr( $post_id ).'" data-link="'.esc_html( $link ).'" data-code="'.esc_attr( $code ).'" data-source-id="'.esc_attr( $source_id ).'" data-method="'.esc_attr( $method ).'">' . esc_html__( 'Pending', 'broken-link-notifier' ) . '</span>';582 583 } else {584 echo esc_html__( 'Auto-verification is paused in settings.', 'broken-link-notifier' );585 }586 587 }588 } // End admin_column_content()589 590 591 /**592 * Make admin columns sortable593 *594 * @param array $columns595 * @return array596 */597 public function sort_columns( $columns ) {598 $columns[ 'bln_type' ] = 'bln_type';599 $columns[ 'bln_source' ] = 'bln_source';600 $columns[ 'bln_source_pt' ] = 'bln_source_pt';601 $columns[ 'bln_date' ] = 'bln_date';602 return $columns;603 } // End sort_columns()604 605 606 /**607 * Sort the order column properly608 *609 * @param object $query610 * @return void611 */612 public function sort_columns_query( $query ) {613 global $current_screen;614 if ( is_admin() && isset( $current_screen ) && $current_screen->id === 'edit-'.$this->post_type ) {615 $orderby = $query->get( 'orderby' );616 if ( 'bln_type' == $orderby ) {617 $query->set( 'meta_key', 'type' );618 $query->set( 'orderby', 'meta_value' );619 } elseif ( 'bln_source' == $orderby ) {620 $query->set( 'meta_key', 'bln_source' );621 $query->set( 'orderby', 'meta_value' );622 } elseif ( 'bln_date' == $orderby ) {623 // $query->set( 'meta_key', 'date' );624 $query->set( 'orderby', 'date' );625 }626 }627 } // End sort_columns_query()628 629 630 /**631 161 * Add an online user count to the admin bar 632 162 * … … 681 211 682 212 /** 213 * Create the database table if it doesn't exist. 214 * 215 * @return void 216 */ 217 public function maybe_create_db() { 218 global $wpdb; 219 220 $table_name = $wpdb->prefix . $this->table_name; 221 222 if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table_name}'" ) !== $table_name ) { 223 require_once ABSPATH . 'wp-admin/includes/upgrade.php'; 224 $charset_collate = $wpdb->get_charset_collate(); 225 226 $sql = "CREATE TABLE $table_name ( 227 id bigint(20) unsigned NOT NULL AUTO_INCREMENT, 228 link varchar(2048) NOT NULL, 229 link_hash char(32) NOT NULL, 230 text varchar(255) NOT NULL, 231 type varchar(20) NOT NULL, 232 code smallint(5) unsigned NOT NULL, 233 source varchar(2048) NOT NULL, 234 location varchar(50) NOT NULL, 235 method varchar(20) NOT NULL, 236 guest tinyint(1) NOT NULL DEFAULT 0, 237 author_id bigint(20) unsigned NOT NULL DEFAULT 0, 238 created_at datetime NOT NULL, 239 PRIMARY KEY (id), 240 UNIQUE KEY link_hash (link_hash), 241 KEY code (code), 242 KEY created_at (created_at) 243 ) $charset_collate;"; 244 245 dbDelta( $sql ); 246 247 // Migrate old posts if they exist 248 $old_posts = get_posts( [ 249 'post_type' => 'blnotifier-results', 250 'posts_per_page' => -1, 251 'post_status' => 'publish', 252 'fields' => 'ids', 253 ] ); 254 255 if ( ! empty( $old_posts ) ) { 256 foreach ( $old_posts as $post_id ) { 257 $code = absint( get_post_meta( $post_id, 'code', true ) ); 258 $location = get_post_meta( $post_id, 'location', true ); 259 $method = get_post_meta( $post_id, 'method', true ); 260 $source = get_post_meta( $post_id, 'source', true ); 261 $type = get_post_meta( $post_id, 'type', true ); 262 263 $post = get_post( $post_id ); 264 $link = $post ? $post->post_title : ''; 265 $text = $post ? $post->post_content : ''; 266 $author = $post ? $post->post_author : 0; 267 $created = $post ? $post->post_date : current_time( 'mysql' ); 268 269 $this->add( [ 270 'link' => $link, 271 'text' => $text, 272 'type' => $type, 273 'code' => $code, 274 'source' => $source, 275 'location' => $location, 276 'method' => $method, 277 'author' => $author, 278 'created_at' => $created, 279 ] ); 280 281 // Delete the old post 282 wp_delete_post( $post_id, true ); 283 } 284 } 285 } 286 } // End maybe_create_db() 287 288 289 /** 683 290 * Check if the link has already been added 684 291 * … … 687 294 */ 688 295 public function already_added( $link ) { 689 return post_exists( sanitize_text_field( $link ), '', '', $this->post_type ); 296 global $wpdb; 297 298 $table_name = $wpdb->prefix . $this->table_name; 299 $link_clean = sanitize_text_field( $link ); 300 $link_hash = md5( strtolower( untrailingslashit( $link_clean ) ) ); 301 302 $exists = $wpdb->get_var( 303 $wpdb->prepare( 304 "SELECT id FROM $table_name WHERE link_hash = %s LIMIT 1", 305 $link_hash 306 ) 307 ); 308 309 return !empty( $exists ); 690 310 } // End already_added() 691 311 … … 693 313 /** 694 314 * Add a new broken or warning link 695 * $args = [696 * 'broken' => true,697 * 'warning' => false,698 * 'code' => 404,699 * 'text' => 'Not found',700 * 'link' => 'https://brokenlink.com',701 * 'source' => 'https://source-url.com',702 * 'author' => 1,703 * 'location' => 'content'704 * ]705 315 * 706 316 * @param array $args … … 708 318 */ 709 319 public function add( $args ) { 710 // If already added, no need to add again 711 if ( $this->already_added( $args[ 'link' ] ) ) { 320 global $wpdb; 321 322 $table_name = $wpdb->prefix . $this->table_name; 323 324 $link = sanitize_text_field( $args[ 'link' ] ); 325 $link_normalized = strtolower( untrailingslashit( $link ) ); 326 $link_hash = md5( $link_normalized ); 327 328 if ( $this->already_added( $link ) ) { 712 329 return 'Link already added'; 713 330 } 714 331 715 // Validate source 716 $source_url = remove_query_arg( (new BLNOTIFIER_HELPERS)->get_qs_to_remove_from_source(), filter_var( $args[ 'source' ], FILTER_SANITIZE_URL ) ); 332 $source_url = remove_query_arg( 333 ( new BLNOTIFIER_HELPERS )->get_qs_to_remove_from_source(), 334 filter_var( $args[ 'source' ], FILTER_SANITIZE_URL ) 335 ); 336 717 337 if ( !$source_url ) { 718 338 return __( 'Invalid source:', 'broken-link-notifier' ) . ' ' . $source_url; 719 339 } 720 340 721 // Args 722 $data = [ 723 'post_title' => sanitize_text_field( $args[ 'link' ] ), 724 'post_content' => sanitize_text_field( $args[ 'text' ] ), 725 'post_status' => 'publish', 726 'post_type' => $this->post_type, 727 'meta_input' => [ 728 'type' => sanitize_key( $args[ 'type' ] ), 729 'code' => absint( $args[ 'code' ] ), 730 'source' => $source_url, 731 'location' => sanitize_key( $args[ 'location' ] ), 732 'guest' => false, 733 'method' => sanitize_key( $args[ 'method' ] ) 341 $inserted = $wpdb->insert( 342 $table_name, 343 [ 344 'link' => $link, 345 'link_hash' => $link_hash, 346 'text' => sanitize_text_field( $args[ 'text' ] ), 347 'type' => sanitize_key( $args[ 'type' ] ), 348 'code' => absint( $args[ 'code' ] ), 349 'source' => esc_url_raw( $source_url ), 350 'location' => sanitize_key( $args[ 'location' ] ), 351 'method' => sanitize_key( $args[ 'method' ] ), 352 'guest' => ( absint( $args[ 'author' ] ) === 0 ) ? 1 : 0, 353 'author_id' => absint( $args[ 'author' ] ), 354 'created_at' => current_time( 'mysql' ), 734 355 ], 735 ]; 736 737 // Guest or not? 738 if ( $args[ 'author' ] == 0 ) { 739 $data[ 'meta_input' ][ 'guest' ] = true; 740 } else { 741 $data[ 'post_author' ] = absint( $args[ 'author' ] ); 742 } 743 744 // Add it 745 $link_id = wp_insert_post( $data ); 746 if ( !is_wp_error( $link_id ) ) { 747 return $link_id; 748 } else { 749 $error = $link_id->get_error_message(); 750 error_log( $error ); // phpcs:ignore 751 return $error; 752 } 356 [ '%s','%s','%s','%s','%d','%s','%s','%s','%d','%d','%s' ] 357 ); 358 359 if ( $inserted ) { 360 return $wpdb->insert_id; 361 } 362 363 return 'Insert failed'; 753 364 } // End add() 754 365 … … 760 371 * @return boolean 761 372 */ 762 public function remove( $link, $link_id = false ) { 763 if ( !$link_id ) { 764 $link_id = post_exists( $link, '', '', $this->post_type ); 765 } 766 if ( $link_id ) { 767 if ( get_post_type( $link_id ) == $this->post_type ) { 768 if ( wp_delete_post( $link_id ) ) { 769 return true; 770 } 771 } 772 } 773 return false; 373 public function remove( $link ) { 374 global $wpdb; 375 376 $table_name = $wpdb->prefix . $this->table_name; 377 $link_hash = md5( strtolower( untrailingslashit( sanitize_text_field( $link ) ) ) ); 378 379 $deleted = $wpdb->delete( 380 $table_name, 381 [ 'link_hash' => $link_hash ], 382 [ '%s' ] 383 ); 384 385 return ( $deleted > 0 ); 774 386 } // End remove() 775 387 … … 928 540 929 541 /** 930 * Ajax call for front end 931 * 932 * @return void 542 * Public AJAX endpoint used by the front-end scanner. 543 * 544 * This endpoint intentionally allows unauthenticated requests because the 545 * plugin scans links from publicly accessible pages visited by guests. 546 * 547 * Security protections implemented: 548 * - Nonce verification to prevent CSRF 549 * - Rate limiting per IP via transient 550 * - Maximum links per scan enforced 551 * - URLs sanitized before processing 552 * - Only HTTP/HTTPS sources allowed 553 * 554 * No privileged actions are performed. The endpoint only scans links and 555 * records results in a custom table that is not publicly accessible. 933 556 */ 934 557 public function ajax_blinks() { … … 937 560 exit( 'No naughty business please.' ); 938 561 } 562 563 // Public endpoint: allow guests, but validate capability for logged-in users. 564 if ( is_user_logged_in() && ! current_user_can( 'read' ) ) { 565 wp_send_json_error( 'Permission denied' ); 566 } 939 567 940 // Get the ID568 // Get the source and links 941 569 $source_url = isset( $_REQUEST[ 'source_url' ] ) ? filter_var( wp_unslash( $_REQUEST[ 'source_url' ] ), FILTER_SANITIZE_URL ) : ''; 942 570 $header_links = isset( $_REQUEST[ 'header_links' ] ) ? wp_unslash( $_REQUEST[ 'header_links' ] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized … … 1116 744 1117 745 // Check permissions 1118 if ( !(new BLNOTIFIER_HELPERS)->user_can_manage_broken_links() ) { 746 $HELPERS = new BLNOTIFIER_HELPERS; 747 if ( !$HELPERS->user_can_manage_broken_links() ) { 1119 748 exit( 'Unauthorized access.' ); 1120 749 } 1121 750 1122 // Get the ID751 // Get the data 1123 752 $link = isset( $_REQUEST[ 'link' ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ 'link' ] ) ) : false; 1124 $ post_id = isset( $_REQUEST[ 'postID' ] ) ? absint( wp_unslash( $_REQUEST[ 'postID' ] ) ) : false;753 $link_id = isset( $_REQUEST[ 'linkID' ] ) ? absint( wp_unslash( $_REQUEST[ 'linkID' ] ) ) : false; 1125 754 $code = isset( $_REQUEST[ 'code' ] ) ? absint( wp_unslash( $_REQUEST[ 'code' ] ) ) : false; 1126 755 $type = isset( $_REQUEST[ 'type' ] ) ? sanitize_key( wp_unslash( $_REQUEST[ 'type' ] ) ) : false; … … 1131 760 if ( $link ) { 1132 761 1133 // Initiate helpers1134 $HELPERS = new BLNOTIFIER_HELPERS;1135 1136 762 // If the source no longer exists, auto remove it 1137 763 if ( !$source_id || !get_post( $source_id ) ) { 1138 $remove = $this->remove( $HELPERS->str_replace_on_link( $link ) , $post_id);764 $remove = $this->remove( $HELPERS->str_replace_on_link( $link ) ); 1139 765 $status = [ 1140 766 'type' => 'n/a', … … 1148 774 $result[ 'status' ] = $status; 1149 775 $result[ 'link' ] = $link; 1150 $result[ ' post_id' ] = $post_id;776 $result[ 'link_id' ] = $link_id; 1151 777 } else { 1152 778 $result[ 'type' ] = 'error'; … … 1162 788 // If it's good now, remove the old post 1163 789 if ( $status[ 'type' ] == 'good' || $status[ 'type' ] == 'omitted' ) { 1164 $remove = $this->remove( $HELPERS->str_replace_on_link( $link ) , $post_id);790 $remove = $this->remove( $HELPERS->str_replace_on_link( $link ) ); 1165 791 if ( $remove ) { 1166 792 $result[ 'type' ] = 'success'; 1167 793 $result[ 'status' ] = $status; 1168 794 $result[ 'link' ] = $link; 1169 $result[ ' post_id' ] = $post_id;795 $result[ 'link_id' ] = $link_id; 1170 796 } else { 1171 797 $result[ 'type' ] = 'error'; … … 1178 804 // If it's still not good, but doesn't have the same code or type, update it 1179 805 } elseif ( $code !== $status[ 'code' ] || $type !== $status[ 'type' ] ) { 1180 $remove = $this->remove( $HELPERS->str_replace_on_link( $link ) , $post_id);806 $remove = $this->remove( $HELPERS->str_replace_on_link( $link ) ); 1181 807 if ( $remove ) { 1182 808 $result[ 'type' ] = 'success'; 1183 809 $result[ 'status' ] = $status; 1184 810 $result[ 'link' ] = $link; 1185 $result[ ' post_id' ] = $post_id;811 $result[ 'link_id' ] = $link_id; 1186 812 1187 813 // Re-add it with new data … … 1191 817 'text' => $status[ 'text' ], 1192 818 'link' => $status[ 'link' ], 1193 'source' => get_the_permalink( $ post_id ),819 'source' => get_the_permalink( $source_id ), 1194 820 'author' => get_current_user_id(), 1195 821 'location' => 'content', … … 1204 830 $result[ 'status' ] = $status; 1205 831 $result[ 'link' ] = $link; 1206 $result[ ' post_id' ] = $post_id;832 $result[ 'link_id' ] = $link_id; 1207 833 } 1208 834 } … … 1230 856 } 1231 857 1232 if ( !(new BLNOTIFIER_HELPERS)->user_can_manage_broken_links() ) { 858 $HELPERS = new BLNOTIFIER_HELPERS; 859 if ( !$HELPERS->user_can_manage_broken_links() ) { 1233 860 exit( 'Unauthorized access.' ); 1234 861 } 1235 862 1236 863 // Get the vars 1237 $result_id = isset( $_REQUEST[ 'resultID' ] ) ? absint( wp_unslash( $_REQUEST[ 'resultID' ] ) ) : false;1238 864 $oldLink = isset( $_REQUEST[ 'oldLink' ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ 'oldLink' ] ) ) : false; 1239 865 $newLink = isset( $_REQUEST[ 'newLink' ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ 'newLink' ] ) ) : false; 1240 866 $source_id = isset( $_REQUEST[ 'sourceID' ] ) ? absint( wp_unslash( $_REQUEST[ 'sourceID' ] ) ) : false; 1241 867 1242 if ( $ result_id && $oldLink && $newLink && $source_id && get_post( $source_id ) ) {868 if ( $oldLink && $newLink && $source_id && get_post( $source_id ) ) { 1243 869 1244 870 // Get the current post content … … 1259 885 1260 886 // Let's also delete the result 1261 if ( get_post_type( $result_id ) == $this->post_type ) { 1262 wp_delete_post( $result_id ); 1263 } 887 $this->remove( $HELPERS->str_replace_on_link( $oldLink ) ); 1264 888 1265 889 // Respond … … 1286 910 } 1287 911 1288 if ( !(new BLNOTIFIER_HELPERS)->user_can_manage_broken_links() ) { 912 $HELPERS = new BLNOTIFIER_HELPERS; 913 if ( !$HELPERS->user_can_manage_broken_links() ) { 1289 914 exit( 'Unauthorized access.' ); 1290 915 } 1291 916 1292 // Get the ID 1293 $post_id = isset( $_REQUEST[ 'postID' ] ) ? absint( $_REQUEST[ 'postID' ] ) : false; 1294 if ( $post_id ) { 1295 1296 // Delete it 1297 if ( get_post_type( $post_id ) == $this->post_type ) { 1298 if ( wp_delete_post( $post_id ) ) { 1299 wp_send_json_success(); 1300 } 1301 } 917 // Remove the link 918 $link = isset( $_REQUEST[ 'link' ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ 'link' ] ) ) : false; 919 if ( $link ) { 920 $this->remove( $HELPERS->str_replace_on_link( $link ) ); 921 wp_send_json_success(); 1302 922 } 1303 923 … … 1333 953 1334 954 // Delete all links with this source 955 global $wpdb; 956 1335 957 $source_url = (new BLNOTIFIER_HELPERS)->get_clean_permalink( $source_id ); 1336 958 if ( $source_url ) { 1337 $query = new WP_Query( [ 1338 'post_type' => $this->post_type, 1339 'meta_query' => [ // phpcs:ignore 1340 [ 1341 'key' => 'source', 1342 'value' => $source_url, 1343 ] 1344 ], 1345 'fields' => 'ids', 1346 'posts_per_page' => -1, 1347 ] ); 1348 1349 if ( !empty( $query->posts ) ) { 1350 foreach ( $query->posts as $post_id ) { 1351 if ( get_post_type( $post_id ) == $this->post_type ) { 1352 wp_delete_post( $post_id, true ); 1353 } 1354 } 1355 } 959 $table_name = $wpdb->prefix . $this->table_name; 960 961 $wpdb->delete( 962 $table_name, 963 [ 'source' => $source_url ], 964 [ '%s' ] 965 ); 1356 966 } 1357 967 … … 1427 1037 */ 1428 1038 public function back_script_enqueuer( $screen ) { 1429 $post_type = get_post_type(); 1430 if ( $screen == 'edit.php' && $post_type == 'blnotifier-results' ) { 1039 if ( $screen == 'toplevel_page_' . BLNOTIFIER_TEXTDOMAIN ) { 1431 1040 $handle = 'blnotifier_results_back_end_script'; 1432 wp_register_script( $handle, BLNOTIFIER_PLUGIN_JS_PATH.'results-back.js', [ 'jquery' ], BLNOTIFIER_VERSION, true );1041 wp_register_script( $handle, BLNOTIFIER_PLUGIN_JS_PATH.'results-back.js', [ 'jquery' ], time(), true ); 1433 1042 wp_localize_script( $handle, 'blnotifier_back_end', [ 1434 1043 'verifying' => !(new BLNOTIFIER_HELPERS())->is_results_verification_paused(), -
broken-link-notifier/trunk/readme.txt
r3471187 r3475895 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.3. 67 Stable tag: 1.3.7 8 8 License: GPLv2 or later 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.txt … … 127 127 128 128 == Changelog == 129 = 1.3.7 = 130 * Update: Changed storage of broken links from custom post type to custom database table 131 * Update: Add option to cleanup on uninstall 132 * Fix: Error when `blnotifier_link_before_prechecks` hook returns status 133 129 134 = 1.3.6 = 130 135 * Update: Added option for Max Links Per Page to help prevent attacks and timeouts
Note: See TracChangeset
for help on using the changeset viewer.