1/** 2 * I Recommend This Plugin Script 3 * 4 * This script handles the AJAX-based "recommend" functionality in WordPress. 5 * It listens for clicks on elements with the .irecommendthis class and sends 6 * an AJAX request to update the recommendation count. 7 * 8 * @package IRecommendThis 9 * @version 4.0.0 10 */ 11 12jQuery( 13 function ( $ ) { 14 'use strict'; 15 16 // IRecommendThis main object - Using object literal pattern for organization. 17 const IRecommendThis = { 18 /** 19 * Initialize the recommendation system. 20 */ 21 init: function () { 22 // Get configuration settings. 23 this.settings = this.getSettings(); 24 25 // Initialize event listeners. 26 this.bindEvents(); 27 }, 28 29 /** 30 * Get settings from the localized variables. 31 * 32 * WordPress passes plugin settings via wp_localize_script() which 33 * creates the 'irecommendthis' global object with all our settings. 34 */ 35 getSettings: function () { 36 // Use only the new variable name. 37 let settings = ( typeof irecommendthis !== 'undefined' ) ? irecommendthis : null; 38 39 if ( ! settings ) { 40 console.error( 'AJAX settings not found - plugin may not be properly initialized.' ); 41 return { 42 ajaxurl: ajaxurl || '', 43 nonce: '', 44 options: '{}', 45 removal_delay: 250 46 }; 47 } 48 49 // Parse options from JSON string to object. 50 try { 51 settings.parsedOptions = JSON.parse( settings.options ); 52 } catch ( e ) { 53 console.error( 'Error parsing options JSON:', e ); 54 settings.parsedOptions = {}; 55 } 56 57 return settings; 58 }, 59 60 /** 61 * Bind event listeners for recommendation buttons. 62 * 63 * Uses event delegation to capture clicks on any current or future 64 * recommendation buttons added to the page. 65 */ 66 bindEvents: function () { 67 // Use event delegation for all recommendation buttons. 68 $( document ).on( 'click', '.irecommendthis', this.handleRecommendClick.bind( this ) ); 69 }, 70 71 /** 72 * Handle recommendation button clicks. 73 * 74 * This function processes user interactions with the recommendation buttons, 75 * determines the post ID, and manages the button state. 76 * 77 * @param {Object} event The click event object. 78 * @return {boolean} Returns false to prevent default action. 79 */ 80 handleRecommendClick: function ( event ) { 81 event.preventDefault(); 82 83 const $link = $( event.currentTarget ); 84 85 // Prevent multiple processing if this button is already in process. 86 if ( $link.hasClass( 'processing' ) ) { 87 return false; 88 } 89 90 // Get post ID from the button. 91 const postId = this.getPostId( $link ); 92 if ( ! postId ) { 93 console.error( 'Could not determine post ID for recommendation.' ); 94 return false; 95 } 96 97 // Get state information - whether this is a like or unlike action. 98 const unrecommend = $link.hasClass( 'active' ); 99 const suffix = $link.find( '.irecommendthis-suffix' ).text(); 100 101 // Process the recommendation via AJAX. 102 this.processRecommendation( postId, unrecommend, suffix, $link ); 103 104 return false; 105 }, 106 107 /** 108 * Extract the post ID from a recommendation link. 109 * 110 * Tries multiple methods to determine the post ID: 111 * 1. From data-post-id attribute (preferred) 112 * 2. From ID attribute 113 * 3. From class names 114 * 115 * @param {Object} $el jQuery element object. 116 * @return {string|null} The post ID or null if not found. 117 */ 118 getPostId: function ( $el ) { 119 // Try data attribute first (best practice). 120 let postId = $el.data( 'post-id' ); 121 122 // Fall back to ID attribute. 123 if ( ! postId && $el.attr( 'id' ) ) { 124 postId = $el.attr( 'id' ).replace( 'irecommendthis-', '' ); 125 } 126 127 // Last resort: check class names. 128 if ( ! postId && $el.attr( 'class' ) ) { 129 // Find class names that match our pattern. 130 $.each( 131 $el.attr( 'class' ).split( ' ' ), 132 function ( _, className ) { 133 if ( className.indexOf( 'irecommendthis-post-' ) === 0 ) { 134 postId = className.replace( 'irecommendthis-post-', '' ); 135 // Break the each loop. 136 return false;
137 } 138 } 139 ); 140 } 141 142 return postId; 143 }, 144 145 /** 146 * Process a recommendation through AJAX. 147 * 148 * Sends the request to the server to update the recommendation count 149 * and handles the response. 150 * 151 * @param {string} postId The post ID to recommend/unrecommend. 152 * @param {boolean} unrecommend Whether this is an unrecommend action. 153 * @param {string} suffix The text suffix for the count. 154 * @param {Object} $link The jQuery button element that was clicked. 155 */ 156 processRecommendation: function ( postId, unrecommend, suffix, $link ) { 157 // Add a 'processing' class to prevent duplicate clicks. 158 $link.addClass( 'processing' ); 159 160 // Get all buttons for this post for later updating (there might be multiple on the page). 161 const allButtons = this.getAllButtonsForPost( postId ); 162 163 // Make a single AJAX request with simpler approach. 164 $.ajax( 165 { 166 url: this.settings.ajaxurl, 167 type: 'POST', 168 data: { 169 action: 'irecommendthis', 170 recommend_id: postId, 171 suffix: suffix, 172 unrecommend: unrecommend, 173 nonce: this.settings.nonce 174 }, 175 success: function ( data ) { 176 // Handle the response. 177 IRecommendThis.handleSuccess( data, postId, unrecommend, allButtons ); 178 }, 179 error: function ( xhr, status, error ) { 180 IRecommendThis.handleError( xhr, status, error, $link ); 181 }, 182 complete: function () { 183 // Clean up processing state with a slight delay for visual feedback. 184 setTimeout( 185 function () { 186 allButtons.removeClass( 'processing' ); 187 }, 188 IRecommendThis.settings.removal_delay || 250 189 ); 190 } 191 } 192 ); 193 }, 194 195 /** 196 * Get all recommendation buttons for a specific post. 197 * 198 * Finds all buttons on the page that refer to the same post ID, 199 * so they can all be updated simultaneously. 200 * 201 * @param {string} postId The post ID to find buttons for. 202 * @return {Object} jQuery collection of matching buttons. 203 */ 204 getAllButtonsForPost: function ( postId ) { 205 // Create selector for all possible buttons matching this post ID. 206 return $( '.irecommendthis[data-post-id="' + postId + '"], #irecommendthis-' + postId + ', .irecommendthis-post-' + postId ); 207 }, 208 209 /** 210 * Handle successful AJAX response. 211 * 212 * Updates all buttons for the post with new count and state. 213 * 214 * @param {string|Object} data The response from the server. 215 * @param {string} postId The post ID that was acted upon. 216 * @param {boolean} unrecommend Whether this was an unrecommend action. 217 * @param {Object} $buttons jQuery collection of buttons to update. 218 */ 219 handleSuccess: function ( data, postId, unrecommend, $buttons ) { 220 // Check if the server responded with an error. 221 if ( typeof data === 'object' && data.success === false ) { 222 console.error( 'Error:', data.data ? data.data.message : 'Unknown error.' ); 223 return; 224 } 225 226 const options = this.settings.parsedOptions; 227 228 // Get text states from data attributes or settings. 229 const defaultLikeText = options.link_title_new || 'Recommend this'; 230 const defaultUnlikeText = options.link_title_active || 'Unrecommend this'; 231 232 // Update each matching button. 233 $buttons.each( 234 function () { 235 const $button = $( this ); 236 237 // Get text state from data attributes (if available). 238 const likeText = $button.data( 'like' ) || defaultLikeText; 239 const unlikeText = $button.data( 'unlike' ) || defaultUnlikeText; 240 241 // Replace the HTML content with the response data. 242 $button.html( data ); 243 244 // Toggle the 'active' state. 245 $button.toggleClass( 'active' ); 246 247 // Update both title and aria-label for accessibility. 248 const newState = $button.hasClass( 'active' ) ? unlikeText : likeText; 249 $button.attr( 'title', newState ); 250 $button.attr( 'aria-label', newState ); 251 252 // Check the numeric count, if present. 253 const $count = $button.find( '.irecommendthis-count' ); 254 if ( $count.length ) { 255 const countText = $count.text().trim(); 256 const count = parseInt( countText, 10 ); 257 258 if ( ! isNaN( count ) ) { 259 // Hide count if zero and user wants to hide zero counts. 260 if ( count === 0 && parseInt( options.hide_zero, 10 ) === 1 ) { 261 $count.hide(); 262 } else { 263 $count.show(); 264 } 265 } 266 } 267 } 268 ); 269 }, 270 271 /** 272 * Handle AJAX errors. 273 * 274 * Logs error information and removes processing state. 275 * 276 * @param {Object} xhr The XHR object. 277 * @param {string} status The status text. 278 * @param {string} error The error message. 279 * @param {Object} $link The jQuery button element. 280 */ 281 handleError: function ( xhr, status, error, $link ) { 282 console.error( 'AJAX Error:', status, error ); 283 if ( xhr.responseJSON && xhr.responseJSON.data ) { 284 console.error( 'Error details:', xhr.responseJSON.data ); 285 } 286 $link.removeClass( 'processing' ); 287 } 288 }; 289 290 // Initialize when DOM is ready. 291 $( 292 function () { 293 IRecommendThis.init(); 294 } 295 ); 296 } 297);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.