1 | <?php |
||
2 | /*------------------------------------------------------------------------------ |
||
3 | |||
4 | Project : CachingProxy |
||
5 | Filename : src/AbstractCachingProxy.php |
||
6 | Autor : (c) Sebastian Krüger <[email protected]> |
||
7 | Date : 15.09.2013 |
||
8 | |||
9 | For the full copyright and license information, please view the LICENSE |
||
10 | file that was distributed with this source code. |
||
11 | |||
12 | Description: Basisklasse die einen Mechanismus zu Cachen von Dateien auf dem |
||
13 | Server implemeniert. Anwendung ist später für CSS und Javscript |
||
14 | Dateien vorgesehen |
||
15 | |||
16 | Die zu Cachenden Dateien werden zu einer gesammten Datei zusammengefasst |
||
17 | Externe Dateien werden nicht zusammengefasst, sondern vorerst einfach nur |
||
18 | als Einbindung ausgegeben. Falls vorhanden wird die minifizierte Version der |
||
19 | Datei vorgezogen. Zu guter letzt werden die Dateien auch noch per GZ gepackt |
||
20 | um statische gepackte Dateien anbieten zu können |
||
21 | |||
22 | ----------------------------------------------------------------------------*/ |
||
23 | |||
24 | namespace secra\CachingProxy; |
||
25 | |||
26 | abstract class AbstractCachingProxy |
||
27 | { |
||
28 | protected $internfilelist = array(); // array with files that should be cached later |
||
29 | private $externfilelist = array(); // array with extern files |
||
30 | |||
31 | protected $docrootpath = null; // webserver document root path |
||
32 | protected $cachepath = null; // absolut path on webserver were cached files should be placed |
||
33 | protected $relcachepath = null; // relative cachepath for scripttags in html |
||
34 | |||
35 | // In debugmode every file will be include in a single tag without modification |
||
36 | protected $debugmode = false; |
||
37 | |||
38 | /** |
||
39 | * Implement later to set the document rootpath and cachepath |
||
40 | * |
||
41 | * @param string $webserverRootPath absolut path to webserver root |
||
42 | * @param string $cachePath path to cachefile location based on webserver root path |
||
43 | * |
||
44 | * @return AbstractCachingProxy|null objectinstance |
||
45 | */ |
||
46 | public function __construct($webserverRootPath, $cachePath) |
||
47 | { |
||
48 | $this->setWebserverRootPath($webserverRootPath); |
||
49 | $this->setCachepath($cachePath); |
||
50 | } |
||
51 | |||
52 | /** |
||
53 | * Implement later html code return |
||
54 | * |
||
55 | * Implement this to get the specific html head code |
||
56 | * |
||
57 | * @codeCoverageIgnore |
||
58 | * |
||
59 | * @return string the html scripttag code |
||
60 | */ |
||
61 | abstract public function getIncludeHtml(); |
||
62 | |||
63 | /** |
||
64 | * Delivers extension for cached files |
||
65 | * |
||
66 | * @codeCoverageIgnore |
||
67 | * |
||
68 | * @return string file extension |
||
69 | * |
||
70 | */ |
||
71 | abstract protected function getCacheFileExtension(); |
||
72 | |||
73 | /** |
||
74 | * Add files to proxy |
||
75 | * |
||
76 | * Add intern, project relative files or extern files, on different domain |
||
77 | * to the filelist |
||
78 | * |
||
79 | * @param string $filename the filepath/URL to script |
||
80 | * |
||
81 | * @return boolean false on error |
||
82 | */ |
||
83 | public function addFile($filename) |
||
84 | { |
||
85 | // Fügt eine Datei zur Cacheliste hinzu, es wird hier schon nach internen oder |
||
86 | // Externen Dateien unterschieden beginnen z.B. mit http, https, ftp und dann :// |
||
87 | // or protocoll less // links |
||
88 | if (!preg_match("#^[a-z]{3,5}://#i", $filename) && !preg_match("#^//#i", $filename)) { |
||
89 | // intern files, work for the cache |
||
90 | $absolutfilename = self::makeAbsolutPath($filename); |
||
91 | if (!file_exists($absolutfilename)) { |
||
92 | // the file did't exist |
||
93 | return false; |
||
94 | } |
||
95 | |||
96 | // Falls möglich Minifizierte Version der Datei benutzen |
||
97 | $minfilename = self::makeMinifiPath($absolutfilename); |
||
98 | |||
99 | if (file_exists($minfilename)) { |
||
100 | // Es scheint jemand die Datei gepackt zu haben |
||
101 | $absolutfilename = $minfilename; |
||
102 | } |
||
103 | |||
104 | $this->internfilelist[] = $absolutfilename; |
||
105 | } else { |
||
106 | // Ist wohl eine Externe Pfadangabe, kann nicht gechached werden |
||
107 | $this->externfilelist[] = $filename; |
||
108 | } |
||
109 | return true; |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Return list of all files can be include |
||
114 | * |
||
115 | * First deliver all intern then all extern files |
||
116 | * the user can decide by himself, what he would like to do with the list |
||
117 | * |
||
118 | * @return string[] list of files |
||
119 | */ |
||
120 | public function getIncludeFileset() |
||
121 | { |
||
122 | // Return list of all files that can include |
||
123 | // first all intern then all extern files |
||
124 | // the user can decide by himself, what he would like to do with the list |
||
125 | |||
126 | // Exclude double files from filelist |
||
127 | $this->internfilelist = array_unique($this->internfilelist); |
||
128 | $this->externfilelist = array_unique($this->externfilelist); |
||
129 | |||
130 | $returnfilelist = array(); |
||
131 | |||
132 | // generate cachefile |
||
133 | // the return will not be used in debugmode, but generate the files anyway |
||
134 | $oneModifiedCacheFile = $this->getCacheFile(); |
||
135 | |||
136 | if ($this->debugmode===false) { |
||
137 | // put intern files into the cached version |
||
138 | if ($oneModifiedCacheFile!=null) { |
||
139 | // only replace the intern file list, if theres intern files and the modified cache file exits |
||
140 | $returnfilelist[] = $oneModifiedCacheFile; |
||
141 | } |
||
142 | } else { |
||
143 | // we are in debugmode! |
||
144 | // only put the internfiles to the list of returned files |
||
145 | foreach ($this->internfilelist as $file) { |
||
146 | // strip the absolut dir for inclusion and put it to the list |
||
147 | // use the $ as reg_exp separater because don't expect it in path |
||
148 | $returnfilelist[] = "/".preg_replace("$^".($this->docrootpath)."$", "", $file); |
||
149 | } |
||
150 | } |
||
151 | |||
152 | // extern files will only add to the list |
||
153 | foreach ($this->externfilelist as $file) { |
||
154 | $returnfilelist[] = $file; |
||
155 | } |
||
156 | |||
157 | return $returnfilelist; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Sweet as simple ... activate the debugmode |
||
162 | * |
||
163 | * @return null |
||
164 | */ |
||
165 | public function enableDebugmode() |
||
166 | { |
||
167 | // sweet as simple ... activate the debugmode |
||
168 | $this->debugmode=true; |
||
169 | return null; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * belive it or not ... deactivate the debugmode |
||
174 | * |
||
175 | * @return null |
||
176 | */ |
||
177 | public function disableDebugmode() |
||
178 | { |
||
179 | $this->debugmode=false; |
||
180 | return null; |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * Set the relative Cachepath |
||
185 | * use simple $_SERVER["DOCUMENT_ROOT"] to set this value |
||
186 | * |
||
187 | * Set absolut webserver rootpath |
||
188 | * |
||
189 | * @param string $documentRootPath path to webserverroot |
||
190 | * |
||
191 | * @return boolean false on error |
||
192 | */ |
||
193 | protected function setWebserverRootPath($documentRootPath) |
||
194 | { |
||
195 | // Reset the documentrootpath |
||
196 | $this->docrootpath = null; |
||
197 | |||
198 | // Add trailing slash if not there |
||
199 | if (!preg_match("#/$#", $documentRootPath)) { |
||
200 | $documentRootPath .= "/"; |
||
201 | } |
||
202 | |||
203 | if (file_exists($documentRootPath)) { |
||
204 | $this->docrootpath = $documentRootPath; |
||
205 | return true; |
||
206 | } else { |
||
207 | return false; |
||
208 | } |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Set path to cachefile folder |
||
213 | * |
||
214 | * The cachingpath must be set relativ from docroot of project |
||
215 | * |
||
216 | * @param string $cachepath path to cachefilefolder |
||
217 | * |
||
218 | * @return boolean true if cachefolder exist, false if not |
||
219 | */ |
||
220 | protected function setCachepath($cachepath) |
||
221 | { |
||
222 | |||
223 | // try to make cachepath absolut |
||
224 | $absolutcachepath = self::makeAbsolutPath($cachepath); |
||
225 | |||
226 | // check if path exist could be false/null because the use of makeAbsolutPath()!! |
||
227 | if (is_dir($absolutcachepath)) { |
||
228 | |||
229 | // extra check on trailing slash of $absolutcachepath because of realpath function use |
||
230 | if (!preg_match("#/$#", $absolutcachepath)) { |
||
231 | $absolutcachepath .= "/"; |
||
232 | } |
||
233 | |||
234 | $this->cachepath=$absolutcachepath; |
||
235 | |||
236 | // check if path has trailing slash, if not add now |
||
237 | if (!preg_match("#/$#", $cachepath)) { |
||
238 | $cachepath .= "/"; |
||
239 | } |
||
240 | |||
241 | // check if path begin with slash, if not add it now because |
||
242 | // later every intern files will include absolut to the webserver |
||
243 | // root path, also importent, if mode rewrite is in use on the server |
||
244 | if (!preg_match("#^/#", $cachepath)) { |
||
245 | $cachepath = "/".$cachepath; |
||
246 | } |
||
247 | |||
248 | $this->relcachepath=$cachepath; |
||
249 | return true; |
||
250 | } else { |
||
251 | // folder did't exist |
||
252 | // TODO: throw next time an error! |
||
253 | return false; |
||
254 | } |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * |
||
259 | * Predefined Function to do something content specific work in |
||
260 | * concrete classes right befor the minify process |
||
261 | * default do nothing and return the sting one2one |
||
262 | * |
||
263 | * @param string $filecontent filecontent to process |
||
264 | * @param string $filepath path to the file to convert it later |
||
265 | * |
||
266 | * @return string modified filecontent |
||
267 | */ |
||
268 | protected function modifyFilecontent($filecontent, $filepath) |
||
0 ignored issues
–
show
|
|||
269 | { |
||
270 | // default -> return content one2one and ignore the $filepath |
||
271 | return $filecontent; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Relative to absolut path |
||
276 | * |
||
277 | * Convert relativ path webserver root path to |
||
278 | * absolut path from root in file system |
||
279 | * |
||
280 | * @param string $path relative path to webserver root |
||
281 | * |
||
282 | * @return string absolut path to webserver root |
||
283 | */ |
||
284 | private function makeAbsolutPath($path) |
||
285 | { |
||
286 | // Note, if file/folder don't exists, realpath will return false |
||
287 | return realpath($this->docrootpath.$path); |
||
288 | } |
||
289 | |||
290 | /** |
||
291 | * Add .min in file path |
||
292 | * |
||
293 | * Check if minified version of file exits |
||
294 | * is exits use it |
||
295 | * |
||
296 | * @param string $path path to notminified version of file |
||
297 | * |
||
298 | * @return string path to minified version of file |
||
299 | */ |
||
300 | private function makeMinifiPath($path) |
||
301 | { |
||
302 | // check if there's a minified version of file, if yes there min version will be used |
||
303 | |||
304 | // split path at the dots |
||
305 | $splitpath = explode(".", $path); |
||
306 | |||
307 | $newfragments = array(); |
||
308 | |||
309 | for ($i=0; $i<count($splitpath); $i++) { |
||
310 | if ($i==(count($splitpath)-1)) { |
||
311 | // insert "min" bevor last element (file ending) |
||
312 | $newfragments[]="min"; |
||
313 | } |
||
314 | $newfragments[]=$splitpath[$i]; |
||
315 | } |
||
316 | |||
317 | // now put the puzzelpices together |
||
318 | return implode(".", $newfragments); |
||
319 | } |
||
320 | |||
321 | /** |
||
322 | * Return one cached file |
||
323 | * |
||
324 | * Check if cached and gzipped version of file exits |
||
325 | * if not convert all intern files to one file and |
||
326 | * write it to one cache |
||
327 | * |
||
328 | * @return string|false|null path to cached file or null if no intern files and false on error |
||
329 | */ |
||
330 | private function getCacheFile() |
||
331 | { |
||
332 | // ask if there file signature match with, requested files in the file list |
||
333 | $cachefilesignature = $this->calculateFileSignature(); |
||
334 | |||
335 | // connect the path, related to document root |
||
336 | $cachefile = $cachefilesignature.$this->getCacheFileExtension(); |
||
337 | |||
338 | $absolutcachepath = $this->cachepath.$cachefile; |
||
339 | |||
340 | // set return value null in case there are no internfiles |
||
341 | $returnfile = null; |
||
342 | |||
343 | if ($cachefilesignature!=null) { |
||
344 | // The cachefilesignature has to be different from null to start |
||
345 | if (!file_exists($absolutcachepath)) { |
||
346 | // the file has never been written, write now -> the hard way! |
||
347 | // put files together |
||
348 | foreach ($this->internfilelist as $file) { |
||
349 | // read content of current file |
||
350 | // if overwritten, modfiy the content and put the files together in one string |
||
351 | $filecontent = $this->modifyFilecontent(file_get_contents($file), $file); |
||
352 | |||
353 | // to be safe, add new line |
||
354 | $filecontent .= "\n"; |
||
355 | |||
356 | // append content while writing and look file on other access tries! |
||
357 | if (file_put_contents($absolutcachepath, $filecontent, FILE_APPEND | LOCK_EX)===false) { |
||
358 | // TODO: this error check won't work well, maybe better use other function to write the file |
||
359 | return false; |
||
360 | } |
||
361 | } |
||
362 | // short delay to be safe |
||
363 | usleep(5000); |
||
364 | |||
365 | // now make the gzip version, once we on the way |
||
366 | file_put_contents($absolutcachepath.".gz", gzencode(file_get_contents($absolutcachepath), 9)); |
||
367 | } |
||
368 | |||
369 | // Files in Cachefolder still exits, assume we created it at another run |
||
370 | // don't create them once again only build the path an return it |
||
371 | $returnfile = $this->relcachepath.$cachefile; |
||
372 | } |
||
373 | |||
374 | return $returnfile; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * Calculate a signature to detect file modification |
||
379 | * |
||
380 | * Calculate a signature of all intern files |
||
381 | * base parameters are filename and file modfied date |
||
382 | * hash function is md5 |
||
383 | * |
||
384 | * @return string|null signature |
||
385 | */ |
||
386 | private function calculateFileSignature() |
||
387 | { |
||
388 | // create a signature with all files in intern array structure |
||
389 | // to make it unified, every filname and filechange date will calculate |
||
390 | // together and return as md5 sum |
||
391 | $tempstingbase = ""; |
||
392 | |||
393 | foreach ($this->internfilelist as $file) { |
||
394 | $tempstingbase .= $file."->"; |
||
395 | $tempstingbase .= "(".filemtime($file).") "; |
||
396 | } |
||
397 | |||
398 | // when there are no intern files, function return null |
||
399 | $signature=null; |
||
400 | |||
401 | if (count($this->internfilelist)>0) { |
||
402 | // there are intern files - simple md5 should do, no secure risk |
||
403 | $signature = md5($tempstingbase); |
||
404 | } |
||
405 | |||
406 | return $signature; |
||
407 | } |
||
408 | |||
409 | // TODO: build cleanup function for old cached files |
||
410 | } |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.