ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç iCalendar_parameters.php000064400000024464152267201730011401 0ustar00 array('PLAIN', 'RICHTEXT', 'ENRICHED', 'TAB-SEPARATED-VALUES', 'HTML', 'SGML', 'VND.LATEX-Z', 'VND.FMI.FLEXSTOR'), 'MULTIPART' => array('MIXED', 'ALTERNATIVE', 'DIGEST', 'PARALLEL', 'APPLEDOUBLE', 'HEADER-SET', 'FORM-DATA', 'RELATED', 'REPORT', 'VOICE-MESSAGE', 'SIGNED', 'ENCRYPTED', 'BYTERANGES'), 'MESSAGE' => array('RFC822', 'PARTIAL', 'EXTERNAL-BODY', 'NEWS', 'HTTP'), 'APPLICATION' => array('OCTET-STREAM', 'POSTSCRIPT', 'ODA', 'ATOMICMAIL', 'ANDREW-INSET', 'SLATE', 'WITA', 'DEC-DX', 'DCA-RFT', 'ACTIVEMESSAGE', 'RTF', 'APPLEFILE', 'MAC-BINHEX40', 'NEWS-MESSAGE-ID', 'NEWS-TRANSMISSION', 'WORDPERFECT5.1', 'PDF', 'ZIP', 'MACWRITEII', 'MSWORD', 'REMOTE-PRINTING', 'MATHEMATICA', 'CYBERCASH', 'COMMONGROUND', 'IGES', 'RISCOS', 'ESHOP', 'X400-BP', 'SGML', 'CALS-1840', 'PGP-ENCRYPTED', 'PGP-SIGNATURE', 'PGP-KEYS', 'VND.FRAMEMAKER', 'VND.MIF', 'VND.MS-EXCEL', 'VND.MS-POWERPOINT', 'VND.MS-PROJECT', 'VND.MS-WORKS', 'VND.MS-TNEF', 'VND.SVD', 'VND.MUSIC-NIFF', 'VND.MS-ARTGALRY', 'VND.TRUEDOC', 'VND.KOAN', 'VND.STREET-STREAM', 'VND.FDF', 'SET-PAYMENT-INITIATION', 'SET-PAYMENT', 'SET-REGISTRATION-INITIATION', 'SET-REGISTRATION', 'VND.SEEMAIL', 'VND.BUSINESSOBJECTS', 'VND.MERIDIAN-SLINGSHOT', 'VND.XARA', 'SGML-OPEN-CATALOG', 'VND.RAPID', 'VND.ENLIVEN', 'VND.JAPANNET-REGISTRATION-WAKEUP', 'VND.JAPANNET-VERIFICATION-WAKEUP', 'VND.JAPANNET-PAYMENT-WAKEUP', 'VND.JAPANNET-DIRECTORY-SERVICE', 'VND.INTERTRUST.DIGIBOX', 'VND.INTERTRUST.NNCP'), 'IMAGE' => array('JPEG', 'GIF', 'IEF', 'G3FAX', 'TIFF', 'CGM', 'NAPLPS', 'VND.DWG', 'VND.SVF', 'VND.DXF', 'PNG', 'VND.FPX', 'VND.NET-FPX'), 'AUDIO' => array('BASIC', '32KADPCM', 'VND.QCELP'), 'VIDEO' => array('MPEG', 'QUICKTIME', 'VND.VIVO', 'VND.MOTOROLA.VIDEO', 'VND.MOTOROLA.VIDEOP') ); $value = strtoupper($value); // Mimetype is enumerated above and anything else results in false. @list($type, $subtype) = explode('/', $value); if(empty($type) || empty($subtype)) { return false; } if(!isset($fmttypes[$type]) || !in_array($subtype, $fmttypes[$type])) { return false; } return true; break; case 'LANGUAGE': $value = strtoupper($value); $parts = explode('-', $value); foreach($parts as $part) { if(empty($part)) { return false; } if(strspn($part, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') != strlen($part)) { return false; } } return true; break; case 'PARTSTAT': $value = strtoupper($value); switch($parent_property->parent_component) { case 'VEVENT': return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE' || $value == 'DELEGATED' || rfc2445_is_xname($value)); break; case 'VTODO': return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || $value == 'TENTATIVE' || $value == 'DELEGATED' || $value == 'COMPLETED' || $value == 'IN-PROCESS' || rfc2445_is_xname($value)); break; case 'VJOURNAL': return ($value == 'NEEDS-ACTION' || $value == 'ACCEPTED' || $value == 'DECLINED' || rfc2445_is_xname($value)); break; } return false; break; case 'RANGE': $value = strtoupper($value); return ($value == 'THISANDPRIOR' || $value == 'THISANDFUTURE'); break; case 'RELATED': $value = strtoupper($value); return ($value == 'START' || $value == 'END'); break; case 'RELTYPE': $value = strtoupper($value); return ($value == 'PARENT' || $value == 'CHILD' || $value == 'SIBLING' || rfc2445_is_xname($value)); break; case 'ROLE': $value = strtoupper($value); return ($value == 'CHAIR' || $value == 'REQ-PARTICIPANT' || $value == 'OPT-PARTICIPANT' || $value == 'NON-PARTICIPANT' || rfc2445_is_xname($value)); break; case 'RSVP': $value = strtoupper($value); return ($value == 'TRUE' || $value == 'FALSE'); break; case 'TZID': if(empty($value)) { return false; } return (strcspn($value, ';:,') == strlen($value)); break; case 'VALUE': $value = strtoupper($value); return ($value == 'BINARY' || $value == 'BOOLEAN' || $value == 'CAL-ADDRESS' || $value == 'DATE' || $value == 'DATE-TIME' || $value == 'DURATION' || $value == 'FLOAT' || $value == 'INTEGER' || $value == 'PERIOD' || $value == 'RECUR' || $value == 'TEXT' || $value == 'TIME' || $value == 'URI' || $value == 'UTC-OFFSET' || rfc2445_is_xname($value)); break; } } static function do_value_formatting($parameter, $value) { switch($parameter) { // Parameters of type CAL-ADDRESS or URI MUST be double-quoted case 'ALTREP': case 'DIR': case 'DELEGATED-FROM': case 'DELEGATED-TO': case 'MEMBER': case 'SENT-BY': return '"'.$value.'"'; break; // Textual parameter types must be double quoted if they contain COLON, SEMICOLON // or COMMA. Quoting always sounds easier and standards-conformant though. case 'CN': return '"'.$value.'"'; break; // Parameters with enumerated legal values, just make them all caps case 'CUTYPE': case 'ENCODING': case 'FBTYPE': case 'FMTTYPE': case 'LANGUAGE': case 'PARTSTAT': case 'RANGE': case 'RELATED': case 'RELTYPE': case 'ROLE': case 'RSVP': case 'VALUE': return strtoupper($value); break; // Parameters we shouldn't be messing with case 'TZID': return str_replace('"', '', $value); break; } } static function undo_value_formatting($parameter, $value) { } } readme_moodle.txt000064400000003401152267201730010104 0ustar00Bennu - PHP iCalendar library ============================= Bennu is an object-oriented library written in PHP that implements the iCalendar standard (RFC 2445). It is easy to use, fully standards compliant, and powerful. Applications can use Bennu to read and write iCalendar files, making them compatible with programs like Microsoft Outlook, Apple iCal, and Mozilla Sunbird. Information ----------- * Bennu version: based on upstream 0.2, heavily customized since then * Licence: GNU LGPL 2.1 * WWW: http://bennu.sourceforge.net/ Changelog --------- 1/ removed ereg functions deprecated as of php 5.3 (18 Nov 2009) 2/ replaced mbstring functions with moodle core_text (28 Nov 2011) 3/ replaced explode in iCalendar_component::unserialize() with preg_split to support various line breaks (20 Nov 2012) 4/ updated rfc2445_is_valid_value() to accept single part rrule as a valid value (16 Jun 2014) 5/ updated DTEND;TZID and DTSTAR;TZID values to support quotations (7 Nov 2014) 6/ MDL-49032: fixed rfc2445_fold() to fix incorrect RFC2445_WSP definition (16 Sep 2015) 7/ added timestamp_to_date function to support zero duration events (16 Sept 2015) 8/ Updated \iCalendar_event::invariant_holds() to allow for same dtstart and dtend timestamps (13 July 2017) 9/ MDL-60391: replace create_function() with lambda function for PHP 7.2 compatibility (13 Oct 2017) 10/ MDL-62914: added handling for TZURL property (13 July 2018) 11/ MDL-67029: replace curly by square brackets for string offsets. PHP 7.4 compatibility (25 Oct 2019) 12/ MDL-74866: fixed parameter parsing if the value is wrapped by DQUOTE character (28 Jul 2022) 13/ MDL-76333: replaced strftime() with date() for PHP 8.1 compatibility (16 Nov 2022) 14/ MDL-78142: fixed PHP 8.2: Dynamic Properties deprecations (9 May 2023) CHANGELOG.txt000064400000000030152267201730006572 0ustar00No changelog yet, sorry.TODO.txt000064400000000114152267201730006053 0ustar00 * Take a look at all those text values. Control chars shouldn't be allowed.bennu.inc.php000064400000001176152267201730007146 0ustar00= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch >= '0' && $ch <= '9' || $ch == '-' || $ch == '+')) { if($ch == '=' && $len - $i <= 2) { continue; } return false; } } return true; break; case RFC2445_TYPE_BOOLEAN: if(is_bool($value)) { return true; } if(is_string($value)) { $value = strtoupper($value); return ($value == 'TRUE' || $value == 'FALSE'); } return false; break; case RFC2445_TYPE_DATE: if(is_int($value)) { if($value < 0) { return false; } $value = "$value"; } else if(!is_string($value)) { return false; } if(strlen($value) != 8) { return false; } $y = intval(substr($value, 0, 4)); $m = intval(substr($value, 4, 2)); $d = intval(substr($value, 6, 2)); return checkdate($m, $d, $y); break; case RFC2445_TYPE_DATE_TIME: if(!is_string($value) || strlen($value) < 15) { return false; } return($value[8] == 'T' && rfc2445_is_valid_value(substr($value, 0, 8), RFC2445_TYPE_DATE) && rfc2445_is_valid_value(substr($value, 9), RFC2445_TYPE_TIME)); break; case RFC2445_TYPE_DURATION: if(!is_string($value)) { return false; } $len = strlen($value); if($len < 3) { // Minimum conformant length: "P1W" return false; } if($value[0] == '+' || $value[0] == '-') { $value = substr($value, 1); --$len; // Don't forget to update this! } if($value[0] != 'P') { return false; } // OK, now break it up $num = ''; $allowed = 'WDT'; for($i = 1; $i < $len; ++$i) { $ch = $value[$i]; if($ch >= '0' && $ch <= '9') { $num .= $ch; continue; } if(strpos($allowed, $ch) === false) { // Non-numeric character which shouldn't be here return false; } if($num === '' && $ch != 'T') { // Allowed non-numeric character, but no digits came before it return false; } // OK, $ch now holds a character which tells us what $num is switch($ch) { case 'W': // If duration in weeks is specified, this must end the string return ($i == $len - 1); break; case 'D': // Days specified, now if anything comes after it must be a 'T' $allowed = 'T'; break; case 'T': // Starting to specify time, H M S are now valid delimiters $allowed = 'HMS'; break; case 'H': $allowed = 'M'; break; case 'M': $allowed = 'S'; break; case 'S': return ($i == $len - 1); break; } // If we 're going to continue, reset $num $num = ''; } // $num is kept for this reason: if we 're here, we ran out of chars // therefore $num must be empty for the period to be legal return ($num === '' && $ch != 'T'); break; case RFC2445_TYPE_FLOAT: if(is_float($value)) { return true; } if(!is_string($value) || $value === '') { return false; } $dot = false; $int = false; $len = strlen($value); for($i = 0; $i < $len; ++$i) { switch($value[$i]) { case '-': case '+': // A sign can only be seen at position 0 and cannot be the only char if($i != 0 || $len == 1) { return false; } break; case '.': // A second dot is an error // Make sure we had at least one int before the dot if($dot || !$int) { return false; } $dot = true; // Make also sure that the float doesn't end with a dot if($i == $len - 1) { return false; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': $int = true; break; default: // Any other char is a no-no return false; break; } } return true; break; case RFC2445_TYPE_INTEGER: if(is_int($value)) { return true; } if(!is_string($value) || $value === '') { return false; } if($value[0] == '+' || $value[0] == '-') { if(strlen($value) == 1) { return false; } $value = substr($value, 1); } if(strspn($value, '0123456789') != strlen($value)) { return false; } return ($value >= -2147483648 && $value <= 2147483647); break; case RFC2445_TYPE_PERIOD: if(!is_string($value) || empty($value)) { return false; } $parts = explode('/', $value); if(count($parts) != 2) { return false; } if(!rfc2445_is_valid_value($parts[0], RFC2445_TYPE_DATE_TIME)) { return false; } // Two legal cases for the second part: if(rfc2445_is_valid_value($parts[1], RFC2445_TYPE_DATE_TIME)) { // It has to be after the start time, so return ($parts[1] > $parts[0]); } else if(rfc2445_is_valid_value($parts[1], RFC2445_TYPE_DURATION)) { // The period MUST NOT be negative return ($parts[1][0] != '-'); } // It seems to be illegal return false; break; case RFC2445_TYPE_RECUR: if(!is_string($value)) { return false; } $parts = explode(';', strtoupper($value)); // We need at least one part for a valid rule, for example: "FREQ=DAILY". if(empty($parts)) { return false; } // Let's get that into a more easily comprehensible format $vars = array(); foreach($parts as $part) { $pieces = explode('=', $part); // There must be exactly 2 pieces, e.g. FREQ=WEEKLY if(count($pieces) != 2) { return false; } // It's illegal for a variable to appear twice if(isset($vars[$pieces[0]])) { return false; } // Sounds good $vars[$pieces[0]] = $pieces[1]; } // OK... now to test everything else // FREQ must be the first thing appearing reset($vars); if(key($vars) != 'FREQ') { return false; } // It's illegal to have both UNTIL and COUNT appear if(isset($vars['UNTIL']) && isset($vars['COUNT'])) { return false; } // Special case: BYWEEKNO is only valid for FREQ=YEARLY if(isset($vars['BYWEEKNO']) && $vars['FREQ'] != 'YEARLY') { return false; } // Special case: BYSETPOS is only valid if another BY option is specified if(isset($vars['BYSETPOS'])) { $options = array('BYSECOND', 'BYMINUTE', 'BYHOUR', 'BYDAY', 'BYMONTHDAY', 'BYYEARDAY', 'BYWEEKNO', 'BYMONTH'); $defined = array_keys($vars); $common = array_intersect($options, $defined); if(empty($common)) { return false; } } // OK, now simply check if each element has a valid value, // unsetting them on the way. If at the end the array still // has some elements, they are illegal. if($vars['FREQ'] != 'SECONDLY' && $vars['FREQ'] != 'MINUTELY' && $vars['FREQ'] != 'HOURLY' && $vars['FREQ'] != 'DAILY' && $vars['FREQ'] != 'WEEKLY' && $vars['FREQ'] != 'MONTHLY' && $vars['FREQ'] != 'YEARLY') { return false; } unset($vars['FREQ']); // Set this, we may need it later $weekdays = explode(',', RFC2445_WEEKDAYS); if(isset($vars['UNTIL'])) { if(rfc2445_is_valid_value($vars['UNTIL'], RFC2445_TYPE_DATE_TIME)) { // The time MUST be in UTC format if(!(substr($vars['UNTIL'], -1) == 'Z')) { return false; } } else if(!rfc2445_is_valid_value($vars['UNTIL'], RFC2445_TYPE_DATE_TIME)) { return false; } } unset($vars['UNTIL']); if(isset($vars['COUNT'])) { if(empty($vars['COUNT'])) { // This also catches the string '0', which makes no sense return false; } if(strspn($vars['COUNT'], '0123456789') != strlen($vars['COUNT'])) { return false; } } unset($vars['COUNT']); if(isset($vars['INTERVAL'])) { if(empty($vars['INTERVAL'])) { // This also catches the string '0', which makes no sense return false; } if(strspn($vars['INTERVAL'], '0123456789') != strlen($vars['INTERVAL'])) { return false; } } unset($vars['INTERVAL']); if(isset($vars['BYSECOND'])) { if($vars['BYSECOND'] == '') { return false; } // Comma also allowed if(strspn($vars['BYSECOND'], '0123456789,') != strlen($vars['BYSECOND'])) { return false; } $secs = explode(',', $vars['BYSECOND']); foreach($secs as $sec) { if($sec == '' || $sec < 0 || $sec > 59) { return false; } } } unset($vars['BYSECOND']); if(isset($vars['BYMINUTE'])) { if($vars['BYMINUTE'] == '') { return false; } // Comma also allowed if(strspn($vars['BYMINUTE'], '0123456789,') != strlen($vars['BYMINUTE'])) { return false; } $mins = explode(',', $vars['BYMINUTE']); foreach($mins as $min) { if($min == '' || $min < 0 || $min > 59) { return false; } } } unset($vars['BYMINUTE']); if(isset($vars['BYHOUR'])) { if($vars['BYHOUR'] == '') { return false; } // Comma also allowed if(strspn($vars['BYHOUR'], '0123456789,') != strlen($vars['BYHOUR'])) { return false; } $hours = explode(',', $vars['BYHOUR']); foreach($hours as $hour) { if($hour == '' || $hour < 0 || $hour > 23) { return false; } } } unset($vars['BYHOUR']); if(isset($vars['BYDAY'])) { if(empty($vars['BYDAY'])) { return false; } // First off, split up all values we may have $days = explode(',', $vars['BYDAY']); foreach($days as $day) { $daypart = substr($day, -2); if(!in_array($daypart, $weekdays)) { return false; } if(strlen($day) > 2) { $intpart = substr($day, 0, strlen($day) - 2); if(!rfc2445_is_valid_value($intpart, RFC2445_TYPE_INTEGER)) { return false; } if(intval($intpart) == 0) { return false; } } } } unset($vars['BYDAY']); if(isset($vars['BYMONTHDAY'])) { if(empty($vars['BYMONTHDAY'])) { return false; } $mdays = explode(',', $vars['BYMONTHDAY']); foreach($mdays as $mday) { if(!rfc2445_is_valid_value($mday, RFC2445_TYPE_INTEGER)) { return false; } $mday = abs(intval($mday)); if($mday == 0 || $mday > 31) { return false; } } } unset($vars['BYMONTHDAY']); if(isset($vars['BYYEARDAY'])) { if(empty($vars['BYYEARDAY'])) { return false; } $ydays = explode(',', $vars['BYYEARDAY']); foreach($ydays as $yday) { if(!rfc2445_is_valid_value($yday, RFC2445_TYPE_INTEGER)) { return false; } $yday = abs(intval($yday)); if($yday == 0 || $yday > 366) { return false; } } } unset($vars['BYYEARDAY']); if(isset($vars['BYWEEKNO'])) { if(empty($vars['BYWEEKNO'])) { return false; } $weeknos = explode(',', $vars['BYWEEKNO']); foreach($weeknos as $weekno) { if(!rfc2445_is_valid_value($weekno, RFC2445_TYPE_INTEGER)) { return false; } $weekno = abs(intval($weekno)); if($weekno == 0 || $weekno > 53) { return false; } } } unset($vars['BYWEEKNO']); if(isset($vars['BYMONTH'])) { if(empty($vars['BYMONTH'])) { return false; } // Comma also allowed if(strspn($vars['BYMONTH'], '0123456789,') != strlen($vars['BYMONTH'])) { return false; } $months = explode(',', $vars['BYMONTH']); foreach($months as $month) { if($month == '' || $month < 1 || $month > 12) { return false; } } } unset($vars['BYMONTH']); if(isset($vars['BYSETPOS'])) { if(empty($vars['BYSETPOS'])) { return false; } $sets = explode(',', $vars['BYSETPOS']); foreach($sets as $set) { if(!rfc2445_is_valid_value($set, RFC2445_TYPE_INTEGER)) { return false; } $set = abs(intval($set)); if($set == 0 || $set > 366) { return false; } } } unset($vars['BYSETPOS']); if(isset($vars['WKST'])) { if(!in_array($vars['WKST'], $weekdays)) { return false; } } unset($vars['WKST']); // Any remaining vars must be x-names if(empty($vars)) { return true; } foreach($vars as $name => $var) { if(!rfc2445_is_xname($name)) { return false; } } // At last, all is OK! return true; break; case RFC2445_TYPE_TEXT: return true; break; case RFC2445_TYPE_TIME: if(is_int($value)) { if($value < 0) { return false; } $value = "$value"; } else if(!is_string($value)) { return false; } if(strlen($value) == 7) { if(strtoupper(substr($value, -1)) != 'Z') { return false; } $value = substr($value, 0, 6); } if(strlen($value) != 6) { return false; } $h = intval(substr($value, 0, 2)); $m = intval(substr($value, 2, 2)); $s = intval(substr($value, 4, 2)); return ($h <= 23 && $m <= 59 && $s <= 60); break; case RFC2445_TYPE_UTC_OFFSET: if(is_int($value)) { if($value >= 0) { $value = "+$value"; } else { $value = "$value"; } } else if(!is_string($value)) { return false; } $s = 0; if(strlen($value) == 7) { $s = intval(substr($value, 5, 2)); $value = substr($value, 0, 5); } if(strlen($value) != 5 || $value == "-0000") { return false; } if($value[0] != '+' && $value[0] != '-') { return false; } $h = intval(substr($value, 1, 2)); $m = intval(substr($value, 3, 2)); return ($h <= 23 && $m <= 59 && $s <= 59); break; } // TODO: remove this assertion trigger_error('bad code path', E_USER_WARNING); var_dump($type); return false; } function rfc2445_do_value_formatting($value, $type) { // Note: this does not only do formatting; it also does conversion to string! switch($type) { case RFC2445_TYPE_CAL_ADDRESS: case RFC2445_TYPE_URI: // Enclose in double quotes $value = '"'.$value.'"'; break; case RFC2445_TYPE_TEXT: // Escape entities $value = strtr($value, array("\r\n" => '\\n', "\n" => '\\n', '\\' => '\\\\', ',' => '\\,', ';' => '\\;')); break; } return $value; } function rfc2445_undo_value_formatting($value, $type) { switch($type) { case RFC2445_TYPE_CAL_ADDRESS: case RFC2445_TYPE_URI: // Trim beginning and end double quote $value = substr($value, 1, strlen($value) - 2); break; case RFC2445_TYPE_TEXT: // Unescape entities $value = strtr($value, array('\\n' => "\n", '\\N' => "\n", '\\\\' => '\\', '\\,' => ',', '\\;' => ';')); break; } return $value; } iCalendar_properties.php000064400000122350152267201730011423 0ustar00parameters = array(); } // If some property needs extra care with its parameters, override this // IMPORTANT: the parameter name MUST BE CAPITALIZED! function is_valid_parameter($parameter, $value) { if(is_array($value)) { if(!iCalendar_parameter::multiple_values_allowed($parameter)) { return false; } foreach($value as $item) { if(!iCalendar_parameter::is_valid_value($this, $parameter, $item)) { return false; } } return true; } return iCalendar_parameter::is_valid_value($this, $parameter, $value); } function invariant_holds() { return true; } // If some property is very picky about its values, it should do the work itself // Only data type validation is done here function is_valid_value($value) { if(is_array($value)) { if(!$this->val_multi) { return false; } else { foreach($value as $oneval) { if(!rfc2445_is_valid_value($oneval, $this->val_type)) { return false; } } } return true; } return rfc2445_is_valid_value($value, $this->val_type); } function default_value() { return $this->val_default; } function set_parent_component($componentname) { if(class_exists('iCalendar_'.strtolower(substr($componentname, 1)))) { $this->parent_component = strtoupper($componentname); return true; } return false; } function set_value($value) { if($this->is_valid_value($value)) { // This transparently formats any value type according to the iCalendar specs if(is_array($value)) { foreach($value as $key => $item) { $value[$key] = rfc2445_do_value_formatting($item, $this->val_type); } $this->value = implode(',', $value); } else { $this->value = rfc2445_do_value_formatting($value, $this->val_type); } return true; } return false; } function get_value() { // First of all, assume that we have multiple values $valarray = explode('\\,', $this->value); // Undo transparent formatting $valtype = $this->val_type; $replace_function = function($a) use ($valtype) { return rfc2445_undo_value_formatting($a, $valtype); }; $valarray = array_map($replace_function, $valarray); // Now, if this property cannot have multiple values, don't return as an array if(!$this->val_multi) { return $valarray[0]; } // Otherwise return an array even if it has one element, for uniformity return $valarray; } function set_parameter($name, $value) { // Uppercase $name = strtoupper($name); // Are we trying to add a valid parameter? $xname = false; if(!isset($this->valid_parameters[$name])) { // If not, is it an x-name as per RFC 2445? if(!rfc2445_is_xname($name)) { return false; } // No more checks -- all components are supposed to allow x-name parameters $xname = true; } if(!$this->is_valid_parameter($name, $value)) { return false; } if(is_array($value)) { foreach($value as $key => $element) { $value[$key] = iCalendar_parameter::do_value_formatting($name, $element); } } else { $value = iCalendar_parameter::do_value_formatting($name, $value); } $this->parameters[$name] = $value; // Special case: if we just changed the VALUE parameter, reflect this // in the object's status so that it only accepts correct type values if($name == 'VALUE') { // TODO: what if this invalidates an already-set value? $this->val_type = constant('RFC2445_TYPE_'.str_replace('-', '_', $value)); } return true; } function get_parameter($name) { // Uppercase $name = strtoupper($name); if(isset($this->parameters[$name])) { // If there are any double quotes in the value, invisibly strip them if(is_array($this->parameters[$name])) { foreach($this->parameters[$name] as $key => $value) { if(substr($value, 0, 1) == '"') { $this->parameters[$name][$key] = substr($value, 1, strlen($value) - 2); } } return $this->parameters[$name]; } else { if(substr($this->parameters[$name], 0, 1) == '"') { return substr($this->parameters[$name], 1, strlen($this->parameters[$name]) - 2); } } } return NULL; } function serialize() { $string = $this->name; if(!empty($this->parameters)) { foreach($this->parameters as $name => $value) { $string .= ';'.$name.'='; if(is_array($value)) { $string .= implode(',', $value); } else { $string .= $value; } } } $string .= ':'.$this->value; return rfc2445_fold($string) . RFC2445_CRLF; } } // 4.7 Calendar Properties // ----------------------- class iCalendar_property_calscale extends iCalendar_property { var $name = 'CALSCALE'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // This is case-sensitive return ($value === 'GREGORIAN'); } } class iCalendar_property_method extends iCalendar_property { var $name = 'METHOD'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // This is case-sensitive // Methods from RFC 2446 $methods = array('PUBLISH', 'REQUEST', 'REPLY', 'ADD', 'CANCEL', 'REFRESH', 'COUNTER', 'DECLINECOUNTER'); return in_array($value, $methods); } } class iCalendar_property_prodid extends iCalendar_property { var $name = 'PRODID'; var $val_type = RFC2445_TYPE_TEXT; var $val_default = NULL; function __construct() { parent::__construct(); $this->val_default = '-//John Papaioannou/NONSGML Bennu '._BENNU_VERSION.'//EN'; $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_version extends iCalendar_property { var $name = 'VERSION'; var $val_type = RFC2445_TYPE_TEXT; var $val_default = '2.0'; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { return($value === '2.0' || $value === 2.0); } } // 4.8.1 Descriptive Component Properties // -------------------------------------- class iCalendar_property_attach extends iCalendar_property { var $name = 'ATTACH'; var $val_type = RFC2445_TYPE_URI; function __construct() { parent::__construct(); $this->valid_parameters = array( 'FMTTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'ENCODING' => RFC2445_OPTIONAL | RFC2445_ONCE, 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function invariant_holds() { if(isset($this->parameters['ENCODING']) && !isset($this->parameters['VALUE'])) { return false; } if(isset($this->parameters['VALUE']) && !isset($this->parameters['ENCODING'])) { return false; } return true; } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter === 'ENCODING' && strtoupper($value) != 'BASE64') { return false; } if($parameter === 'VALUE' && strtoupper($value) != 'BINARY') { return false; } return true; } } class iCalendar_property_categories extends iCalendar_property { var $name = 'CATEGORIES'; var $val_type = RFC2445_TYPE_TEXT; var $val_multi = true; function __construct() { parent::__construct(); $this->valid_parameters = array( 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_class extends iCalendar_property { var $name = 'CLASS'; var $val_type = RFC2445_TYPE_TEXT; var $val_default = 'PUBLIC'; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // If this is not an xname, it is case-sensitive return ($value === 'PUBLIC' || $value === 'PRIVATE' || $value === 'CONFIDENTIAL' || rfc2445_is_xname(strtoupper($value))); } } class iCalendar_property_comment extends iCalendar_property { var $name = 'COMMENT'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_description extends iCalendar_property { var $name = 'DESCRIPTION'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_geo extends iCalendar_property { var $name = 'GEO'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // This MUST be two floats separated by a semicolon if(!is_string($value)) { return false; } $floats = explode(';', $value); if(count($floats) != 2) { return false; } return rfc2445_is_valid_value($floats[0], RFC2445_TYPE_FLOAT) && rfc2445_is_valid_value($floats[1], RFC2445_TYPE_FLOAT); } function set_value($value) { // Must override this, otherwise the semicolon separating // the two floats would get auto-quoted, which is illegal if($this->is_valid_value($value)) { $this->value = $value; return true; } return false; } } class iCalendar_property_location extends iCalendar_property { var $name = 'LOCATION'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_percent_complete extends iCalendar_property { var $name = 'PERCENT-COMPLETE'; var $val_type = RFC2445_TYPE_INTEGER; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // Only integers between 0 and 100 inclusive allowed if(!parent::is_valid_value($value)) { return false; } $value = intval($value); return ($value >= 0 && $value <= 100); } } class iCalendar_property_priority extends iCalendar_property { var $name = 'PRIORITY'; var $val_type = RFC2445_TYPE_INTEGER; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // Only integers between 0 and 9 inclusive allowed if(!parent::is_valid_value($value)) { return false; } $value = intval($value); return ($value >= 0 && $value <= 9); } } class iCalendar_property_resources extends iCalendar_property { var $name = 'RESOURCES'; var $val_type = RFC2445_TYPE_TEXT; var $val_multi = true; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_status extends iCalendar_property { var $name = 'STATUS'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { // This is case-sensitive switch ($this->parent_component) { case 'VEVENT': $allowed = array('TENTATIVE', 'CONFIRMED', 'CANCELLED'); break; case 'VTODO': $allowed = array('NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED'); break; case 'VJOURNAL': $allowed = array('DRAFT', 'FINAL', 'CANCELLED'); break; } return in_array($value, $allowed); } } class iCalendar_property_summary extends iCalendar_property { var $name = 'SUMMARY'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } // 4.8.2 Date and Time Component Properties // ---------------------------------------- class iCalendar_property_completed extends iCalendar_property { var $name = 'COMPLETED'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Time MUST be in UTC format return(substr($value, -1) == 'Z'); } } class iCalendar_property_dtend extends iCalendar_property { var $name = 'DTEND'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // If present in a FREEBUSY component, must be in UTC format if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') { return false; } return true; } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) { return false; } return true; } } class iCalendar_property_due extends iCalendar_property { var $name = 'DUE'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // If present in a FREEBUSY component, must be in UTC format if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') { return false; } return true; } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) { return false; } return true; } } class iCalendar_property_dtstart extends iCalendar_property { var $name = 'DTSTART'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } // TODO: unimplemented stuff when parent is a VTIMEZONE component function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // If present in a FREEBUSY component, must be in UTC format if($this->parent_component == 'VFREEBUSY' && substr($value, -1) != 'Z') { return false; } return true; } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) { return false; } return true; } } class iCalendar_property_duration extends iCalendar_property { var $name = 'DURATION'; var $val_type = RFC2445_TYPE_DURATION; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Value must be positive return ($value[0] != '-'); } } class iCalendar_property_freebusy extends iCalendar_property { var $name = 'FREEBUSY'; var $val_type = RFC2445_TYPE_PERIOD; var $val_multi = true; function __construct() { parent::__construct(); $this->valid_parameters = array( 'FBTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } $pos = strpos($value, '/'); // We know there's only one / in there if($value[$pos - 1] != 'Z') { // Start time MUST be in UTC return false; } if($value[$pos + 1] != 'P' && substr($value, -1) != 'Z') { // If the second part is not a period, it MUST be in UTC return false; } return true; } // TODO: these properties SHOULD be shorted in ascending order (by start time and end time as tiebreak) } class iCalendar_property_transp extends iCalendar_property { var $name = 'TRANSP'; var $val_type = RFC2445_TYPE_TEXT; var $val_default = 'OPAQUE'; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { return ($value === 'TRANSPARENT' || $value === 'OPAQUE'); } } // TODO: 4.8.3 timezone component properties // 4.8.4 Relationship Component Properties // --------------------------------------- class iCalendar_property_attendee extends iCalendar_property { var $name = 'ATTENDEE'; var $val_type = RFC2445_TYPE_CAL_ADDRESS; // TODO: MUST NOT be specified when the calendar object has METHOD=PUBLISH // TODO: standard has lots of detail here, make triple sure that we eventually conform function __construct() { parent::__construct(); $this->valid_parameters = array( 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'CN' => RFC2445_OPTIONAL | RFC2445_ONCE, 'ROLE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'PARTSTAT' => RFC2445_OPTIONAL | RFC2445_ONCE, 'RSVP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'CUTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'MEMBER' => RFC2445_OPTIONAL | RFC2445_ONCE, 'DELEGATED-TO' => RFC2445_OPTIONAL | RFC2445_ONCE, 'DELEGATED-FROM' => RFC2445_OPTIONAL | RFC2445_ONCE, 'SENT-BY' => RFC2445_OPTIONAL | RFC2445_ONCE, 'DIR' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function set_parent_component($componentname) { if(!parent::set_parent_component($componentname)) { return false; } if($this->parent_component == 'VFREEBUSY' || $this->parent_component == 'VALARM') { // Most parameters become invalid in this case, the full allowed set is now: $this->valid_parameters = array( 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } return false; } } class iCalendar_property_contact extends iCalendar_property { var $name = 'CONTACT'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'ALTREP' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_organizer extends iCalendar_property { var $name = 'ORGANIZER'; var $val_type = RFC2445_TYPE_CAL_ADDRESS; function __construct() { parent::__construct(); $this->valid_parameters = array( 'CN' => RFC2445_OPTIONAL | RFC2445_ONCE, 'DIR' => RFC2445_OPTIONAL | RFC2445_ONCE, 'SENT-BY' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } // TODO: /* Conformance: This property MUST be specified in an iCalendar object that specifies a group scheduled calendar entity. This property MUST be specified in an iCalendar object that specifies the publication of a calendar user's busy time. This property MUST NOT be specified in an iCalendar object that specifies only a time zone definition or that defines calendar entities that are not group scheduled entities, but are entities only on a single user's calendar. */ } class iCalendar_property_recurrence_id extends iCalendar_property { // TODO: can only be specified when defining recurring components in the calendar /* Conformance: This property can be specified in an iCalendar object containing a recurring calendar component. Description: The full range of calendar components specified by a recurrence set is referenced by referring to just the "UID" property value corresponding to the calendar component. The "RECURRENCE-ID" property allows the reference to an individual instance within the recurrence set. */ var $name = 'RECURRENCE-ID'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( 'RANGE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE, 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) { return false; } return true; } } class iCalendar_property_related_to extends iCalendar_property { var $name = 'RELATED-TO'; var $val_type = RFC2445_TYPE_TEXT; // TODO: the value of this property must reference another component's UID function __construct() { parent::__construct(); $this->valid_parameters = array( 'RELTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_url extends iCalendar_property { var $name = 'URL'; var $val_type = RFC2445_TYPE_URI; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_uid extends iCalendar_property { var $name = 'UID'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); // The exception to the rule: this is not a static value, so we // generate it on-the-fly here. Guaranteed to be different for // each instance of this property, too. Nice. $this->val_default = Bennu::generate_guid(); } } // 4.8.5 Recurrence Component Properties // ------------------------------------- class iCalendar_property_exdate extends iCalendar_property { var $name = 'EXDATE'; var $val_type = RFC2445_TYPE_DATE_TIME; var $val_multi = true; function __construct() { parent::__construct(); $this->valid_parameters = array( 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE, 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME')) { return false; } return true; } } class iCalendar_property_exrule extends iCalendar_property { var $name = 'EXRULE'; var $val_type = RFC2445_TYPE_RECUR; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_rdate extends iCalendar_property { var $name = 'RDATE'; var $val_type = RFC2445_TYPE_DATE_TIME; var $val_multi = true; function __construct() { parent::__construct(); $this->valid_parameters = array( 'TZID' => RFC2445_OPTIONAL | RFC2445_ONCE, 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_parameter($parameter, $value) { $parameter = strtoupper($parameter); if(!parent::is_valid_parameter($parameter, $value)) { return false; } if($parameter == 'VALUE' && !($value == 'DATE' || $value == 'DATE-TIME' || $value == 'PERIOD')) { return false; } return true; } } class iCalendar_property_rrule extends iCalendar_property { var $name = 'RRULE'; var $val_type = RFC2445_TYPE_RECUR; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } // 4.8.6 Alarm Component Properties // ------------------------------------------- class iCalendar_property_action extends iCalendar_property { var $name = 'ACTION'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Value must be one of the following, or an x-name. $valid_values = array('ACTION', 'DISPLAY', 'EMAIL', 'PROCEDURE'); return(in_array($value, $valid_values) || rfc2445_is_xname($value)); } } class iCalendar_property_repeat extends iCalendar_property { var $name = 'REPEAT'; var $val_type = RFC2445_TYPE_INTEGER; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } class iCalendar_property_trigger extends iCalendar_property { var $name = 'TRIGGER'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'VALUE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'RELATED' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Must either be DURATION or DATE_TIME type return(rfc2445_is_valid_value($value, RFC2445_TYPE_DURATION) || rfc2445_is_valid_value($value, RFC2445_TYPE_DATE_TIME)); } } // 4.8.7 Change Management Component Properties // -------------------------------------------- class iCalendar_property_created extends iCalendar_property { var $name = 'CREATED'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Time MUST be in UTC format return(substr($value, -1) == 'Z'); } } class iCalendar_property_dtstamp extends iCalendar_property { var $name = 'DTSTAMP'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Time MUST be in UTC format return(substr($value, -1) == 'Z'); } } class iCalendar_property_last_modified extends iCalendar_property { var $name = 'LAST-MODIFIED'; var $val_type = RFC2445_TYPE_DATE_TIME; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } // Time MUST be in UTC format return(substr($value, -1) == 'Z'); } } class iCalendar_property_sequence extends iCalendar_property { var $name = 'SEQUENCE'; var $val_type = RFC2445_TYPE_INTEGER; var $val_default = 0; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } $value = intval($value); return ($value >= 0); } } // 4.8.8 Miscellaneous Component Properties // ---------------------------------------- class iCalendar_property_x extends iCalendar_property { var $name = RFC2445_XNAME; var $val_type = NULL; function __construct() { parent::__construct(); $this->valid_parameters = array( // X-ALT-DESC (Description) can have FMTTYPE declaration of text/html is a property for HTML content. 'FMTTYPE' => RFC2445_OPTIONAL | RFC2445_ONCE, 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function set_name($name) { $name = strtoupper($name); if(rfc2445_is_xname($name)) { $this->name = $name; return true; } return false; } } class iCalendar_property_request_status extends iCalendar_property { // IMPORTANT NOTE: This property value includes TEXT fields // separated by semicolons. Unfortunately, auto-value-formatting // cannot be used in this case. As an exception, the value passed // to this property MUST be already escaped. var $name = 'REQUEST-STATUS'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!is_string($value) || empty($value)) { return false; } $len = strlen($value); $parts = array(); $from = 0; $escch = false; for($i = 0; $i < $len; ++$i) { if($value[$i] == ';' && !$escch) { // Token completed $parts[] = substr($value, $from, $i - $from); $from = $i + 1; continue; } $escch = ($value[$i] == '\\'); } // Add one last token with the remaining text; if the value // ended with a ';' it was illegal, so check that this token // is not the empty string. $parts[] = substr($value, $from); $count = count($parts); // May have 2 or 3 tokens (last one is optional) if($count != 2 && $count != 3) { return false; } // REMEMBER: if ANY part is empty, we have an illegal value // First token must be hierarchical numeric status (3 levels max) if(strlen($parts[0]) == 0) { return false; } if($parts[0][0] < '1' || $parts[0][0] > '4') { return false; } $len = strlen($parts[0]); // Max 3 levels, and can't end with a period if($len > 5 || $parts[0][$len - 1] == '.') { return false; } for($i = 1; $i < $len; ++$i) { if(($i & 1) == 1 && $parts[0][$i] != '.') { // Even-indexed chars must be periods return false; } else if(($i & 1) == 0 && ($parts[0][$i] < '0' || $parts[0][$i] > '9')) { // Odd-indexed chars must be numbers return false; } } // Second and third tokens must be TEXT, and already escaped, so // they are not allowed to have UNESCAPED semicolons, commas, slashes, // or any newlines at all for($i = 1; $i < $count; ++$i) { if(strpos($parts[$i], "\n") !== false) { return false; } $len = strlen($parts[$i]); if($len == 0) { // Cannot be empty return false; } $parts[$i] .= '#'; // This guard token saves some conditionals in the loop for($j = 0; $j < $len; ++$j) { $thischar = $parts[$i][$j]; $nextchar = $parts[$i][$j + 1]; if($thischar == '\\') { // Next char must now be one of ";,\nN" if($nextchar != ';' && $nextchar != ',' && $nextchar != '\\' && $nextchar != 'n' && $nextchar != 'N') { return false; } // OK, this escaped sequence is correct, bypass next char ++$j; continue; } if($thischar == ';' || $thischar == ',' || $thischar == '\\') { // This wasn't escaped as it should return false; } } } return true; } function set_value($value) { // Must override this, otherwise the value would be quoted again if($this->is_valid_value($value)) { $this->value = $value; return true; } return false; } } class iCalendar_property_tzid extends iCalendar_property { var $name = 'TZID'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } else { return true; } } } class iCalendar_property_tzname extends iCalendar_property { var $name = 'TZNAME'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( 'LANGUAGE' => RFC2445_OPTIONAL | RFC2445_ONCE, RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } else { return true; } } } class iCalendar_property_tzoffsetfrom extends iCalendar_property { var $name = 'TZOFFSETFROM'; var $val_type = RFC2445_TYPE_UTC_OFFSET; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } else { return true; } } } class iCalendar_property_tzoffsetto extends iCalendar_property { var $name = 'TZOFFSETTO'; var $val_type = RFC2445_TYPE_UTC_OFFSET; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } function is_valid_value($value) { if(!parent::is_valid_value($value)) { return false; } else { return true; } } } class iCalendar_property_tzurl extends iCalendar_property { var $name = 'TZURL'; var $val_type = RFC2445_TYPE_URI; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } ####################### /* class iCalendar_property_class extends iCalendar_property { var $name = 'CLASS'; var $val_type = RFC2445_TYPE_TEXT; function __construct() { parent::__construct(); $this->valid_parameters = array( RFC2445_XNAME => RFC2445_OPTIONAL ); } } */ iCalendar_components.php000064400000066431152267201730011423 0ustar00components)) { $this->components = array(); foreach($this->valid_components as $name) { $this->components[$name] = array(); } } } function get_name() { return $this->name; } function add_property($name, $value = NULL, $parameters = NULL) { // Uppercase first of all $name = strtoupper($name); // Are we trying to add a valid property? $xname = false; if(!isset($this->valid_properties[$name])) { // If not, is it an x-name as per RFC 2445? if(!rfc2445_is_xname($name)) { return false; } // Since this is an xname, all components are supposed to allow this property $xname = true; } // Create a property object of the correct class if($xname) { $property = new iCalendar_property_x; $property->set_name($name); } else { $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $name)); $property = new $classname; } // If $value is NULL, then this property must define a default value. if($value === NULL) { $value = $property->default_value(); if($value === NULL) { return false; } } // Set this property's parent component to ourselves, because some // properties behave differently according to what component they apply to. $property->set_parent_component($this->name); // Set parameters before value; this helps with some properties which // accept a VALUE parameter, and thus change their default value type. // The parameters must be valid according to property specifications if(!empty($parameters)) { foreach($parameters as $paramname => $paramvalue) { if(!$property->set_parameter($paramname, $paramvalue)) { return false; } } // Some parameters interact among themselves (e.g. ENCODING and VALUE) // so make sure that after the dust settles, these invariants hold true if(!$property->invariant_holds()) { return false; } } // $value MUST be valid according to the property data type if(!$property->set_value($value)) { return false; } // Check if the property already exists, and is limited to one occurrance, // DON'T overwrite the value - this can be done explicity with set_value() instead. if(!$xname && $this->valid_properties[$name] & RFC2445_ONCE && isset($this->properties[$name])) { return false; } else { // Otherwise add it to the instance array for this property $this->properties[$name][] = $property; } // Finally: after all these, does the component invariant hold? if(!$this->invariant_holds()) { // If not, completely undo the property addition array_pop($this->properties[$name]); if(empty($this->properties[$name])) { unset($this->properties[$name]); } return false; } return true; } function add_component($component) { // With the detailed interface, you can add only components with this function if(!is_object($component) || !is_subclass_of($component, 'iCalendar_component')) { return false; } $name = $component->get_name(); // Only valid components as specified by this component are allowed if(!in_array($name, $this->valid_components)) { return false; } // Add it $this->components[$name][] = $component; return true; } function get_property_list($name) { } function invariant_holds() { return true; } function is_valid() { // If we have any child components, check that they are all valid if(!empty($this->components)) { foreach($this->components as $component => $instances) { foreach($instances as $number => $instance) { if(!$instance->is_valid()) { return false; } } } } // Finally, check the valid property list for any mandatory properties // that have not been set and do not have a default value foreach($this->valid_properties as $property => $propdata) { if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) { $classname = 'iCalendar_property_'.strtolower(str_replace('-', '_', $property)); $object = new $classname; if($object->default_value() === NULL) { return false; } unset($object); } } return true; } function serialize() { // Check for validity of the object if(!$this->is_valid()) { return false; } // Maybe the object is valid, but there are some required properties that // have not been given explicit values. In that case, set them to defaults. foreach($this->valid_properties as $property => $propdata) { if(($propdata & RFC2445_REQUIRED) && empty($this->properties[$property])) { $this->add_property($property); } } // Start tag $string = rfc2445_fold('BEGIN:'.$this->name) . RFC2445_CRLF; // List of properties if(!empty($this->properties)) { foreach($this->properties as $name => $properties) { foreach($properties as $property) { $string .= $property->serialize(); } } } // List of components if(!empty($this->components)) { foreach($this->components as $name => $components) { foreach($components as $component) { $string .= $component->serialize(); } } } // End tag $string .= rfc2445_fold('END:'.$this->name) . RFC2445_CRLF; return $string; } /** * unserialize() * * I needed a way to convert an iCalendar component back to a Bennu object so I could * easily access and modify it after it had been stored; if this functionality is already * present somewhere in the library, I apologize for adding it here unnecessarily; however, * I couldn't find it so I added it myself. * @param string $string the iCalendar object to load in to this iCalendar_component * @return bool true if the file parsed with no errors. False if there were errors. */ function unserialize($string) { $string = rfc2445_unfold($string); // Unfold any long lines $lines = preg_split("<".RFC2445_CRLF."|\n|\r>", $string, 0, PREG_SPLIT_NO_EMPTY); // Create an array of lines. $components = array(); // Initialise a stack of components $this->clear_errors(); foreach ($lines as $key => $line) { // ignore empty lines if (trim($line) == '') { continue; } // Divide the line up into label, parameters and data fields. if (!preg_match('#^(?P