WordPress.org

Plugin Directory

Changeset 358685


Ignore:
Timestamp:
03/11/11 09:44:40 (3 years ago)
Author:
joelhardi
Message:

Version bump to 0.9.1. SQL performance improvements and minor bug fixes, see changelog in readme.txt for details. Big props Raph Koster for helping debug SQL issues.

Location:
user-spam-remover/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • user-spam-remover/trunk/readme.txt

    r356436 r358685  
    8787== Changelog == 
    8888 
     89= 0.9.1 = 
     90 +  Now detects and adds absent MySQL indexes to wp_comments.user_id and 
     91    wp_links.link_owner columns. Greatly speeds performance and enables use on  
     92    much larger databases. Big props to Raph Koster for help debugging! 
     93 +  Enables MySQL sql_big_selects config var at runtime for use on shared 
     94    hosts and other installations where this is disabled by default. 
     95 +  MySQL SELECT errors now logged/shown to the user as appropriate. 
     96 +  Hard limit of 1000 users per deletion to prevent long-running operations. 
     97    Upped limit to 10000 records per SELECT, thanks to improved SQL and indexes. 
     98 +  No longer deletes users with only comments marked as "spam." This is a 
     99    small functional regression, but it speeds SQL performance. Once the spam 
     100    is permanently removed these users will be deleted anyway. 
     101 +  Minor bug fix affecting settings page user list display w/ bbPress users. 
     102 +  Changes to method visibility. Many previously public methods now protected. 
     103 +  Code refactoring. 
     104 
    89105= 0.9 = 
    90106 +  Version/compatibility bump so that wordpress.org plugin repository info is 
  • user-spam-remover/trunk/user-spam-remover.php

    r356436 r358685  
    44Plugin URI: http://lyncd.com/user-spam-remover/ 
    55Description: Automatically removes spam user registrations and other old, never-used user accounts. Blocks annoying e-mail to administrator after every new registration. Full logging and backup of deleted data. Requires PHP5. After activating, go to <a href="users.php?page=user_spam_remover">settings page</a> to enable. 
    6 Version: 0.9 
     6Version: 0.9.1 
    77Author: Joel Hardi 
    88Author URI: http://lyncd.com/ 
     
    8080  protected $manualRun = FALSE; 
    8181 
     82  // SQL query fragment cached during runtime since mysql_real_escape_string 
     83  // is expensive. See getUserWhitelistSQL() for details 
     84  protected $whitelistSQL; 
     85 
    8286  // wpdb object 
    8387  protected $wpdb; 
     
    116120  // Static function to execute remove regardless of 'enabled' setting 
    117121  public static function manualRemove() { 
    118     $usp = self::getInstance(); 
    119     $usp->manualRun = TRUE; 
    120     return $usp->remove(); 
     122    $usr = self::getInstance(); 
     123    $usr->manualRun = TRUE; 
     124    return $usr->remove(1000); 
    121125  } 
    122126 
    123127  // Static function for WordPress to use to execute remove 
    124128  public static function scheduledRemove() { 
    125     $usp = self::getInstance(); 
    126     $usp->logDebug('scheduledRemove() called'); 
    127     return $usp->remove(); 
     129    $usr = self::getInstance(); 
     130    $usr->logDebug('scheduledRemove() called'); 
     131    return $usr->remove(1000); 
    128132  } 
    129133 
    130134  // Add to WordPress schedule, runs on plugin activation only 
    131135  public static function activate() { 
     136    $usr = self::getInstance(); 
    132137    if (self::$debug) { 
    133       $usp = self::getInstance(); 
    134       $usp->logDebug('activate() called'); 
     138      $usr->logDebug('activate() called'); 
    135139      // (can't log result of wp_schedule_event(), it doesn't return anything) 
    136140      wp_schedule_event(time() + 1800, 'hourly', self::$pluginURLName); 
     
    138142      wp_schedule_event(time() + 1800, 'daily', self::$pluginURLName); 
    139143    } 
     144    $usr->checkMySQL(); 
    140145  } 
    141146 
     
    143148  public static function deactivate() { 
    144149    if (self::$debug) { 
    145       $usp = self::getInstance(); 
    146       $usp->logDebug('deactivate() called'); 
     150      $usr = self::getInstance(); 
     151      $usr->logDebug('deactivate() called'); 
    147152      // (can't log result of wp_clear_scheduled_hook(), it doesn't return) 
    148153    } 
     
    174179        !current_user_can('remove_users')) 
    175180      wp_die(__('You do not have sufficient permissions to access this page.')); 
    176  
    177 ?> 
    178 <div class="wrap"> 
    179 <?php screen_icon(); ?> 
    180   <h2>User Spam Remover</h2> 
    181 <?php 
     181    $usr = self::getInstance(); 
     182    $usr->optionsPage(); 
     183  } 
     184 
     185  // Saves record of new user's registration to activity log 
     186  // i.e., replaces annoying e-mails that are normally sent to administrator 
     187  // public method because called from our modified version of  
     188  // wp_new_user_notification() (see end of this file) 
     189  public function logNewUser(WP_User $user) { 
     190    $id = $user->ID; 
     191    $login = stripslashes($user->user_login); 
     192    $blog = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES); 
     193 
     194    // (could also log things like email, password and name but leaving them 
     195    // out for privacy reasons, especially since this log could be saved to  
     196    // publicly accessible location.) 
     197    return $this->logAction("New user added with ID: $id, login: $login on blog: $blog"); 
     198  } 
     199 
     200  // Logs $str to activity log if debugging and logging are enabled 
     201  // (and log file is writable) 
     202  public function logDebug($str) { 
     203    if (self::$debug) 
     204      return $this->logAction('debug: '.$str); 
     205    else 
     206      return FALSE; 
     207  } 
     208 
     209  // Print options page 
     210  protected function optionsPage() { 
     211    echo "<div class=\"wrap\">\n"; 
     212    screen_icon(); 
     213    echo "<h2>User Spam Remover</h2>\n"; 
    182214    settings_errors(); 
    183     $usp = self::getInstance(); 
    184215    $removeNowBool = 'remove_users_now'; 
    185216    $nonceRemoveUsersNow = self::$pluginURLName . $removeNowBool; 
     
    205236    // Print "Remove spam/unused accounts now" button if settings consistent 
    206237    try { 
    207       $usp->checkEnabled(TRUE); 
     238      $this->checkEnabled(TRUE); 
    208239?> 
    209240  <form method="post" action="<?php echo htmlspecialchars($_SERVER['REQUEST_URI']); ?>" style="float: right; text-align: right; width: 22em; overflow: hidden;"><input type="hidden" name="<?php echo $removeNowBool; ?>" value="1" /> 
     
    220251    // Print last X lines in activity log if they exist 
    221252    $linesToPrint = 10; 
    222     $pathname = realpath($usp->getOption('logDir').'/'. 
    223                          $usp->getOption('logFilename')); 
     253    $pathname = realpath($this->getOption('logDir').'/'. 
     254                         $this->getOption('logFilename')); 
    224255    if (is_readable($pathname)) { 
    225256      // emulate "tail -X" of log file 
     
    237268          // Be sure to protect against XSS and injection attacks on logfile 
    238269          // Here, using restrictive character whitelist, better than escaping 
    239           if (preg_match('#[\s\w\-\.\+\:\'\\\(\),/"]+$#',  
     270          if (preg_match('#[\s\w=\-\.\+\:\'\\\(\),;/"\#]+$#',  
    240271                         trim(implode('', $lines)), $matches)) { 
    241272            echo "<h4>Latest $linesToPrint lines in activity log</h4>\n"; 
    242273            echo '<pre style="background: white; overflow: auto; max-height: 5em; padding: 0.5em;">'; 
    243             echo $matches[0]."</pre>\n"; 
     274            echo esc_html($matches[0])."</pre>\n"; 
    244275          } 
    245276        } 
     
    248279 
    249280    // Preview users pending deletion 
    250     $maxShow = 300; 
    251     $days = self::sanitizePosInt($usp->getOption('daysGrace')); 
    252     if ($usp->getOption('enabled')) { 
     281    $maxShow = 100; 
     282    $days = self::sanitizePosInt($this->getOption('daysGrace')); 
     283    if ($this->getOption('enabled')) { 
    253284      if ($days > 0) 
    254285        $days = $days - 1; 
    255286      echo "<h4>Unused accounts pending deletion</h4>\n"; 
    256287      echo "<p>These unused user accounts are within 24 hours of the age threshold you've set below and will be automatically deleted in the next 48 hours.</p>\n"; 
    257       $users = $usp->getIDList($days, TRUE); 
    258288    } else { 
    259289      echo "<h4>Unused accounts over the age threshold</h4>\n"; 
    260290      echo "<p>These unused user accounts are older than the age threshold you've set below. To remove them, either enable automatic deletion or click the \"Remove spam/unused accounts now\" button above.</p>\n"; 
    261       $users = $usp->getIDList($days, TRUE); 
    262     } 
    263     echo '<p style="background: white; overflow: auto; max-height: 5em; padding: 0.5em;">'; 
    264     if (count($users) > 0) { 
    265       $shown = 0; 
    266       foreach ($users as $id => $login) { 
    267         // for $edit_link, see WP_Users_List_Table::single_row() in  
    268         // wp-admin/includes/class-wp-users-list-table.php 
    269         $edit_link = esc_url( 
    270           add_query_arg('wp_http_referer',  
    271                         urlencode(stripslashes($_SERVER['REQUEST_URI'])),  
    272                         "user-edit.php?user_id=$id")); 
    273         echo '<a href="'.$edit_link.'">'.$login.'</a> '; 
    274         $shown++; 
    275         if ($shown == $maxShow) { 
    276           $leftover = count($users) - $shown; 
    277           if ($leftover > 0) 
    278             echo " and $leftover more"; 
    279           break; 
    280         } 
    281       } 
    282     } else { 
    283       echo "no matching accounts found"; 
    284     } 
    285     echo "</p>\n"; 
     291    } 
     292    try { 
     293      $users = $this->getIDList($days, 10000, TRUE); 
     294    } catch (UserSpamRemoverException $e) { 
     295      self::errorMsg($e->getMessage()); 
     296    } 
     297    if (isset($users)) { 
     298      echo '<p style="background: white; overflow: auto; max-height: 5em; padding: 0.5em;">'; 
     299      if (count($users) > 0) { 
     300        $shown = 0; 
     301        foreach ($users as $id => $login) { 
     302          // for $edit_link, see WP_Users_List_Table::single_row() in  
     303          // wp-admin/includes/class-wp-users-list-table.php 
     304          $edit_link = esc_url( 
     305            add_query_arg('wp_http_referer',  
     306                          urlencode(stripslashes($_SERVER['REQUEST_URI'])),  
     307                          "user-edit.php?user_id=$id")); 
     308          echo '<a href="'.$edit_link.'">'.$login.'</a> '; 
     309          $shown++; 
     310          if ($shown == $maxShow) { 
     311            $leftover = count($users) - $shown; 
     312            if ($leftover > 0) 
     313              echo " and $leftover more."; 
     314            break; 
     315          } 
     316        } 
     317      } else { 
     318        echo "no matching accounts found"; 
     319      } 
     320      echo "</p>\n"; 
     321    } 
    286322?> 
    287323  <h3>Settings</h3> 
     
    291327<?php 
    292328  try { 
    293     $usp->checkEnabled(); 
     329    $this->checkEnabled(); 
    294330  } catch (UserSpamRemoverException $e) { 
    295331    self::errorMsg(str_replace('was not', 'will not be', $e->getMessage()). ' See below for details.'); 
     
    301337        <th scope="row"><label for="<?php echo $o; ?>">Enable</label></th> 
    302338        <td> 
    303           <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($usp->getOption('enabled')) { echo 'checked="checked" '; } ?>/> 
     339          <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($this->getOption('enabled')) { echo 'checked="checked" '; } ?>/> 
    304340          <span class="description">Check to enable automatic removal of never-used user accounts</span> 
    305341        </td> 
     
    308344        <th scope="row"><label for="<?php echo $o; ?>">Age threshold (in days)</label></th> 
    309345        <td> 
    310           <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo (int) $usp->getOption('daysGrace'); ?>" size="3" /> 
     346          <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo (int) $this->getOption('daysGrace'); ?>" size="3" /> 
    311347          <span class="description">Only unused accounts older than this are removed (gives new users a chance to post!)</span> 
    312348        </td> 
     
    315351        <th scope="row"><label for="<?php echo $o; ?>">User whitelist</label></th> 
    316352        <td> 
    317           <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($usp->getOption('userWhitelist')); ?>" size="75" /><br /> 
     353          <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($this->getOption('userWhitelist')); ?>" size="75" /><br /> 
    318354          <span class="description">Comma-separated list of usernames to protect from deletion</span> 
    319355        </td> 
     
    327363        <th scope="row"><label for="<?php echo $o; ?>">Enable</label></th> 
    328364        <td> 
    329           <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($usp->getOption('noAdminEmails')) { echo 'checked="checked" '; } ?>/> 
     365          <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($this->getOption('noAdminEmails')) { echo 'checked="checked" '; } ?>/> 
    330366          <span class="description">Check to block new user e-mail notifications to administrator</span> 
    331367        </td> 
     
    340376        <th scope="row"><label for="<?php echo $o; ?>">Log directory</label></th> 
    341377        <td> 
    342           <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($usp->getOption('logDir')); ?>" size="75" /><br /> 
     378          <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($this->getOption('logDir')); ?>" size="75" /><br /> 
    343379          <span class="description">Filesystem directory where logs are saved (do not use trailing slash)</span> 
    344380<?php 
    345381  try { 
    346382    $dirOK = TRUE; 
    347     if ($usp->getOption('activityLog') or $usp->getOption('restoreLog')) 
    348       $usp->checkLogDir(); 
     383    if ($this->getOption('activityLog') or $this->getOption('restoreLog')) 
     384      $this->checkLogDir(); 
    349385  } catch (UserSpamRemoverException $e) { 
    350386    $dirOK = FALSE; 
     
    357393        <th scope="row"><label for="<?php echo $o; ?>">Activity log</label></th> 
    358394        <td> 
    359           <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($usp->getOption('activityLog')) { echo 'checked="checked" '; } ?>/> 
     395          <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($this->getOption('activityLog')) { echo 'checked="checked" '; } ?>/> 
    360396          <span class="description">Check to enable activity log</span> 
    361397        </td> 
     
    364400        <th scope="row"><label for="<?php echo $o; ?>">Activity log filename</label></th> 
    365401        <td> 
    366           <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($usp->getOption('logFilename')); ?>" size="35" /> 
     402          <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($this->getOption('logFilename')); ?>" size="35" /> 
    367403<?php 
    368404  try { 
    369     if ($dirOK and $usp->getOption('activityLog')) 
    370       $usp->checkFilePathname($usp->getOption('logFilename')); 
     405    if ($dirOK and $this->getOption('activityLog')) 
     406      $this->checkFilePathname($this->getOption('logFilename')); 
    371407  } catch (UserSpamRemoverException $e) { 
    372408    self::errorMsg($e->getMessage()); 
     
    378414        <th scope="row"><label for="<?php echo $o; ?>">User backup log</label></th> 
    379415        <td> 
    380           <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($usp->getOption('restoreLog')) { echo 'checked="checked" '; } ?>/> 
     416          <input type="checkbox" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="1" <?php if ($this->getOption('restoreLog')) { echo 'checked="checked" '; } ?>/> 
    381417          <span class="description">Check to enable user backup log</span> 
    382418        </td> 
     
    385421        <th scope="row"><label for="<?php echo $o; ?>">Backup log filename</label></th> 
    386422        <td> 
    387           <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($usp->getOption('restoreFilename')); ?>" size="35" /> 
     423          <input type="text" id="<?php echo $o; ?>" name="<?php echo $o; ?>" value="<?php echo esc_attr($this->getOption('restoreFilename')); ?>" size="35" /> 
    388424<?php 
    389425  try { 
    390     if ($dirOK and $usp->getOption('restoreLog')) 
    391       $usp->checkFilePathname($usp->getOption('restoreFilename')); 
     426    if ($dirOK and $this->getOption('restoreLog')) 
     427      $this->checkFilePathname($this->getOption('restoreFilename')); 
    392428  } catch (UserSpamRemoverException $e) { 
    393429    self::errorMsg($e->getMessage()); 
     
    406442  } 
    407443 
    408   // Prints WordPress <div class="error"><p> style msg inline 
    409   public static function errorMsg($str) { 
    410     echo '<div class="error inline"><p><strong>'.$str."</strong></p></div>\n"; 
    411   } 
    412  
    413   public function remove() { 
    414     // Check pruning is enabled and log files are writable if they're enabled 
     444  // Remove either all old empty user accounts up to (int) $limit accounts 
     445  protected function remove($limit = FALSE) { 
     446    $pre = $this->wpdb->prefix; 
     447    $db = $this->wpdb->dbh; 
     448    $days = self::sanitizePosInt($this->getOption('daysGrace')); 
     449    $daysPlural = ($days == 1) ? '' : 's'; 
     450    $ids = array(); 
     451    if ($limit !== FALSE) 
     452      $limit = (int) $limit; 
     453 
    415454    try { 
     455      // Check pruning is enabled and log files are writable if they're enabled 
    416456      if (!$this->checkEnabled($this->manualRun)) 
    417457        return FALSE; 
     458      // Fetch list of IDs to remove 
     459      $ids = $this->getIDList($days, $limit); 
    418460    } catch (UserSpamRemoverException $e) { 
    419461      $this->logAction($e->getMessage()); 
     
    423465    } 
    424466 
    425     $pre = $this->wpdb->prefix; 
    426     $db = $this->wpdb->dbh; 
    427     $days = self::sanitizePosInt($this->getOption('daysGrace')); 
    428     if ($days != 1) 
    429       $daysPlural = 's'; 
    430     else 
    431       $daysPlural = ''; 
    432  
    433467    // Remove identified unused user records. Cancel remove and roll back  
    434468    // database if there's a problem writing to restore log 
    435     $ids = $this->getIDList($days); 
    436     if (count($ids) > 0) { 
     469    $idCnt = count($ids); 
     470    if ($idCnt > 0) { 
    437471      // Format list of users to delete 
    438472      $idList = implode(', ', $ids); 
    439       $idCnt = count($ids); 
    440       if ($idCnt != 1) 
    441         $idPlural = 's'; 
    442       else 
    443         $idPlural = ''; 
    444473      $this->timestamp = time(); 
    445474      $error = FALSE; 
    446475 
    447476      // Begin SQL transation 
    448       mysql_query("BEGIN", $db); 
     477      mysql_query("START TRANSACTION", $db); 
    449478 
    450479      // Back up users about to be deleted 
     
    480509      } else { 
    481510        mysql_query("COMMIT", $db); 
     511        $idPlural = ($idCnt == 1) ? '' : 's'; 
    482512        $result = "Removed $idCnt unused user account$idPlural older than $days day$daysPlural."; 
    483513        $this->logAction($result); 
     
    488518  } 
    489519 
    490   // Saves record of new user's registration to activity log 
    491   // i.e., replaces annoying e-mails that are normally sent to administrator 
    492   // public method because called from our modified version of  
    493   // wp_new_user_notification() (see end of this file) 
    494   public function logNewUser(WP_User $user) { 
    495     $id = $user->ID; 
    496     $login = stripslashes($user->user_login); 
    497     $blog = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES); 
    498  
    499     // (could also log things like email, password and name but leaving them 
    500     // out for privacy reasons, especially since this log could be saved to  
    501     // publicly accessible location.) 
    502     return $this->logAction("New user added with ID: $id, login: $login on blog: $blog"); 
    503   } 
    504  
    505   // Logs $str to activity log if debugging and logging are enabled 
    506   // (and log file is writable) 
    507   public function logDebug($str) { 
    508     if (self::$debug) 
    509       return $this->logAction('debug: '.$str); 
    510     else 
    511       return FALSE; 
    512   } 
    513  
    514   // Prepend date and save $str (should be a single line only) to activity log. 
    515   // Returns TRUE on success or FALSE on failure (i.e. log file not writable) 
    516   protected function logAction($str) { 
    517     $fh = @fopen($this->getOption('logDir').'/'. 
    518                  $this->getOption('logFilename'), 'a'); 
    519     if (!$fh) 
    520       return FALSE; 
    521  
    522     $str = $this->date($this->timeFormat).' '.$str; 
    523  
    524     $numbytes = fwrite($fh, $str."\n"); 
    525     fclose($fh); 
    526  
    527     return (bool) $numbytes; 
    528   } 
    529  
    530   // Returns array of user IDs to delete older than $daysGrace 
    531   //  * if $returnNames is TRUE, returns assoc array(id => user_login) 
    532   protected function getIDList($daysGrace, $returnNames = FALSE) { 
     520  // Returns array of user IDs to delete that are older than $daysGrace  
     521  // with a max of $limit records 
     522  //  * if $returnNames is TRUE, returns assoc array(ID => user_login) 
     523  protected function getIDList($daysGrace, $limit = FALSE, $returnNames = FALSE) { 
     524    $this->checkMySQL(); 
     525    $db = $this->wpdb->dbh; 
     526    $ids = array(); 
     527 
     528    // Identify unused user records older than $daysGrace and populate $ids 
     529    $sql = $this->getListSQL($daysGrace, $limit, $returnNames); 
     530    $result = mysql_query($sql, $db); 
     531    if ($result) { 
     532      if (mysql_num_rows($result) > 0) { 
     533        if ($returnNames) { 
     534          while ($row = mysql_fetch_row($result)) 
     535            $ids[$row[0]] = $row[1]; 
     536        } else { 
     537          while ($row = mysql_fetch_row($result)) 
     538            $ids[] = $row[0]; 
     539        } 
     540        $sql = $this->getPostedListSQL($daysGrace, $limit); 
     541        $result = mysql_query($sql, $db); 
     542        if ($result and mysql_num_rows($result) > 0) { 
     543          if ($returnNames) { 
     544            while ($row = mysql_fetch_row($result)) 
     545              unset($ids[$row[0]]); 
     546          } else { 
     547            while ($row = mysql_fetch_row($result)) { 
     548              $key = array_search($row[0], $ids, TRUE); 
     549              if ($key !== FALSE) 
     550                unset($ids[$key]); 
     551            } 
     552          } 
     553        } 
     554      } 
     555      mysql_free_result($result); 
     556    } else { 
     557      throw new UserSpamRemoverException( 
     558        "Could not retrieve user list. ".mysql_error()); 
     559    } 
     560 
     561    return $ids; 
     562  } 
     563 
     564  // Returns SQL SELECT statement to fetch user IDs older than $daysGrace 
     565  //  * if $returnNames is TRUE, query SELECTs ID, user_login 
     566  protected function getListSQL($daysGrace, $limit = FALSE, $returnNames = FALSE) { 
    533567    $pre = $this->wpdb->prefix; 
    534     $db = $this->wpdb->dbh; 
    535  
    536     // Format escaped SQL for username whitelist if it is non-empty 
    537     $wlSQL = ''; 
    538     if ($whitelist = $this->getOption('userWhitelist')) { 
    539       $ns = preg_split("#[\s,]+#", trim($whitelist)); 
    540       $us = array(); 
    541       foreach ($ns as $n) { 
    542         if (strlen($n) > 0) 
    543           $us[] = "'".mysql_real_escape_string($n)."'"; 
    544       } 
    545       if (count($us) > 0) 
    546         $wlSQL = 'AND u.user_login NOT IN ('.implode(', ', $us).')'; 
    547     } 
    548  
    549     // Identify unused user records older than $daysGrace and populate $ids 
    550     $ids = array(); 
    551568    $select = 'u.ID'; 
    552569    if ($returnNames) 
    553570      $select .= ', u.user_login'; 
     571    if ($limit) 
     572      $limit = " LIMIT $limit"; 
     573    else 
     574      $limit = ''; 
    554575    $sql = "SELECT $select FROM ${pre}users AS u ". 
    555576           "LEFT OUTER JOIN ${pre}comments AS c ON u.ID = c.user_id ". 
    556577           "LEFT OUTER JOIN ${pre}posts AS p ON u.ID = p.post_author ". 
    557578           "LEFT OUTER JOIN ${pre}links AS l ON u.ID = l.link_owner ". 
    558            "WHERE (c.comment_approved = 'spam' OR c.user_id IS NULL) ". 
    559            "AND p.post_author IS NULL AND l.link_owner IS NULL $wlSQL ". 
     579           "WHERE c.user_id IS NULL ". 
     580           "AND p.post_author IS NULL AND l.link_owner IS NULL ". 
     581           $this->getUserWhitelistSQL()." ". 
    560582           "AND u.user_registered < DATE_ADD(NOW(), INTERVAL -$daysGrace DAY) ". 
    561            "GROUP BY u.ID LIMIT 5000;"; 
    562     $result = mysql_query($sql, $db); 
    563     if ($result and mysql_num_rows($result) > 0) { 
    564       while ($row = mysql_fetch_row($result)) { 
    565         if ($returnNames) 
    566           $ids[$row[0]] = $row[1]; 
    567         else 
    568           $ids[] = $row[0]; 
    569       } 
    570       mysql_free_result($result); 
    571  
    572       // Protect users with a usermeta record where meta_key = 'last_posted'. 
    573       // Database-integrated bbPress installations set/update this value when a 
    574       // user posts, so we will protect database-integrated bbPress users 
    575       // from deletion if they have written a post. 
    576       $sql = str_replace('WHERE',  
    577         "LEFT OUTER JOIN ${pre}usermeta AS m ON u.ID = m.user_id WHERE", $sql); 
    578       $sql = str_replace('AND p.post',  
    579         "AND m.meta_key = 'last_posted' AND p.post", $sql); 
    580       $result = mysql_query($sql, $db); 
    581       if ($result and mysql_num_rows($result) > 0) { 
    582         while ($row = mysql_fetch_row($result)) { 
    583           $key = array_search($row[0], $ids, TRUE); 
    584           if ($key !== FALSE) 
    585             unset($ids[$key]); 
    586         } 
    587         mysql_free_result($result); 
    588       } 
    589     } 
    590  
    591     return $ids; 
     583           "GROUP BY u.ID${limit};"; 
     584    return $sql; 
     585  } 
     586 
     587  // Returns SQL SELECT statement to fetch user IDs older than $daysGrace 
     588  // that should be *protected* because they have a usermeta record where 
     589  // meta_key = 'last_posted'. 
     590  // Database-integrated bbPress installations set/update this value when a 
     591  // user posts, so we will protect database-integrated bbPress users 
     592  // from deletion if they have written a post. 
     593  protected function getPostedListSQL($daysGrace, $limit) { 
     594    $pre = $this->wpdb->prefix; 
     595    $sql = $this->getListSQL($daysGrace, $limit); 
     596    $sql = str_replace('WHERE ',  
     597      "LEFT OUTER JOIN ${pre}usermeta AS m ON u.ID = m.user_id WHERE ". 
     598      "m.meta_key = 'last_posted' AND ",  
     599      $sql); 
     600    return $sql; 
     601  } 
     602 
     603  // If user has set userWhitelist option, returns mysql_real_escaped SQL 
     604  // query fragment for insertion into WHERE clause of a query. 
     605  // Otherwise, returns empty string. Caches result in $this->whitelistSQL 
     606  protected function getUserWhitelistSQL() { 
     607    if (!isset($this->whitelistSQL)) { 
     608      // Format escaped SQL for username whitelist if whitelist is non-empty 
     609      $sql = ''; 
     610      if ($whitelist = $this->getOption('userWhitelist')) { 
     611        $ns = preg_split("#[\s,]+#", trim($whitelist)); 
     612        $us = array(); 
     613        foreach ($ns as $n) { 
     614          if (strlen($n) > 0) 
     615            $us[] = "'".mysql_real_escape_string($n)."'"; 
     616        } 
     617        if (count($us) > 0) 
     618          $sql = 'AND u.user_login NOT IN ('.implode(', ', $us).')'; 
     619      } 
     620      $this->whitelistSQL = $sql; 
     621    } 
     622    return $this->whitelistSQL; 
    592623  } 
    593624 
     
    652683  } 
    653684 
     685  // Prepend date and save $str (should be a single line only) to activity log. 
     686  // Returns TRUE on success or FALSE on failure (i.e. log file not writable) 
     687  protected function logAction($str) { 
     688    $fh = @fopen($this->getOption('logDir').'/'. 
     689                 $this->getOption('logFilename'), 'a'); 
     690    if (!$fh) 
     691      return FALSE; 
     692 
     693    $str = $this->date($this->timeFormat).' '.$str; 
     694 
     695    $numbytes = fwrite($fh, $str."\n"); 
     696    fclose($fh); 
     697 
     698    return (bool) $numbytes; 
     699  } 
     700 
    654701  /* 
    655702   * Helper methods 
     
    657704 
    658705  // lcfirst() equivalent. lcfirst only in PHP 5.3+ 
    659   public static function lcfirst($str) { 
     706  protected static function lcfirst($str) { 
    660707    $str[0] = strtolower($str[0]); 
    661708    return $str; 
     709  } 
     710 
     711  // Prints WordPress <div class="error"><p> style msg inline 
     712  protected static function errorMsg($str) { 
     713    echo '<div class="error inline"><p><strong>'.$str."</strong></p></div>\n"; 
    662714  } 
    663715 
     
    683735  } 
    684736 
     737  /* 
     738   * Validation and sanitization methods 
     739   */ 
     740 
    685741  // Determines whether user pruning is enabled and, when requested,  
    686742  // log files are writable 
    687   public function checkEnabled($ignoreEnabledSetting = FALSE) { 
     743  protected function checkEnabled($ignoreEnabledSetting = FALSE) { 
    688744    if (!$this->getOption('enabled') and !$ignoreEnabledSetting) 
    689745      return FALSE; 
     
    701757  } 
    702758 
    703   /* 
    704    * Validation and sanitization methods 
    705    */ 
    706  
    707759  // Tests whether log dir exists and is writable 
    708   public function checkLogDir($filename = NULL) { 
     760  protected function checkLogDir($filename = NULL) { 
    709761    if (is_null($filename)) 
    710762      $filename = $this->getOption('logDir'); 
     
    721773  // Tests whether file exists or can be created, and is writable. 
    722774  // returns full file pathname or FALSE 
    723   public function checkFilePathname($filename) { 
     775  protected function checkFilePathname($filename) { 
    724776    $this->checkLogDir($this->getOption('logDir')); 
    725777    $pathname = realpath($this->getOption('logDir').'/'.$filename); 
     
    733785  } 
    734786 
    735   // Admin form input sanitization methods 
     787  // Checks MySQL execution environment and modifies if necessary 
     788  // To function efficiently, User Spam Remover needs indexes to be on these 
     789  // two table columns: 
     790  //   * wp_comments.user_id 
     791  //   * wp_links.link_owner 
     792  // These indexes are created if they do not exist. SQL_BIG_SELECTS is also  
     793  // set to TRUE. 
     794  protected function checkMySQL() { 
     795    $this->logDebug('checkMySQL() called'); 
     796    $db = $this->wpdb->dbh; 
     797    mysql_query('SET SQL_BIG_SELECTS = 1;', $db); 
     798    $this->checkMySQLIndex('comments', 'user_id'); 
     799    $this->checkMySQLIndex('links', 'link_owner'); 
     800  } 
     801 
     802  // Verifies that there is a regular index on $column of $wptable. 
     803  // Adds the index if it does not exist. 
     804  protected function checkMySQLIndex($wptable, $column) { 
     805    $table = $this->wpdb->prefix . $wptable; 
     806    $db = $this->wpdb->dbh; 
     807    if ($result = mysql_query("SHOW INDEX FROM $table;")) { 
     808      $found = FALSE; 
     809      while ($row = mysql_fetch_assoc($result)) { 
     810        if ($row['Column_name'] == $column) { 
     811          $found = TRUE; 
     812          break; 
     813        } 
     814      } 
     815      if (!$found) { 
     816        if (!mysql_query("ALTER TABLE $table ADD INDEX($column);")) { 
     817          throw new UserSpamRemoverException( 
     818            "Could not ADD INDEX $column on table $table. ".mysql_error()); 
     819        } 
     820      } 
     821    } else { 
     822      throw new UserSpamRemoverException( 
     823        "Could not SHOW INDEX on database table $table. ".mysql_error()); 
     824    } 
     825  } 
     826 
     827  /* 
     828   * Form input sanitization callbacks 
     829   * (must be public since registered thru register_setting() 
     830   */ 
     831 
    736832  public static function sanitizeBool($v) { 
    737833    settype($v, 'bool'); 
     
    766862// Override wp_new_user_notification() to cancel admin e-mail notifications 
    767863 
    768 // This is simply copy/pasted from /wp-includes/pluggable.php from WP 3.0.1 and  
    769 // section that sends admin email is commented out. Unfortunately, WordPress 
    770 // doesn't do something sane like a config option or overloadable class method 
    771 // to avoid this copy-and-paste coding result. 
     864// This is simply copy/pasted from /wp-includes/pluggable.php from  
     865// WP 3.0.1 - 3.1 (no changes during that time from r15380 to r17517 of trunk)  
     866// and the section that sends admin email is commented out. Unfortunately,  
     867// WordPress doesn't do something sane like a config option or overloadable  
     868// class method to avoid this copy-and-paste coding result. 
    772869if ( !function_exists('wp_new_user_notification') ) : 
    773870/** 
     
    790887 
    791888// Start edits -- Override admin email notification if option is enabled 
    792 $usp = UserSpamRemover::getInstance(); 
    793 if (!$usp->getOption('noAdminEmails')) { 
     889$usr = UserSpamRemover::getInstance(); 
     890if (!$usr->getOption('noAdminEmails')) { 
    794891    $message  = sprintf(__('New user registration on your site %s:'), $blogname) . "\r\n\r\n"; 
    795892    $message .= sprintf(__('Username: %s'), $user_login) . "\r\n\r\n"; 
     
    799896} 
    800897// Log addition of new user if logging enabled 
    801   if ($usp->getOption('activityLog')) { 
    802     $usp->logNewUser($user); 
     898  if ($usr->getOption('activityLog')) { 
     899    $usr->logNewUser($user); 
    803900  } 
    804901// end edits 
Note: See TracChangeset for help on using the changeset viewer.