1<?php 2/** 3 * 4 * Interface for writing files, retrieving files and checking caches 5 * 6 */ 7namespace CssCrush; 8 9class IO 10{ 11 protected $process; 12 13 public function __construct(Process $process) 14 { 15 $this->process = $process; 16 } 17 18 public function init() 19 { 20 $this->process->cacheFile = "{$this->process->output->dir}/.csscrush"; 21 } 22 23 public function getOutputDir() 24 { 25 $outputDir = $this->process->options->output_dir; 26 27 return $outputDir ? $outputDir : $this->process->input->dir; 28 } 29 30 public function getOutputFilename() 31 { 32 $options = $this->process->options; 33 34 $inputBasename = basename($this->process->input->filename, '.css'); 35 $outputBasename = $inputBasename; 36 37 if (! empty($options->output_file)) { 38 $outputBasename = basename($options->output_file, '.css'); 39 } 40 41 if ($this->process->input->dir === $this->getOutputDir() && $inputBasename === $outputBasename) { 42 $outputBasename .= '.crush'; 43 } 44 45 return "$outputBasename.css"; 46 } 47 48 public function getOutputUrl() 49 { 50 $process = $this->process; 51 $options = $process->options; 52 $filename = $process->output->filename; 53 54 $url = $process->output->dirUrl . '/' . $filename; 55 56 // Make URL relative if the input path was relative. 57 $input_path = new Url($process->input->raw); 58 if ($input_path->isRelative) { 59 $url = Util::getLinkBetweenPaths(Crush::$config->scriptDir, $process->output->dir) . $filename; 60 } 61 62 // Optional query-string timestamp. 63 if ($options->versioning !== false) { 64 $url .= '?'; 65 if (isset($process->cacheData[$filename]['datem_sum'])) { 66 $url .= $process->cacheData[$filename]['datem_sum']; 67 } 68 else { 69 $url .= time(); 70 } 71 } 72 73 return $url; 74 } 75 76 public function validateCache() 77 { 78 $process = $this->process; 79 $options = $process->options; 80 $input = $process->input; 81 82 $dir = $this->getOutputDir(); 83 $filename = $this->getOutputFilename(); 84 $path = "$dir/$filename"; 85 86 if (! file_exists($path)) { 87 debug('No file cached.'); 88 89 return false; 90 } 91 92 if (! isset($process->cacheData[$filename])) { 93 debug('Cached file exists but is not registered.'); 94 95 return false; 96 } 97 98 $data =& $process->cacheData[$filename]; 99 100 // Make stack of file mtimes starting with the input file. 101 $file_sums = array($input->mtime); 102 foreach ($data['imports'] as $import_file) { 103 104 // Check if this is docroot relative or input dir relative. 105 $root = strpos($import_file, '/') === 0 ? $process->docRoot : $input->dir; 106 $import_filepath = realpath($root) . "/$import_file"; 107 108 if (file_exists($import_filepath)) { 109 $file_sums[] = filemtime($import_filepath); 110 } 111 else { 112 // File has been moved, remove old file and skip to compile. 113 debug('Recompiling - an import file has been moved.'); 114 115 return false; 116 } 117 } 118 119 $files_changed = $data['datem_sum'] != array_sum($file_sums); 120 if ($files_changed) { 121 debug('Files have been modified. Recompiling.'); 122 } 123 124 // Compare runtime options and cached options for differences. 125 // Cast because the cached options may be a \stdClass if an IO adapter has been used. 126 $options_changed = false; 127 $cached_options = (array) $data['options']; 128 $active_options = $options->get(); 129 foreach ($cached_options as $key => &$value) { 130 if (isset($active_options[$key]) && $active_options[$key] !== $value) { 131 debug('Options have been changed. Recompiling.'); 132 $options_changed = true; 133 break; 134 } 135 } 136 137 if (! $options_changed && ! $files_changed) { 138 debug("Files and options have not been modified, returning cached file."); 139 140 return true; 141 } 142 else { 143 $data['datem_sum'] = array_sum($file_sums); 144 145 return false; 146 } 147 } 148 149 public function getCacheData() 150 { 151 $process = $this->process; 152 153 if (file_exists($process->cacheFile) && $process->cacheData) { 154 155 // Already loaded and config file exists in the current directory 156 return; 157 } 158 159 $cache_data_exists = file_exists($process->cacheFile); 160 $cache_data_file_is_writable = $cache_data_exists ? is_writable($process->cacheFile) : false; 161 $cache_data = array(); 162 163 if ( 164 $cache_data_exists && 165 $cache_data_file_is_writable && 166 $cache_data = json_decode(file_get_contents($process->cacheFile), true) 167 ) { 168 // Successfully loaded config file. 169 debug('Cache data loaded.'); 170 } 171 else { 172 // Config file may exist but not be writable (may not be visible in some ftp situations?) 173 if ($cache_data_exists) { 174 if (! @unlink($process->cacheFile)) { 175 notice('Could not delete cache data file.'); 176 } 177 } 178 else { 179 debug('Creating cache data file.'); 180 } 181 Util::filePutContents($process->cacheFile, json_encode(array()), __METHOD__); 182 } 183 184 return $cache_data; 185 } 186 187 public function saveCacheData() 188 { 189 $process = $this->process; 190 191 debug('Saving config.'); 192 193 $flags = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; 194 Util::filePutContents($process->cacheFile, json_encode($process->cacheData, $flags), __METHOD__); 195 } 196 197 public function write(StringObject $string) 198 { 199 $process = $this->process; 200 201 $dir = $this->getOutputDir(); 202 $filename = $this->getOutputFilename(); 203 $sourcemapFilename = "$filename.map"; 204 205 if ($process->sourceMap) { 206 $string->append($process->newline . "/*# sourceMappingURL=$sourcemapFilename */"); 207 } 208 209 if (Util::filePutContents("$dir/$filename", $string, __METHOD__)) { 210 211 $jsonFlags = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0; 212 213 if ($process->sourceMap) { 214 Util::filePutContents("$dir/$sourcemapFilename", 215 json_encode($process->sourceMap, $jsonFlags), __METHOD__); 216 } 217 218 if ($process->options->stat_dump) { 219 $statFile = is_string($process->options->stat_dump) ? 220 $process->options->stat_dump : "$dir/$filename.json"; 221 222 $GLOBALS['CSSCRUSH_STAT_FILE'] = $statFile; 223 Util::filePutContents($statFile, json_encode(csscrush_stat(), $jsonFlags), __METHOD__); 224 } 225 226 return true; 227 } 228 229 return false; 230 } 231} 232