"Fossies" - the Fresh Open Source Software Archive

Member "tine20-2019.12.4/tine20/Tinebase/Convert/VCalendar/Abstract.php" (16 Jan 2020, 11975 Bytes) of package /linux/www/tine20-2019.12.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Abstract.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 /**
    3  * Tine 2.0
    4  *
    5  * @package     Tinebase
    6  * @subpackage  Convert
    7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
    8  * @author      Philipp Schüle <p.schuele@metaways.de>
    9  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
   10  *
   11  */
   12 
   13 /**
   14  * abstract class for VCALENDAR/VTODO/VCARD/... conversion
   15  *
   16  * @package     Tinebase
   17  * @subpackage  Convert
   18  */
   19 abstract class Tinebase_Convert_VCalendar_Abstract
   20 {
   21     /**
   22      * use servers modlogProperties instead of given DTSTAMP & SEQUENCE
   23      * use this if the concurrency checks are done differntly like in CalDAV
   24      * where the etag is checked
   25      */
   26     const OPTION_USE_SERVER_MODLOG = 'useServerModlog';
   27     
   28     protected $_supportedFields = array();
   29     
   30     protected $_version;
   31     
   32     protected $_modelName = null;
   33     
   34     /**
   35      * @param string  $version  the version of the client
   36      * @throws Tinebase_Exception
   37      */
   38     public function __construct($version = null)
   39     {
   40         if (! $this->_modelName) {
   41             throw new Tinebase_Exception('modelName is required');
   42         }
   43         $this->_version = $version;
   44     }
   45 
   46     /**
   47      * returns VObject of input data
   48      * 
   49      * @param   mixed  $blob
   50      * @return  \Sabre\VObject\Component\VCalendar
   51      */
   52     public static function getVObject($blob)
   53     {
   54         if ($blob instanceof \Sabre\VObject\Component\VCalendar) {
   55             return $blob;
   56         }
   57         
   58         if (is_resource($blob)) {
   59             $blob = stream_get_contents($blob);
   60         }
   61         
   62         $blob = Tinebase_Core::filterInputForDatabase($blob);
   63 
   64         try {
   65             $vcalendar = self::readVCalBlob($blob);
   66         } catch (Sabre\VObject\ParseException $svpe) {
   67             // try again with utf8_encoded blob
   68             $utf8_blob = Tinebase_Helper::mbConvertTo($blob);
   69             // alse replace some linebreaks and \x00's
   70             $search = array("\r\n", "\x00");
   71             $replace = array("\n", '');
   72             $utf8_blob = str_replace($search, $replace, $utf8_blob);
   73             $vcalendar = self::readVCalBlob($utf8_blob);
   74         }
   75         
   76         return $vcalendar;
   77     }
   78     
   79     /**
   80      * reads vcal blob and tries to repair some parsing problems that Sabre has
   81      *
   82      * @param string $blob
   83      * @param integer $failcount
   84      * @param integer $spacecount
   85      * @param integer $lastBrokenLineNumber
   86      * @param array $lastLines
   87      * @throws Sabre\VObject\ParseException
   88      * @return Sabre\VObject\Component\VCalendar
   89      *
   90      * @see 0006110: handle iMIP messages from outlook
   91      *
   92      * @todo maybe we can remove this when #7438 is resolved
   93      */
   94     public static function readVCalBlob($blob, $failcount = 0, $spacecount = 0, $lastBrokenLineNumber = 0, $lastLines = array())
   95     {
   96         // convert to utf-8
   97         $blob = Tinebase_Helper::mbConvertTo($blob);
   98     
   99         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
  100                 ' ' . $blob);
  101     
  102         try {
  103             $vcalendar = \Sabre\VObject\Reader::read($blob);
  104         } catch (Sabre\VObject\ParseException $svpe) {
  105             // NOTE: we try to repair\Sabre\VObject\Reader as it fails to detect followup lines that do not begin with a space or tab
  106             if ($failcount < 10 && preg_match(
  107                     '/Invalid VObject, line ([0-9]+) did not follow the icalendar\/vcard format/', $svpe->getMessage(), $matches
  108             )) {
  109                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  110                     ' ' . $svpe->getMessage() .
  111                     ' lastBrokenLineNumber: ' . $lastBrokenLineNumber);
  112 
  113                 $brokenLineNumber = $matches[1] - 1 + $spacecount;
  114 
  115                 if ($lastBrokenLineNumber === $brokenLineNumber) {
  116                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  117                         ' Try again: concat this line to previous line.');
  118                     $lines = $lastLines;
  119                     $brokenLineNumber--;
  120                     // increase spacecount because one line got removed
  121                     $spacecount++;
  122                 } else {
  123                     $lines = preg_split('/[\r\n]*\n/', $blob);
  124                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  125                         ' Concat next line to this one.');
  126                     $lastLines = $lines; // for retry
  127                 }
  128                 $lines[$brokenLineNumber] .= $lines[$brokenLineNumber + 1];
  129                 unset($lines[$brokenLineNumber + 1]);
  130 
  131                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
  132                     ' failcount: ' . $failcount .
  133                     ' brokenLineNumber: ' . $brokenLineNumber .
  134                     ' spacecount: ' . $spacecount);
  135 
  136                 $vcalendar = self::readVCalBlob(implode("\n", $lines), $failcount + 1, $spacecount, $brokenLineNumber, $lastLines);
  137             } else if ($failcount < 10 && preg_match('/%0A/', $blob)) {
  138                 // maybe the input file is urlencoded
  139                 $vcalendar = \Sabre\VObject\Reader::read(urldecode($blob));
  140             } else {
  141                 throw $svpe;
  142             }
  143         }
  144     
  145         return $vcalendar;
  146     }
  147     
  148     /**
  149      * to be overwriten in extended classes to modify/cleanup $_vcalendar
  150      *
  151      * @param \Sabre\VObject\Component\VCalendar $vcalendar
  152      */
  153     protected function _afterFromTine20Model(\Sabre\VObject\Component\VCalendar $vcalendar)
  154     {
  155     }
  156     
  157     /**
  158      * parse valarm properties
  159      * 
  160      * @param Tinebase_Record_Interface $record
  161      * @param Traversable $valarms
  162      * @param \Sabre\VObject\Component $vcomponent
  163      */
  164     protected function _parseAlarm(Tinebase_Record_Interface $record, $valarms, \Sabre\VObject\Component $vcomponent)
  165     {
  166         foreach ($valarms as $valarm) {
  167             
  168             if ($valarm->ACTION == 'NONE') {
  169                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  170                         . ' We can\'t cope with action NONE: iCal 6.0 sends default alarms in the year 1976 with action NONE. Skipping alarm.');
  171                 continue;
  172             }
  173             
  174             if (! is_object($valarm->TRIGGER)) {
  175                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  176                 . ' Alarm has no TRIGGER value. Skipping it.');
  177                 continue;
  178             }
  179             
  180             # TRIGGER:-PT15M
  181             if (is_string($valarm->TRIGGER->getValue()) && $valarm->TRIGGER instanceof Sabre\VObject\Property\ICalendar\Duration) {
  182                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  183                 . ' Adding DURATION trigger value for ' . $valarm->TRIGGER->getValue());
  184                 $valarm->TRIGGER->add('VALUE', 'DURATION');
  185             }
  186             
  187             $trigger = is_object($valarm->TRIGGER['VALUE']) ? $valarm->TRIGGER['VALUE'] : (is_object($valarm->TRIGGER['RELATED']) ? $valarm->TRIGGER['RELATED'] : NULL);
  188             
  189             if ($trigger === NULL) {
  190                 // added Trigger/Related for eM Client alarms
  191                 // 2014-01-03 - Bullshit, why don't we have testdata for emclient alarms?
  192                         //              this alarm handling should be refactored, the logic is scrambled
  193                 // @see 0006110: handle iMIP messages from outlook
  194                 // @todo fix 0007446: handle broken alarm in outlook invitation message
  195                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  196                 . ' Alarm has no TRIGGER value. Skipping it.');
  197                 continue;
  198             }
  199             
  200             switch (strtoupper($trigger->getValue())) {
  201                 # TRIGGER;VALUE=DATE-TIME:20111031T130000Z
  202                 case 'DATE-TIME':
  203                     $alarmTime = new Tinebase_DateTime($valarm->TRIGGER->getValue());
  204                     $alarmTime->setTimezone('UTC');
  205                     
  206                     $alarm = new Tinebase_Model_Alarm(array(
  207                         'alarm_time'        => $alarmTime,
  208                         'minutes_before'    => 'custom',
  209                         'model'             => $this->_modelName,
  210                     ));
  211                     
  212                     break;
  213                 
  214                 # TRIGGER;VALUE=DURATION:-PT1H15M
  215                 case 'DURATION':
  216                 default:
  217                     $durationBaseTime = isset($vcomponent->DTSTART) ? $vcomponent->DTSTART : $vcomponent->DUE;
  218                     $alarmTime = $this->_convertToTinebaseDateTime($durationBaseTime);
  219                     $alarmTime->setTimezone('UTC');
  220                     
  221                     preg_match('/(?P<invert>[+-]?)(?P<spec>P.*)/', $valarm->TRIGGER->getValue(), $matches);
  222                     // PRODID:-//DDay.iCal//NONSGML ddaysoftware.com//EN issue: "TRIGGER:P"
  223                     if ('P' === $matches['spec']) {
  224                         $matches['spec'] = 'PT15M';
  225                     }
  226                     $duration = new DateInterval($matches['spec']);
  227                     $duration->invert = !!($matches['invert'] === '-');
  228                     
  229                     $alarm = new Tinebase_Model_Alarm(array(
  230                         'alarm_time'        => $alarmTime->add($duration),
  231                         'minutes_before'    => ($duration->format('%d') * 60 * 24) + ($duration->format('%h') * 60) + ($duration->format('%i')),
  232                         'model'             => $this->_modelName,
  233                     ));
  234                     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  235                         . ' Adding DURATION alarm ' . print_r($alarm->toArray(), true));
  236             }
  237             
  238             if ($valarm->ACKNOWLEDGED) {
  239                 $dtack = $valarm->ACKNOWLEDGED->getDateTime();
  240                 Calendar_Controller_Alarm::setAcknowledgeTime($alarm, $dtack);
  241             }
  242             
  243             $record->alarms->addRecord($alarm);
  244         }
  245     }
  246     
  247     /**
  248      * get datetime from sabredav datetime property (user TZ is fallback)
  249      * 
  250      * @param  Sabre\VObject\Property  $dateTimeProperty
  251      * @param  boolean                 $_useUserTZ
  252      * @return Tinebase_DateTime
  253      * 
  254      * @todo try to guess some common timezones
  255      */
  256     protected function _convertToTinebaseDateTime(\Sabre\VObject\Property $dateTimeProperty, $_useUserTZ = FALSE)
  257     {
  258         $defaultTimezone = date_default_timezone_get();
  259         date_default_timezone_set((string) Tinebase_Core::getUserTimezone());
  260         
  261         if ($dateTimeProperty instanceof Sabre\VObject\Property\ICalendar\DateTime) {
  262             $dateTime = $dateTimeProperty->getDateTime();
  263 
  264             $isFloatingTime = !$dateTimeProperty['TZID'] && !preg_match('/Z$/i', $dateTimeProperty->getValue());
  265             $isDate = (isset($dateTimeProperty['VALUE']) && strtoupper($dateTimeProperty['VALUE']) == 'DATE');
  266 
  267             $tz = ($_useUserTZ || $isFloatingTime || $isDate) ?
  268                 (string) Tinebase_Core::getUserTimezone() : 
  269                 $dateTime->getTimezone();
  270             
  271             $result = new Tinebase_DateTime($dateTime->format(Tinebase_Record_Abstract::ISO8601LONG), $tz);
  272         } else {
  273             $result = new Tinebase_DateTime($dateTimeProperty->getValue());
  274         }
  275         
  276         date_default_timezone_set($defaultTimezone);
  277         
  278         return $result;
  279     }
  280 }