Back to index

awl  0.53
iCalendar.php
Go to the documentation of this file.
00001 <?php
00049 require_once('XMLElement.php');
00050 require_once('AwlQuery.php');
00051 
00057 class iCalProp {
00067   var $name;
00068 
00074   var $parameters;
00075 
00081   var $content;
00082 
00088   var $rendered;
00089 
00100   function iCalProp( $propstring = null ) {
00101     $this->name = "";
00102     $this->content = "";
00103     $this->parameters = array();
00104     unset($this->rendered);
00105     if ( $propstring != null && gettype($propstring) == 'string' ) {
00106       $this->ParseFrom($propstring);
00107     }
00108   }
00109 
00110 
00119   function ParseFrom( $propstring ) {
00120     $this->rendered = (strlen($propstring) < 72 ? $propstring : null);  // Only pre-rendered if we didn't unescape it
00121 
00122     $unescaped = preg_replace( '{\\\\[nN]}', "\n", $propstring);
00123  
00124     // Split into two parts on : which is not preceded by a \
00125     list( $start, $values) = preg_split( '{(?<!\\\\):}', $unescaped, 2);
00126     $this->content = preg_replace( "/\\\\([,;:\"\\\\])/", '$1', $values);
00127 
00128     // Split on ; which is not preceded by a \
00129     $parameters = preg_split( '{(?<!\\\\);}', $start);
00130 
00131     $parameters = explode(';',$start);
00132     $this->name = array_shift( $parameters );
00133     $this->parameters = array();
00134     foreach( $parameters AS $k => $v ) {
00135       $pos = strpos($v,'=');
00136       $name = substr( $v, 0, $pos);
00137       $value = substr( $v, $pos + 1);
00138       $this->parameters[$name] = $value;
00139     }
00140 //    dbg_error_log('iCalendar', " iCalProp::ParseFrom found '%s' = '%s' with %d parameters", $this->name, substr($this->content,0,200), count($this->parameters) );
00141   }
00142 
00143 
00151   function Name( $newname = null ) {
00152     if ( $newname != null ) {
00153       $this->name = $newname;
00154       if ( isset($this->rendered) ) unset($this->rendered);
00155 //      dbg_error_log('iCalendar', " iCalProp::Name(%s)", $this->name );
00156     }
00157     return $this->name;
00158   }
00159 
00160 
00168   function Value( $newvalue = null ) {
00169     if ( $newvalue != null ) {
00170       $this->content = $newvalue;
00171       if ( isset($this->rendered) ) unset($this->rendered);
00172     }
00173     return $this->content;
00174   }
00175 
00176 
00184   function Parameters( $newparams = null ) {
00185     if ( $newparams != null ) {
00186       $this->parameters = $newparams;
00187       if ( isset($this->rendered) ) unset($this->rendered);
00188     }
00189     return $this->parameters;
00190   }
00191 
00192 
00200   function TextMatch( $search ) {
00201     if ( isset($this->content) ) {
00202       return (stristr( $this->content, $search ) !== false);      
00203     }
00204     return false;
00205   }
00206 
00207 
00215   function GetParameterValue( $name ) {
00216     if ( isset($this->parameters[$name]) ) return $this->parameters[$name];
00217   }
00218 
00226   function SetParameterValue( $name, $value ) {
00227     if ( isset($this->rendered) ) unset($this->rendered);
00228     $this->parameters[$name] = $value;
00229   }
00230 
00235   function RenderParameters() {
00236     $rendered = "";
00237     foreach( $this->parameters AS $k => $v ) {
00238       $escaped = preg_replace( "/([;:])/", '\\\\$1', $v);
00239       $rendered .= sprintf( ";%s=%s", $k, $escaped );
00240     }
00241     return $rendered;
00242   }
00243 
00244 
00248   function Render() {
00249     // If we still have the string it was parsed in from, it hasn't been screwed with
00250     // and we can just return that without modification.
00251     if ( isset($this->rendered) ) return $this->rendered;
00252 
00253     $property = preg_replace( '/[;].*$/', '', $this->name );
00254     $escaped = $this->content;
00255     switch( $property ) {
00257       case 'ATTACH':                case 'GEO':                       case 'PERCENT-COMPLETE':      case 'PRIORITY':
00258       case 'DURATION':              case 'FREEBUSY':                  case 'TZOFFSETFROM':          case 'TZOFFSETTO':
00259       case 'TZURL':                 case 'ATTENDEE':                  case 'ORGANIZER':             case 'RECURRENCE-ID':
00260       case 'URL':                   case 'EXRULE':                    case 'SEQUENCE':              case 'CREATED':
00261       case 'RRULE':                 case 'REPEAT':                    case 'TRIGGER':
00262         break;
00263 
00264       case 'COMPLETED':             case 'DTEND':
00265       case 'DUE':                   case 'DTSTART':
00266       case 'DTSTAMP':               case 'LAST-MODIFIED':
00267       case 'CREATED':               case 'EXDATE':
00268       case 'RDATE':
00269         if ( isset($this->parameters['VALUE']) && $this->parameters['VALUE'] == 'DATE' ) {
00270           $escaped = substr( $escaped, 0, 8);
00271         }
00272         break;
00273 
00275       default:
00276         $escaped = str_replace( '\\', '\\\\', $escaped);
00277         $escaped = preg_replace( '/\r?\n/', '\\n', $escaped);
00278         $escaped = preg_replace( "/([,;])/", '\\\\$1', $escaped);
00279     }
00280     $property = sprintf( "%s%s:", $this->name, $this->RenderParameters() );
00281     if ( (strlen($property) + strlen($escaped)) <= 72 ) {
00282       $this->rendered = $property . $escaped;
00283     }
00284     else if ( (strlen($property) + strlen($escaped)) > 72 && (strlen($property) < 72) && (strlen($escaped) < 72) ) {
00285       $this->rendered = $property . "\r\n " . $escaped;
00286     }
00287     else {
00288       $this->rendered = preg_replace( '/(.{72})/u', '$1'."\r\n ", $property . $escaped );
00289     }
00290     return $this->rendered;
00291   }
00292 
00293 }
00294 
00295 
00301 class iCalComponent {
00311   var $type;
00312 
00318   var $properties;
00319 
00325   var $components;
00326 
00332   var $rendered;
00333 
00339   function iCalComponent( $content = null ) {
00340     $this->type = "";
00341     $this->properties = array();
00342     $this->components = array();
00343     $this->rendered = "";
00344     if ( $content != null && (gettype($content) == 'string' || gettype($content) == 'array') ) {
00345       $this->ParseFrom($content);
00346     }
00347   }
00348 
00349 
00354   function VCalendar( $extra_properties = null ) {
00355     $this->SetType('VCALENDAR');
00356     $this->AddProperty('PRODID', '-//davical.org//NONSGML AWL Calendar//EN');
00357     $this->AddProperty('VERSION', '2.0');
00358     $this->AddProperty('CALSCALE', 'GREGORIAN');
00359     if ( is_array($extra_properties) ) {
00360       foreach( $extra_properties AS $k => $v ) {
00361         $this->AddProperty($k,$v);
00362       }
00363     }
00364   }
00365 
00370   function CollectParameterValues( $parameter_name ) {
00371     $values = array();
00372     foreach( $this->components AS $k => $v ) {
00373       $also = $v->CollectParameterValues($parameter_name);
00374       $values = array_merge( $values, $also );
00375     }
00376     foreach( $this->properties AS $k => $v ) {
00377       $also = $v->GetParameterValue($parameter_name);
00378       if ( isset($also) && $also != "" ) {
00379 //        dbg_error_log( 'iCalendar', "::CollectParameterValues(%s) : Found '%s'", $parameter_name, $also);
00380         $values[$also] = 1;
00381       }
00382     }
00383     return $values;
00384   }
00385 
00386 
00391   function ParseFrom( $content ) {
00392     $this->rendered = $content;
00393     $content = $this->UnwrapComponent($content);
00394 
00395     $type = false;
00396     $subtype = false;
00397     $finish = null;
00398     $subfinish = null;
00399 
00400     $length = strlen($content);
00401     $linefrom = 0;
00402     while( $linefrom < $length ) {
00403       $lineto = strpos( $content, "\n", $linefrom );
00404       if ( $lineto === false ) {
00405         $lineto = strpos( $content, "\r", $linefrom );
00406       }
00407       if ( $lineto > 0 ) {
00408         $line = substr( $content, $linefrom, $lineto - $linefrom);
00409         $linefrom = $lineto + 1;
00410       }
00411       else {
00412         $line = substr( $content, $linefrom );
00413         $linefrom = $length;
00414       }
00415       if ( preg_match('/^\s*$/', $line ) ) continue;
00416       $line = rtrim( $line, "\r\n" );
00417 //      dbg_error_log( 'iCalendar',  "::ParseFrom: Parsing line: $line");
00418 
00419       if ( $type === false ) {
00420         if ( preg_match( '/^BEGIN:(.+)$/', $line, $matches ) ) {
00421           // We have found the start of the main component
00422           $type = $matches[1];
00423           $finish = "END:$type";
00424           $this->type = $type;
00425           dbg_error_log( 'iCalendar', "::ParseFrom: Start component of type '%s'", $type);
00426         }
00427         else {
00428           dbg_error_log( 'iCalendar', "::ParseFrom: Ignoring crap before start of component: $line");
00429           // unset($lines[$k]);  // The content has crap before the start
00430           if ( $line != "" ) $this->rendered = null;
00431         }
00432       }
00433       else if ( $type == null ) {
00434         dbg_error_log( 'iCalendar', "::ParseFrom: Ignoring crap after end of component");
00435         if ( $line != "" ) $this->rendered = null;
00436       }
00437       else if ( $line == $finish ) {
00438         dbg_error_log( 'iCalendar', "::ParseFrom: End of component");
00439         $type = null;  // We have reached the end of our component
00440       }
00441       else {
00442         if ( $subtype === false && preg_match( '/^BEGIN:(.+)$/', $line, $matches ) ) {
00443           // We have found the start of a sub-component
00444           $subtype = $matches[1];
00445           $subfinish = "END:$subtype";
00446           $subcomponent = $line . "\r\n";
00447           dbg_error_log( 'iCalendar', "::ParseFrom: Found a subcomponent '%s'", $subtype);
00448         }
00449         else if ( $subtype ) {
00450           // We are inside a sub-component
00451           $subcomponent .= $this->WrapComponent($line);
00452           if ( $line == $subfinish ) {
00453             dbg_error_log( 'iCalendar', "::ParseFrom: End of subcomponent '%s'", $subtype);
00454             // We have found the end of a sub-component
00455             $this->components[] = new iCalComponent($subcomponent);
00456             $subtype = false;
00457           }
00458 //          else
00459 //            dbg_error_log( 'iCalendar', "::ParseFrom: Inside a subcomponent '%s'", $subtype );
00460         }
00461         else {
00462 //          dbg_error_log( 'iCalendar', "::ParseFrom: Parse property of component");
00463           // It must be a normal property line within a component.
00464           $this->properties[] = new iCalProp($line);
00465         }
00466       }
00467     }
00468   }
00469 
00470 
00476   function UnwrapComponent( $content ) {
00477     return preg_replace('/\r?\n[ \t]/', '', $content );
00478   }
00479 
00488   function WrapComponent( $content ) {
00489     $strs = preg_split( "/\r?\n/", $content );
00490     $wrapped = "";
00491     foreach ($strs as $str) {
00492       $wrapped .= preg_replace( '/(.{72})/u', '$1'."\r\n ", $str ) ."\r\n";
00493     }
00494     return $wrapped;
00495   }
00496 
00500   function GetType() {
00501     return $this->type;
00502   }
00503 
00504 
00508   function SetType( $type ) {
00509     if ( isset($this->rendered) ) unset($this->rendered);
00510     $this->type = $type;
00511     return $this->type;
00512   }
00513 
00514 
00518   function GetProperties( $type = null ) {
00519     $properties = array();
00520     foreach( $this->properties AS $k => $v ) {
00521       if ( $type == null || $v->Name() == $type ) {
00522         $properties[$k] = $v;
00523       }
00524     }
00525     return $properties;
00526   }
00527 
00528 
00536   function GetPValue( $type ) {
00537     foreach( $this->properties AS $k => $v ) {
00538       if ( $v->Name() == $type ) return $v->Value();
00539     }
00540     return null;
00541   }
00542 
00543 
00552   function GetPParamValue( $type, $parameter_name ) {
00553     foreach( $this->properties AS $k => $v ) {
00554       if ( $v->Name() == $type ) return $v->GetParameterValue($parameter_name);
00555     }
00556     return null;
00557   }
00558 
00559 
00564   function ClearProperties( $type = null ) {
00565     if ( $type != null ) {
00566       // First remove all the existing ones of that type
00567       foreach( $this->properties AS $k => $v ) {
00568         if ( $v->Name() == $type ) {
00569           unset($this->properties[$k]);
00570           if ( isset($this->rendered) ) unset($this->rendered);
00571         }
00572       }
00573       $this->properties = array_values($this->properties);
00574     }
00575     else {
00576       if ( isset($this->rendered) ) unset($this->rendered);
00577       $this->properties = array();
00578     }
00579   }
00580 
00581 
00585   function SetProperties( $new_properties, $type = null ) {
00586     if ( isset($this->rendered) && count($new_properties) > 0 ) unset($this->rendered);
00587     $this->ClearProperties($type);
00588     foreach( $new_properties AS $k => $v ) {
00589       $this->AddProperty($v);
00590     }
00591   }
00592 
00593 
00601   function AddProperty( $new_property, $value = null, $parameters = null ) {
00602     if ( isset($this->rendered) ) unset($this->rendered);
00603     if ( isset($value) && gettype($new_property) == 'string' ) {
00604       $new_prop = new iCalProp();
00605       $new_prop->Name($new_property);
00606       $new_prop->Value($value);
00607       if ( $parameters != null ) $new_prop->Parameters($parameters);
00608       dbg_error_log('iCalendar'," Adding new property '%s'", $new_prop->Render() );
00609       $this->properties[] = $new_prop;
00610     }
00611     else if ( gettype($new_property) ) {
00612       $this->properties[] = $new_property;
00613     }
00614   }
00615 
00616 
00621   function &FirstNonTimezone( $type = null ) {
00622     foreach( $this->components AS $k => $v ) {
00623       if ( $v->GetType() != 'VTIMEZONE' ) return $this->components[$k];
00624     }
00625     $result = false;
00626     return $result;
00627   }
00628 
00629 
00636   function IsOrganizer( $email ) {
00637     if ( !preg_match( '#^mailto:#', $email ) ) $email = 'mailto:'.$email;
00638     $props = $this->GetPropertiesByPath('!VTIMEZONE/ORGANIZER');
00639     foreach( $props AS $k => $prop ) {
00640       if ( $prop->Value() == $email ) return true;
00641     }
00642     return false;
00643   }
00644 
00645 
00652   function IsAttendee( $email ) {
00653     if ( !preg_match( '#^mailto:#', $email ) ) $email = 'mailto:'.$email;
00654     if ( $this->IsOrganizer($email) ) return true; 
00655     $props = $this->GetPropertiesByPath('!VTIMEZONE/ATTENDEE');
00656     foreach( $props AS $k => $prop ) {
00657       if ( $prop->Value() == $email ) return true;
00658     }
00659     return false;
00660   }
00661 
00662 
00671   function GetComponents( $type = null, $normal_match = true ) {
00672     $components = $this->components;
00673     if ( $type != null ) {
00674       foreach( $components AS $k => $v ) {
00675         if ( ($v->GetType() != $type) === $normal_match ) {
00676           unset($components[$k]);
00677         }
00678       }
00679       $components = array_values($components);
00680     }
00681     return $components;
00682   }
00683 
00684 
00689   function ClearComponents( $type = null ) {
00690     if ( $type != null ) {
00691       // First remove all the existing ones of that type
00692       foreach( $this->components AS $k => $v ) {
00693         if ( $v->GetType() == $type ) {
00694           unset($this->components[$k]);
00695           if ( isset($this->rendered) ) unset($this->rendered);
00696         }
00697         else {
00698           if ( ! $this->components[$k]->ClearComponents($type) ) {
00699             if ( isset($this->rendered) ) unset($this->rendered);
00700           }
00701         }
00702       }
00703       return isset($this->rendered);
00704     }
00705     else {
00706       if ( isset($this->rendered) ) unset($this->rendered);
00707       $this->components = array();
00708     }
00709   }
00710 
00711 
00718   function SetComponents( $new_component, $type = null ) {
00719     if ( isset($this->rendered) ) unset($this->rendered);
00720     if ( count($new_component) > 0 ) $this->ClearComponents($type);
00721     foreach( $new_component AS $k => $v ) {
00722       $this->components[] = $v;
00723     }
00724   }
00725 
00726 
00732   function AddComponent( $new_component ) {
00733     if ( is_array($new_component) && count($new_component) == 0 ) return;
00734     if ( isset($this->rendered) ) unset($this->rendered);
00735     if ( is_array($new_component) ) {
00736       foreach( $new_component AS $k => $v ) {
00737         $this->components[] = $v;
00738       }
00739     }
00740     else {
00741       $this->components[] = $new_component;
00742     }
00743   }
00744 
00745 
00750   function MaskComponents( $keep ) {
00751     foreach( $this->components AS $k => $v ) {
00752       if ( ! in_array( $v->GetType(), $keep ) ) {
00753         unset($this->components[$k]);
00754         if ( isset($this->rendered) ) unset($this->rendered);
00755       }
00756       else {
00757         $v->MaskComponents($keep);
00758       }
00759     }
00760   }
00761 
00762 
00768   function MaskProperties( $keep, $component_list=null ) {
00769     foreach( $this->components AS $k => $v ) {
00770       $v->MaskProperties($keep, $component_list);
00771     }
00772 
00773     if ( !isset($component_list) || in_array($this->GetType(),$component_list) ) {
00774       foreach( $this->components AS $k => $v ) {
00775         if ( ! in_array( $v->GetType(), $keep ) ) {
00776           unset($this->components[$k]);
00777           if ( isset($this->rendered) ) unset($this->rendered);
00778         }
00779       }
00780     }
00781   }
00782 
00783 
00789   function CloneConfidential() {
00790     $confidential = clone($this);
00791     $keep_properties = array( 'DTSTAMP', 'DTSTART', 'RRULE', 'DURATION', 'DTEND', 'DUE', 'UID', 'CLASS', 'TRANSP', 'CREATED', 'LAST-MODIFIED' );
00792     $resource_components = array( 'VEVENT', 'VTODO', 'VJOURNAL' );
00793     $confidential->MaskComponents(array( 'VTIMEZONE', 'VEVENT', 'VTODO', 'VJOURNAL' ));
00794     $confidential->MaskProperties($keep_properties, $resource_components );
00795     if ( in_array( $confidential->GetType(), $resource_components ) ) {
00796       $confidential->AddProperty( 'SUMMARY', translate('Busy') );
00797     }
00798     foreach( $confidential->components AS $k => $v ) {
00799       if ( in_array( $v->GetType(), $resource_components ) ) {
00800         $v->AddProperty( 'SUMMARY', translate('Busy') );
00801       }
00802     }
00803 
00804     return $confidential;
00805   }
00806 
00807 
00811   function Render( $restricted_properties = null) {
00812 
00813     $unrestricted = (!isset($restricted_properties) || count($restricted_properties) == 0);
00814 
00815     if ( isset($this->rendered) && $unrestricted )
00816       return $this->rendered;
00817 
00818     $rendered = "BEGIN:$this->type\r\n";
00819     foreach( $this->properties AS $k => $v ) {
00820       if ( method_exists($v, 'Render') ) {
00821         if ( $unrestricted || isset($restricted_properties[$v]) ) $rendered .= $v->Render() . "\r\n";
00822       }
00823     }
00824     foreach( $this->components AS $v ) {   $rendered .= $v->Render();  }
00825     $rendered .= "END:$this->type\r\n";
00826 
00827     $rendered = preg_replace('{(?<!\r)\n}', "\r\n", $rendered);
00828     if ( $unrestricted ) $this->rendered = $rendered;
00829 
00830     return $rendered;
00831   }
00832 
00833 
00843   function GetPropertiesByPath( $path ) {
00844     $properties = array();
00845     dbg_error_log( 'iCalendar', "GetPropertiesByPath: Querying within '%s' for path '%s'", $this->type, $path );
00846     if ( !preg_match( '#(/?)(!?)([^/]+)(/?.*)$#', $path, $matches ) ) return $properties;
00847 
00848     $adrift = ($matches[1] == '');
00849     $normal = ($matches[2] == '');
00850     $ourtest = $matches[3];
00851     $therest = $matches[4];
00852     dbg_error_log( 'iCalendar', "GetPropertiesByPath: Matches: %s -- %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3], $matches[4] );
00853     if ( $ourtest == '*' || (($ourtest == $this->type) === $normal) && $therest != '' ) {
00854       if ( preg_match( '#^/(!?)([^/]+)$#', $therest, $matches ) ) {
00855         $normmatch = ($matches[1] =='');
00856         $proptest  = $matches[2];
00857         foreach( $this->properties AS $k => $v ) {
00858           if ( $proptest == '*' || (($v->Name() == $proptest) === $normmatch ) ) {
00859             $properties[] = $v;
00860           }
00861         }
00862       }
00863       else {
00867         foreach( $this->components AS $k => $v ) {
00868           $properties = array_merge( $properties, $v->GetPropertiesByPath($therest) );
00869         }
00870       }
00871     }
00872 
00873     if ( $adrift ) {
00877       foreach( $this->components AS $k => $v ) {
00878         $properties = array_merge( $properties, $v->GetPropertiesByPath($path) );
00879       }
00880     }
00881     dbg_error_log('iCalendar', "GetPropertiesByPath: Found %d within '%s' for path '%s'\n", count($properties), $this->type, $path );
00882     return $properties;
00883   }
00884 
00885 }
00886 
00899 class iCalendar {  // DEPRECATED
00908   var $component;
00909 
00914   var $properties;
00915 
00920   var $lines;
00921 
00926   var $tz_locn;
00927 
00932   var $type;
00933 
00942   function iCalendar( $args ) {
00943     global $c;
00944 
00945     deprecated('iCalendar::iCalendar');
00946     $this->tz_locn = "";
00947     if ( !isset($args) || !(is_array($args) || is_object($args)) ) return;
00948     if ( is_object($args) ) {
00949       settype($args,'array');
00950     }
00951 
00952     $this->component = new iCalComponent();
00953     if ( isset($args['icalendar']) ) {
00954       $this->component->ParseFrom($args['icalendar']);
00955       $this->lines = preg_split('/\r?\n/', $args['icalendar'] );
00956       $this->SaveTimeZones();
00957       $first =& $this->component->FirstNonTimezone();
00958       if ( $first ) {
00959         $this->type = $first->GetType();
00960         $this->properties = $first->GetProperties();
00961       }
00962       else {
00963         $this->properties = array();
00964       }
00965       $this->properties['VCALENDAR'] = array('***ERROR*** This class is being referenced in an unsupported way!');
00966       return;
00967     }
00968 
00969     if ( isset($args['type'] ) ) {
00970       $this->type = $args['type'];
00971       unset( $args['type'] );
00972     }
00973     else {
00974       $this->type = 'VEVENT';  // Default to event
00975     }
00976     $this->component->SetType('VCALENDAR');
00977     $this->component->SetProperties(
00978         array(
00979           new iCalProp('PRODID:-//davical.org//NONSGML AWL Calendar//EN'),
00980           new iCalProp('VERSION:2.0'),
00981           new iCalProp('CALSCALE:GREGORIAN')
00982         )
00983     );
00984     $first = new iCalComponent();
00985     $first->SetType($this->type);
00986     $this->properties = array();
00987 
00988     foreach( $args AS $k => $v ) {
00989       dbg_error_log( 'iCalendar', ":Initialise: %s to >>>%s<<<", $k, $v );
00990       $property = new iCalProp();
00991       $property->Name($k);
00992       $property->Value($v);
00993       $this->properties[] = $property;
00994     }
00995     $first->SetProperties($this->properties);
00996     $this->component->SetComponents( array($first) );
00997 
00998     $this->properties['VCALENDAR'] = array('***ERROR*** This class is being referenced in an unsupported way!');
00999 
01003     if ( $this->tz_locn == "" ) {
01004       $this->tz_locn = $this->Get("tzid");
01005       if ( (!isset($this->tz_locn) || $this->tz_locn == "") && isset($c->local_tzid) ) {
01006         $this->tz_locn = $c->local_tzid;
01007       }
01008     }
01009   }
01010 
01011 
01016   function SaveTimeZones() {
01017     global $c;
01018 
01019     deprecated('iCalendar::SaveTimeZones');
01020     $this->tzid_list = array_keys($this->component->CollectParameterValues('TZID'));
01021     if ( ! isset($this->tzid) && count($this->tzid_list) > 0 ) {
01022       dbg_error_log( 'iCalendar', "::TZID_List[0] = '%s', count=%d", $this->tzid_list[0], count($this->tzid_list) );
01023       $this->tzid = $this->tzid_list[0];
01024     }
01025 
01026     $timezones = $this->component->GetComponents('VTIMEZONE');
01027     if ( $timezones === false || count($timezones) == 0 ) return;
01028     $this->vtimezone = $timezones[0]->Render();  // Backward compatibility
01029 
01030     $tzid = $this->Get('TZID');
01031     if ( isset($c->save_time_zone_defs) && $c->save_time_zone_defs ) {
01032       foreach( $timezones AS $k => $tz ) {
01033         $tzid = $tz->GetPValue('TZID');
01034 
01035         $qry = new AwlQuery( "SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $tzid );
01036         if ( $qry->Exec('iCalendar') && $qry->rows() == 1 ) {
01037           $row = $qry->Fetch();
01038           if ( !isset($first_tzid) ) $first_tzid = $row->tz_locn;
01039           continue;
01040         }
01041 
01042         if ( $tzid != "" && $qry->rows() == 0 ) {
01043 
01044           $tzname = $tz->GetPValue('X-LIC-LOCATION');
01045           if ( !isset($tzname) ) $tzname = olson_from_tzstring($tzid);
01046 
01047           $qry2 = new AwlQuery( "INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES( ?, ?, ? );",
01048                                       $tzid, $tzname, $tz->Render() );
01049           $qry2->Exec('iCalendar');
01050         }
01051       }
01052     }
01053     if ( ! isset($this->tzid) && isset($first_tzid) ) $this->tzid = $first_tzid;
01054 
01055     if ( (!isset($this->tz_locn) || $this->tz_locn == '') && isset($first_tzid) && $first_tzid != '' ) {
01056       $tzname = preg_replace('#^(.*[^a-z])?([a-z]+/[a-z]+)$#i','$2', $first_tzid );
01057       if ( preg_match( '#\S+/\S+#', $tzname) ) {
01058         $this->tz_locn = $tzname;
01059       }
01060       dbg_error_log( 'iCalendar', " TZCrap1: TZID '%s', Location '%s', Perhaps: %s", $tzid, $this->tz_locn, $tzname );
01061     }
01062 
01063     if ( (!isset($this->tz_locn) || $this->tz_locn == "") && isset($c->local_tzid) ) {
01064       $this->tz_locn = $c->local_tzid;
01065     }
01066     if ( ! isset($this->tzid) && isset($this->tz_locn) ) $this->tzid = $this->tz_locn;
01067   }
01068 
01069 
01076   function DefaultPropertyList() {
01077     dbg_error_log( "LOG", " iCalendar: Call to deprecated method '%s'", 'DefaultPropertyList' );
01078     return array( "UID" => 1, "DTSTAMP" => 1, "DTSTART" => 1, "DURATION" => 1,
01079                   "LAST-MODIFIED" => 1,"CLASS" => 1, "TRANSP" => 1, "SEQUENCE" => 1,
01080                   "DUE" => 1, "SUMMARY" => 1, "RRULE" => 1 );
01081   }
01082 
01095   function JustThisBitPlease( $type, $count=1 ) {
01096     deprecated('iCalendar::JustThisBitPlease' );
01097     $answer = "";
01098     $intags = false;
01099     $start = "BEGIN:$type";
01100     $finish = "END:$type";
01101     dbg_error_log( 'iCalendar', ":JTBP: Looking for %d subsets of type %s", $count, $type );
01102     reset($this->lines);
01103     foreach( $this->lines AS $k => $v ) {
01104       if ( !$intags && $v == $start ) {
01105         $answer .= $v . "\n";
01106         $intags = true;
01107       }
01108       else if ( $intags && $v == $finish ) {
01109         $answer .= $v . "\n";
01110         $intags = false;
01111       }
01112       else if ( $intags ) {
01113         $answer .= $v . "\n";
01114       }
01115     }
01116     return $answer;
01117   }
01118 
01119 
01129   function &ParseSomeLines( $type ) {
01130     deprecated('iCalendar::ParseSomeLines' );
01131     $props = array();
01132     $properties =& $props;
01133     while( isset($this->lines[$this->_current_parse_line]) ) {
01134       $i = $this->_current_parse_line++;
01135       $line =& $this->lines[$i];
01136       dbg_error_log( 'iCalendar', ":Parse:%s LINE %03d: >>>%s<<<", $type, $i, $line );
01137       if ( $this->parsing_vtimezone ) {
01138         $this->vtimezone .= $line."\n";
01139       }
01140       if ( preg_match( '/^(BEGIN|END):([^:]+)$/', $line, $matches ) ) {
01141         if ( $matches[1] == 'END' && $matches[2] == $type ) {
01142           if ( $type == 'VTIMEZONE' ) {
01143             $this->parsing_vtimezone = false;
01144           }
01145           return $properties;
01146         }
01147         else if( $matches[1] == 'END' ) {
01148           dbg_error_log("ERROR"," iCalendar: parse error: Unexpected END:%s when we were looking for END:%s", $matches[2], $type );
01149           return $properties;
01150         }
01151         else if( $matches[1] == 'BEGIN' ) {
01152           $subtype = $matches[2];
01153           if ( $subtype == 'VTIMEZONE' ) {
01154             $this->parsing_vtimezone = true;
01155             $this->vtimezone = $line."\n";
01156           }
01157           if ( !isset($properties['INSIDE']) ) $properties['INSIDE'] = array();
01158           $properties['INSIDE'][] = $subtype;
01159           if ( !isset($properties[$subtype]) ) $properties[$subtype] = array();
01160           $properties[$subtype][] = $this->ParseSomeLines($subtype);
01161         }
01162       }
01163       else {
01164         // Parse the property
01165         @list( $property, $value ) = explode(':', $line, 2 );
01166         if ( strpos( $property, ';' ) > 0 ) {
01167           $parameterlist = explode(';', $property );
01168           $property = array_shift($parameterlist);
01169           foreach( $parameterlist AS $pk => $pv ) {
01170             if ( $pv == "VALUE=DATE" ) {
01171               $value .= 'T000000';
01172             }
01173             elseif ( preg_match('/^([^;:=]+)=([^;:=]+)$/', $pv, $matches) ) {
01174               switch( $matches[1] ) {
01175                 case 'TZID': $properties['TZID'] = $matches[2];  break;
01176                 default:
01177                   dbg_error_log( 'iCalendar', " FYI: Ignoring Resource '%s', Property '%s', Parameter '%s', Value '%s'", $type, $property, $matches[1], $matches[2] );
01178               }
01179             }
01180           }
01181         }
01182         if ( $this->parsing_vtimezone && (!isset($this->tz_locn) || $this->tz_locn == "") && $property == 'X-LIC-LOCATION' ) {
01183           $this->tz_locn = $value;
01184         }
01185         $properties[strtoupper($property)] = $this->RFC2445ContentUnescape($value);
01186       }
01187     }
01188     return $properties;
01189   }
01190 
01191 
01200   function BuildFromText( $icalendar ) {
01201     deprecated('iCalendar::BuildFromText' );
01207     $icalendar = preg_replace('/\r?\n[ \t]/', '', $icalendar );
01208 
01209     $this->lines = preg_split('/\r?\n/', $icalendar );
01210 
01211     $this->_current_parse_line = 0;
01212     $this->properties = $this->ParseSomeLines('');
01213 
01217     if ( isset($this->properties['VCALENDAR'][0]['INSIDE']) ) {
01218       foreach ( $this->properties['VCALENDAR'][0]['INSIDE']  AS $k => $v ) {
01219         if ( $v == 'VTIMEZONE' ) continue;
01220         $this->type = $v;
01221         break;
01222       }
01223     }
01224 
01225   }
01226 
01227 
01237   function RFC2445ContentUnescape( $escaped ) {
01238     deprecated( 'RFC2445ContentUnescape' );
01239     $unescaped = str_replace( '\\n', "\n", $escaped);
01240     $unescaped = str_replace( '\\N', "\n", $unescaped);
01241     $unescaped = preg_replace( "/\\\\([,;:\"\\\\])/", '$1', $unescaped);
01242     return $unescaped;
01243   }
01244 
01245 
01246 
01254   function DealWithTimeZones() {
01255     global $c;
01256 
01257     deprecated('iCalendar::DealWithTimeZones' );
01258     $tzid = $this->Get('TZID');
01259     if ( isset($c->save_time_zone_defs) && $c->save_time_zone_defs ) {
01260       $qry = new AwlQuery( "SELECT tz_locn FROM time_zone WHERE tz_id = ?;", $tzid );
01261       if ( $qry->Exec('iCalendar') && $qry->rows() == 1 ) {
01262         $row = $qry->Fetch();
01263         $this->tz_locn = $row->tz_locn;
01264       }
01265       dbg_error_log( 'iCalendar', " TZCrap2: TZID '%s', DB Rows=%d, Location '%s'", $tzid, $qry->rows(), $this->tz_locn );
01266     }
01267 
01268     if ( (!isset($this->tz_locn) || $this->tz_locn == '') && $tzid != '' ) {
01273       $tzname = preg_replace('#^(.*[^a-z])?([a-z]+/[a-z]+)$#i','$1',$tzid );
01286       if ( preg_match( '#\S+/\S+#', $tzname) ) {
01287         $this->tz_locn = $tzname;
01288       }
01289       dbg_error_log( 'iCalendar', " TZCrap3: TZID '%s', Location '%s', Perhaps: %s", $tzid, $this->tz_locn, $tzname );
01290     }
01291 
01292     if ( $tzid != '' && isset($c->save_time_zone_defs) && $c->save_time_zone_defs && $qry->rows() != 1 && isset($this->vtimezone) && $this->vtimezone != "" ) {
01293       $qry2 = new AwlQuery( "INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES( ?, ?, ? );",
01294                                    $tzid, $this->tz_locn, $this->vtimezone );
01295       $qry2->Exec('iCalendar');
01296     }
01297 
01298     if ( (!isset($this->tz_locn) || $this->tz_locn == "") && isset($c->local_tzid) ) {
01299       $this->tz_locn = $c->local_tzid;
01300     }
01301   }
01302 
01303 
01308   function Get( $key ) {
01309     deprecated('iCalendar::Get' );
01310     if ( strtoupper($key) == 'TZID' ) {
01311       // backward compatibility hack
01312       dbg_error_log( 'iCalendar', " Get(TZID): TZID '%s', Location '%s'", (isset($this->tzid)?$this->tzid:"[not set]"), $this->tz_locn );
01313       if ( isset($this->tzid) ) return $this->tzid;
01314       return $this->tz_locn;
01315     }
01319     $component =& $this->component->FirstNonTimezone();
01320     if ( $component === false ) return null;
01321     return $component->GetPValue(strtoupper($key));
01322   }
01323 
01324 
01329   function Set( $key, $value ) {
01330     deprecated('iCalendar::Set' );
01331     if ( $value == "" ) return;
01332     $key = strtoupper($key);
01333     $property = new iCalProp();
01334     $property->Name($key);
01335     $property->Value($value);
01336     if (isset($this->component->rendered) ) unset( $this->component->rendered );
01337     $component =& $this->component->FirstNonTimezone();
01338     $component->SetProperties( array($property), $key);
01339     return $this->Get($key);
01340   }
01341 
01342 
01351   function Add( $key, $value, $parameters = null ) {
01352     deprecated('iCalendar::Add' );
01353     if ( $value == "" ) return;
01354     $key = strtoupper($key);
01355     $property = new iCalProp();
01356     $property->Name($key);
01357     $property->Value($value);
01358     if ( isset($parameters) && is_array($parameters) ) {
01359       $property->parameters = $parameters;
01360     }
01361     $component =& $this->component->FirstNonTimezone();
01362     $component->AddProperty($property);
01363     if (isset($this->component->rendered) ) unset( $this->component->rendered );
01364   }
01365 
01366 
01376   function GetComponents( $type = null, $normal_match = true ) {
01377     deprecated('iCalendar::GetComponents' );
01378     return $this->component->GetComponents($type,$normal_match);
01379   }
01380 
01381 
01387   function ClearComponents( $type = null ) {
01388     deprecated('iCalendar::ClearComponents' );
01389     $this->component->ClearComponents($type);
01390   }
01391 
01392 
01400   function SetComponents( $new_component, $type = null ) {
01401     deprecated('iCalendar::SetComponents' );
01402     $this->component->SetComponents( $new_component, $type );
01403   }
01404 
01405 
01412   function AddComponent( $new_component ) {
01413     deprecated('iCalendar::AddComponent' );
01414     $this->component->AddComponent($new_component);
01415   }
01416 
01417 
01423   function MaskComponents( $keep ) {
01424     deprecated('iCalendar::MaskComponents' );
01425     $this->component->MaskComponents($keep);
01426   }
01427 
01428 
01434   static function HttpDateFormat() {
01435     return "'Dy, DD Mon IYYY HH24:MI:SS \"GMT\"'";
01436   }
01437 
01438 
01443   static function SqlDateFormat() {
01444     return "'YYYYMMDD\"T\"HH24MISS'";
01445   }
01446 
01447 
01453   static function SqlUTCFormat() {
01454     return "'YYYYMMDD\"T\"HH24MISS\"Z\"'";
01455   }
01456 
01457 
01463   static function SqlDurationFormat() {
01464     return "'\"PT\"HH24\"H\"MI\"M\"'";
01465   }
01466 
01477   function RFC2445ContentEscape( $name, $value ) {
01478     deprecated('iCalendar::RFC2445ContentEscape' );
01479     $property = preg_replace( '/[;].*$/', '', $name );
01480     switch( $property ) {
01482       case 'ATTACH':                case 'GEO':                       case 'PERCENT-COMPLETE':      case 'PRIORITY':
01483       case 'COMPLETED':             case 'DTEND':                     case 'DUE':                   case 'DTSTART':
01484       case 'DURATION':              case 'FREEBUSY':                  case 'TZOFFSETFROM':          case 'TZOFFSETTO':
01485       case 'TZURL':                 case 'ATTENDEE':                  case 'ORGANIZER':             case 'RECURRENCE-ID':
01486       case 'URL':                   case 'EXDATE':                    case 'EXRULE':                case 'RDATE':
01487       case 'RRULE':                 case 'REPEAT':                    case 'TRIGGER':               case 'CREATED':
01488       case 'DTSTAMP':               case 'LAST-MODIFIED':             case 'SEQUENCE':
01489         break;
01490 
01492       default:
01493         $value = str_replace( '\\', '\\\\', $value);
01494         $value = preg_replace( '/\r?\n/', '\\n', $value);
01495         $value = preg_replace( "/([,;:\"])/", '\\\\$1', $value);
01496     }
01497     $result = preg_replace( '/(.{72})/u', '$1'."\r\n ", $name.':'.$value ) ."\r\n";
01498     return $result;
01499   }
01500 
01512   function ExtractSubComponent( $component, $type, $count=9999 ) {
01513     deprecated('iCalendar::ExtractSubComponent' );
01514     $answer = array();
01515     $intags = false;
01516     $start = "BEGIN:$type";
01517     $finish = "END:$type";
01518     dbg_error_log( 'iCalendar', ":ExtractSubComponent: Looking for %d subsets of type %s", $count, $type );
01519     reset($component);
01520     foreach( $component AS $k => $v ) {
01521       if ( !$intags && $v == $start ) {
01522         $answer[] = $v;
01523         $intags = true;
01524       }
01525       else if ( $intags && $v == $finish ) {
01526         $answer[] = $v;
01527         $intags = false;
01528       }
01529       else if ( $intags ) {
01530         $answer[] = $v;
01531       }
01532     }
01533     return $answer;
01534   }
01535 
01536 
01548   function ExtractProperty( $component, $type, $count=9999 ) {
01549     deprecated('iCalendar::ExtractProperty' );
01550     $answer = array();
01551     dbg_error_log( 'iCalendar', ":ExtractProperty: Looking for %d properties of type %s", $count, $type );
01552     reset($component);
01553     foreach( $component AS $k => $v ) {
01554       if ( preg_match( "/$type"."[;:]/i", $v ) ) {
01555         $answer[] = new iCalProp($v);
01556         dbg_error_log( 'iCalendar', ":ExtractProperty: Found property %s", $type );
01557         if ( --$count < 1 ) return $answer;
01558       }
01559     }
01560     return $answer;
01561   }
01562 
01563 
01580   function ApplyFilter( $filter, $value ) {
01581     deprecated('iCalendar::ApplyFilter' );
01582     foreach( $filter AS $k => $v ) {
01583       $tag = $v->GetNSTag();
01584       $value_type = gettype($value);
01585       $value_defined = (isset($value) && $value_type == 'string') || ($value_type == 'array' && count($value) > 0 );
01586       if ( $tag == 'urn:ietf:params:xml:ns:caldav:is-not-defined' && $value_defined ) {
01587         dbg_error_log( 'iCalendar', ":ApplyFilter: Value is set ('%s'), want unset, for filter %s", count($value), $tag );
01588         return false;
01589       }
01590       elseif ( $tag == 'urn:ietf:params:xml:ns:caldav:is-defined' && !$value_defined ) {
01591         dbg_error_log( 'iCalendar', ":ApplyFilter: Want value, but it is not set for filter %s", $tag );
01592         return false;
01593       }
01594       else {
01595         dbg_error_log( 'iCalendar', ":ApplyFilter: Have values for '%s' filter", $tag );
01596         switch( $tag ) {
01597           case 'urn:ietf:params:xml:ns:caldav:time-range':
01599             break;
01600           case 'urn:ietf:params:xml:ns:caldav:text-match':
01601             $search = $v->GetContent();
01602             // In this case $value will either be a string, or an array of iCalProp objects
01603             // since TEXT-MATCH does not apply to COMPONENT level - only property/parameter
01604             if ( !is_string($value) ) {
01605               if ( is_array($value) ) {
01606                 $match = false;
01607                 foreach( $value AS $k1 => $v1 ) {
01608                   // $v1 MUST be an iCalProp object
01609                   if ( $match = $v1->TextMatch($search)) break;
01610                 }
01611               }
01612               else {
01613                 dbg_error_log( 'iCalendar', ":ApplyFilter: TEXT-MATCH will only work on strings or arrays of iCalProp.  %s unsupported", gettype($value) );
01614                 return true;  // We return _true_ in this case, so the client sees the item
01615               }
01616             }
01617             else {
01618               $match = (stristr( $value, $search ) !== false);      
01619             }
01620             $negate = $v->GetAttribute("negate-condition");
01621             if ( isset($negate) && strtolower($negate) == "yes" ) $match = !$match;
01622 //            dbg_error_log( 'iCalendar', ":ApplyFilter: TEXT-MATCH returning %s", ($match?"yes":"no") );
01623             return $match;
01624             break;
01625           case 'urn:ietf:params:xml:ns:caldav:comp-filter':
01626             $subfilter = $v->GetContent();
01627             $component = $this->ExtractSubComponent($value,$v->GetAttribute("name"));
01628             if ( ! $this->ApplyFilter($subfilter,$component) ) return false;
01629             break;
01630           case 'urn:ietf:params:xml:ns:caldav:prop-filter':
01631             $subfilter = $v->GetContent();
01632             $properties = $this->ExtractProperty($value,$v->GetAttribute("name"));
01633             if ( ! $this->ApplyFilter($subfilter,$properties) ) return false;
01634             break;
01635           case 'urn:ietf:params:xml:ns:caldav:param-filter':
01636             $subfilter = $v->GetContent();
01637             $parameter = $this->ExtractParameter($value,$v->GetAttribute("NAME"));
01638             if ( ! $this->ApplyFilter($subfilter,$parameter) ) return false;
01639             break;
01640         }
01641       }
01642     }
01643     return true;
01644   }
01645 
01656   function TestFilter( $filters ) {
01657     deprecated('iCalendar::TestFilter' );
01658 
01659 //    dbg_error_log('iCalendar', ':TestFilter we have %d filters to test', count($filters) );
01660     foreach( $filters AS $k => $v ) {
01661       $tag = $v->GetNSTag();
01662 //      dbg_error_log('iCalendar', ':TestFilter working on tag "%s" %s"', $k, $tag );
01663       $name = $v->GetAttribute("name");
01664       $filter = $v->GetContent();
01665       if ( $tag == "urn:ietf:params:xml:ns:caldav:prop-filter" ) {
01666         $value = $this->ExtractProperty($this->lines,$name);
01667       }
01668       else {
01669         $value = $this->ExtractSubComponent($this->lines,$v->GetAttribute("name"));
01670       }
01671       if ( count($value) == 0 ) unset($value);
01672       if ( ! $this->ApplyFilter($filter,$value) ) return false;
01673     }
01674     return true;
01675   }
01676 
01683   static function iCalHeader() {
01684     deprecated('iCalendar::iCalHeader' );
01685     return <<<EOTXT
01686 BEGIN:VCALENDAR\r
01687 PRODID:-//davical.org//NONSGML AWL Calendar//EN\r
01688 VERSION:2.0\r
01689 
01690 EOTXT;
01691   }
01692 
01693 
01694 
01701   static function iCalFooter() {
01702     deprecated('iCalendar::iCalFooter' );
01703     return "END:VCALENDAR\r\n";
01704   }
01705 
01706 
01715   function Render( $as_calendar = true, $type = null, $restrict_properties = null ) {
01716     deprecated('iCalendar::Render' );
01717     if ( $as_calendar ) {
01718       return $this->component->Render();
01719     }
01720     else {
01721       $components = $this->component->GetComponents($type);
01722       $rendered = "";
01723       foreach( $components AS $k => $v ) {
01724         $rendered .= $v->Render($restrict_properties);
01725       }
01726       return $rendered;
01727     }
01728   }
01729 
01730 }