ugrsr.class.php (13128B)
1 <?php 2 3 /** 4 * UGRSR 5 * 6 * @package Universal Global RegEx Search/Replace 7 * @author Qphoria - http://theqdomain.com/ & Jay Gilford - http://jaygilford.com/ 8 * @copyright Qphoria & Jay Gilford 2011 9 * @version 0.3 10 * @access public 11 * 12 * @information 13 * This class will perform mass search and replace actions 14 * based on regex pattern matching. It recursively grabs all files 15 * below it's given path and applies the specified change(s) 16 * 17 * @license 18 * Permission is hereby granted, free of charge, to any person to 19 * use, copy, modify, distribute, sublicense, and/or sell copies 20 * of the Software, subject to the following conditions: 21 * 22 * The above copyright notice and this permission notice shall be 23 * included in all copies or substantial portions of the Software 24 * 25 * @warning 26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 * EXPRESSED OR IMPLIED. 28 * 29 */ 30 class UGRSR { 31 32 public $debug = false; // Show debug messages switch 33 public $flags = 0; // Flags for the file recursive glob 34 public $pattern = '*.php'; // File pattern to match with glob 35 public $recursive = true; // Recursion into subdirectories switch 36 public $test_mode = false; // Test mode only switch 37 public $file_search = true; // Search for files switch 38 39 40 private $_regexes = array(); // Array for regex patterns and replaces 41 private $_path = ''; // Path to directory to work with 42 private $_protected = array(); // Array of protected files 43 private $_files = array(); // Array of manually added file locations 44 45 /** 46 * UGRSR::__construct() 47 * 48 * @param string $path 49 * @return null 50 */ 51 function __construct($path = '') { 52 53 // Use current working directory if none given as a parameter 54 if(empty($path)) { 55 $path = getcwd(); 56 } 57 58 // Apply path to var 59 $this->setPath($path); 60 61 // Check to make sure the script calling the class is set to be protected 62 if(!isset($_SERVER['SCRIPT_FILENAME'])) { 63 DIE('SCRIPT FILENAME COULD NOT BE DETERMINED'); 64 } 65 66 // Set default file protections 67 $this->resetProtected(); 68 } 69 70 /** 71 * UGRSR::addPattern() 72 * 73 * @param string $pattern 74 * @param string $replace 75 * @return bool 76 */ 77 public function addPattern($pattern, $replace) { 78 79 // If pattern is empty throw error 80 if(empty($pattern)) { 81 $this->_dbg('PATTERN EMPTY'); 82 return false; 83 } 84 85 // Add regex pattern and replace vars to _regexes array 86 $this->_regexes[] = array( 87 'pattern' => $pattern, 88 'replace' => $replace 89 ); 90 91 return true; 92 } 93 94 /** 95 * UGRSR::mergePatterns() 96 * 97 * @param array $pattern_array 98 * @return bool 99 */ 100 public function mergePatterns($pattern_array) { 101 102 // If the param is not an array throw error 103 if(!is_array($pattern_array)) { 104 $this->_dbg('PARAM IS NOT AN ARRAY'); 105 return false; 106 } 107 108 //Loop through pattern array 109 foreach($pattern_array as $data) { 110 111 // If pattern or replace keys not set throw error and continue loop 112 if(!isset($data['pattern']) || !isset($data['replace'])) { 113 $this->_dbg('ARRAY KEYS NOT SET'); 114 continue; 115 } 116 117 // Add regex and replace 118 $this->addPattern($data['pattern'], $data['replace']); 119 } 120 121 return true; 122 } 123 124 /** 125 * UGRSR::clearPatterns() 126 * 127 * @return null 128 */ 129 public function clearPatterns() { 130 131 // Set regexes var to empty array 132 $this->_regexes = array(); 133 } 134 135 /** 136 * UGRSR::addFile() 137 * 138 * @param string $filename 139 * @param bool 140 * @return bool 141 */ 142 public function addFile($filename, $omit_path = false) { 143 144 $file = $omit_path ? $filename : $this->_path . $filename; 145 146 // If the protection isnt for a file throw an error 147 if(!is_file($file)) { 148 $this->_dbg('FILE [' . $file . '] IS NOT A FILE'); 149 return false; 150 } 151 152 // Get real full path to file 153 $real_filename = realpath($file); 154 155 // If real path for file can't be found throw error 156 if(!$real_filename) { 157 $this->_dbg('FILE [' . $file . '] IS NOT A FILE'); 158 return false; 159 } 160 161 // Don't add file if it's already in the file list 162 if(in_array($real_filename, $this->_files)) { 163 $this->_dbg('FILE [' . $file . '] ALREADY IN FILE LIST'); 164 return false; 165 } 166 167 // Add filename to file list 168 $this->_dbg('FILE [' . $real_filename . '] ADDED TO FILE LIST'); 169 $this->_files[] = $real_filename; 170 171 return true; 172 } 173 174 /** 175 * UGRSR::resetFileList() 176 * 177 * @return true 178 */ 179 public function resetFileList() { 180 // Clear file list 181 $this->_files = array(); 182 183 $this->_dbg('FILE LIST RESET'); 184 185 return true; 186 } 187 188 /** 189 * UGRSR::addProtected() 190 * 191 * @param string $filename 192 * @return bool 193 */ 194 public function addProtected($filename) { 195 196 // If the protection isnt for a file throw an error 197 if(!is_file($filename)) { 198 $this->_dbg('FILE [' . $filename . '] IS NOT A FILE'); 199 return false; 200 } 201 202 // Get real full path to file 203 $real_filename = realpath($filename); 204 205 // If real path for file can't be found throw error 206 if(!$real_filename) { 207 $this->_dbg('FILE [' . $filename . '] IS NOT A FILE'); 208 return false; 209 } 210 211 // Add filename to protected list 212 $this->_dbg('FILE [' . $filename . '] ADDED TO PROTECTED LIST'); 213 $this->_protected[] = $real_filename; 214 215 return true; 216 } 217 218 /** 219 * UGRSR::resetProtected() 220 * 221 * @return true 222 */ 223 public function resetProtected() { 224 // Clear protected list 225 $this->_protected = array(); 226 227 $this->_dbg('PROTECTED FILES RESET'); 228 //Add this class to protected list 229 $this->_protected[] = realpath(__FILE__); 230 231 // Add script that called the class to protected list 232 $this->_protected[] = realpath($_SERVER['SCRIPT_FILENAME']); 233 234 return true; 235 } 236 237 /** 238 * UGRSR::setPath() 239 * 240 * @param string $path 241 * @return bool 242 */ 243 public function setPath($path) { 244 245 // Get full real path to given path 246 if(is_executable($path)) { 247 $realpath = realpath($path) . '/'; 248 } else { 249 $realpath = $path; 250 } 251 252 // If path can't be found or isn't a directory throw an error 253 if(!file_exists($realpath)) { 254 $this->_dbg('INVALID PATH [' . $realpath . ']'); 255 return false; 256 } 257 258 // Set path to new value 259 $this->_dbg('NEW PATH SET [' . $realpath . ']'); 260 $this->_path = $realpath; 261 262 return true; 263 } 264 265 /** 266 * UGRSR::run() 267 * 268 * @return bool 269 */ 270 public function run() { 271 272 // If regexes array is empty throw an error 273 if(empty($this->_regexes)) { 274 $this->_dbg('REGEX LIST IS EMPTY'); 275 return false; 276 } 277 278 $this->_dbg('STARTING RUN'); 279 $this->_dbg(); 280 281 // Set files to list of manually added files 282 $files = $this->_files; 283 284 // Check if file searching is enabled 285 if($this->file_search) { 286 $this->_dbg('GETTING FILE LIST'); 287 288 // Get a list of files under defined path 289 $found = array_merge($this->_rglob()); 290 291 $this->_dbg(count($found) . ' FILES FOUND'); 292 $this->_dbg(); 293 294 // merge list of files with manually added file list 295 $files = array_merge($files, $found); 296 } 297 298 // Check files found or throw error and return 299 if(count($files) == 0) { 300 $this->_dbg('NO FILES TO BE PROCESSED'); 301 return false; 302 } 303 304 $this->_dbg('STARTING FILE PROCESSING'); 305 306 // Var for total regex matches throughout files 307 $global_change_count = 0; 308 309 // Var to hold 310 $global_write_count = 0; 311 312 // Var to hold number of bytes saved 313 $bytes_saved = 0; 314 315 // Loop through files one at a time 316 foreach($files as $filename) { 317 318 // Var for total regex matches in current file 319 $file_change_count = 0; 320 321 // Load file contents 322 $content = $original_content = file_get_contents($filename); 323 324 // If content couldn't be loaded throw error 325 if($content === FALSE) { 326 $this->_dbg('COULD NOT OPEN [' . $filename . ']'); 327 continue; 328 } 329 330 // If file length is 0 throw error 331 if(strlen($content) == 0) { 332 $this->_dbg('EMPTY FILE SKIPPED [' . $filename . ']'); 333 continue; 334 } 335 336 // Loop through _regexes array applying changes to content 337 foreach($this->_regexes as $regex) { 338 339 // Var for total regex matches for individual pattern 340 $change_count = 0; 341 342 // Try replacing content 343 $content = preg_replace($regex['pattern'], $regex['replace'], $content, -1, $change_count); 344 345 // If regex operation fails throw error and abort all operations 346 if($content === NULL) { 347 $this->_dbg('REGEX PATTERN ERROR <strong>' . $regex['pattern'] . '</strong>'); 348 $this->_dbg('ABORTING ALL OPERATIONS'); 349 break 2; 350 } 351 352 // Add individual pattern change count to file change count 353 $file_change_count += $change_count; 354 $this->_dbg('REGEX <strong>' . $regex['pattern'] . '</strong> FOUND ' . ($change_count ? $change_count : 'NO') . ' MATCHES IN [' . $filename . ']'); 355 356 } 357 358 // If not in test mode and content has changed attempt to write back to file 359 if($content !== $original_content && !$this->test_mode) { 360 $this->_dbg('ATTEMPTING TO WRITE TO FILE [' . $filename . ']'); 361 362 // If file isn't writeable throw error 363 if(!is_writeable($filename)) { 364 $this->_dbg('CANNOT WRITE TO [' . $filename . ']'); 365 } else { 366 367 // Write file data back to file and show result 368 $result = file_put_contents($filename, $content); 369 if($result) { 370 $this->_dbg('SUCCESSFULLY WROTE ' . $result . ' BYTES TO [' . $filename . ']'); 371 $global_write_count++; 372 } else { 373 $this->_dbg('WRITE OPERATION FAILED IN [' . $filename . ']'); 374 } 375 } 376 } 377 378 // Add byte difference to $bytes_saved 379 $bytes_saved += (strlen($original_content) - strlen($content)); 380 381 // Add total file changes count to global file changes count 382 $global_change_count += $file_change_count; 383 384 $this->_dbg('TOTAL NUMBER OF FILE CHANGES: ' . $file_change_count); 385 $this->_dbg(); 386 } 387 388 $this->_dbg(); 389 $this->_dbg('FINISHED FILE PROCESSING'); 390 $this->_dbg('TOTAL CHANGES APPLIED ACROSS ALL FILES: <strong>' . $global_change_count . '</strong>'); 391 $this->_dbg('TOTAL BYTES SAVED ACROSS ALL FILES: <strong>' . $bytes_saved . '</strong>'); 392 $this->_dbg(); 393 394 $this->_dbg('FINISHED RUN'); 395 $this->_dbg(); 396 397 // Pass back the number of changes and writes matched 398 return array( 399 'changes' => $global_change_count, 400 'writes' => $global_write_count 401 ); 402 } 403 404 /** 405 * UGRSR::_dbg() 406 * 407 * @param string $message 408 * @return NULL; 409 */ 410 private function _dbg($message = '') { 411 412 // If in debug mode show output 413 if($this->debug) { 414 415 // Set mode type 416 $mode = $this->test_mode ? 'TEST MODE' : 'LIVE MODE'; 417 418 // If there's a message echo that otherwise echo some whitespace for formatting 419 if(!empty($message)) { 420 echo $mode . ': *** ' . $message . " ***<br />\r\n"; 421 } else { 422 echo str_repeat("<br />\r\n", 2); 423 } 424 } 425 } 426 427 /** 428 * UGRSR::_rglob() 429 * 430 * @param string $path 431 * @return array 432 */ 433 private function _rglob($path = NULL) { 434 435 // If the path isn't supplied use the one stored in _path 436 if($path === NULL) $path = $this->_path; 437 $this->_dbg('SEARCHING PATH [' . $path .']'); 438 439 // Get list of files under current directory 440 $files = glob($path . $this->pattern, $this->flags); 441 442 // Loop through all files 443 foreach($files as $key => &$file) { 444 // Flag to allow file to be kept in file array 445 $remove_file = false; 446 447 // Get full path of file 448 $realfile = realpath($file); 449 450 // Report if file path can't be resolved 451 if($realfile === FALSE) { 452 $this->_dbg('REAL PATH OF FILE [' . $file . '] COULD NOT BE RESOLVED'); 453 $remove_file = true; 454 } 455 456 // Report if file path is in the protected list 457 if($realfile && in_array($realfile, $this->_protected)) { 458 $this->_dbg('PROTECTED FILE [' . $realfile . '] REMOVED FROM FILES LIST'); 459 $remove_file = true; 460 } 461 462 // Report if file path is in the protected list 463 if($realfile && in_array($realfile, $this->_files)) { 464 $this->_dbg('FILE [' . $realfile . '] SKIPPED. ALREADY IN FILE LIST'); 465 $remove_file = true; 466 } 467 468 // Report if write access cannot be granted for file 469 if($realfile && !is_writeable($realfile)) { 470 $this->_dbg('FILE [' . $file . '] SKIPPED AS CANNOT WRITE TO IT'); 471 $remove_file = true; 472 } 473 474 // Remove from file list if any issues 475 if($remove_file) { 476 unset($files[$key]); 477 } 478 } 479 480 // If recursion is set get files in subdirectories 481 if($this->recursive) { 482 483 // Get list of directories under current path 484 $paths = glob($path . '*', GLOB_MARK|GLOB_ONLYDIR|GLOB_NOSORT); 485 486 // Loop through subdirectories and merge files into directory list 487 foreach($paths as $p) { 488 $files = array_merge($files, $this->_rglob($p)); 489 } 490 } 491 492 // Pass file array back 493 return $files; 494 } 495 }