Merge branch 'pu/2013.03/modelconfig-hr'
[tine20] / tine20 / Tinebase / Frontend / Http.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Server
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * HTTP interface to Tine
14  *
15  * @package     Tinebase
16  * @subpackage  Server
17  */
18 class Tinebase_Frontend_Http extends Tinebase_Frontend_Http_Abstract
19 {
20     /**
21      * get json-api service map
22      * 
23      * @return string
24      */
25     public static function getServiceMap()
26     {
27         $smd = Tinebase_Server_Json::getServiceMap();
28         
29         $smdArray = $smd->toArray();
30         unset($smdArray['methods']);
31         
32         if (! isset($_REQUEST['method']) || $_REQUEST['method'] != 'Tinebase.getServiceMap') {
33             return $smdArray;
34         }
35         
36         header('Content-type: application/json');
37         echo '_smd = ' . json_encode($smdArray);
38         die();
39     }
40     
41     /**
42      * return xrds file
43      * used to autodiscover openId servers
44      * 
45      * @return void
46      */
47     public function getXRDS() 
48     {
49         // selfUrl == http://servername/pathtotine20/users/loginname
50         $url = dirname(dirname(Zend_OpenId::selfUrl())) . '/index.php?method=Tinebase.openId';
51         
52         header('Content-type: application/xrds+xml');
53         
54         echo '
55             <?xml version="1.0"?>
56             <xrds:XRDS xmlns="xri://$xrd*($v*2.0)" xmlns:xrds="xri://$xrds">
57               <XRD>
58                 <Service priority="0">
59                   <Type>http://specs.openid.net/auth/2.0/signon</Type>
60                   <URI>' . $url . '</URI>
61                 </Service>
62                 <Service priority="1">
63                   <Type>http://openid.net/signon/1.1</Type>
64                   <URI>' . $url . '</URI>
65                 </Service>
66                 <Service priority="2">
67                   <Type>http://openid.net/signon/1.0</Type>
68                   <URI>' . $url . '</URI>
69                 </Service>
70               </XRD>
71             </xrds:XRDS>';
72     }
73     
74     /**
75      * display user info page
76      * 
77      * in the future we can display public informations about the user here too
78      * currently it is only used as entry point for openId
79      * 
80      * @param string $username the username
81      * @return void
82      */
83     public function userInfoPage($username)
84     {
85         // selfUrl == http://servername/pathtotine20/users/loginname
86         $openIdUrl = dirname(dirname(Zend_OpenId::selfUrl())) . '/index.php?method=Tinebase.openId';
87         
88         $view = new Zend_View();
89         $view->setScriptPath('Tinebase/views');
90         
91         $view->openIdUrl = $openIdUrl;
92         $view->username = $username;
93
94         header('Content-Type: text/html; charset=utf-8');
95         echo $view->render('userInfoPage.php');
96     }
97     
98     /**
99      * handle all kinds of openId requests
100      * 
101      * @return void
102      */
103     public function openId()
104     {
105         $server = new Tinebase_OpenId_Provider(
106             null,
107             null,
108             new Tinebase_OpenId_Provider_User_Tine20(),
109             new Tinebase_OpenId_Provider_Storage()
110         );
111         $server->setOpEndpoint(dirname(Zend_OpenId::selfUrl()) . '/index.php?method=Tinebase.openId');
112         
113         // handle openId login form
114         if (isset($_POST['openid_action']) && $_POST['openid_action'] === 'login') {
115             $server->login($_POST['openid_identifier'], $_POST['password'], $_POST['username']);
116             unset($_GET['openid_action']);
117             Zend_OpenId::redirect(dirname(Zend_OpenId::selfUrl()) . '/index.php', $_GET);
118
119         // display openId login form
120         } else if (isset($_GET['openid_action']) && $_GET['openid_action'] === 'login') {
121             $view = new Zend_View();
122             $view->setScriptPath('Tinebase/views');
123             
124             $view->openIdIdentity = $_GET['openid_identity'];
125             $view->loginName = $_GET['openid_identity'];
126     
127             header('Content-Type: text/html; charset=utf-8');
128             echo $view->render('openidLogin.php');
129
130         // handle openId trust form
131         } else if (isset($_POST['openid_action']) && $_POST['openid_action'] === 'trust') {
132             if (isset($_POST['allow'])) {
133                 if (isset($_POST['forever'])) {
134                     $server->allowSite($server->getSiteRoot($_GET));
135                 }
136                 $server->respondToConsumer($_GET);
137             } else if (isset($_POST['deny'])) {
138                 if (isset($_POST['forever'])) {
139                     $server->denySite($server->getSiteRoot($_GET));
140                 }
141                 Zend_OpenId::redirect($_GET['openid_return_to'],
142                                       array('openid.mode'=>'cancel'));
143             }
144
145         // display openId trust form
146         } else if (isset($_GET['openid_action']) && $_GET['openid_action'] === 'trust') {
147             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " display openId trust screen");
148             $view = new Zend_View();
149             $view->setScriptPath('Tinebase/views');
150
151             $view->openIdConsumer = $server->getSiteRoot($_GET);
152             $view->openIdIdentity = $server->getLoggedInUser();
153     
154             header('Content-Type: text/html; charset=utf-8');
155             echo $view->render('openidTrust.php');
156
157         // handle all other openId requests
158         } else {
159             $result = $server->handle();
160
161             if (is_string($result)) {
162                 echo $result;
163             } elseif ($result !== true) {
164                 header('HTTP/1.0 403 Forbidden');
165                 return;
166             }
167         }        
168         
169     }
170     
171     /**
172      * checks if a user is logged in. If not we redirect to login
173      */
174     protected function checkAuth()
175     {
176         try {
177             Tinebase_Core::getUser();
178         } catch (Exception $e) {
179             header('HTTP/1.0 403 Forbidden');
180             exit;
181         }
182     }
183     
184     /**
185      * renders the login dialog
186      *
187      * @todo perhaps we could add a config option to display the update dialog if it is set
188      */
189     public function login()
190     {
191         // redirect to REDIRECTURL if set
192         $redirectUrl = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, '');
193
194         if ($redirectUrl !== '' && Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTALWAYS, FALSE)) {
195             header('Location: ' . $redirectUrl);
196             return;
197         }
198         
199         // check if setup/update required
200         $setupController = Setup_Controller::getInstance();
201         $applications = Tinebase_Application::getInstance()->getApplications();
202         foreach ($applications as $application) {
203             if ($application->status == 'enabled' && $setupController->updateNeeded($application)) {
204                 $this->setupRequired();
205             }
206         }
207         
208         $this->_renderMainScreen();
209         
210         /**
211          * old code used to display user registration
212          * @todo must be reworked
213          * 
214         
215         $view = new Zend_View();
216         $view->setScriptPath('Tinebase/views');
217
218         header('Content-Type: text/html; charset=utf-8');
219         
220         // check if registration is active
221         if(isset(Tinebase_Core::getConfig()->login)) {
222             $registrationConfig = Tinebase_Core::getConfig()->registration;
223             $view->userRegistration = (isset($registrationConfig->active)) ? $registrationConfig->active : '';
224         } else {
225             $view->userRegistration = 0;
226         }        
227         
228         echo $view->render('jsclient.php');
229         */
230     }
231     
232     /**
233      * renders the main screen
234      * 
235      * @return void
236      */
237     protected function _renderMainScreen()
238     {
239         $view = new Zend_View();
240         $baseDir = dirname(dirname(dirname(__FILE__)));
241         $view->setScriptPath("$baseDir/Tinebase/views");
242         
243         $requiredApplications = array('Tinebase', 'Admin', 'Addressbook');
244         $enabledApplications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
245         $orderedApplications = array_merge($requiredApplications, array_diff($enabledApplications, $requiredApplications));
246         
247         require_once 'jsb2tk/jsb2tk.php';
248         $view->jsb2tk = new jsb2tk(array(
249             'deploymode'    => jsb2tk::DEPLOYMODE_STATIC,
250             'includemode'   => jsb2tk::INCLUDEMODE_INDIVIDUAL,
251             'appendctime'   => TRUE,
252             'htmlindention' => "    ",
253         ));
254         
255         
256         foreach($orderedApplications as $appName) {
257             $view->jsb2tk->register("$baseDir/$appName/$appName.jsb2", $appName);
258         }
259         
260         $view->registryData = array();
261         
262         $this->_setMainscreenHeaders();
263         
264         echo $view->render('jsclient.php');
265     }
266     
267     /**
268      * set headers for mainscreen
269      * 
270      * @todo think about CSP: is only supported by FF atm, which options/exceptions should we choose?
271      * @todo allow to configure security headers?
272      * @todo add violation report for CSP? @see https://developer.mozilla.org/en/Security/CSP/Using_CSP_violation_reports
273      */
274     protected function _setMainscreenHeaders()
275     {
276         if (headers_sent()) {
277             return;
278         }
279         
280         header('Content-Type: text/html; charset=utf-8');
281         
282         // set x-frame header against clickjacking
283         // @see https://developer.mozilla.org/en/the_x-frame-options_response_header
284         header('X-Frame-Options: SAMEORIGIN');
285         
286         // set X-Content-Security-Policy header against clickjacking and XSS
287         // @see https://developer.mozilla.org/en/Security/CSP/CSP_policy_directives
288         //header("X-Content-Security-Policy: allow 'self' https://*.officespot20.com;");
289         
290         // cache mainscreen for 10 minutes
291         $maxAge = 600;
292         header('Cache-Control: private, max-age=' . $maxAge);
293         header("Expires: " . gmdate('D, d M Y H:i:s', Tinebase_DateTime::now()->addSecond($maxAge)->getTimestamp()) . " GMT");
294         
295         // overwrite Pragma header from session
296         header("Pragma: cache");
297  
298     }
299     
300     /**
301      * renders the setup/update required dialog
302      */
303     public function setupRequired()
304     {
305         $view = new Zend_View();
306         $view->setScriptPath('Tinebase/views');
307
308         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->DEBUG(__CLASS__ . '::' . __METHOD__ . ' (' . __LINE__ .') Update/Setup required!');
309
310         header('Content-Type: text/html; charset=utf-8');
311         echo $view->render('update.php');
312         exit();
313     }
314     
315     /**
316      * login from HTTP post 
317      * 
318      * renders the tine main screen if authentication is successfull
319      * otherwise redirects back to login url 
320      */
321     public function loginFromPost($username, $password)
322     {
323         Tinebase_Core::startSession();
324         
325         if (!empty($username)) {
326             
327             // try to login user
328             $success = (Tinebase_Controller::getInstance()->login($username, $password, $_SERVER['REMOTE_ADDR'], 'TineHttpPost') === TRUE);
329         } else {
330             $success = FALSE;
331         }
332         
333         if ($success === TRUE) {
334             $ccAdapter = Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter();
335             if (Tinebase_Core::isRegistered(Tinebase_Core::USERCREDENTIALCACHE)) {
336                 $ccAdapter->setCache(Tinebase_Core::get(Tinebase_Core::USERCREDENTIALCACHE));
337             } else {
338                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Something went wrong with the CredentialCache / no CC registered.');
339                 $success = FALSE;
340                 $ccAdapter->resetCache();
341             }
342         
343         }
344
345         // authentication failed
346         if ($success !== TRUE) {
347             $_SESSION = array();
348             Zend_Session::destroy();
349             
350             // redirect back to loginurl if needed
351             $defaultUrl = (array_key_exists('HTTP_REFERER', $_SERVER)) ? $_SERVER['HTTP_REFERER'] : '';
352             $redirectUrl = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, $defaultUrl);
353             if (! empty($redirectUrl)) {
354                 header('Location: ' . $redirectUrl);
355             }
356             return;
357         }
358
359         $this->_renderMainScreen();
360     }
361     
362     /**
363      * checks authentication and display Tine 2.0 main screen 
364      */
365     public function mainScreen()
366     {
367         $this->checkAuth();
368         
369         $this->_renderMainScreen();
370     }
371     
372     /**
373      * handle session exception for http requests
374      * 
375      * we force the client to delete session cookie, but we don't destroy
376      * the session on server side. This way we prevent session DOS from thrid party
377      */
378     public function sessionException()
379     {
380         Zend_Session::expireSessionCookie();
381         echo "
382             <script type='text/javascript'>
383                 window.location.href = window.location.href;
384             </script>
385         ";
386         /*
387         ob_start();
388         $html = $this->login();
389         $html = ob_get_clean();
390         
391         $script = "
392             <script type='text/javascript'>
393                 exception = {code: 401};
394                 Ext.onReady(function() {
395                     Ext.MessageBox.show({
396                         title: _('Authorisation Required'), 
397                         msg: _('Your session is not valid. You need to login again.'),
398                         buttons: Ext.Msg.OK,
399                         icon: Ext.MessageBox.WARNING
400                     });
401                 });
402             </script>";
403         
404         echo preg_replace('/<\/head.*>/', $script . '</head>', $html);
405         */
406     }
407     
408     /**
409      * generic http exception occurred
410      */
411     public function exception()
412     {
413         ob_start();
414         $this->_renderMainScreen();
415         $html = ob_get_clean();
416         
417         $script = "
418         <script type='text/javascript'>
419             exception = {code: 400};
420             Ext.onReady(function() {
421                 Ext.MessageBox.show({
422                     title: _('Abnormal End'), 
423                     msg: _('An error occurred, the program ended abnormal.'),
424                     buttons: Ext.Msg.OK,
425                     icon: Ext.MessageBox.WARNING
426                 });
427             });
428         </script>";
429         
430         echo preg_replace('/<\/head.*>/', $script . '</head>', $html);
431     }
432     
433     /**
434      * returns javascript of translations for the currently configured locale
435      * 
436      * Note: This function is only used in development mode. In debug/release mode
437      * we can include the static build files in the view. With this we avoid the 
438      * need to start a php process and stream static js files through it.
439      * 
440      * @return javascript
441      *
442      */
443     public function getJsTranslations()
444     {
445         if (! in_array(TINE20_BUILDTYPE, array('DEBUG', 'RELEASE'))) {
446             $locale = Tinebase_Core::get('locale');
447             $translations = Tinebase_Translation::getJsTranslations($locale);
448             header('Content-Type: application/javascript');
449             die($translations);
450         }
451         
452         $this->_deliverChangedFiles('lang');
453         
454         die();
455     }
456     
457     /**
458      * return javascript files if changed
459      * 
460      */
461     public function getJsFiles()
462     {
463         $this->_deliverChangedFiles('js');
464         
465         die();
466     }
467       
468     /**
469      * return hash for js files
470      * hash changes if files get changed or list of filesnames changes
471      * 
472      */
473     public function getJsCssHash()
474     {
475         // hash for js files
476         $filesToWatch = $this->_getFilesToWatch('js');
477         
478         $serverETag = hash('sha1', implode('', $filesToWatch));
479         $lastModified = $this->_getLastModified($filesToWatch);
480         
481         $hash = hash('sha1', $serverETag . $lastModified);
482         
483         // hash for css files + hash of js files
484         $filesToWatch = $this->_getFilesToWatch('css');
485         
486         $serverETag = hash('sha1', implode('', $filesToWatch));
487         $lastModified = $this->_getLastModified($filesToWatch);
488         
489         $hash = hash('sha1', $hash . $serverETag . $lastModified);
490         
491         return $hash;
492     }
493     
494     /**
495      * return css files if changed
496      * 
497      */
498     public function getCssFiles()
499     {
500         $this->_deliverChangedFiles('css');
501         
502         die();
503     }
504     
505     /**
506      * check if js or css files have changed and return all js/css as one big file or return "HTTP/1.0 304 Not Modified" if files don't have changed
507      * 
508      * @param string $_fileType
509      */
510     protected function _deliverChangedFiles($_fileType)
511     {
512         // close session to allow other requests
513         Zend_Session::writeClose(true);
514         
515         $cacheId         = null;
516         $clientETag      = null;
517         $ifModifiedSince = null;
518         $filesToWatch    = array();
519         
520         if (isset($_SERVER['If_None_Match'])) {
521             $clientETag     = trim($_SERVER['If_None_Match'], '"');
522             $ifModifiedSince = trim($_SERVER['If_Modified_Since'], '"');
523         } elseif (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
524             $clientETag     = trim($_SERVER['HTTP_IF_NONE_MATCH'], '"');
525             $ifModifiedSince = trim($_SERVER['HTTP_IF_MODIFIED_SINCE'], '"');
526         }
527         
528         $filesToWatch = $this->_getFilesToWatch($_fileType);
529         $lastModified = $this->_getLastModified($filesToWatch);
530         
531         // use last modified time also
532         $serverETag = hash('sha1', implode('', $filesToWatch) . $lastModified);
533         
534         $cache = new Zend_Cache_Frontend_File(array(
535             'master_files' => $filesToWatch
536         ));
537         $cache->setBackend(Tinebase_Core::get(Tinebase_Core::CACHE)->getBackend());
538         
539         if ($clientETag && $ifModifiedSince) {
540             $cacheId = __CLASS__ . "_". __FUNCTION__ . hash('sha1', $clientETag . $ifModifiedSince);
541         }
542         
543         // cache for 60 seconds
544         $maxAge = 60;
545         header('Cache-Control: private, max-age=' . $maxAge);
546         header("Expires: " . gmdate('D, d M Y H:i:s', Tinebase_DateTime::now()->addSecond($maxAge)->getTimestamp()) . " GMT");
547         
548         // overwrite Pragma header from session
549         header("Pragma: cache");
550         
551         // if the cache id is still valid, the files don't have changed on disk
552         if ($clientETag == $serverETag && $cache->test($cacheId)) {
553             header("Last-Modified: " . $ifModifiedSince);
554             header("HTTP/1.0 304 Not Modified");
555             header('Content-Length: 0');
556         } else {
557             // get new cacheId
558             $cacheId = __CLASS__ . "_". __FUNCTION__ . hash('sha1', $serverETag . $lastModified);
559             
560             // do we need to update the cache? maybe the client did not send an etag
561             if (!$cache->test($cacheId)) {
562                 $cache->save(TINE20_BUILDTYPE, $cacheId, array(), null);
563             }
564             
565             header("Last-Modified: " . $lastModified);
566             header('Content-Type: ' . ($_fileType == 'css' ? 'text/css' : 'application/javascript'));
567             header('Etag: "' . $serverETag . '"');
568             
569             flush();
570             
571             // send files to client
572             foreach ($filesToWatch as $file) {
573                 readfile($file);
574             }
575         }
576     }
577     
578     /**
579      * @param string $_fileType
580      * @return array
581      */
582     protected function _getFilesToWatch($_fileType)
583     {
584         $requiredApplications = array('Tinebase', 'Admin', 'Addressbook');
585         $enabledApplications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
586         $orderedApplications = array_merge($requiredApplications, array_diff($enabledApplications, $requiredApplications));
587         
588         foreach ($orderedApplications as $application) {
589             switch($_fileType) {
590                 case 'css':
591                     $filesToWatch[] = "{$application}/css/{$application}-FAT.css.inc";
592                     break;
593                 case 'js':
594                     $filesToWatch[] = "{$application}/js/{$application}-FAT"  . (TINE20_BUILDTYPE == 'DEBUG' ? '-debug' : null) . '.js.inc';
595                     break;
596                 case 'lang':
597                     $fileName = "{$application}/js/{$application}-lang-" . Tinebase_Core::getLocale() . (TINE20_BUILDTYPE == 'DEBUG' ? '-debug' : null) . '.js';
598                     $lang = Tinebase_Core::getLocale();
599                     $customPath = Tinebase_Config::getInstance()->translations;
600                     $basePath = is_readable("$customPath/$lang/$fileName") ?  "$customPath/$lang" : '.';
601                     
602                     $filesToWatch[] = "{$basePath}/{$application}/js/{$application}-lang-" . Tinebase_Core::getLocale() . (TINE20_BUILDTYPE == 'DEBUG' ? '-debug' : null) . '.js';
603                     break;
604                 default:
605                     throw new Exception('no such fileType');
606                     break;
607             }
608         }
609         
610         return $filesToWatch;
611     }
612     
613     /**
614      * return last modified timestamp formated in gmt
615      * 
616      * @param  array  $_files
617      * @return array
618      */
619     protected function _getLastModified(array $_files)
620     {
621         $timeStamp = null;
622         
623         foreach ($_files as $file) {
624             $mtime = filemtime($file);
625             if ($mtime > $timeStamp) {
626                 $timeStamp = $mtime;
627             }
628         }
629
630         return gmdate("D, d M Y H:i:s", $timeStamp) . " GMT";
631     }
632     
633     /**
634      * activate user account
635      *
636      * @param     string $id
637      * 
638      */
639     public function activateUser( $id ) 
640     {
641         // update registration table and get username / account values
642         $account = Tinebase_User_Registration::getInstance()->activateUser( $id );
643
644         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' activated account for ' . $account['accountLoginName']);
645         
646         $view = new Zend_View();
647         $view->title="Tine 2.0 User Activation";
648         $view->username = $account['accountLoginName'];
649         $view->loginUrl = $_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF'];
650
651         $view->setScriptPath('Tinebase/views');
652         
653         header('Content-Type: text/html; charset=utf-8');
654         echo $view->render('activate.php');
655     
656     }
657     
658     /**
659      * show captcha
660      *
661      * @todo    add to user registration process
662      */
663     public function showCaptcha () 
664     {
665         $captcha = Tinebase_User_Registration::getInstance()->generateCaptcha();
666         
667         //Tell the browser what kind of file is come in
668         header("Content-Type: image/jpeg");
669
670         //Output the newly created image in jpeg format
671         ImageJpeg($captcha);
672         
673     }
674
675     /**
676      * receives file uploads and stores it in the file_uploads db
677      * 
678      * @throws Tinebase_Exception_UnexpectedValue
679      * @throws Tinebase_Exception_NotFound
680      */
681     public function uploadTempFile()
682     {
683         try {
684             $this->checkAuth();
685             
686             // close session to allow other requests
687             Zend_Session::writeClose(true);
688         
689             $tempFile = Tinebase_TempFile::getInstance()->uploadTempFile();
690             
691             die(Zend_Json::encode(array(
692                'status'   => 'success',
693                'tempFile' => $tempFile->toArray(),
694             )));
695         } catch (Tinebase_Exception $exception) {
696             Tinebase_Core::getLogger()->WARN(__METHOD__ . '::' . __LINE__ . " File upload could not be done, due to the following exception: \n" . $exception);
697             
698             if (! headers_sent()) {
699                header("HTTP/1.0 500 Internal Server Error");
700             }
701             die(Zend_Json::encode(array(
702                'status'   => 'failed',
703             )));
704         }
705     }
706     
707     /**
708      * downloads an image/thumbnail at a given size
709      *
710      * @param unknown_type $application
711      * @param string $id
712      * @param string $location
713      * @param int $width
714      * @param int $height
715      * @param int $ratiomode
716      */
717     public function getImage($application, $id, $location, $width, $height, $ratiomode)
718     {
719         $this->checkAuth();
720
721         // close session to allow other requests
722         Zend_Session::writeClose(true);
723         
724         $clientETag      = null;
725         $ifModifiedSince = null;
726         
727         if (isset($_SERVER['If_None_Match'])) {
728             $clientETag     = trim($_SERVER['If_None_Match'], '"');
729             $ifModifiedSince = trim($_SERVER['If_Modified_Since'], '"');
730         } elseif (isset($_SERVER['HTTP_IF_NONE_MATCH']) && isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
731             $clientETag     = trim($_SERVER['HTTP_IF_NONE_MATCH'], '"');
732             $ifModifiedSince = trim($_SERVER['HTTP_IF_MODIFIED_SINCE'], '"');
733         }
734         
735         if ($application == 'Tinebase' && $location == 'tempFile') {
736             
737             $tempFile = Tinebase_TempFile::getInstance()->getTempFile($id);
738
739             $imgInfo = Tinebase_ImageHelper::getImageInfoFromBlob(file_get_contents($tempFile->path));
740             $image = new Tinebase_Model_Image($imgInfo + array(
741                 'application' => $application,
742                 'id'          => $id,
743                 'location'    => $location
744             ));
745         } else {
746             $image = Tinebase_Controller::getInstance()->getImage($application, $id, $location);
747         }
748         
749         $serverETag = sha1($image->blob . $width . $height . $ratiomode);
750         
751         // cache for 3600 seconds
752         $maxAge = 3600;
753         header('Cache-Control: private, max-age=' . $maxAge);
754         header("Expires: " . gmdate('D, d M Y H:i:s', Tinebase_DateTime::now()->addSecond($maxAge)->getTimestamp()) . " GMT");
755         
756         // overwrite Pragma header from session
757         header("Pragma: cache");
758         
759         // if the cache id is still valid
760         if ($clientETag == $serverETag) {
761             header("Last-Modified: " . $ifModifiedSince);
762             header("HTTP/1.0 304 Not Modified");
763             header('Content-Length: 0');
764         } else {
765             #$cache = Tinebase_Core::getCache();
766             
767             #if ($cache->test($serverETag) === true) {
768             #    $image = $cache->load($serverETag);
769             #} else {
770                 if ($width != -1 && $height != -1) {
771                     Tinebase_ImageHelper::resize($image, $width, $height, $ratiomode);
772                 }
773             #    $cache->save($image, $serverETag);
774             #}
775         
776             header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
777             header('Content-Type: '. $image->mime);
778             header('Etag: "' . $serverETag . '"');
779             
780             flush();
781             
782             die($image->blob);
783         }
784     }
785     
786     /**
787      * crops a image identified by an imgageURL and returns a new tempFileImage
788      * 
789      * @param  string $imageurl imageURL of the image to be croped
790      * @param  int    $left     left position of crop window
791      * @param  int    $top      top  position of crop window
792      * @param  int    $widht    widht  of crop window
793      * @param  int    $height   heidht of crop window
794      * @return string imageURL of new temp image
795      * 
796      */
797     public function cropImage($imageurl, $left, $top, $widht, $height)
798     {
799         $this->checkAuth();
800         
801         $image = Tinebase_Model_Image::getImageFromImageURL($imageurl);
802         Tinebase_ImageHelper::crop($image, $left, $top, $widht, $height);
803         
804     }
805     
806     /**
807      * download file attachment
808      * 
809      * @param string $nodeId
810      * @param string $recordId
811      * @param string $modelName
812      */
813     public function downloadRecordAttachment($nodeId, $recordId, $modelName)
814     {
815         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
816             . ' Downloading attachment of ' . $modelName . ' record with id ' . $recordId);
817         
818         $recordController = Tinebase_Core::getApplicationInstance($modelName);
819         $record = $recordController->get($recordId);
820         
821         $node = Tinebase_FileSystem::getInstance()->get($nodeId);
822         $path = Tinebase_Model_Tree_Node_Path::STREAMWRAPPERPREFIX
823             . Tinebase_FileSystem_RecordAttachments::getInstance()->getRecordAttachmentPath($record)
824             . '/' . $node->name;
825         
826         $this->_downloadFileNode($node, $path);
827         exit;
828     }
829 }