Skip to content
This repository
Browse code

Improved cache handling when sessions and cookies are used. Bug fix i…

…n cache handling for the new API setting 'www-cache-load-timestamp'.
  • Loading branch information...
commit c8ab4d1837ccd4cba5b6b5eb9c577926ac1b767a 1 parent abe5d19
Kristo Vaher authored
2  .version
... ... @@ -1,2 +1,2 @@
1   -www:3.4.2
  1 +www:3.4.3
2 2 system:1.0.0
2  controllers/controller.view.php
@@ -31,7 +31,7 @@ class WWW_controller_view extends WWW_Factory {
31 31 public function load($input){
32 32
33 33 // Unsetting input data that are only used by API and are accessible elsewhere by the user
34   - unset($input['www-command'],$input['www-return-type'],$input['www-cache-timeout'],$input['www-request'],$input['www-session'],$input['www-cache-tags']);
  34 + unset($input['www-command'],$input['www-return-type'],$input['www-cache-timeout'],$input['www-cache-load-timeout'],$input['www-request'],$input['www-session'],$input['www-cache-tags']);
35 35
36 36 // Getting view information
37 37 $view=$this->viewData();
2  doc/pages/api_io.htm
@@ -139,7 +139,7 @@
139 139
140 140 <h3>www-session</h3>
141 141
142   - <p>This variable holds data from session variable, but not the whole $_SESSION array. Wave Framework namespaces its session data and only the namespaced session data is stored in this 'www-session' array. This variable is automatically defined for API and should not be sent through input variables.</p>
  142 + <p>This variable tells Wave Framework API to load session data from active session and apply it as input data for any API call. This is set to 1 by default, so session data gets always included, with the exception of Factory api() calls, which disable it by default.</p>
143 143
144 144 <h3>www-crypt-input</h3>
145 145
7 doc/pages/faq.htm
@@ -125,5 +125,12 @@
125 125
126 126 <p>Usually this compression error comes as a result of empty space existing after your PHP scripts, so make sure that the scripts you have edited do not have empty space after closing PHP tags. Alternative is to not use closing tags on PHP scripts at all.</p>
127 127
  128 +
  129 + <h3>I am using page caching and I have a problem caching pages with and without active sessions, what can I do?</h3>
  130 +
  131 + <p>There is a little-known bug in both Safari and Opera web browsers that ignore 'Vary' header from response HTTP request headers. 'Vary' header tells caching engines what header components to take into account when writing cache and validating cache in the storage. While Firefox, Chrome and Internet Explorer browsers all listen to this 'Vary' header as expected, Safari and Opera don't which means that sometimes these browsers may display cached content from time when there was no session cookie.</p>
  132 +
  133 + <p>What is recommended is to not cache any pages (by setting www-cache-timestamp to 0 in Sitemap file or not defining it at all) that have user-specific information or simply wait until user reloads the page manually.</p>
  134 +
128 135 </body>
129 136 </html>
1  doc/pages/history.htm
@@ -25,6 +25,7 @@
25 25 <h2>3.x.x</h2>
26 26
27 27 <ul>
  28 + <li><b>3.4.3</b> - Improved cache handling when sessions and cookies are used. Bug fix in cache handling for the new API setting 'www-cache-load-timestamp'.</li>
28 29 <li><b>3.4.2</b> - Integrated a new method callTool() to Factory and State, which allows you to call system specific functions such as filesystem cleaner or to get an index of files. More tools will be added in the future. The additional JavaScript and PHP loading now targets /scripts/ folder and not /libraries/ folder. Added the option to load external JS and CSS based on sitemap URL. File handler can also use overrides now, if a file is in resources folder. Moved '/resources/autoload.php' to '/resources/scripts/script.php'.</li>
29 30 <li><b>3.4.1</b> - Added options for serving Appcache manifests. Fixed some dynamic URL routing bugs in URL Controller.</li>
30 31 <li><b>3.4.0</b> - A new Factory JavaScript class is now available, which allows you to dynamically load classes from /resources/classes/ subfolder (or another folder, since it is configurable within Factory). It is now also possible to dynamically load class.www-wrapper.js and class.www-factory.js together with other main JavaScript files, Resource Handler tests for this internally and loads them from /engine/ folder instead. Also JavaScript and CSS files have a separate folder now under /resources/ subfolder. Added the ability to also dynamically load function libraries from both PHP and JavaScript. Documentation updated. Added options that allow to load a set of libraries as defined in Sitemap as well as allowing View Controller to also load view-specific PHP autoload-style scripts. Updated state messenger and its documentation to work so that it 'makes more sense'.</li>
5 doc/pages/state.htm
@@ -181,6 +181,7 @@
181 181 <li><b>robots-cache-timeout</b> - Cache lifetime of robots.txt files.</li>
182 182 <li><b>server-ip</b> - Server IP address.</li>
183 183 <li><b>session-data</b> - This holds data from session storage or temporary data before it is stored in session storage.</li>
  184 + <li><b>session-original-data</b> - This is the session data at the start of the request, used for comparing.</li>
184 185 <li><b>session-domain</b> - Session cookie domain.</li>
185 186 <li><b>session-fingerprint</b> - True or false flag for whether sessions are tied to IP and browser fingerprint.</li>
186 187 <li><b>session-fingerprint-key</b> - Key value that the fingerprint will be stored under in sessions.</li>
@@ -236,10 +237,6 @@
236 237
237 238 <p>This is an internal variable that State uses to check if it has already validated sessions or not.</p>
238 239
239   - <h3>private $originalSessionData=array()</h3>
240   -
241   - <p>This carries the session data that was active at the moment sessions were created. It is used to compare later on if sessions need to be commited or not.</p>
242   -
243 240 <h3>private $messenger=false</h3>
244 241
245 242 <p>This holds the 'keyword' or 'passkey' of currently used <a href="guide_messenger.htm">State Messenger</a> .</p>
68 engine/class.www-api.php
@@ -17,7 +17,7 @@
17 17 * @license GNU Lesser General Public License Version 3
18 18 * @tutorial /doc/pages/api.htm
19 19 * @since 1.0.0
20   - * @version 3.4.2
  20 + * @version 3.4.3
21 21 */
22 22
23 23 final class WWW_API {
@@ -324,7 +324,7 @@
324 324 'disable-callbacks'=>(isset($apiInputData['www-disable-callbacks']))?$apiInputData['www-disable-callbacks']:false,
325 325 'crypt-output'=>false,
326 326 'profile'=>$this->state->data['api-public-profile'],
327   - 'push-output'=>(isset($apiInputData['www-output']))?$apiInputData['www-output']:true,
  327 + 'push-output'=>(isset($apiInputData['www-output']))?$apiInputData['www-output']:1,
328 328 'return-hash'=>(isset($apiInputData['www-return-hash']))?$apiInputData['www-return-hash']:false,
329 329 'return-timestamp'=>(isset($apiInputData['www-return-timestamp']))?$apiInputData['www-return-timestamp']:false,
330 330 'return-type'=>(isset($apiInputData['www-return-type']))?$apiInputData['www-return-type']:'json',
@@ -342,7 +342,7 @@
342 342
343 343 // Turning output off if the HTTP HEAD request is made
344 344 if($this->state->data['http-request-method']=='HEAD'){
345   - $apiState['push-output']=false;
  345 + $apiState['push-output']=0;
346 346 }
347 347
348 348 // If API command is not set and is not set in input array either, the system will return an error
@@ -360,9 +360,13 @@
360 360 }
361 361
362 362 // If session data is set
363   - if(!empty($this->state->data['session-data'])){
364   - $apiInputData['www-session']=$this->state->data['session-data'];
365   - unset($apiInputData['www-session'][$this->state->data['session-fingerprint-key']]);
  363 + if(!isset($apiInputData['www-session']) || $apiInputData['www-session']==1){
  364 + if(!empty($this->state->data['session-data'])){
  365 + // Grabbing session data for input
  366 + $apiInputData['www-session']=$this->state->data['session-data'];
  367 + // Unsetting the session cookie and other variables from the input data
  368 + unset($apiInputData['www-session'][$this->state->data['session-fingerprint-key']],$apiInputData['www-session'][$this->state->data['session-timestamp-key']],$apiInputData['www-session'][$this->state->data['session-token-key']]);
  369 + }
366 370 }
367 371
368 372 // Sorting the input array
@@ -393,13 +397,14 @@
393 397
394 398 // This tests if cache value sent through input is valid
395 399 if($apiState['command']!='www-create-session'){
  400 + // If cache timeout is set then it is also applied as default to load timeout
396 401 if(isset($apiInputData['www-cache-timeout']) && $apiInputData['www-cache-timeout']>=0){
397 402 $apiState['cache-timeout']=$apiInputData['www-cache-timeout'];
398 403 $apiState['cache-load-timeout']=$apiInputData['www-cache-timeout'];
399 404 }
400 405 // Loading cache timestamp must be less than the actual cache timeout setting that is set
401 406 if(isset($apiInputData['www-cache-load-timeout']) && $apiInputData['www-cache-load-timeout']>=0 && $apiInputData['www-cache-load-timeout']<=$apiState['cache-timeout']){
402   - $apiState['cache-load-timeout']=$apiState['cache-timeout'];
  407 + $apiState['cache-load-timeout']=$apiInputData['www-cache-load-timeout'];
403 408 }
404 409 }
405 410
@@ -574,7 +579,7 @@
574 579 // Validation hash is calculated from input data
575 580 $validationData=$apiInputData;
576 581 // Session input is not considered for validation hash and is unset
577   - unset($validationData['www-hash'],$validationData['www-session']);
  582 + unset($validationData['www-hash'],$validationData['www-session'],$validationData[$this->state->data['session-name']]);
578 583
579 584 // Unsetting any possible exceptions (such as file uploads and cookie input)
580 585 if(is_array($apiValidation) && !empty($apiValidation)){
@@ -640,10 +645,11 @@
640 645 } else {
641 646 return $this->output(array('www-message'=>'Problem decrypting encrypted data: No tools to decrypt data','www-response-code'=>114),$apiState);
642 647 }
  648 + // This variable is not used anymore
  649 + unset($apiInputData['www-crypt-input']);
643 650 }
644 651
645 652 // If this is set, then the value of this is used to crypt the output
646   - // Please note that while www-crypt-output key can be set outside www-crypt-input data, it is recommended to keep that key within crypted input when making a request
647 653 if(isset($apiInputData['www-crypt-output'])){
648 654 $apiState['crypt-output']=$apiInputData['www-crypt-output'];
649 655 }
@@ -706,15 +712,14 @@
706 712 // This stores a flag about whether cache is used or not
707 713 $this->apiLoggerData['cache-used']=false;
708 714 $this->apiLoggerData['www-command']=$apiState['command'];
709   -
710   - // If cache timeout is set
711   - // If this value is 0, then no cache is used for command
712   - if($apiState['cache-load-timeout']){
  715 +
  716 + // Calculating cache folder locations, if either load or write cache is used
  717 + if($apiState['cache-load-timeout'] || $apiState['cache-timeout']){
713 718
714 719 // Calculating cache validation string
715 720 $cacheValidator=$apiInputData;
716 721 // If session namespace is defined, it is removed from cookies for cache validation
717   - unset($cacheValidator[$this->state->data['session-name']],$cacheValidator['www-headers'],$cacheValidator['www-cache-tags'],$cacheValidator['www-hash'],$cacheValidator['www-state'],$cacheValidator['www-timestamp'],$cacheValidator['www-crypt-output'],$cacheValidator['www-cache-timeout'],$cacheValidator['www-cache-load-timeout'],$cacheValidator['www-return-type'],$cacheValidator['www-output'],$cacheValidator['www-return-hash'],$cacheValidator['www-return-timestamp'],$cacheValidator['www-content-type'],$cacheValidator['www-minify'],$cacheValidator['www-crypt-input'],$cacheValidator['www-xml'],$cacheValidator['www-json'],$cacheValidator['www-ip-session'],$cacheValidator['www-disable-callbacks'],$cacheValidator[$this->state->data['session-token-key']]);
  722 + unset($cacheValidator[$this->state->data['session-name']],$cacheValidator['www-headers'],$cacheValidator['www-cache-tags'],$cacheValidator['www-hash'],$cacheValidator['www-state'],$cacheValidator['www-timestamp'],$cacheValidator['www-crypt-output'],$cacheValidator['www-cache-timeout'],$cacheValidator['www-cache-load-timeout'],$cacheValidator['www-return-type'],$cacheValidator['www-output'],$cacheValidator['www-return-hash'],$cacheValidator['www-return-timestamp'],$cacheValidator['www-content-type'],$cacheValidator['www-minify'],$cacheValidator['www-ip-session'],$cacheValidator['www-disable-callbacks'],$cacheValidator[$this->state->data['session-token-key']]);
718 723
719 724 // MD5 is used for slight performance benefits over sha1() when calculating cache validation hash string
720 725 $cacheValidator=md5($apiState['command'].serialize($cacheValidator).$apiState['return-type'].$apiState['push-output'].$this->state->data['version']);
@@ -724,6 +729,11 @@
724 729 // Setting cache folder
725 730 $cacheFolder=$this->state->data['system-root'].'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'output'.DIRECTORY_SEPARATOR.substr($cacheFile,0,2).DIRECTORY_SEPARATOR;
726 731
  732 + }
  733 +
  734 + // If cache is actually loaded
  735 + if($apiState['cache-load-timeout']){
  736 +
727 737 // If cache file exists, it will be parsed and set as API value
728 738 if(file_exists($cacheFolder.$cacheFile)){
729 739
@@ -735,19 +745,23 @@
735 745
736 746 // If server detects its cache to still within cache limit
737 747 if($apiState['last-modified']>=($this->state->data['request-time']-$apiState['cache-load-timeout'])){
  748 +
738 749 // If this request has already been made and the last-modified timestamp is exactly the same
739   - if($apiState['push-output'] && $this->state->data['http-if-modified-since'] && $this->state->data['http-if-modified-since']>=$apiState['last-modified']){
  750 + if($apiState['push-output'] && $this->state->data['http-if-modified-since'] && $this->state->data['http-if-modified-since']==$apiState['last-modified']){
740 751 // Adding log data
741 752 if($useLogger){
742 753 $this->apiLoggerData['cache-used']=true;
743 754 $this->apiLoggerData['response-code']=304;
744 755 }
745 756 // Cache headers (Last modified is never sent with 304 header)
746   - if($this->state->data['http-authentication']==true){
  757 + if($this->state->data['http-authentication']==true || isset($this->state->data['session-data'][$this->state->data['session-user-key']]) || isset($this->state->data['session-data'][$this->state->data['session-permissions-key']])){
747 758 header('Cache-Control: private,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
748 759 } else {
749 760 header('Cache-Control: public,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
750 761 }
  762 + // This tells caching engine to take cookies and content encoding into account
  763 + header('Vary: Accept-Encoding,Cookie');
  764 + // Expires header based on timeout
751 765 header('Expires: '.gmdate('D, d M Y H:i:s',($apiState['last-modified']+$apiState['cache-timeout'])).' GMT');
752 766 // Returning 304 header
753 767 header('HTTP/1.1 304 Not Modified');
@@ -756,23 +770,16 @@
756 770
757 771 // System loads the result from cache file based on return data type
758 772 $apiResult=$this->getCache($cacheFolder.$cacheFile);
759   -
760 773 // Since cache was used
761 774 $this->apiLoggerData['cache-used']=true;
762 775
763 776 } else {
764   - // Since cache seems to be outdated, a new one will be generated with new request time
  777 + // Since cache seems to be outdated, last modified time is reset to request time
765 778 $apiState['last-modified']=$this->state->data['request-time'];
766 779 }
767 780
768   - } else {
769   - // Current cache timeout is used to return to browser information about how long browser should store this result
770   - $apiState['last-modified']=$this->state->data['request-time'];
771 781 }
772 782
773   - } else {
774   - // Since cache is not used, last modified time is the time of the request
775   - $apiState['last-modified']=$this->state->data['request-time'];
776 783 }
777 784
778 785 // SOLVING API RESULT IF RESULT WAS NOT FOUND IN CACHE
@@ -1042,7 +1049,7 @@
1042 1049 break;
1043 1050 case 'php':
1044 1051 // If PHP is used, then it can not be 'echoed' out due to being a PHP variable, so this is turned off
1045   - $apiState['push-output']=false;
  1052 + $apiState['push-output']=0;
1046 1053 break;
1047 1054 }
1048 1055
@@ -1115,7 +1122,7 @@
1115 1122 // Cache control settings sent to the user agent depend on cache timeout settings
1116 1123 if($apiState['cache-timeout']!=0){
1117 1124 // Cache control depends whether HTTP authentication is used or not
1118   - if($this->state->data['http-authentication']==true){
  1125 + if($this->state->data['http-authentication']==true || isset($this->state->data['session-data'][$this->state->data['session-user-key']]) || isset($this->state->data['session-data'][$this->state->data['session-permissions-key']])){
1119 1126 header('Cache-Control: private,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
1120 1127 } else {
1121 1128 header('Cache-Control: public,max-age='.($apiState['last-modified']+$apiState['cache-timeout']-$this->state->data['request-time']).'');
@@ -1124,11 +1131,14 @@
1124 1131 header('Last-Modified: '.gmdate('D, d M Y H:i:s',$apiState['last-modified']).' GMT');
1125 1132 } else {
1126 1133 // When no cache is used, request tells specifically that
1127   - header('Cache-Control: no-store;');
  1134 + header('Cache-Control: no-cache,no-store;');
1128 1135 header('Expires: '.gmdate('D, d M Y H:i:s',$this->state->data['request-time']).' GMT');
1129   - header('Last-Modified: '.$apiState['last-modified'].' GMT');
  1136 + header('Last-Modified: '.gmdate('D, d M Y H:i:s',$apiState['last-modified']).' GMT');
1130 1137 }
1131 1138
  1139 + // This tells caching engine to take cookies and content encoding into account
  1140 + header('Vary: Accept-Encoding,Cookie');
  1141 +
1132 1142 // If custom header was assigned, it is added
1133 1143 if($apiState['custom-header']){
1134 1144 header($apiState['custom-header']);
@@ -1197,8 +1207,6 @@
1197 1207 case 'gzip':
1198 1208 // Notifying user agent of gzipped output
1199 1209 header('Content-Encoding: gzip');
1200   - // This tells proxies to store both compressed and uncompressed version
1201   - header('Vary: Accept-Encoding');
1202 1210 $apiResult=gzencode(ob_get_clean(),9);
1203 1211 break;
1204 1212 default:
6 engine/class.www-factory.php
@@ -15,7 +15,7 @@
15 15 * @license GNU Lesser General Public License Version 3
16 16 * @tutorial /doc/pages/factory.htm
17 17 * @since 1.0.0
18   - * @version 3.4.2
  18 + * @version 3.4.3
19 19 */
20 20
21 21 class WWW_Factory {
@@ -96,6 +96,10 @@ class WWW_Factory {
96 96 if(!isset($inputData['www-output'])){
97 97 $inputData['www-output']=0;
98 98 }
  99 + // This tells API not to include session as input data for the API call
  100 + if(!isset($inputData['www-session'])){
  101 + $inputData['www-session']=0;
  102 + }
99 103 // Returning the result from API
100 104 return $this->WWW_API->command($inputData,$useBuffer,false);
101 105
4 engine/class.www-sessions.php
@@ -17,7 +17,7 @@
17 17 * @license GNU Lesser General Public License Version 3
18 18 * @tutorial /doc/pages/sessions.htm
19 19 * @since 3.2.0
20   - * @version 3.2.7
  20 + * @version 3.4.3
21 21 */
22 22
23 23 final class WWW_Sessions {
@@ -52,7 +52,7 @@
52 52 'http-only'=>true
53 53 );
54 54
55   - /*
  55 + /**
56 56 * This is a true-false flag about whether session ID should be regenerated. This can be sent by
57 57 * the system that uses the Session Handler at any time before sessions are commited. Wave
58 58 * Framework uses this to regenerate cookies when it detects the cookie lifetime is about to end.
91 engine/class.www-state.php
@@ -19,7 +19,7 @@
19 19 * @license GNU Lesser General Public License Version 3
20 20 * @tutorial /doc/pages/state.htm
21 21 * @since 1.0.0
22   - * @version 3.4.2
  22 + * @version 3.4.3
23 23 */
24 24
25 25 class WWW_State {
@@ -49,12 +49,6 @@ class WWW_State {
49 49 private $sessionStarted=false;
50 50
51 51 /**
52   - * This carries the session data that was active at the moment sessions were created. It is
53   - * used to compare later on if sessions need to be commited or not.
54   - */
55   - private $originalSessionData=array();
56   -
57   - /**
58 52 * This holds the 'keyword' or 'passkey' of currently used State messenger.
59 53 */
60 54 private $messenger=false;
@@ -199,6 +193,7 @@ class WWW_State {
199 193 'session-id'=>false,
200 194 'session-lifetime'=>0,
201 195 'session-name'=>'WWW'.crc32(__ROOT__),
  196 + 'session-original-data'=>array(),
202 197 'session-path'=>false,
203 198 'session-permissions-key'=>'www-permissions',
204 199 'session-regenerate'=>0,
@@ -578,19 +573,23 @@ class WWW_State {
578 573 */
579 574 final public function commitHeaders(){
580 575
581   - // Checking if there are more data in sessions than just internal keys
582   - $sessionSize=count($this->data['session-data']);
583   - if(empty($this->data['session-data']) || ($sessionSize==1 && isset($this->data['session-data'][$this->data['session-timestamp-key']])) || ($sessionSize==1 && isset($this->data['session-data'][$this->data['session-fingerprint-key']])) || ($sessionSize==2 && isset($this->data['session-data'][$this->data['session-timestamp-key']]) && isset($this->data['session-data'][$this->data['session-fingerprint-key']]))){
584   - // This will make the session handler destroy the session
585   - $this->data['session-data']=array();
586   - }
  576 + // If sessions have been started
  577 + if($this->sessionStarted){
  578 +
  579 + // If session data has changed in any way or session is assigned to be regenerated
  580 + if($this->sessionHandler->regenerateId || $this->data['session-original-data']!=$this->data['session-data']){
  581 + // Checking if there are more data in sessions than just internal keys
  582 + $sessionSize=count($this->data['session-data']);
  583 + if($sessionSize==0 || ($sessionSize==1 && isset($this->data['session-data'][$this->data['session-timestamp-key']])) || ($sessionSize==1 && isset($this->data['session-data'][$this->data['session-fingerprint-key']])) || ($sessionSize==2 && isset($this->data['session-data'][$this->data['session-timestamp-key']]) && isset($this->data['session-data'][$this->data['session-fingerprint-key']]))){
  584 + // This will make the session handler destroy the session
  585 + $this->data['session-data']=array();
  586 + }
  587 + // Sending State session data to session handler
  588 + $this->sessionHandler->sessionData=$this->data['session-data'];
  589 + // Closing sessions
  590 + $this->sessionHandler->sessionCommit();
  591 + }
587 592
588   - // If session data has changed in any way or session is assigned to be regenerated
589   - if($this->sessionHandler->regenerateId || $this->originalSessionData!=$this->data['session-data']){
590   - // Sending State session data to session handler
591   - $this->sessionHandler->sessionData=$this->data['session-data'];
592   - // Closing sessions
593   - $this->sessionHandler->sessionCommit();
594 593 }
595 594
596 595 // Commiting headers for added headers
@@ -997,13 +996,19 @@ class WWW_State {
997 996 * @param array $data data array set to user
998 997 * @return boolean
999 998 */
1000   - final public function setUser($data){
1001   -
1002   - // Setting the session
1003   - $this->setSession($this->data['session-user-key'],$data);
1004   - // Setting the state variable
1005   - $this->data['user-data']=$data;
1006   - return true;
  999 + final public function setUser($data=false){
  1000 +
  1001 + // If user is not being unset
  1002 + if($data){
  1003 + // Setting the session
  1004 + $this->setSession($this->data['session-user-key'],$data);
  1005 + // Setting the state variable
  1006 + $this->data['user-data']=$data;
  1007 + return true;
  1008 + } else {
  1009 + // Unsetting the user
  1010 + return $this->unsetUser();
  1011 + }
1007 1012
1008 1013 }
1009 1014
@@ -1049,6 +1054,8 @@ class WWW_State {
1049 1054
1050 1055 // Unsetting the session
1051 1056 $this->unsetSession($this->data['session-user-key']);
  1057 + // Regenerating session cookie
  1058 + $this->regenerateSession(true,true);
1052 1059 // Unsetting the state variable
1053 1060 $this->data['user-data']=false;
1054 1061 return true;
@@ -1062,17 +1069,23 @@ class WWW_State {
1062 1069 * @param array|string $permissions an array or a string of permissions
1063 1070 * @return boolean
1064 1071 */
1065   - final public function setPermissions($permissions){
  1072 + final public function setPermissions($permissions=false){
1066 1073
1067   - // If permissions were sent as a string
1068   - if(!is_array($permissions)){
1069   - $permissions=explode(',',$permissions);
  1074 + // If permissions are set
  1075 + if($permissions){
  1076 + // If permissions were sent as a string
  1077 + if(!is_array($permissions)){
  1078 + $permissions=explode(',',$permissions);
  1079 + }
  1080 + // Setting the session variable
  1081 + $this->setSession($this->data['session-permissions-key'],$permissions);
  1082 + // Setting the state variable
  1083 + $this->data['user-permissions']=$permissions;
  1084 + return true;
  1085 + } else {
  1086 + // Unsetting permissions
  1087 + return $this->unsetPermissions();
1070 1088 }
1071   - // Setting the session variable
1072   - $this->setSession($this->data['session-permissions-key'],$permissions);
1073   - // Setting the state variable
1074   - $this->data['user-permissions']=$permissions;
1075   - return true;
1076 1089
1077 1090 }
1078 1091
@@ -1156,6 +1169,8 @@ class WWW_State {
1156 1169
1157 1170 // Unsetting the session
1158 1171 $this->unsetSession($this->data['session-permissions-key']);
  1172 + // Regenerating session cookie
  1173 + $this->regenerateSession(true,true);
1159 1174 // Unsetting the state variable
1160 1175 $this->data['user-permissions']=false;
1161 1176 return true;
@@ -1245,12 +1260,6 @@ class WWW_State {
1245 1260 'http-only'=>$this->data['session-http-only']
1246 1261 ));
1247 1262
1248   - // Getting sessions data
1249   - $this->data['session-data']=$this->sessionHandler->sessionData;
1250   -
1251   - // Storing original session data for later comparison
1252   - $this->originalSessionData=$this->data['session-data'];
1253   -
1254 1263 // This can regenerate the session ID, if enough time has passed
1255 1264 if($this->data['session-regenerate']){
1256 1265 if(!isset($this->data['session-data'][$this->data['session-timestamp-key']])){
13 engine/handler.api.php
@@ -15,7 +15,7 @@
15 15 * @license GNU Lesser General Public License Version 3
16 16 * @tutorial /doc/pages/handler_api.htm
17 17 * @since 1.5.0
18   - * @version 3.4.2
  18 + * @version 3.4.3
19 19 */
20 20
21 21 //INITIALIZATION
@@ -48,6 +48,11 @@
48 48 require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-sessions.php');
49 49 // Loading sessions class with the session namespace
50 50 $state->sessionHandler=new WWW_Sessions($state->data['session-name'],$state->data['session-lifetime'],$databaseConnection);
  51 + // Assigning session data to State
  52 + if(!empty($state->sessionHandler->sessionData)){
  53 + $state->data['session-original-data']=$state->sessionHandler->sessionData;
  54 + $state->data['session-data']=$state->sessionHandler->sessionData;
  55 + }
51 56
52 57 // AUTOLOAD AND SESSIONS FUNCTIONALITY
53 58
@@ -109,6 +114,12 @@
109 114 }
110 115 }
111 116
  117 + // Removing input stream related data that was read in the previous section
  118 + if($state->data['http-input']){
  119 + // Removing input stream related data
  120 + unset($inputData['www-xml'],$inputData['www-json']);
  121 + }
  122 +
112 123 // SENDING COMMAND TO API
113 124
114 125 // Setting current API profile in state
15 engine/handler.data.php
@@ -16,7 +16,7 @@
16 16 * @license GNU Lesser General Public License Version 3
17 17 * @tutorial /doc/pages/handler_data.htm
18 18 * @since 1.5.0
19   - * @version 3.4.2
  19 + * @version 3.4.3
20 20 */
21 21
22 22 // INITIALIZATION
@@ -75,6 +75,11 @@
75 75 require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-sessions.php');
76 76 // Loading sessions class with the session namespace
77 77 $state->sessionHandler=new WWW_Sessions($state->data['session-name'],$state->data['session-lifetime'],$databaseConnection);
  78 + // Assigning session data to State
  79 + if(!empty($state->sessionHandler->sessionData)){
  80 + $state->data['session-original-data']=$state->sessionHandler->sessionData;
  81 + $state->data['session-data']=$state->sessionHandler->sessionData;
  82 + }
78 83
79 84 // AUTOLOAD AND SESSIONS FUNCTIONALITY
80 85
@@ -114,12 +119,14 @@
114 119 if(!empty($_GET)){
115 120 $inputData+=$_GET;
116 121 }
117   - if(!empty($_COOKIE)){
118   - $inputData+=$_COOKIE;
119   - }
120 122 if(!empty($_FILES)){
121 123 $inputData+=$_FILES;
122 124 }
  125 + if(!empty($_COOKIE)){
  126 + $inputData+=$_COOKIE;
  127 + }
  128 + // Removing input stream related data
  129 + unset($inputData['www-xml'],$inputData['www-json']);
123 130
124 131 // If index view cache is not configured, it is turned of by default
125 132 if(isset($view['cache-timeout'])){
2  engine/handler.file.php
... ... @@ -1 +1 @@
1   -<?php /** * Wave Framework <http://www.waveframework.com> * File Handler * * File Handler is used for returning a file from web server. It is loaded by Index Controller * when a request is made to a file that has an extension that has not already been served by * another Handler (such as Resource and Image handlers). File Handler is usually considered * for returning documents, videos and audio from the web server. It also allows to return a * file within a byte range, like in the case of file streams. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_file.htm * @since 1.5.0 * @version 3.4.2 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); // Getting the downloadable file name $resourceFile=array_pop($parameters); // The amount of non-filenames in the request $parameterCount=count($parameters); // Range of bytes to return // This allows user agent to request only part of the file if(isset($_SERVER['HTTP_RANGE'])){ $tmp=explode('=',$_SERVER['HTTP_RANGE']); $bytesData=explode('-',array_pop($tmp)); if(isset($bytesData) && is_numeric($bytesData[0]) && is_numeric($bytesData[1])){ $bytesFrom=$bytesData[0]; $bytesTo=$bytesData[1]; } } // No cache flag if(in_array('nocache',$parameters)){ $noCache=true; } else { $noCache=false; } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // Checking if the file might be loaded from overrides folder if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\//',$_SERVER['REQUEST_URI'])){ // Solving possible overrides folder $overridesFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); if(file_exists($overridesFolder.$resourceFile)){ $resourceFolder=$overridesFolder; } } // Default cache timeout of one month, unless timeout is set if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // CHECK FOR PARAMETER SUPPORT // If more than one parameter is set, it returns 404 // 404 is also returned if file does not actually exist if($parameterCount>1 || ($parameterCount==1 && !$noCache) || !file_exists($resourceFolder.$resourceFile)){ // Adding log entry if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'file','response-code'=>'404')); // Writing log entry $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // Last-modified date $lastModified=filemtime($resourceFolder.$resourceFile); // NOT MODIFIED CHECK // Checking if file has been modified or not if(!$noCache){ // If the request timestamp is exactly the same, then we let the browser know of this if((isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified) || (isset($_SERVER['HTTP_IF_RANGE']) && strtotime($_SERVER['HTTP_IF_RANGE'])>=$lastModified)){ // Adding log entry if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>true,'category'=>'image','response-code'=>'304')); // Writing log entry $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } // DETECTING MIME TYPE // Currently assumed MIME type $mimeType=''; // Finding the proper MIME type if(extension_loaded('fileinfo')){ // This opens MIME type 'magic' resource for use if($fileInfo=finfo_open(FILEINFO_MIME_TYPE)){ // Finding MIME type with magic resource $mimeType=finfo_file($fileInfo,$resourceFolder.$resourceFile); // Resourse is not needed further, so it is closed finfo_close($fileInfo); } } else { // Since Fileinfo was not available, we use extension-based detection as fallback if(isset($resourceExtension)){ switch($resourceExtension){ case 'ico': $mimeType='image/vnd.microsoft.icon;'; break; case 'zip': $mimeType='application/zip'; break; case 'pdf': $mimeType='application/pdf'; break; case 'mp3': $mimeType='audio/mpeg'; break; case 'gif': $mimeType='image/gif'; break; case 'tif': $mimeType='image/tiff'; break; } } } // HEADERS // Assigning MIME type if it was found if($mimeType && $mimeType!=''){ // Detected mime type is set as content-type header header('Content-Type: '.$mimeType.';'); } else { // Octet stream is a general-use unknown resource, and browsers will often attempt to 'download' such a file header('Content-Type: application/octet-stream;'); header('Content-Disposition: attachment; filename='.$resourceFile); } // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: public,max-age=0'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.$lastModified.' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Robots header if(isset($config['file-robots'])){ // If file-specific robots setting is defined header('Robots-Tag: '.$config['file-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // If user agent only requested part of the file to be returned if(isset($bytesFrom,$bytesTo)){ // Getting current output length $contentLength=filesize($resourceFolder.$resourceFile); if($bytesTo<=$contentLength){ // Required for range response header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes '.$bytesFrom.'-'.$bytesTo.'/'.$contentLength); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.($bytesTo-$bytesFrom)); // Returning part of the file $fileHandle=fopen($resourceFolder.$resourceFile,'r'); fseek($fileHandle,$bytesFrom); // Returning the data to user agent echo fread($fileHandle,($bytesTo-$bytesFrom)); } else { header('HTTP/1.1 416 Requested Range Not Satisfiable'); } } else { // Getting current output length $contentLength=filesize($resourceFolder.$resourceFile); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file to user agent readfile($resourceFolder.$resourceFile); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>false,'category'=>'file','content-length-used'=>$contentLength)); // Writing log entry $logger->writeLog(); } ?>
  1 +<?php /** * Wave Framework <http://www.waveframework.com> * File Handler * * File Handler is used for returning a file from web server. It is loaded by Index Controller * when a request is made to a file that has an extension that has not already been served by * another Handler (such as Resource and Image handlers). File Handler is usually considered * for returning documents, videos and audio from the web server. It also allows to return a * file within a byte range, like in the case of file streams. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_file.htm * @since 1.5.0 * @version 3.4.3 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); // Getting the downloadable file name $resourceFile=array_pop($parameters); // The amount of non-filenames in the request $parameterCount=count($parameters); // Range of bytes to return // This allows user agent to request only part of the file if(isset($_SERVER['HTTP_RANGE'])){ $tmp=explode('=',$_SERVER['HTTP_RANGE']); $bytesData=explode('-',array_pop($tmp)); if(isset($bytesData) && is_numeric($bytesData[0]) && is_numeric($bytesData[1])){ $bytesFrom=$bytesData[0]; $bytesTo=$bytesData[1]; } } // No cache flag if(in_array('nocache',$parameters)){ $noCache=true; } else { $noCache=false; } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // Checking if the file might be loaded from overrides folder if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\//',$_SERVER['REQUEST_URI'])){ // Solving possible overrides folder $overridesFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); if(file_exists($overridesFolder.$resourceFile)){ $resourceFolder=$overridesFolder; } } // Default cache timeout of one month, unless timeout is set if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // CHECK FOR PARAMETER SUPPORT // If more than one parameter is set, it returns 404 // 404 is also returned if file does not actually exist if($parameterCount>1 || ($parameterCount==1 && !$noCache) || !file_exists($resourceFolder.$resourceFile)){ // Adding log entry if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'file','response-code'=>'404')); // Writing log entry $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // Last-modified date $lastModified=filemtime($resourceFolder.$resourceFile); // NOT MODIFIED CHECK // Checking if file has been modified or not if(!$noCache){ // If the request timestamp is exactly the same, then we let the browser know of this if((isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified) || (isset($_SERVER['HTTP_IF_RANGE']) && strtotime($_SERVER['HTTP_IF_RANGE'])==$lastModified)){ // Adding log entry if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>true,'category'=>'image','response-code'=>'304')); // Writing log entry $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } // DETECTING MIME TYPE // Currently assumed MIME type $mimeType=''; // Finding the proper MIME type if(extension_loaded('fileinfo')){ // This opens MIME type 'magic' resource for use if($fileInfo=finfo_open(FILEINFO_MIME_TYPE)){ // Finding MIME type with magic resource $mimeType=finfo_file($fileInfo,$resourceFolder.$resourceFile); // Resourse is not needed further, so it is closed finfo_close($fileInfo); } } else { // Since Fileinfo was not available, we use extension-based detection as fallback if(isset($resourceExtension)){ switch($resourceExtension){ case 'ico': $mimeType='image/vnd.microsoft.icon;'; break; case 'zip': $mimeType='application/zip'; break; case 'pdf': $mimeType='application/pdf'; break; case 'mp3': $mimeType='audio/mpeg'; break; case 'gif': $mimeType='image/gif'; break; case 'tif': $mimeType='image/tiff'; break; } } } // HEADERS // Assigning MIME type if it was found if($mimeType && $mimeType!=''){ // Detected mime type is set as content-type header header('Content-Type: '.$mimeType.';'); } else { // Octet stream is a general-use unknown resource, and browsers will often attempt to 'download' such a file header('Content-Type: application/octet-stream;'); header('Content-Disposition: attachment; filename='.$resourceFile); } // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Robots header if(isset($config['file-robots'])){ // If file-specific robots setting is defined header('Robots-Tag: '.$config['file-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // If user agent only requested part of the file to be returned if(isset($bytesFrom,$bytesTo)){ // Getting current output length $contentLength=filesize($resourceFolder.$resourceFile); if($bytesTo<=$contentLength){ // Required for range response header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes '.$bytesFrom.'-'.$bytesTo.'/'.$contentLength); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.($bytesTo-$bytesFrom)); // Returning part of the file $fileHandle=fopen($resourceFolder.$resourceFile,'r'); fseek($fileHandle,$bytesFrom); // Returning the data to user agent echo fread($fileHandle,($bytesTo-$bytesFrom)); } else { header('HTTP/1.1 416 Requested Range Not Satisfiable'); } } else { // Getting current output length $contentLength=filesize($resourceFolder.$resourceFile); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file to user agent readfile($resourceFolder.$resourceFile); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>false,'category'=>'file','content-length-used'=>$contentLength)); // Writing log entry $logger->writeLog(); } ?>
2  engine/handler.image.php
... ... @@ -1 +1 @@
1   -<?php /** * Wave Framework <http://www.waveframework.com> * Image Handler * * Image Handler is used by Index Gateway to return all image files to the user agent HTTP * requests. Handler adds proper cache headers as well as supports on-demand image-editing, * where it is possible to load an image file with specific resize algorithms and even image * filtering. It also checks for files from overrides folder, which can be returned instead * of the actual file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_image.htm * @since 1.5.0 * @version 3.4.2 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // Web root is the subfolder on public site $systemRoot=str_replace('index.php','',$_SERVER['SCRIPT_FILENAME']); // Dynamic resource loading can be turned off in configuration if(!isset($config['dynamic-image-loading']) || $config['dynamic-image-loading']==true){ // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); } else { $parameters=array(); $parameters[0]=$resourceFile; } // True filename is the last string in the string separated by & character $resourceFile=array_pop($parameters); // Current true file position $resource=$resourceFolder.$resourceFile; // Files from /resources/ folder can be overwritten if file with the same name is placed to /overrides/resources/ if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources/',$resourceRequest)){ //Checking if file of the same name exists in overrides folder $overrideFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); if(file_exists($overrideFolder.$resourceFile)){ // System will use an override as a resource, since it exists $resource=$overrideFolder.$resourceFile; } } // RESOURCE EXISTENCE CHECK // If file does not exist then 404 is thrown if(!file_exists($resource) && (!isset($config['404-image-placeholder']) || $config['404-image-placeholder']==true) && (file_exists(__ROOT__.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg') || file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'))){ // It's possible to overwrite the default image used for 404 placeholder if(file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg')){ $resource=__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'; } else { $resource=__ROOT__.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'; } // 404 header header('HTTP/1.1 404 Not Found'); // Notifying Logger of 404 response code if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404)); } // This variable is used by cache to calculate cache filename, but since system is returning a placeholder instead, it is overwritten // This allows system to keep all 404 placeholder image cache in the same cache file $tmp=explode('/',str_replace($resourceFile,'placeholder.jpg',$resourceRequest)); $resourceRequest=array_pop($tmp); } elseif(!file_exists($resource)){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // CACHE SETTINGS // Default cache timeout of one month, unless timeout is set if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // Last-modified time of the original resource $lastModified=filemtime($resource); // This flag stores whether cache was used $cacheUsed=false; // No cache flag if(in_array('nocache',$parameters)){ $noCache=true; } else { $noCache=false; } // GENERATION BASED ON PARAMETERS // If file seems to carry additional configuration options, then it is generated or loaded from cache if(empty($parameters)){ // Pure image file request is considered 'cache used' due to it not needing any processing $cacheUsed=true; // IF NOT MODIFIED // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>304,'category'=>'image','cache-used'=>true)); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header, since it is often ignored) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Solving cache folders and directory $cacheFilename=md5($lastModified.$config['version'].$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // IF NOT MODIFIED // If cache file exists then cache modified is considered that time if(!$noCache && file_exists($cacheDirectory.$cacheFilename)){ // Getting last modified time from cache file $lastModified=filemtime($cacheDirectory.$cacheFilename); // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>304,'category'=>'image','cache-used'=>true)); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING RESOURCE // If resource cannot be found from cache, it is generated if($noCache || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['resource-cache-timeout']))){ // DEFAULT SETTINGS // Get existing image size $resolution=getimagesize($resource); // Default settings for dynamically resized image // This values will be changed based on if parameters are set $width=$resolution[0]; $height=$resolution[1]; $algorithm='fitcrop'; $red=0; $green=0; $blue=0; $top='center'; $left='center'; $quality=90; $filters=array(); $filterSettings=array(); $format=false; // FINDING SETTINGS FROM SET PARAMETERS // Looping over the data bits to find additional parameters foreach($parameters as $parameter){ switch($parameter){ case 'fitcrop': // This is a resize algorithm flag $algorithm='fitcrop'; break; case 'crop': // This is a resize algorithm flag $algorithm='crop'; break; case 'fitwithbackground': // This is a resize algorithm flag $algorithm='fitwithbackground'; break; case 'fitwithoutbackground': // This is a resize algorithm flag $algorithm='fitwithoutbackground'; break; case 'widthonly': // This is a resize algorithm flag $algorithm='widthonly'; break; case 'jpg': // This is a resize algorithm flag $format='jpg'; break; case 'png': // This is a resize algorithm flag $format='png'; break; case 'heightonly': // This is a resize algorithm flag $algorithm='heightonly'; break; default: // If any of the resize algorithm and cache flags were not hit, the parameter is matched for other conditions if(strpos($parameter,'filter(')!==false){ // Background color setting is assumed if rgb is present $settings=str_replace(array('filter(',')'),'',$parameter); $settings=explode(',',$settings); // Storing data of new filter $newFilter=array(); // First number is the filter type if($settings[0]!=''){ // Filter type can also have parameters $typeSettings=explode('@',$settings[0]); // First parameter is the filter type $newFilter['type']=$typeSettings[0]; // It is possible to 'layer' the effect by defining alpha level as the second parameter if(isset($typeSettings[1])){ $newFilter['alpha']=$typeSettings[1]; } else { // Filter effect is 100% if alpha was not defined $newFilter['alpha']=100; } } // Storing data of new filters settings $newFilter['settings']=array(); // Storing other filter variables for($i=1;isset($settings[$i]);$i++){ $newFilter['settings'][]=$settings[$i]; } // Adding filter to list of filters $filters[]=$newFilter; } elseif(strpos($parameter,'rgb(')!==false){ // Background color setting is assumed if rgb is present $colors=str_replace(array('rgb(',')'),'',$parameter); $colors=explode(',',$colors); // First number in parameter is red color amount if($colors[0]!=''){ $red=$colors[0]; } // Second number in parameter is green color amount if(isset($colors[1]) && $colors[1]!=''){ $green=$colors[1]; } // Third number in parameter is blue color amount if(isset($colors[2]) && $colors[2]!=''){ $blue=$colors[2]; } } elseif(strpos($parameter,'@')!==false){ // Quality setting is assumed if @ sign is present $quality=str_replace('@','',$parameter); } elseif(strpos($parameter,'-')!==false){ // Position setting is assumed if dash is present $positions=explode('-',$parameter); // First value is top position // This can be 'top', 'center', 'bottom' or a number in pixels if($positions[0]!=''){ $top=$positions[0]; } // Second value is left position // This can be 'left', 'center', 'right' or a number in pixels if($positions[1]!=''){ $left=$positions[1]; } } elseif(strpos($parameter,'x')!==false){ // It is assumed that the remaining parameter is for image dimensions $dimensions=explode('x',$parameter); // First number is width if($dimensions[0]!=''){ $width=$dimensions[0]; } // Second number, if defined, is height if(isset($dimensions[1]) && $dimensions[1]!=''){ $height=$dimensions[1]; } else { // If height is not defined then height is considered to be as long as width $height=$width; } // If algorithm is still undefined, it is given a default value // This is needed when size is set, but algorithm is not if(!$algorithm){ $algorithm='fitcrop'; } } elseif($parameter!='nocache'){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } break; } } // IMAGE SETTING VALIDATION // System checks for legality of the entered values // Whitelists allow to protect the server better from possible abuse and denial of service attacks $allowed=true; // If configuration file has not been set for dynamic max size, then it is defaulted to 1000x1000 maximum if(!isset($config['dynamic-max-size'])){ // Default maximum image dimension height/width $config['dynamic-max-size']=4096; } // Checking if image is within allowed parameters if($width>$config['dynamic-max-size'] || $height>$config['dynamic-max-size'] || $height==0 || $width==0){ // If image dimensions are beyond allowed values $allowed=false; } elseif(isset($config['dynamic-size-whitelist']) && $config['dynamic-size-whitelist']!='' && ($width!=$resolution[0] || $height!=$resolution[1]) && !in_array($width.'x'.$height,explode(' ',$config['dynamic-size-whitelist']))){ // For size whitelist check // If resolution has been changed and this resolution is not found in whitelist $allowed=false; } elseif(isset($config['dynamic-color-whitelist']) && $config['dynamic-color-whitelist']!='' && ($red || $green || $blue) && !in_array($red.','.$green.','.$blue,explode(' ',$config['dynamic-color-whitelist']))){ // For RGB whitelist check // If RGB values are not defaults and this setting is not found in color whitelist $allowed=false; } elseif(isset($config['dynamic-quality-whitelist']) && $config['dynamic-quality-whitelist']!='' && $quality && !in_array('@'.$quality,explode(' ',$config['dynamic-quality-whitelist']))){ // For quality whitelist check // If quality values are not defaults and this setting is not found in quality whitelist $allowed=false; } elseif(isset($config['dynamic-position-whitelist']) && $config['dynamic-position-whitelist']!='' && ($top || $left) && !in_array($top.'-'.$left,explode(' ',$config['dynamic-position-whitelist']))){ // For position whitelist check // If position values are not defaults and this setting is not found in position whitelist $allowed=false; } elseif($allowed && isset($config['dynamic-filter-whitelist']) && $config['dynamic-filter-whitelist']!='' && !empty($filters)){ // For filter whitelist check foreach($filters as $filter){ // If filters are not in filter whitelist then processing is canceled if($allowed && !in_array($filter['type'].'@'.$filter['alpha'].','.implode(',',$filter['settings']),explode(' ',$config['dynamic-filter-whitelist']))){ $allowed=false; } } } // IMAGE GENERATION BASED ON SETTINGS // If whitelist checks did not fail and image dimensions are good if($allowed){ // If algorithm, quality setting or a filter is set if($algorithm || $quality || !empty($filters)){ // If cache folder does not exist, it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // This functionality only works if GD library is loaded if(extension_loaded('gd')){ // IMAGE EDITING // Requiring WWW_Imager class that is used to do basic image manipulation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-imager.php'); // New imager object, this is a wrapper around GD or ImageMagick library $picture=new WWW_Imager(); // Current image file is loaded into Imager if(!$picture->input($resource)){ trigger_error('Cannot load image',E_USER_ERROR); } // IMAGE RESIZES // Image is filtered through resize algorithm and saved in cache directory switch($algorithm){ case 'fitcrop': // Crop algorithm fits the image into set dimensions, cutting the edges that do not fit if(!$picture->resizeFitCrop($width,$height,$left,$top)){ trigger_error('Cannot resize image with fit-crop algorithm',E_USER_ERROR); } break; case 'crop': // Crop algorithm places image in new dimensions box cutting the edges that do not fit if(!$picture->resizeCrop($width,$height,$left,$top,$red,$green,$blue)){ trigger_error('Cannot resize image with crop algorithm',E_USER_ERROR); } break; case 'fitwithbackground': // This fits image inside the box and gives it certain color background (if applicable) if(!$picture->resizeFit($width,$height,$left,$top,$red,$green,$blue)){ trigger_error('Cannot resize image with fit-with-background algorithm',E_USER_ERROR); } break; case 'fitwithoutbackground': // This simply resizes the image to fit specific dimensions if(!$picture->resizeFitNoBackground($width,$height)){ trigger_error('Cannot resize image with fit-without-background algorithm',E_USER_ERROR); } break; case 'widthonly': // This resizes the image to fixed width if(!$picture->resizeWidth($width)){ trigger_error('Cannot resize image with width-only algorithm',E_USER_ERROR); } break; case 'heightonly': // This resizes the image to fixed height if(!$picture->resizeHeight($height)){ trigger_error('Cannot resize image with height-only algorithm',E_USER_ERROR); } break; } // IMAGE FILTERS // If filtering is also requested and system does not have it turned off if((!isset($config['dynamic-image-filters']) || $config['dynamic-image-filters']==true)){ // As long as there are set filters if(!empty($filters)){ // Each filter is applied, one by one foreach($filters as $filter){ if(!$picture->applyFilter($filter['type'],$filter['alpha'],$filter['settings'])){ trigger_error('Cannot apply filter '.$filter['type'],E_USER_ERROR); } } } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // STORING THE RESULTING IMAGE // Resulting image is saved to cache if(!$picture->output($cacheDirectory.$cacheFilename,$quality,$format)){ trigger_error('Cannot output image file',E_USER_ERROR); } } else { // Without GD library the file is simply stored instead if(!file_put_contents($cacheDirectory.$cacheFilename,file_get_contents($resource))){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } } else { // Without needing to process the image the file contents are stimply stored in cache if(!file_put_contents($cacheDirectory.$cacheFilename,file_get_contents($resource))){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } else { // To notify logger that cache was used $cacheUsed=true; } // File URL is set to cache address $resource=$cacheDirectory.$cacheFilename; } // HEADERS // Proper content-type is set based on file extension if(isset($resourceExtension)){ switch($resourceExtension){ case 'jpg': header('Content-Type: image/jpeg;'); break; case 'jpeg': header('Content-Type: image/jpeg;'); break; case 'png': header('Content-Type: image/png;'); break; } } // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: public,max-age=0'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Robots header if(isset($config['image-robots'])){ // If image-specific robots setting is defined header('Robots-Tag: '.$config['image-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // Getting current output length $contentLength=filesize($resource); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file to user agent readfile($resource); // Cache resource is deleted if cache was requested to be off if($noCache){ unlink($resource); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>$cacheUsed,'content-length'=>$contentLength,'category'=>'image')); // Writing log entry $logger->writeLog(); } ?>
  1 +<?php /** * Wave Framework <http://www.waveframework.com> * Image Handler * * Image Handler is used by Index Gateway to return all image files to the user agent HTTP * requests. Handler adds proper cache headers as well as supports on-demand image-editing, * where it is possible to load an image file with specific resize algorithms and even image * filtering. It also checks for files from overrides folder, which can be returned instead * of the actual file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_image.htm * @since 1.5.0 * @version 3.4.3 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // Web root is the subfolder on public site $systemRoot=str_replace('index.php','',$_SERVER['SCRIPT_FILENAME']); // Dynamic resource loading can be turned off in configuration if(!isset($config['dynamic-image-loading']) || $config['dynamic-image-loading']==true){ // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); } else { $parameters=array(); $parameters[0]=$resourceFile; } // True filename is the last string in the string separated by & character $resourceFile=array_pop($parameters); // Current true file position $resource=$resourceFolder.$resourceFile; // Files from /resources/ folder can be overwritten if file with the same name is placed to /overrides/resources/ if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources/',$resourceRequest)){ //Checking if file of the same name exists in overrides folder $overrideFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); if(file_exists($overrideFolder.$resourceFile)){ // System will use an override as a resource, since it exists $resource=$overrideFolder.$resourceFile; } } // RESOURCE EXISTENCE CHECK // If file does not exist then 404 is thrown if(!file_exists($resource) && (!isset($config['404-image-placeholder']) || $config['404-image-placeholder']==true) && (file_exists(__ROOT__.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg') || file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'))){ // It's possible to overwrite the default image used for 404 placeholder if(file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg')){ $resource=__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'; } else { $resource=__ROOT__.'resources'.DIRECTORY_SEPARATOR.'placeholder.jpg'; } // 404 header header('HTTP/1.1 404 Not Found'); // Notifying Logger of 404 response code if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404)); } // This variable is used by cache to calculate cache filename, but since system is returning a placeholder instead, it is overwritten // This allows system to keep all 404 placeholder image cache in the same cache file $tmp=explode('/',str_replace($resourceFile,'placeholder.jpg',$resourceRequest)); $resourceRequest=array_pop($tmp); } elseif(!file_exists($resource)){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // CACHE SETTINGS // Default cache timeout of one month, unless timeout is set if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // Last-modified time of the original resource $lastModified=filemtime($resource); // This flag stores whether cache was used $cacheUsed=false; // No cache flag if(in_array('nocache',$parameters)){ $noCache=true; } else { $noCache=false; } // GENERATION BASED ON PARAMETERS // If file seems to carry additional configuration options, then it is generated or loaded from cache if(empty($parameters)){ // Pure image file request is considered 'cache used' due to it not needing any processing $cacheUsed=true; // IF NOT MODIFIED // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>304,'category'=>'image','cache-used'=>true)); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header, since it is often ignored) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Solving cache folders and directory $cacheFilename=md5($lastModified.$config['version'].$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // IF NOT MODIFIED // If cache file exists then cache modified is considered that time if(!$noCache && file_exists($cacheDirectory.$cacheFilename)){ // Getting last modified time from cache file $lastModified=filemtime($cacheDirectory.$cacheFilename); // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>304,'category'=>'image','cache-used'=>true)); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING RESOURCE // If resource cannot be found from cache, it is generated if($noCache || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['resource-cache-timeout']))){ // DEFAULT SETTINGS // Get existing image size $resolution=getimagesize($resource); // Default settings for dynamically resized image // This values will be changed based on if parameters are set $width=$resolution[0]; $height=$resolution[1]; $algorithm='fitcrop'; $red=0; $green=0; $blue=0; $top='center'; $left='center'; $quality=90; $filters=array(); $filterSettings=array(); $format=false; // FINDING SETTINGS FROM SET PARAMETERS // Looping over the data bits to find additional parameters foreach($parameters as $parameter){ switch($parameter){ case 'fitcrop': // This is a resize algorithm flag $algorithm='fitcrop'; break; case 'crop': // This is a resize algorithm flag $algorithm='crop'; break; case 'fitwithbackground': // This is a resize algorithm flag $algorithm='fitwithbackground'; break; case 'fitwithoutbackground': // This is a resize algorithm flag $algorithm='fitwithoutbackground'; break; case 'widthonly': // This is a resize algorithm flag $algorithm='widthonly'; break; case 'jpg': // This is a resize algorithm flag $format='jpg'; break; case 'png': // This is a resize algorithm flag $format='png'; break; case 'heightonly': // This is a resize algorithm flag $algorithm='heightonly'; break; default: // If any of the resize algorithm and cache flags were not hit, the parameter is matched for other conditions if(strpos($parameter,'filter(')!==false){ // Background color setting is assumed if rgb is present $settings=str_replace(array('filter(',')'),'',$parameter); $settings=explode(',',$settings); // Storing data of new filter $newFilter=array(); // First number is the filter type if($settings[0]!=''){ // Filter type can also have parameters $typeSettings=explode('@',$settings[0]); // First parameter is the filter type $newFilter['type']=$typeSettings[0]; // It is possible to 'layer' the effect by defining alpha level as the second parameter if(isset($typeSettings[1])){ $newFilter['alpha']=$typeSettings[1]; } else { // Filter effect is 100% if alpha was not defined $newFilter['alpha']=100; } } // Storing data of new filters settings $newFilter['settings']=array(); // Storing other filter variables for($i=1;isset($settings[$i]);$i++){ $newFilter['settings'][]=$settings[$i]; } // Adding filter to list of filters $filters[]=$newFilter; } elseif(strpos($parameter,'rgb(')!==false){ // Background color setting is assumed if rgb is present $colors=str_replace(array('rgb(',')'),'',$parameter); $colors=explode(',',$colors); // First number in parameter is red color amount if($colors[0]!=''){ $red=$colors[0]; } // Second number in parameter is green color amount if(isset($colors[1]) && $colors[1]!=''){ $green=$colors[1]; } // Third number in parameter is blue color amount if(isset($colors[2]) && $colors[2]!=''){ $blue=$colors[2]; } } elseif(strpos($parameter,'@')!==false){ // Quality setting is assumed if @ sign is present $quality=str_replace('@','',$parameter); } elseif(strpos($parameter,'-')!==false){ // Position setting is assumed if dash is present $positions=explode('-',$parameter); // First value is top position // This can be 'top', 'center', 'bottom' or a number in pixels if($positions[0]!=''){ $top=$positions[0]; } // Second value is left position // This can be 'left', 'center', 'right' or a number in pixels if($positions[1]!=''){ $left=$positions[1]; } } elseif(strpos($parameter,'x')!==false){ // It is assumed that the remaining parameter is for image dimensions $dimensions=explode('x',$parameter); // First number is width if($dimensions[0]!=''){ $width=$dimensions[0]; } // Second number, if defined, is height if(isset($dimensions[1]) && $dimensions[1]!=''){ $height=$dimensions[1]; } else { // If height is not defined then height is considered to be as long as width $height=$width; } // If algorithm is still undefined, it is given a default value // This is needed when size is set, but algorithm is not if(!$algorithm){ $algorithm='fitcrop'; } } elseif($parameter!='nocache'){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } break; } } // IMAGE SETTING VALIDATION // System checks for legality of the entered values // Whitelists allow to protect the server better from possible abuse and denial of service attacks $allowed=true; // If configuration file has not been set for dynamic max size, then it is defaulted to 1000x1000 maximum if(!isset($config['dynamic-max-size'])){ // Default maximum image dimension height/width $config['dynamic-max-size']=4096; } // Checking if image is within allowed parameters if($width>$config['dynamic-max-size'] || $height>$config['dynamic-max-size'] || $height==0 || $width==0){ // If image dimensions are beyond allowed values $allowed=false; } elseif(isset($config['dynamic-size-whitelist']) && $config['dynamic-size-whitelist']!='' && ($width!=$resolution[0] || $height!=$resolution[1]) && !in_array($width.'x'.$height,explode(' ',$config['dynamic-size-whitelist']))){ // For size whitelist check // If resolution has been changed and this resolution is not found in whitelist $allowed=false; } elseif(isset($config['dynamic-color-whitelist']) && $config['dynamic-color-whitelist']!='' && ($red || $green || $blue) && !in_array($red.','.$green.','.$blue,explode(' ',$config['dynamic-color-whitelist']))){ // For RGB whitelist check // If RGB values are not defaults and this setting is not found in color whitelist $allowed=false; } elseif(isset($config['dynamic-quality-whitelist']) && $config['dynamic-quality-whitelist']!='' && $quality && !in_array('@'.$quality,explode(' ',$config['dynamic-quality-whitelist']))){ // For quality whitelist check // If quality values are not defaults and this setting is not found in quality whitelist $allowed=false; } elseif(isset($config['dynamic-position-whitelist']) && $config['dynamic-position-whitelist']!='' && ($top || $left) && !in_array($top.'-'.$left,explode(' ',$config['dynamic-position-whitelist']))){ // For position whitelist check // If position values are not defaults and this setting is not found in position whitelist $allowed=false; } elseif($allowed && isset($config['dynamic-filter-whitelist']) && $config['dynamic-filter-whitelist']!='' && !empty($filters)){ // For filter whitelist check foreach($filters as $filter){ // If filters are not in filter whitelist then processing is canceled if($allowed && !in_array($filter['type'].'@'.$filter['alpha'].','.implode(',',$filter['settings']),explode(' ',$config['dynamic-filter-whitelist']))){ $allowed=false; } } } // IMAGE GENERATION BASED ON SETTINGS // If whitelist checks did not fail and image dimensions are good if($allowed){ // If algorithm, quality setting or a filter is set if($algorithm || $quality || !empty($filters)){ // If cache folder does not exist, it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // This functionality only works if GD library is loaded if(extension_loaded('gd')){ // IMAGE EDITING // Requiring WWW_Imager class that is used to do basic image manipulation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-imager.php'); // New imager object, this is a wrapper around GD or ImageMagick library $picture=new WWW_Imager(); // Current image file is loaded into Imager if(!$picture->input($resource)){ trigger_error('Cannot load image',E_USER_ERROR); } // IMAGE RESIZES // Image is filtered through resize algorithm and saved in cache directory switch($algorithm){ case 'fitcrop': // Crop algorithm fits the image into set dimensions, cutting the edges that do not fit if(!$picture->resizeFitCrop($width,$height,$left,$top)){ trigger_error('Cannot resize image with fit-crop algorithm',E_USER_ERROR); } break; case 'crop': // Crop algorithm places image in new dimensions box cutting the edges that do not fit if(!$picture->resizeCrop($width,$height,$left,$top,$red,$green,$blue)){ trigger_error('Cannot resize image with crop algorithm',E_USER_ERROR); } break; case 'fitwithbackground': // This fits image inside the box and gives it certain color background (if applicable) if(!$picture->resizeFit($width,$height,$left,$top,$red,$green,$blue)){ trigger_error('Cannot resize image with fit-with-background algorithm',E_USER_ERROR); } break; case 'fitwithoutbackground': // This simply resizes the image to fit specific dimensions if(!$picture->resizeFitNoBackground($width,$height)){ trigger_error('Cannot resize image with fit-without-background algorithm',E_USER_ERROR); } break; case 'widthonly': // This resizes the image to fixed width if(!$picture->resizeWidth($width)){ trigger_error('Cannot resize image with width-only algorithm',E_USER_ERROR); } break; case 'heightonly': // This resizes the image to fixed height if(!$picture->resizeHeight($height)){ trigger_error('Cannot resize image with height-only algorithm',E_USER_ERROR); } break; } // IMAGE FILTERS // If filtering is also requested and system does not have it turned off if((!isset($config['dynamic-image-filters']) || $config['dynamic-image-filters']==true)){ // As long as there are set filters if(!empty($filters)){ // Each filter is applied, one by one foreach($filters as $filter){ if(!$picture->applyFilter($filter['type'],$filter['alpha'],$filter['settings'])){ trigger_error('Cannot apply filter '.$filter['type'],E_USER_ERROR); } } } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // STORING THE RESULTING IMAGE // Resulting image is saved to cache if(!$picture->output($cacheDirectory.$cacheFilename,$quality,$format)){ trigger_error('Cannot output image file',E_USER_ERROR); } } else { // Without GD library the file is simply stored instead if(!file_put_contents($cacheDirectory.$cacheFilename,file_get_contents($resource))){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } } else { // Without needing to process the image the file contents are stimply stored in cache if(!file_put_contents($cacheDirectory.$cacheFilename,file_get_contents($resource))){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>404,'category'=>'image')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } else { // To notify logger that cache was used $cacheUsed=true; } // File URL is set to cache address $resource=$cacheDirectory.$cacheFilename; } // HEADERS // Proper content-type is set based on file extension if(isset($resourceExtension)){ switch($resourceExtension){ case 'jpg': header('Content-Type: image/jpeg;'); break; case 'jpeg': header('Content-Type: image/jpeg;'); break; case 'png': header('Content-Type: image/png;'); break; } } // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Robots header if(isset($config['image-robots'])){ // If image-specific robots setting is defined header('Robots-Tag: '.$config['image-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // Getting current output length $contentLength=filesize($resource); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file to user agent readfile($resource); // Cache resource is deleted if cache was requested to be off if($noCache){ unlink($resource); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>$cacheUsed,'content-length'=>$contentLength,'category'=>'image')); // Writing log entry $logger->writeLog(); } ?>
2  engine/handler.resource.php
... ... @@ -1 +1 @@
1   -<?php /** * Wave Framework <http://www.waveframework.com> * Resource Handler * * Resource Handler is used to return files that are considered web resources that are not * media. This includes things like JavaScript, CSS stylesheets, XML, HTML and other file * formats (this is based on configuration). Resource Handler uses Wave Frameworks on-demand * resource loader, which allows to combine multiple files to a single resource file or * minify contents of the scripts. It also checks for files from overrides folder, which * can be returned instead of the actual file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_resource.htm * @since 1.5.0 * @version 3.4.2 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // System returns proper content type based on file extension if(isset($resourceExtension)){ switch($resourceExtension){ case 'js': header('Content-Type: application/javascript;charset=utf-8;'); break; case 'css': header('Content-Type: text/css;charset=utf-8;'); break; case 'xml': header('Content-Type: text/xml;charset=utf-8;'); break; case 'txt': header('Content-Type: text/plain;charset=utf-8;'); break; case 'csv': header('Content-Type: text/csv;charset=utf-8;'); break; case 'html': header('Content-Type: text/html;charset=utf-8;'); break; case 'htm': header('Content-Type: text/html;charset=utf-8;'); break; case 'rss': header('Content-Type: application/rss+xml;charset=utf-8;'); break; case 'vcard': header('Content-Type: text/vcard;charset=utf-8;'); break; default: header('Content-Type: text/plain;charset=utf-8;'); break; } } // Dynamic resource loading can be turned off in configuration if(!isset($config['dynamic-resource-loading']) || $config['dynamic-resource-loading']==true){ // Comma separated filenames will mean that the result will be unified $parameters=array_unique(explode('&',$resourceFile)); } else { // If dynamic resource loading was turned off, then the entire 'first parameter' is considered to be the full string for parsing purposes $parameters=array(); $parameters[0]=$resourceFile; } // Storing last modified time here $lastModified=false; // This flag stores whether cache was used $cacheUsed=false; // If cache is not defined in configuration file then pre-set is used if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // If minification is used for CSS and JS $minify=false; // Default cache flag $noCache=false; // Checking if the file might be loaded from overrides folder $overridesFolder=false; if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\//',$_SERVER['REQUEST_URI'])){ // Solving possible overrides folder $overridesFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); } // GETTING RESOURCE CONTENTS // If file does not carry any additional parameters, then there is no need to dynamically generate it if(!isset($parameters[1])){ // FILE EXISTENCE CHECK // Testing file name $fileName=explode('.',$parameters[0]); // If file does not exist in regular nor overrides folder if(file_exists($overridesFolder.$parameters[0]) && in_array(array_pop($fileName),$config['resource-extensions'])){ // Getting the last modified time from overrides folder $lastModified=filemtime($overridesFolder.$parameters[0]); // This is the loaded resource $parameters[0]=$overridesFolder.$resourceFile; } elseif((file_exists($resourceFolder.$parameters[0]) && in_array(array_pop($fileName),$config['resource-extensions'])) || $parameters[0]=='class.www-factory.js' || $parameters[0]=='class.www-wrapper.js'){ // Wave Framework files can be loaded unified together with files from /resources/ folders if($parameters[0]=='class.www-factory.js' || $parameters[0]=='class.www-wrapper.js'){ // Wave Framework specific file is stored in engine folder $parameters[0]=__ROOT__.'engine'.DIRECTORY_SEPARATOR.$parameters[0]; // Last modified time of the file stored in overrides folder $lastModified=filemtime($parameters[0]); } else { // This is the loaded resource $parameters[0]=$resourceFolder.$parameters[0]; // Getting the last modified time from the expected resource folder $lastModified=filemtime($parameters[0]); } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','response-code'=>'404')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // NOT MODIFIED CHECK // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','cache-used'=>true,'response-code'=>'304')); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Newest last-modified file is considered for the last modified time foreach($parameters as $key=>$parameter){ // PARAMETER CONDITIONS // Cache can be turned off and minification can be turned on with parameters. // Full stop including parameters are also entirely ignored for parameter considerations if($parameter!='nocache' && $parameter!='minify'){ // MULTIPLE REQUESTED FILES // Possible version number if(is_numeric($parameter)){ // This is probably just a version number unset($parameters[$key]); } else { // Making sure that parent folders are not requested $parameters[$key]=str_replace('..','',$parameter); // Testing file name $fileName=explode('.',$parameters[$key]); // Overrides can be used if file with the same name is stored in same folder under /overrides/ folder if($overridesFolder && file_exists($overridesFolder.$parameter) && in_array(array_pop($fileName),$config['resource-extensions'])){ // File was found and the filename will be replaced by file location for later processing $parameters[$key]=$overridesFolder.$parameter; // Last modified time of the file stored in overrides folder $thisLastModified=filemtime($overridesFolder.$parameter); // Only the newest last modified time will be used for output headers if($lastModified==false || $lastModified<$thisLastModified){ $lastModified=$thisLastModified; } } elseif((file_exists($resourceFolder.$parameter) && in_array(array_pop($fileName),$config['resource-extensions'])) || $parameter=='class.www-factory.js' || $parameter=='class.www-wrapper.js'){ if($parameter=='class.www-factory.js' || $parameter=='class.www-wrapper.js'){ // Wave Framework specific file is stored in engine folder $parameters[$key]=__ROOT__.'engine'.DIRECTORY_SEPARATOR.$parameter; // Last modified time of the file stored in overrides folder $thisLastModified=filemtime($parameters[$key]); } else { // File was found and the filename will be replaced by file location for later processing $parameters[$key]=$resourceFolder.$parameter; // Last modified time of the file stored in overrides folder $thisLastModified=filemtime($parameters[$key]); } // Only the newest last modified time will be used for output headers if($lastModified==false || $lastModified<$thisLastModified){ $lastModified=$thisLastModified; } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','response-code'=>'404')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } } elseif($parameter=='minify'){ // This will use minify for CSS and JS files $minify=true; // Unsetting the parameter as it will not be used later unset($parameters[$key]); } elseif($parameter=='nocache'){ // Caching $noCache=true; // Unsetting the parameter as it will not be used later unset($parameters[$key]); } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','response-code'=>'404')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } } // COMPRESSION SETTINGS // This stores currently used compression mode $compression=''; // If output compression is turned on then the content is compressed if((isset($config['output-compression']) && $config['output-compression']!=false) && extension_loaded('Zlib')){ // Different compression options can be used switch($config['output-compression']){ case 'deflate': $compression='deflate'; break; case 'gzip': $compression='gzip'; break; } } elseif(extension_loaded('Zlib')){ // User agent accepted methods are checked when compression is not set in configuration itself if(isset($_SERVER['HTTP_ACCEPT_ENCODING'])){ if(in_array('deflate',explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']))){ // This tells proxies to store both compressed and uncompressed version header('Vary: Accept-Encoding'); $compression='deflate'; } elseif(in_array('gzip',explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']))){ // This tells proxies to store both compressed and uncompressed version header('Vary: Accept-Encoding'); $compression='gzip'; } } } // CACHE AND NOT MODIFIED SETTINGS // Solving cache folders and directory $cacheFilename=md5($lastModified.$config['version'].$_SERVER['REQUEST_URI']).(($compression!='')?'_'.$compression:'').'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // If cache file exists then cache modified is considered that time if(file_exists($cacheDirectory.$cacheFilename)){ $lastModified=filemtime($cacheDirectory.$cacheFilename); } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','cache-used'=>true,'response-code'=>'304')); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } // GENERATING RESOURCE // If resource cannot be found from cache, it is generated if($noCache || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['resource-cache-timeout']))){ // LOADING CONTENTS // Resource data is stored as a string $data=''; // All requested files are appended foreach($parameters as $parameter){ // Loading data into string. $data.=file_get_contents($parameter)."\n"; } // MINIFICATION AND COMPRESSION // Minification of data for smaller filesize and less clutter. if($minify){ // Including minification class require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-minifier.php'); // Minification is based on the type of class switch($resourceExtension){ case 'js': $data=WWW_Minifier::minifyJS($data); break; case 'css': $data=WWW_Minifier::minifyCSS($data); break; case 'xml': $data=WWW_Minifier::minifyXML($data); break; case 'htm': $data=WWW_Minifier::minifyHTML($data); break; case 'html': $data=WWW_Minifier::minifyHTML($data); break; case 'rss': $data=WWW_Minifier::minifyXML($data); break; } } // Data is compressed based on current compression settings switch($compression){ case 'deflate': $data=gzdeflate($data,9); break; case 'gzip': $data=gzencode($data,9); break; } // STORING IN CACHE // Resource cache is cached in subdirectories, if directory does not exist then it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Data is written to cache file if(!file_put_contents($cacheDirectory.$cacheFilename,$data)){ trigger_error('Cannot create resource cache',E_USER_ERROR); } // Unsetting the variable due to memory reasons unset($data); } else { // Logger is notified that cache was used $cacheUsed=true; } // HEADERS // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: public,max-age=0'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Proper compression header if($compression!=''){ header('Content-Encoding: '.$compression); } // Robots header if(isset($config['resource-robots'])){ // If resource-specific robots setting is defined header('Robots-Tag: '.$config['resource-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // Getting current output length $contentLength=filesize($cacheDirectory.$cacheFilename); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file contents to user agent readfile($cacheDirectory.$cacheFilename); // File is deleted if cache was requested to be off if($noCache){ unlink($cacheDirectory.$cacheFilename); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>$cacheUsed,'category'=>'resource','content-length'=>$contentLength)); // Writing log entry $logger->writeLog(); } ?>
  1 +<?php /** * Wave Framework <http://www.waveframework.com> * Resource Handler * * Resource Handler is used to return files that are considered web resources that are not * media. This includes things like JavaScript, CSS stylesheets, XML, HTML and other file * formats (this is based on configuration). Resource Handler uses Wave Frameworks on-demand * resource loader, which allows to combine multiple files to a single resource file or * minify contents of the scripts. It also checks for files from overrides folder, which * can be returned instead of the actual file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_resource.htm * @since 1.5.0 * @version 3.4.3 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); } // System returns proper content type based on file extension if(isset($resourceExtension)){ switch($resourceExtension){ case 'js': header('Content-Type: application/javascript;charset=utf-8;'); break; case 'css': header('Content-Type: text/css;charset=utf-8;'); break; case 'xml': header('Content-Type: text/xml;charset=utf-8;'); break; case 'txt': header('Content-Type: text/plain;charset=utf-8;'); break; case 'csv': header('Content-Type: text/csv;charset=utf-8;'); break; case 'html': header('Content-Type: text/html;charset=utf-8;'); break; case 'htm': header('Content-Type: text/html;charset=utf-8;'); break; case 'rss': header('Content-Type: application/rss+xml;charset=utf-8;'); break; case 'vcard': header('Content-Type: text/vcard;charset=utf-8;'); break; default: header('Content-Type: text/plain;charset=utf-8;'); break; } } // Dynamic resource loading can be turned off in configuration if(!isset($config['dynamic-resource-loading']) || $config['dynamic-resource-loading']==true){ // Comma separated filenames will mean that the result will be unified $parameters=array_unique(explode('&',$resourceFile)); } else { // If dynamic resource loading was turned off, then the entire 'first parameter' is considered to be the full string for parsing purposes $parameters=array(); $parameters[0]=$resourceFile; } // Storing last modified time here $lastModified=false; // This flag stores whether cache was used $cacheUsed=false; // If cache is not defined in configuration file then pre-set is used if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']); // If minification is used for CSS and JS $minify=false; // Default cache flag $noCache=false; // Checking if the file might be loaded from overrides folder $overridesFolder=false; if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\//',$_SERVER['REQUEST_URI'])){ // Solving possible overrides folder $overridesFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); } // GETTING RESOURCE CONTENTS // If file does not carry any additional parameters, then there is no need to dynamically generate it if(!isset($parameters[1])){ // FILE EXISTENCE CHECK // Testing file name $fileName=explode('.',$parameters[0]); // If file does not exist in regular nor overrides folder if(file_exists($overridesFolder.$parameters[0]) && in_array(array_pop($fileName),$config['resource-extensions'])){ // Getting the last modified time from overrides folder $lastModified=filemtime($overridesFolder.$parameters[0]); // This is the loaded resource $parameters[0]=$overridesFolder.$resourceFile; } elseif((file_exists($resourceFolder.$parameters[0]) && in_array(array_pop($fileName),$config['resource-extensions'])) || $parameters[0]=='class.www-factory.js' || $parameters[0]=='class.www-wrapper.js'){ // Wave Framework files can be loaded unified together with files from /resources/ folders if($parameters[0]=='class.www-factory.js' || $parameters[0]=='class.www-wrapper.js'){ // Wave Framework specific file is stored in engine folder $parameters[0]=__ROOT__.'engine'.DIRECTORY_SEPARATOR.$parameters[0]; // Last modified time of the file stored in overrides folder $lastModified=filemtime($parameters[0]); } else { // This is the loaded resource $parameters[0]=$resourceFolder.$parameters[0]; // Getting the last modified time from the expected resource folder $lastModified=filemtime($parameters[0]); } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','response-code'=>'404')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } // NOT MODIFIED CHECK // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','cache-used'=>true,'response-code'=>'304')); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } } else { // Newest last-modified file is considered for the last modified time foreach($parameters as $key=>$parameter){ // PARAMETER CONDITIONS // Cache can be turned off and minification can be turned on with parameters. // Full stop including parameters are also entirely ignored for parameter considerations if($parameter!='nocache' && $parameter!='minify'){ // MULTIPLE REQUESTED FILES // Possible version number if(is_numeric($parameter)){ // This is probably just a version number unset($parameters[$key]); } else { // Making sure that parent folders are not requested $parameters[$key]=str_replace('..','',$parameter); // Testing file name $fileName=explode('.',$parameters[$key]); // Overrides can be used if file with the same name is stored in same folder under /overrides/ folder if($overridesFolder && file_exists($overridesFolder.$parameter) && in_array(array_pop($fileName),$config['resource-extensions'])){ // File was found and the filename will be replaced by file location for later processing $parameters[$key]=$overridesFolder.$parameter; // Last modified time of the file stored in overrides folder $thisLastModified=filemtime($overridesFolder.$parameter); // Only the newest last modified time will be used for output headers if($lastModified==false || $lastModified<$thisLastModified){ $lastModified=$thisLastModified; } } elseif((file_exists($resourceFolder.$parameter) && in_array(array_pop($fileName),$config['resource-extensions'])) || $parameter=='class.www-factory.js' || $parameter=='class.www-wrapper.js'){ if($parameter=='class.www-factory.js' || $parameter=='class.www-wrapper.js'){ // Wave Framework specific file is stored in engine folder $parameters[$key]=__ROOT__.'engine'.DIRECTORY_SEPARATOR.$parameter; // Last modified time of the file stored in overrides folder $thisLastModified=filemtime($parameters[$key]); } else { // File was found and the filename will be replaced by file location for later processing $parameters[$key]=$resourceFolder.$parameter; // Last modified time of the file stored in overrides folder $thisLastModified=filemtime($parameters[$key]); } // Only the newest last modified time will be used for output headers if($lastModified==false || $lastModified<$thisLastModified){ $lastModified=$thisLastModified; } } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','response-code'=>'404')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } } elseif($parameter=='minify'){ // This will use minify for CSS and JS files $minify=true; // Unsetting the parameter as it will not be used later unset($parameters[$key]); } elseif($parameter=='nocache'){ // Caching $noCache=true; // Unsetting the parameter as it will not be used later unset($parameters[$key]); } else { // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','response-code'=>'404')); $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); } } } // COMPRESSION SETTINGS // This stores currently used compression mode $compression=''; // If output compression is turned on then the content is compressed if((isset($config['output-compression']) && $config['output-compression']!=false) && extension_loaded('Zlib')){ // Different compression options can be used switch($config['output-compression']){ case 'deflate': $compression='deflate'; break; case 'gzip': $compression='gzip'; break; } } elseif(extension_loaded('Zlib')){ // User agent accepted methods are checked when compression is not set in configuration itself if(isset($_SERVER['HTTP_ACCEPT_ENCODING'])){ if(in_array('deflate',explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']))){ $compression='deflate'; } elseif(in_array('gzip',explode(',',$_SERVER['HTTP_ACCEPT_ENCODING']))){ $compression='gzip'; } } } // CACHE AND NOT MODIFIED SETTINGS // Solving cache folders and directory $cacheFilename=md5($lastModified.$config['version'].$_SERVER['REQUEST_URI']).(($compression!='')?'_'.$compression:'').'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // If cache file exists then cache modified is considered that time if(file_exists($cacheDirectory.$cacheFilename)){ $lastModified=filemtime($cacheDirectory.$cacheFilename); } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // If the request timestamp is exactly the same, then we let the browser know of this if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified){ // Adding log entry if(isset($logger)){ $logger->setCustomLogData(array('category'=>'resource','cache-used'=>true,'response-code'=>'304')); $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } // GENERATING RESOURCE // If resource cannot be found from cache, it is generated if($noCache || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['resource-cache-timeout']))){ // LOADING CONTENTS // Resource data is stored as a string $data=''; // All requested files are appended foreach($parameters as $parameter){ // Loading data into string. $data.=file_get_contents($parameter)."\n"; } // MINIFICATION AND COMPRESSION // Minification of data for smaller filesize and less clutter. if($minify){ // Including minification class require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-minifier.php'); // Minification is based on the type of class switch($resourceExtension){ case 'js': $data=WWW_Minifier::minifyJS($data); break; case 'css': $data=WWW_Minifier::minifyCSS($data); break; case 'xml': $data=WWW_Minifier::minifyXML($data); break; case 'htm': $data=WWW_Minifier::minifyHTML($data); break; case 'html': $data=WWW_Minifier::minifyHTML($data); break; case 'rss': $data=WWW_Minifier::minifyXML($data); break; } } // Data is compressed based on current compression settings switch($compression){ case 'deflate': $data=gzdeflate($data,9); break; case 'gzip': $data=gzencode($data,9); break; } // STORING IN CACHE // Resource cache is cached in subdirectories, if directory does not exist then it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Data is written to cache file if(!file_put_contents($cacheDirectory.$cacheFilename,$data)){ trigger_error('Cannot create resource cache',E_USER_ERROR); } // Unsetting the variable due to memory reasons unset($data); } else { // Logger is notified that cache was used $cacheUsed=true; } // HEADERS // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // This tells proxies to store both compressed and uncompressed version header('Vary: Accept-Encoding'); // Proper compression header if($compression!=''){ header('Content-Encoding: '.$compression); } // Robots header if(isset($config['resource-robots'])){ // If resource-specific robots setting is defined header('Robots-Tag: '.$config['resource-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT // Getting current output length $contentLength=filesize($cacheDirectory.$cacheFilename); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file contents to user agent readfile($cacheDirectory.$cacheFilename); // File is deleted if cache was requested to be off if($noCache){ unlink($cacheDirectory.$cacheFilename); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>$cacheUsed,'category'=>'resource','content-length'=>$contentLength)); // Writing log entry $logger->writeLog(); } ?>
2  engine/handler.robots.php
... ... @@ -1 +1 @@
1   -<?php /** * Wave Framework <http://www.waveframework.com> * Robots Handler * * Robots Handler is used to return robots.txt files, if a request is made to such a file. This * handler either returns the existing /robots.txt file, or generates a new one that allows * all-access to robots. Robot directives for search engines and other crawlers are actually * stored on files and pages themselves, so it is not needed to specifically allow or deny * anything through robots.txt file. Robots Handler also pinpoints to sitemap.xml file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_robots.htm * @since 1.5.0 * @version 3.4.1 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // Robots.txt file is always returned in plain text format header('Content-Type: text/plain;charset=utf-8;'); // This flag stores whether cache was used $cacheUsed=false; // Default cache timeout of one month, unless timeout is set if(!isset($config['robots-cache-timeout'])){ $config['robots-cache-timeout']=14400; // Four hours } // GENERATING ROBOTS FILE // Robots file is generated only if it does not exist in root if(!file_exists(__ROOT__.'robots.txt')){ // ASSIGNING PARAMETERS FROM REQUEST // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); // Looking for cache $cacheFilename=md5('robots.txt'.$config['version'].$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // If cache file exists then cache modified is considered that time if(file_exists($cacheDirectory.$cacheFilename)){ $lastModified=filemtime($cacheDirectory.$cacheFilename); } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING NEW ROBOTS FILE OR LOADING FROM CACHE // If robots cannot be found from cache, it is generated if(in_array('nocache',$parameters) || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['robots-cache-timeout']))){ // STATE AND DATABASE // State stores a lot of settings that are taken into account during Sitemap generation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-state.php'); $state=new WWW_State($config); // Connecting to database, if configuration is set // Uncomment this if you actually need to use database connection for robots.txt file // if(isset($config['database-name']) && $config['database-name']!='' && isset($config['database-type']) && isset($config['database-host']) && isset($config['database-username']) && isset($config['database-password'])){ // require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-database.php'); // $databaseConnection=new WWW_Database($config['database-type'],$config['database-host'],$config['database-name'],$config['database-username'],$config['database-password'],((isset($config['database-errors']))?$config['database-errors']:false),((isset($config['database-persistent']))?$config['database-persistent']:false)); // } // GENERATING ROBOTS STRING // Robots string is stored here $robots=''; $robots.='User-agent: *'."\n"; $robots.='Disallow: '."\n"; $robots.='Sitemap: '.((isset($config['https-limiter']) && $config['https-limiter']==true)?'https://':'http://').$_SERVER['HTTP_HOST'].$state->data['web-root'].'sitemap.xml'; // WRITING TO CACHE // Resource cache is cached in subdirectories, if directory does not exist then it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Data is written to cache file if(!file_put_contents($cacheDirectory.$cacheFilename,$robots)){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } else { // Setting the flag for logger $cacheUsed=true; } // HEADERS // If cache is used, then proper headers will be sent if(in_array('nocache',$parameters)){ // user agent is told to cache these results for set duration header('Cache-Control: public,max-age=0'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // user agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['robots-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['robots-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Content length of the file $contentLength=filesize($cacheDirectory.$cacheFilename); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // OUTPUT // Returning the file to user agent readfile($cacheDirectory.$cacheFilename); // File is deleted if cache was requested to be off if(in_array('nocache',$parameters)){ unlink($cacheDirectory.$cacheFilename); } } else { // RETURNING EXISTING ROBOTS FILE // This is technically considered as using cache $cacheUsed=true; // Cache headers header('Cache-Control: public,max-age='.$config['robots-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['robots-cache-timeout'])).' GMT'); // Last modified header header('Last-Modified: '.gmdate('D, d M Y H:i:s',filemtime(__ROOT__.'robots.txt')).' GMT'); // Content length of the file $contentLength=filesize(__ROOT__.'robots.txt'); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Since robots.txt did exist in root, it is simply returned readfile(__ROOT__.'robots.txt'); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'robots','cache-used'=>$cacheUsed,'content-length-used'=>$contentLength,'database-query-count'=>((isset($databaseConnection))?$databaseConnection->queryCounter:0))); // Writing log entry $logger->writeLog(); } ?>
  1 +<?php /** * Wave Framework <http://www.waveframework.com> * Robots Handler * * Robots Handler is used to return robots.txt files, if a request is made to such a file. This * handler either returns the existing /robots.txt file, or generates a new one that allows * all-access to robots. Robot directives for search engines and other crawlers are actually * stored on files and pages themselves, so it is not needed to specifically allow or deny * anything through robots.txt file. Robots Handler also pinpoints to sitemap.xml file. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_robots.htm * @since 1.5.0 * @version 3.4.3 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // Robots.txt file is always returned in plain text format header('Content-Type: text/plain;charset=utf-8;'); // This flag stores whether cache was used $cacheUsed=false; // Default cache timeout of one month, unless timeout is set if(!isset($config['robots-cache-timeout'])){ $config['robots-cache-timeout']=14400; // Four hours } // GENERATING ROBOTS FILE // Robots file is generated only if it does not exist in root if(!file_exists(__ROOT__.'robots.txt')){ // ASSIGNING PARAMETERS FROM REQUEST // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); // Looking for cache $cacheFilename=md5('robots.txt'.$config['version'].$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // If cache file exists then cache modified is considered that time if(file_exists($cacheDirectory.$cacheFilename)){ $lastModified=filemtime($cacheDirectory.$cacheFilename); } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING NEW ROBOTS FILE OR LOADING FROM CACHE // If robots cannot be found from cache, it is generated if(in_array('nocache',$parameters) || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['robots-cache-timeout']))){ // STATE AND DATABASE // State stores a lot of settings that are taken into account during Sitemap generation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-state.php'); $state=new WWW_State($config); // Connecting to database, if configuration is set // Uncomment this if you actually need to use database connection for robots.txt file // if(isset($config['database-name']) && $config['database-name']!='' && isset($config['database-type']) && isset($config['database-host']) && isset($config['database-username']) && isset($config['database-password'])){ // require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-database.php'); // $databaseConnection=new WWW_Database($config['database-type'],$config['database-host'],$config['database-name'],$config['database-username'],$config['database-password'],((isset($config['database-errors']))?$config['database-errors']:false),((isset($config['database-persistent']))?$config['database-persistent']:false)); // } // GENERATING ROBOTS STRING // Robots string is stored here $robots=''; $robots.='User-agent: *'."\n"; $robots.='Disallow: '."\n"; $robots.='Sitemap: '.((isset($config['https-limiter']) && $config['https-limiter']==true)?'https://':'http://').$_SERVER['HTTP_HOST'].$state->data['web-root'].'sitemap.xml'; // WRITING TO CACHE // Resource cache is cached in subdirectories, if directory does not exist then it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Data is written to cache file if(!file_put_contents($cacheDirectory.$cacheFilename,$robots)){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } else { // Setting the flag for logger $cacheUsed=true; } // HEADERS // If cache is used, then proper headers will be sent if(in_array('nocache',$parameters)){ // user agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // user agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['robots-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['robots-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Content length of the file $contentLength=filesize($cacheDirectory.$cacheFilename); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // OUTPUT // Returning the file to user agent readfile($cacheDirectory.$cacheFilename); // File is deleted if cache was requested to be off if(in_array('nocache',$parameters)){ unlink($cacheDirectory.$cacheFilename); } } else { // RETURNING EXISTING ROBOTS FILE // This is technically considered as using cache $cacheUsed=true; // Cache headers header('Cache-Control: public,max-age='.$config['robots-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['robots-cache-timeout'])).' GMT'); // Last modified header header('Last-Modified: '.gmdate('D, d M Y H:i:s',filemtime(__ROOT__.'robots.txt')).' GMT'); // Content length of the file $contentLength=filesize(__ROOT__.'robots.txt'); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Since robots.txt did exist in root, it is simply returned readfile(__ROOT__.'robots.txt'); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'robots','cache-used'=>$cacheUsed,'content-length-used'=>$contentLength,'database-query-count'=>((isset($databaseConnection))?$databaseConnection->queryCounter:0))); // Writing log entry $logger->writeLog(); } ?>
2  engine/handler.sitemap.php
... ... @@ -1 +1 @@
1   -<?php /** * Wave Framework <http://www.waveframework.com> * Sitemap Handler * * Sitemap Handler is used to return sitemap.xml files, if a request is made to such a file. This * handler either returns the existing /sitemap.xml file, or generates a new one based on sitemap * files in /resources/ folder and the languages defined in configuration. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_sitemap.htm * @since 1.5.0 * @version 3.4.1 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // Sitemap is always returned in XML format header('Content-Type: text/xml;charset=utf-8;'); // This flag stores whether cache was used $cacheUsed=false; // Default cache timeout of one month, unless timeout is set if(!isset($config['sitemap-cache-timeout'])){ $config['sitemap-cache-timeout']=14400; // Four hours } // GENERATING SITEMAP // Sitemap is generated only if it does not exist in root if(!file_exists(__ROOT__.'sitemap.xml')){ // ASSIGNING PARAMETERS FROM REQUEST // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); // Looking for cache $cacheFilename=md5('sitemap.xml'.$config['version'].$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // If cache file exists then cache modified is considered that time if(file_exists($cacheDirectory.$cacheFilename)){ $lastModified=filemtime($cacheDirectory.$cacheFilename); } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING NEW SITEMAP OR LOADING FROM CACHE // If sitemap cannot be found from cache, it is generated if(in_array('nocache',$parameters) || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['sitemap-cache-timeout']))){ // STATE AND DATABASE // State stores a lot of settings that are taken into account during Sitemap generation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-state.php'); $state=new WWW_State($config); // Connecting to database, if configuration is set // Uncomment this if you actually need to use database connection for sitemap.txt file // if(isset($config['database-name']) && $config['database-name']!='' && isset($config['database-type']) && isset($config['database-host']) && isset($config['database-username']) && isset($config['database-password'])){ // require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-database.php'); // $databaseConnection=new WWW_Database($config['database-type'],$config['database-host'],$config['database-name'],$config['database-username'],$config['database-password'],((isset($config['database-errors']))?$config['database-errors']:false),((isset($config['database-persistent']))?$config['database-persistent']:false)); // } // GENERATING SITEMAP STRING // Sitemap XML string is stored here $siteMapXML=''; // Sitemap can only be generated if system actually uses languages and sitemap files if(!empty($state->data['languages'])){ // XML header $siteMapXML.='<?xml version="1.0" encoding="utf-8"?>'; // Defining sitemap schema $siteMapXML.='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">'; // Root depends on whether website is forced to work on HTTPS or not $root=((isset($config['https-limiter']) && $config['https-limiter']==true)?'https://':'http://').$_SERVER['HTTP_HOST'].$state->data['web-root']; // Every language defined in state is generated for sitemap foreach($state->data['languages'] as $language){ // Checking for existence of URL Map file if(file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini')){ // Overrides can be used if they are stored in /overrides/resources/ subfolder $siteMap=parse_ini_file(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini',true,INI_SCANNER_RAW); } elseif(file_exists(__ROOT__.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini')){ // If there was no override, the URL Map is loaded from /resources/ $siteMap=parse_ini_file(__ROOT__.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini',true,INI_SCANNER_RAW); } // If first language does not require language node in URL's then it is ignored if($language==$state->data['language'] && $state->data['enforce-first-language-url']==false){ $langRoot=$root; } else { $langRoot=$root.$language.'/'; } // As long as sitemap file is not empty, the nodes are added to output if(!empty($siteMap)){ // Read more about $node values from Sitemap files foreach($siteMap as $url=>$node){ // Hidden URL's and URL's with redirects are not placed in sitemap if((!isset($node['permissions']) || $node['permissions']=='*' || $node['permissions']=='') && (!isset($node['hidden']) || $node['hidden']!=true) && !isset($node['temporary-redirect']) && !isset($node['permanent-redirect'])){ // Building single URL node $siteMapXML.='<url>'; // Location is the full URL of the page if(strpos($url,':')!==false){ $url=explode(':',$url); $siteMapXML.='<loc>'.$langRoot.$url[0].'</loc>'; } else { $siteMapXML.='<loc>'.$langRoot.$url.'/</loc>'; } // Priority is a value from 0.0 to 1.0 (default is 0.5). This tells how important this URL is in relation to other URL's if(isset($node['priority'])){ $siteMapXML.='<priority>'.$node['priority'].'</priority>'; } // This can be 'always','hourly','daily','weekly','monthly','yearly','never' and tell robots how often this URL changes if(isset($node['change-frequency'])){ $siteMapXML.='<changefreq>'.$node['change-frequency'].'</changefreq>'; } // It is possible to state in Sitemap when the URL was last modified if(isset($node['last-modified'])){ // This should be in YYYY-MM-DD format $siteMapXML.='<lastmod>'.$node['last-modified'].'</lastmod>'; } $siteMapXML.='</url>'; } } } } // Closing the sitemap tag $siteMapXML.='</urlset>'; } // WRITING TO CACHE // Resource cache is cached in subdirectories, if directory does not exist then it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Data is written to cache file if(!file_put_contents($cacheDirectory.$cacheFilename,$siteMapXML)){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } else { // Notifying logger that cache was used $cacheUsed=true; } // HEADERS // If cache is used, then proper headers will be sent if(in_array('nocache',$parameters)){ // user agent is told to cache these results for set duration header('Cache-Control: public,max-age=0'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // user agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['sitemap-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['sitemap-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Content length of the file $contentLength=filesize($cacheDirectory.$cacheFilename); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // OUTPUT // Returning the file to user agent readfile($cacheDirectory.$cacheFilename); // File is deleted if cache was requested to be off if(in_array('nocache',$parameters)){ unlink($cacheDirectory.$cacheFilename); } } else { // RETURNING EXISTING SITEMAP // This is technically considered as using cache $cacheUsed=true; // Cache headers header('Cache-Control: public,max-age='.$config['sitemap-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['sitemap-cache-timeout'])).' GMT'); // Last modified header header('Last-Modified: '.gmdate('D, d M Y H:i:s',filemtime(__ROOT__.'sitemap.xml')).' GMT'); // Content length of the file $contentLength=filesize(__ROOT__.'sitemap.xml'); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Since sitemap.xml did exist in root, it is simply returned readfile(__ROOT__.'sitemap.xml'); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'sitemap','cache-used'=>$cacheUsed,'content-length-used'=>$contentLength,'database-query-count'=>((isset($databaseConnection))?$databaseConnection->queryCounter:0))); // Writing log entry $logger->writeLog('sitemap'); } ?>
  1 +<?php /** * Wave Framework <http://www.waveframework.com> * Sitemap Handler * * Sitemap Handler is used to return sitemap.xml files, if a request is made to such a file. This * handler either returns the existing /sitemap.xml file, or generates a new one based on sitemap * files in /resources/ folder and the languages defined in configuration. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/handler_sitemap.htm * @since 1.5.0 * @version 3.4.3 */ // INITIALIZATION // Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // Sitemap is always returned in XML format header('Content-Type: text/xml;charset=utf-8;'); // This flag stores whether cache was used $cacheUsed=false; // Default cache timeout of one month, unless timeout is set if(!isset($config['sitemap-cache-timeout'])){ $config['sitemap-cache-timeout']=14400; // Four hours } // GENERATING SITEMAP // Sitemap is generated only if it does not exist in root if(!file_exists(__ROOT__.'sitemap.xml')){ // ASSIGNING PARAMETERS FROM REQUEST // If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile)); // Looking for cache $cacheFilename=md5('sitemap.xml'.$config['version'].$resourceRequest).'.tmp'; $cacheDirectory=__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.substr($cacheFilename,0,2).DIRECTORY_SEPARATOR; // If cache file exists then cache modified is considered that time if(file_exists($cacheDirectory.$cacheFilename)){ $lastModified=filemtime($cacheDirectory.$cacheFilename); } else { // Otherwise it is server request time $lastModified=$_SERVER['REQUEST_TIME']; } // GENERATING NEW SITEMAP OR LOADING FROM CACHE // If sitemap cannot be found from cache, it is generated if(in_array('nocache',$parameters) || ($lastModified==$_SERVER['REQUEST_TIME'] || $lastModified<($_SERVER['REQUEST_TIME']-$config['sitemap-cache-timeout']))){ // STATE AND DATABASE // State stores a lot of settings that are taken into account during Sitemap generation require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-state.php'); $state=new WWW_State($config); // Connecting to database, if configuration is set // Uncomment this if you actually need to use database connection for sitemap.txt file // if(isset($config['database-name']) && $config['database-name']!='' && isset($config['database-type']) && isset($config['database-host']) && isset($config['database-username']) && isset($config['database-password'])){ // require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-database.php'); // $databaseConnection=new WWW_Database($config['database-type'],$config['database-host'],$config['database-name'],$config['database-username'],$config['database-password'],((isset($config['database-errors']))?$config['database-errors']:false),((isset($config['database-persistent']))?$config['database-persistent']:false)); // } // GENERATING SITEMAP STRING // Sitemap XML string is stored here $siteMapXML=''; // Sitemap can only be generated if system actually uses languages and sitemap files if(!empty($state->data['languages'])){ // XML header $siteMapXML.='<?xml version="1.0" encoding="utf-8"?>'; // Defining sitemap schema $siteMapXML.='<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">'; // Root depends on whether website is forced to work on HTTPS or not $root=((isset($config['https-limiter']) && $config['https-limiter']==true)?'https://':'http://').$_SERVER['HTTP_HOST'].$state->data['web-root']; // Every language defined in state is generated for sitemap foreach($state->data['languages'] as $language){ // Checking for existence of URL Map file if(file_exists(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini')){ // Overrides can be used if they are stored in /overrides/resources/ subfolder $siteMap=parse_ini_file(__ROOT__.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini',true,INI_SCANNER_RAW); } elseif(file_exists(__ROOT__.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini')){ // If there was no override, the URL Map is loaded from /resources/ $siteMap=parse_ini_file(__ROOT__.'resources'.DIRECTORY_SEPARATOR.$language.'.sitemap.ini',true,INI_SCANNER_RAW); } // If first language does not require language node in URL's then it is ignored if($language==$state->data['language'] && $state->data['enforce-first-language-url']==false){ $langRoot=$root; } else { $langRoot=$root.$language.'/'; } // As long as sitemap file is not empty, the nodes are added to output if(!empty($siteMap)){ // Read more about $node values from Sitemap files foreach($siteMap as $url=>$node){ // Hidden URL's and URL's with redirects are not placed in sitemap if((!isset($node['permissions']) || $node['permissions']=='*' || $node['permissions']=='') && (!isset($node['hidden']) || $node['hidden']!=true) && !isset($node['temporary-redirect']) && !isset($node['permanent-redirect'])){ // Building single URL node $siteMapXML.='<url>'; // Location is the full URL of the page if(strpos($url,':')!==false){ $url=explode(':',$url); $siteMapXML.='<loc>'.$langRoot.$url[0].'</loc>'; } else { $siteMapXML.='<loc>'.$langRoot.$url.'/</loc>'; } // Priority is a value from 0.0 to 1.0 (default is 0.5). This tells how important this URL is in relation to other URL's if(isset($node['priority'])){ $siteMapXML.='<priority>'.$node['priority'].'</priority>'; } // This can be 'always','hourly','daily','weekly','monthly','yearly','never' and tell robots how often this URL changes if(isset($node['change-frequency'])){ $siteMapXML.='<changefreq>'.$node['change-frequency'].'</changefreq>'; } // It is possible to state in Sitemap when the URL was last modified if(isset($node['last-modified'])){ // This should be in YYYY-MM-DD format $siteMapXML.='<lastmod>'.$node['last-modified'].'</lastmod>'; } $siteMapXML.='</url>'; } } } } // Closing the sitemap tag $siteMapXML.='</urlset>'; } // WRITING TO CACHE // Resource cache is cached in subdirectories, if directory does not exist then it is created if(!is_dir($cacheDirectory)){ if(!mkdir($cacheDirectory,0755)){ trigger_error('Cannot create cache folder',E_USER_ERROR); } } // Data is written to cache file if(!file_put_contents($cacheDirectory.$cacheFilename,$siteMapXML)){ trigger_error('Cannot create resource cache',E_USER_ERROR); } } else { // Notifying logger that cache was used $cacheUsed=true; } // HEADERS // If cache is used, then proper headers will be sent if(in_array('nocache',$parameters)){ // user agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // user agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['sitemap-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['sitemap-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } // Content length of the file $contentLength=filesize($cacheDirectory.$cacheFilename); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // OUTPUT // Returning the file to user agent readfile($cacheDirectory.$cacheFilename); // File is deleted if cache was requested to be off if(in_array('nocache',$parameters)){ unlink($cacheDirectory.$cacheFilename); } } else { // RETURNING EXISTING SITEMAP // This is technically considered as using cache $cacheUsed=true; // Cache headers header('Cache-Control: public,max-age='.$config['sitemap-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['sitemap-cache-timeout'])).' GMT'); // Last modified header header('Last-Modified: '.gmdate('D, d M Y H:i:s',filemtime(__ROOT__.'sitemap.xml')).' GMT'); // Content length of the file $contentLength=filesize(__ROOT__.'sitemap.xml'); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Since sitemap.xml did exist in root, it is simply returned readfile(__ROOT__.'sitemap.xml'); } // WRITING TO LOG // If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'sitemap','cache-used'=>$cacheUsed,'content-length-used'=>$contentLength,'database-query-count'=>((isset($databaseConnection))?$databaseConnection->queryCounter:0))); // Writing log entry $logger->writeLog('sitemap'); } ?>
2  index.php
@@ -16,7 +16,7 @@
16 16 * @license GNU Lesser General Public License Version 3
17 17 * @tutorial /doc/pages/gateway.htm
18 18 * @since 1.0.0
19   - * @version 3.4.1
  19 + * @version 3.4.2
20 20 */
21 21
22 22 // SOLVING THE HTTP REQUEST

0 comments on commit c8ab4d1

Please sign in to comment.
Something went wrong with that request. Please try again.