ROOT = __DIR__; // the root of directories $this->DIFFMODE = "mtime"; // mtime/md5 $this->WATCHMODE = "auto"; // auto/includes/added/tags $this->IGNORE = []; // file or folders to ignore $this->ADDED = []; // extra files to be watched } /** * Set Root * * Set the Reloader root path for the add() and the ignore() * methods. If, when initialized, there is no Root path, the * location of the hotreloader.php file will be setted as root * * @param String * @return void */ public function setRoot($root){ $this->ROOT = $root; } /** * Set Diff Mode * * Set the way the Reloader must hash the files and * folders. It can be 'mtime' or 'md5'. The mtime is the * default mode. Anyway, final unique checksums will always * use md5 * * @param String * @return void */ public function setDiffMode($mode){ $this->DIFFMODE = $mode; } /** * Set Watch Mode * * Set what things will be whatched by the reloader. * It haves 4 modes: * * 1. "auto" - will react to changes in included/required files * on the code, added files using add() method, and the script * and link tags that incudes js and css files on the code * * 2. "includes" - will react to changes only in the included/required * files on the code, and on the script and link tags * * 3. "added" - will react to changes only in the files setted from * the add() method, and on the script and link tags * * 4. "tags" - will react to changes only in the script and link tags * on the code * * @param String * @return void */ public function setWatchMode($mode){ $valid_modes = "auto, includes, added, tags"; if(!in_array($mode, explode(", ", $valid_modes))){ $mode .= " (Not a Valid Mode. You can use: $valid_modes)"; } $this->WATCHMODE = $mode; } /** * Ignore * * Set an array of files or folder paths to be ignored by the reloader. * The paths and filenames must be relative to the setted ROOT * * @param Array * @return void */ public function ignore($array){ $this->IGNORE = array_filter(array_unique($array)); } /** * Add * * Set an array of files or folder paths to be watched by the reloader. * The files or folders included with add() will trigger a page reload * when changed even if they have any link with the current code. Folders * are recursively added, so, a change in any file or subfolder will be * relevant * * @param Array * @return void */ public function add($array){ $this->ADDED = array_filter(array_unique($array)); } /** * Set Shorthand Method * * This is a shorthand for all setters, it allows to set parameters to * the reloader with one function. The parameters can be: * * ROOT : set the Root Path * DIFFMODE : set the diff mode * WATCHMODE : set the watch mode * IGNORE : an array of files or folders to ignore * ADDED : an array of files or folders to watch * * @param Array * @return void */ public function set($options){ foreach($options as $key => $val){ if(isset($this->$key)) $this->$key = $val; } } /** * Current Config * * This function prints relevant information about the reloader current * configuration and state. Its to debug and information purposes only. * The information will be printed on the browser console via javascript * * @return void */ public function currentConfig(){ $apphash = $this->createStateHash(); $root = is_dir($this->ROOT) ? $this->ROOT." (OK)" : $this->ROOT." (NOT FOUND)"; $watchmode = $this->WATCHMODE; $diffmode = $this->DIFFMODE; $includes = implode('\n', get_included_files()); $ignore = implode( '\n', array_filter(array_unique($this->IGNORE)) ); $added = implode( '\n', array_filter(array_unique($this->ADDED)) ); // ob_start();?> addEtagOnHeader(); } // add the JS Watcher $this->addJsWatcher(); } /** * Add the application state hash on the Etag of the Headers * * This function will get a new state hash of the current code and its dependencies * an will treat this hash as a fingerprint of your script state. then will set this * hash as an etag on the current script headers, a hash change means a code change * * @return void */ private function addEtagOnHeader(){ $hash = $this->createStateHash(); if( $hash ) header( "Etag: " . $hash ); return true; echo "Hot Reloader failed to generate Application State Hash"; } /** * Create the application state hash * * Collects all the timestamps/hashes from the included files and from the added() * method, and than transforms it into a unique md5 hash. this unique hash is your * app fingerprint. * * @return String */ private function createStateHash(){ $hashes = []; // when in 'tags' watch mode, we send a fake hash just to // satisfty the JS watch and not trigger changes by Etag if($this->WATCHMODE == "tags") return "HotReloaderTagsOnly"; // get the includes mtime/hashlist if($this->WATCHMODE == "auto" || $this->WATCHMODE == "includes"){ $hashes = array_merge($hashes, $this->getIncludesHashList()); } // get the ADDED files/folders mtime/hashlist if($this->WATCHMODE == "auto" || $this->WATCHMODE == "added"){ $hashes = array_merge($hashes, $this->getADDEDHashList()); } // avoid duplicated or empty values $hashes = array_unique(array_filter($hashes)); // transform all hashes into a unique md5 checksum return md5(implode("",$hashes)); } /** * Generates a hash list of all included/required files of the running file * * This function gets all the included/required files on the current code * and return a list of timestamps or md5 checksums of each file * * @return Array */ private function getIncludesHashList(){ $hashes = []; // this will hash all includes/requires on current code if(!empty(get_included_files())){ foreach(get_included_files() as $file){ // check if the file is not setted on in a dir setted on ignore list if( !$this->willBeIgnored($file) ){ $hashes[] = ($this->DIFFMODE == "mtime" ? stat($file)['mtime'] : md5_file($file)); } } } return $hashes; } /** * Generates a hash list of all files added to watch with the method add() * * This function build the hash/timestamp list of the files and folders which * came from the added() method * * @return Array */ private function getADDEDHashList(){ $hashes = []; // this will hash all files and folders in ADDED array if(!empty($this->ADDED)){ foreach($this->ADDED as $add){ // create the added path relative to the ROOT const $DS = !strpos($this->ROOT, DIRECTORY_SEPARATOR) == count($this->ROOT) ? DIRECTORY_SEPARATOR : ""; $add = $this->ROOT.$DS.$add; // do the hash if(is_dir($add)){ if( !$this->willBeIgnored($add) ){ // if is a dir, hash the entire directory (mtime or md5) // the directorie hash is an implode of hashes of all files there // the getDirectoryHash always return a md5 checksum of the directory $hashes[] = $this->getDirectoryHash($add); } } else { if( file_exists($add) && !$this->willBeIgnored($add) ){ // if is a file, get the file hash mtime/md5 $hashes[] = ($this->DIFFMODE == "mtime" ? stat($add)['mtime'] : md5_file($add)); } } } } return $hashes; } /** * Generates a unique hash of an entire directory * * Generates a hash/timestamp array of all files and folders inside the * given directory, than transform this array in a unique md5 checksum * * @return String */ private function getDirectoryHash($directory){ $mode = $this->DIFFMODE; if (! is_dir($directory)) return false; $files = array(); $dir = dir($directory); while (false !== ($file = $dir->read())){ if ($file != '.' and $file != '..'){ if (is_dir($directory . DIRECTORY_SEPARATOR . $file)){ $files[] = $this->hashDirectory($directory . DIRECTORY_SEPARATOR . $file, $mode); } else{ $curr_file = $directory.DIRECTORY_SEPARATOR.$file; if(!$this->willBeIgnored($curr_file)){ $files[] = ($mode == "mtime" ? stat($curr_file)['mtime'] : md5_file($curr_file)); } } } } $dir->close(); return md5(implode("",$files)); } /** * Will Be Ignored ? * * Check if a given path (abs path) must be ignored by the Reloader * The given paths are always converted to be relative to the Root * * @return Boolean */ private function willBeIgnored($file){ // if the ignore list is not empty if( !empty(array_filter($this->IGNORE)) ){ // check if the file passed existis on the array foreach( $this->IGNORE as $ignore ){ // get the absolute path os files to be ignored // the files in IGNORE are relative to $this->ROOT $DS = !strpos($this->ROOT, DIRECTORY_SEPARATOR) == count($this->ROOT) ? DIRECTORY_SEPARATOR : ""; $ignore = $this->ROOT.$DS.$ignore; //check if must ignore the file (is in ignore or in a folder which is) if($file == $ignore || strpos(dirname($file),$ignore) !== false && strpos(dirname($file),$ignore) == 0){ return true; } } } // if the file will not be ignored return false; } /** * Add the modified Live.js script to the current page * * This is the HotReloader JS Watcher. This script derives from live.js, due * several modifications it turned into another script already. This script * will watch the page headers, scripts and links, based on the Reloader * configuration. When a change is catched a page reload will be triggered * or the changes will be directly applied. * * @return void */ private function addJsWatcher(){ ob_start(); ?> init(); ?>