Overview

Packages

  • awl
    • AuthPlugin
    • AwlDatabase
    • Browser
    • classEditor
    • DataEntry
    • DataUpdate
    • EMail
    • iCalendar
    • MenuSet
    • PgQuery
    • Session
    • Translation
    • User
    • Utilities
    • Validation
    • vCalendar
    • vComponent
    • XMLDocument
    • XMLElement
  • None
  • PHP

Classes

  • vProperty
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: 
  3: require_once('XMLElement.php');
  4: /**
  5:  * A Class for representing properties within a myComponent (VCALENDAR or VCARD)
  6:  *
  7:  * @package awl
  8:  */
  9: class vProperty extends vObject {
 10:     /**#@+
 11:      * @access private
 12:      */
 13: 
 14:     /**
 15:      * The name of this property
 16:      *
 17:      * @var string
 18:      */
 19:     protected $name;
 20: 
 21:     /**
 22:      * An array of parameters to this property, represented as key/value pairs.
 23:      *
 24:      * @var array
 25:      */
 26:     protected $parameters;
 27: 
 28:     /**
 29:      * The value of this property.
 30:      *
 31:      * @var string
 32:      */
 33:     protected $content;
 34: 
 35:     /**
 36:      * The original value that this was parsed from, if that's the way it happened.
 37:      *
 38:      * @var ArrayIterator
 39:      */
 40:     protected $iterator;
 41: 
 42:     /**
 43:      * The original seek of iterator
 44:      * @var int
 45:      */
 46:     protected $seek;
 47: 
 48:     protected $line;
 49: 
 50:     //protected $rendered;
 51: 
 52: 
 53:     /**#@-*/
 54: 
 55:     /**
 56:      * Parsing of the incoming string is now performed lazily, in ParseFromIterator.
 57:      * You should use getter methods such as Value() and getParameterValue() instead of direct access
 58:      * to $content, $parameters etc, to ensure that parsing has occurred.
 59:      *
 60:      */
 61:     function __construct( $name = null, &$master = null, &$refData = null, $seek = null ) {
 62:         parent::__construct($master);
 63: 
 64: 
 65:         if(isset($name) && strlen($name) > 0){
 66:             $this->name = $name;
 67:         } else {
 68:             unset($this->name);
 69:         }
 70: 
 71:         unset($this->content);
 72:         unset($this->parameters);
 73: 
 74:         if ( isset($refData)){
 75: 
 76:             if(gettype($refData) == 'object') {
 77:                 $this->iterator = &$refData;
 78:                 $this->seek = &$seek;
 79:                 unset($this->line);
 80:             } else {
 81:                 $this->line = $refData;
 82: 
 83:                 unset($this->iterator);
 84:                 unset($this->seek);
 85:             }
 86:         } else {
 87:             unset($this->iterator);
 88:             unset($this->seek);
 89: 
 90:         }
 91:     }
 92: 
 93:     /**
 94:      * Parses the incoming string, which is formatted as per RFC2445 as a
 95:      *   propname[;param1=pval1[; ... ]]:propvalue
 96:      * However we allow ourselves to assume that the RFC2445 content unescaping has already
 97:      * happened when myComponent::ParseFrom() called myComponent::UnwrapComponent().
 98:      *
 99:      * Note this function is called lazily, from the individual getter methods. This avoids the cost of parsing at
100:      * the point of object instantiation.
101:      */
102:     function ParseFromIterator()
103:     {
104:         $unescaped;
105: 
106:         if (isset($this->iterator)) {
107:             $this->iterator->seek($this->seek);
108:             $unescaped = $this->iterator->current();
109:         } else if (isset($this->line)) {
110:             $unescaped = $this->line;
111:         } else {
112:             $unescaped = '';
113:         }
114: 
115:         $this->ParseFrom($unescaped);
116:         unset($unescaped);
117:     }
118: 
119:     function ParseFrom( &$unescaped ) {
120:         // unescape \r and \n in the value
121:         $unescaped = preg_replace( array('{\\\\[nN]}', '{\\\\[rR]}'), array("\n", "\r"), $unescaped);
122: 
123:         // Split into two parts on : which is not preceded by a \, or within quotes like "str:ing".
124:         $offset = 0;
125:         do {
126:             $splitpos = strpos($unescaped,':',$offset);
127:             $start = substr($unescaped,0,$splitpos);
128:             if ( substr($start,-1) == '\\' ) {
129:                 $offset = $splitpos + 1;
130:                 continue;
131:             }
132:             $quotecount = strlen(preg_replace('{[^"]}', '', $start ));
133:             if ( ($quotecount % 2) != 0 ) {
134:                 $offset = $splitpos + 1;
135:                 continue;
136:             }
137:             break;
138:         }
139:         while( true );
140:         $values = substr($unescaped, $splitpos+1);
141: 
142:         $possiblecontent = preg_replace( "/\\\\([,;:\"\\\\])/", '$1', $values);
143:         // in case if the name was set manualy content by function Valued
144:         // -> don't reset it by $rendered data
145:         if(!isset($this->content)){
146:             // TODO: add "\r" to preg_replace at begin
147:             $len = strlen($possiblecontent);
148:             if($len > 0 && "\r" == $possiblecontent[$len-1]){
149: 
150:                 $possiblecontent = substr($possiblecontent, 0, $len-1);
151:             }
152:             $this->content = $possiblecontent;
153:         }
154: 
155: 
156:         // Split on ; which is not preceded by a \
157:         $parameters = preg_split( '{(?<!\\\\);}', $start);
158: 
159: 
160:         $possiblename = strtoupper(array_shift( $parameters ));
161:         // in case if the name was set manualy by function Name
162:         // -> don't reset it by $rendered data
163:         if(!isset($this->name)){
164:             $this->name = $possiblename;
165:         }
166: 
167:         // in case if the parameter was set manualy by function Parameters
168:         // -> don't reset it by $rendered data
169:         if(!isset($this->parameters)){
170:             $this->parameters = array();
171:             foreach( $parameters AS $k => $v ) {
172:                 $pos = strpos($v,'=');
173:                 if($pos !== FALSE) {
174:                     $name = strtoupper(substr( $v, 0, $pos));
175:                     $value = substr( $v, $pos + 1);
176:                 }
177:                 else {
178:                     $name = strtoupper($v);
179:                     $value = null;
180:                 }
181:                 if ( preg_match( '{^"(.*)"$}', $value, $matches) ) {
182:                     $value = $matches[1];
183:                 }
184:                 if ( isset($this->parameters[$name]) && is_array($this->parameters[$name]) ) {
185:                     $this->parameters[$name][] = $value;
186:                 }
187:                 elseif ( isset($this->parameters[$name]) ) {
188:                     $this->parameters[$name] = array( $this->parameters[$name], $value);
189:                 }
190:                 else
191:                     $this->parameters[$name] = $value;
192:             }
193:         }
194: //    dbg_error_log('myComponent', " vProperty::ParseFrom found '%s' = '%s' with %d parameters", $this->name, substr($this->content,0,200), count($this->parameters) );
195:     }
196: 
197: 
198:     /**
199:      * Get/Set name property
200:      *
201:      * @param string $newname [optional] A new name for the property
202:      *
203:      * @return string The name for the property.
204:      */
205:     function Name( $newname = null ) {
206:         if ( $newname != null ) {
207:             $this->name = strtoupper($newname);
208:             if ( $this->isValid() ) $this->invalidate();
209: //      dbg_error_log('myComponent', " vProperty::Name(%s)", $this->name );
210:         } else if(!isset($this->name)){
211:             $this->ParseFromIterator();
212:         }
213:         return $this->name;
214:     }
215: 
216: 
217:     /**
218:      * Get/Set the content of the property
219:      *
220:      * @param string $newvalue [optional] A new value for the property
221:      *
222:      * @return string The value of the property.
223:      */
224:     function Value( $newvalue = null ) {
225:         if ( $newvalue != null ) {
226:             $this->content = $newvalue;
227:             if ( $this->isValid() ) $this->invalidate();
228:         } else if(!isset($this->content)){
229:             $this->ParseFromIterator();
230:         }
231:         return $this->content;
232:     }
233: 
234: 
235:     /**
236:      * Get/Set parameters in their entirety
237:      *
238:      * @param array $newparams An array of new parameter key/value pairs.  The 'value' may be an array of values.
239:      *
240:      * @return array The current array of parameters for the property.
241:      */
242:     function Parameters( $newparams = null ) {
243:         if ( $newparams != null ) {
244:             $this->parameters = array();
245:             foreach( $newparams AS $k => $v ) {
246:                 $this->parameters[strtoupper($k)] = $v;
247:             }
248:             if ( $this->isValid() ) $this->invalidate();
249:         } else if(!isset($this->parameters)){
250:             $this->ParseFromIterator();
251:         }
252:         return $this->parameters;
253:     }
254: 
255: 
256:     /**
257:      * Test if our value contains a string
258:      *
259:      * @param string $search The needle which we shall search the haystack for.
260:      *
261:      * @return string The name for the property.
262:      */
263:     function TextMatch( $search ) {
264:         if ( isset($this->content) ) return strstr( $this->content, $search );
265:         return false;
266:     }
267: 
268: 
269:     /**
270:      * Get the value of a parameter
271:      *
272:      * @param string $name The name of the parameter to retrieve the value for
273:      *
274:      * @return string The value of the parameter
275:      */
276:     function GetParameterValue( $name ) {
277:         $name = strtoupper($name);
278: 
279:         if(!isset($this->parameters)){
280:             $this->ParseFromIterator();
281:         }
282: 
283:         if ( isset($this->parameters[$name]) ){
284:             return $this->parameters[$name];
285:         }
286:         return null;
287:     }
288: 
289:     /**
290:      * Set the value of a parameter
291:      *
292:      * @param string $name The name of the parameter to set the value for
293:      *
294:      * @param string $value The value of the parameter
295:      */
296:     function SetParameterValue( $name, $value ) {
297:         if(!isset($this->parameters)){
298:             $this->ParseFromIterator();
299:         }
300: 
301:         if ( $this->isValid() ) {
302:             $this->invalidate();
303:         }
304:             //tests/regression-suite/0831-Spec-RRULE-1.result
305:         //./dav_test --dsn 'davical_milan;port=5432' --webhost 127.0.0.1 --althost altcaldav --suite 'regression-suite' --case 'tests/regression-suite/0831-Spec-RRULE-1'
306:         $this->parameters[strtoupper($name)] = $value;
307: //    dbg_error_log('PUT', $this->name.$this->RenderParameters().':'.$this->content );
308:     }
309: 
310:     /**
311:      * Clear all parameters, or the parameters matching a particular type
312:      * @param string|array $type The type of parameters or an
313:      * array associating parameter names with true values: array( 'PARAMETER' => true, 'PARAMETER2' => true )
314:      */
315:     function ClearParameters( $type = null ) {
316:         if(!isset($this->parameters)){
317:             $this->ParseFromIterator();
318:         }
319: 
320:         if ( $this->isValid() ) {
321:             $this->invalidate();
322:         }
323: 
324:         if ( $type != null ) {
325:             $testtypes = (gettype($type) == 'string' ? array( $type => true ) : $type );
326:             // First remove all the existing ones of that type
327:             foreach( $this->parameters AS $k => $v ) {
328:                 if ( isset($testtypes[$k]) && $testtypes[$k] ) {
329:                     unset($this->parameters[$k]);
330:                 }
331:             }
332:         }
333:     }
334: 
335:     private static function escapeParameter($p) {
336:         if ( strpos($p, ';') === false && strpos($p, ':') === false ) return $p;
337:         return '"'.str_replace('"','\\"',$p).'"';
338:     }
339: 
340:     /**
341:      * Render the set of parameters as key1=value1[;key2=value2[; ...]] with
342:      * any colons or semicolons escaped.
343:      */
344:     function RenderParameters() {
345:         $rendered = "";
346:         if(isset($this->parameters)){
347:             foreach( $this->parameters AS $k => $v ) {
348:                 if ( is_array($v) ) {
349:                     foreach( $v AS $vv ) {
350:                         $rendered .= sprintf( ';%s=%s', $k, vProperty::escapeParameter($vv) );
351:                     }
352:                 }
353:                 else {
354:                     if($v !== null) {
355:                         $rendered .= sprintf( ';%s=%s', $k, vProperty::escapeParameter($v) );
356:                     }
357:                     else {
358:                         $rendered .= sprintf( ';%s', $k);
359:                     }
360:                 }
361:             }
362:         }
363: 
364:         return $rendered;
365:     }
366: 
367: 
368:     /**
369:      * Render a suitably escaped RFC2445 content string.
370:      */
371:     function Render( $force = false ) {
372:         // If we still have the string it was parsed in from, it hasn't been screwed with
373:         // and we can just return that without modification.
374: //        if ( $force === false && $this->isValid() && isset($this->rendered) && strlen($this->rendered) < 73 ) {
375: //            return $this->rendered;
376: //        }
377: 
378:         // in case one of the memberts doesn't set -> try parse from rendered
379:         if(!isset($this->name) || !isset($this->content) || !isset($this->parameters)) {
380:             $this->ParseFromIterator();
381:         }
382: 
383:         $property = preg_replace( '/[;].*$/', '', $this->name );
384:         $escaped = $this->content;
385:         $property = preg_replace( '/^.*[.]/', '', $property ); //temporarily remove grouping prefix from CARDDAV attributes ("item1.", "item2.", etc)
386:         switch( $property ) {
387:             /** Content escaping does not apply to these properties culled from RFC2445 */
388:             case 'ATTACH':                case 'GEO':                       case 'PERCENT-COMPLETE':      case 'PRIORITY':
389:             case 'DURATION':              case 'FREEBUSY':                  case 'TZOFFSETFROM':          case 'TZOFFSETTO':
390:             case 'TZURL':                 case 'ATTENDEE':                  case 'ORGANIZER':             case 'RECURRENCE-ID':
391:             case 'URL':                   case 'EXRULE':                    case 'SEQUENCE':              case 'CREATED':
392:             case 'RRULE':                 case 'REPEAT':                    case 'TRIGGER':               case 'RDATE':
393:             case 'COMPLETED':             case 'DTEND':                     case 'DUE':                   case 'DTSTART':
394:             case 'DTSTAMP':               case 'LAST-MODIFIED':             case 'CREATED':               case 'EXDATE':
395:             case 'CATEGORIES' :
396:             break;
397: 
398:             /** Content escaping does not apply to these properties culled from RFC6350 / RFC2426 */
399:             case 'ADR':                case 'N':            case 'ORG':
400:             // escaping for ';' for these fields also needs to happen to the components they are built from.
401:             $escaped = str_replace( '\\', '\\\\', $escaped);
402:             $escaped = preg_replace( '/\r?\n/', '\\n', $escaped);
403:             $escaped = str_replace( ',', '\\,', $escaped);
404:             break;
405:             /** Content escaping applies by default to other properties */
406:             default:
407:                 $escaped = str_replace( '\\', '\\\\', $escaped);
408:                 $escaped = preg_replace( '/\r?\n/', '\\n', $escaped);
409:                 $escaped = preg_replace( "/([,;])/", '\\\\$1', $escaped);
410:         }
411: 
412:         $rendered = '';
413:         $property = sprintf( "%s%s:", $this->name, $this->RenderParameters() );
414:         if ( (strlen($property) + strlen($escaped)) <= 72 ) {
415:             $rendered = $property . $escaped;
416:         }
417:         else if ( (strlen($property) <= 72) && (strlen($escaped) <= 72) ) {
418:             $rendered = $property . "\r\n " . $escaped;
419:         }
420:         else {
421:             $rendered = preg_replace( '/(.{72})/u', '$1'."\r\n ", $property.$escaped );
422:         }
423: //    trace_bug( 'Re-rendered "%s" property.', $this->name );
424:         return $rendered;
425:     }
426: 
427: 
428:     public function __toString() {
429:         return $this->Render();
430:     }
431: 
432: 
433:     /**
434:      * Test a PROP-FILTER or PARAM-FILTER and return a true/false
435:      * PROP-FILTER (is-defined | is-not-defined | ((time-range | text-match)?, param-filter*))
436:      * PARAM-FILTER (is-defined | is-not-defined | ((time-range | text-match)?, param-filter*))
437:      *
438:      * Changed by GitLab user moosemark 2015-09-18 to fix initialisation of $content property (AWL issue 10)
439:      *
440:      * @param array $filter An array of XMLElement defining the filter
441:      *
442:      * @return boolean Whether or not this vProperty passes the test
443:      */
444:     function TestFilter( $filters ) {
445:         foreach( $filters AS $k => $v ) {
446:             $tag = $v->GetNSTag();
447: //      dbg_error_log( 'vCalendar', "vProperty:TestFilter: '%s'='%s' => '%s'", $this->name, $tag, $this->content );
448:             switch( $tag ) {
449:                 case 'urn:ietf:params:xml:ns:caldav:is-defined':
450:                 case 'urn:ietf:params:xml:ns:carddav:is-defined':
451:                     if ( empty($this->content) ) return false;
452:                     break;
453: 
454:                 case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
455:                 case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
456:                     if ( ! empty($this->content) ) return false;
457:                     break;
458: 
459:                 case 'urn:ietf:params:xml:ns:caldav:time-range':
460:                     /** @todo: While this is unimplemented here at present, most time-range tests should occur at the SQL level. */
461:                     break;
462: 
463:                 case 'urn:ietf:params:xml:ns:carddav:text-match':
464:                 case 'urn:ietf:params:xml:ns:caldav:text-match':
465:                     $search = $v->GetContent();
466:                     // Call the Value() getter method to get hold of the vProperty content - need to ensure parsing has occurred
467:                     $haystack = $this->Value();
468:                     $match = isset($haystack);
469:                     if ( $match ) {
470:                              $collation = $v->GetAttribute("collation");
471:                              switch( strtolower($collation) ) {
472:                              case 'i;octet':
473:                                  // don't change search and haystack
474:                                  break;
475:                              case 'i;ascii-casemap':
476:                              case 'i;unicode-casemap':
477:                              default:
478:                                  // for ignore case search we transform
479:                                  // search and haystack to lowercase
480:                                  $search   = strtolower( $search );
481:                                  $haystack = strtolower( $haystack );
482:                                  break;
483:                              }
484: 
485:                              $matchType = $v->GetAttribute("match-type");
486:                              switch( strtolower($matchType) ) {
487:                              case 'equals':
488:                                  $match = ( $haystack === $search );
489:                                  break;
490:                              case 'starts-with':
491:                                 $length = strlen($search);
492:                                  if ($length == 0) {
493:                                      $match = true;
494:                                  } else {
495:                                      $match = !strncmp($haystack, $search, $length);
496:                                  }
497:                                  break;
498:                              case 'ends-with':
499:                                 $length = strlen($search);
500:                                  if ($length == 0) {
501:                                      $match = true;
502:                                  } else {
503:                                      $match = ( substr($haystack, -$length) === $search );
504:                                  }
505:                                  break;
506:                              default: // contains
507:                                  $match = strstr( $haystack, $search );
508:                                  break;
509:                              }
510:                     }
511: 
512:                     $negate = $v->GetAttribute("negate-condition");
513:                     if ( isset($negate) && strtolower($negate) == "yes" ) {
514:                              $match = !$match;
515:                     }
516:                     if ( ! $match ) return false;
517:                     break;
518: 
519:                 case 'urn:ietf:params:xml:ns:carddav:param-filter':
520:                 case 'urn:ietf:params:xml:ns:caldav:param-filter':
521:                     $subfilter = $v->GetContent();
522:                     $parameter = $this->GetParameterValue($v->GetAttribute("name"));
523:                     if ( ! $this->TestParamFilter($subfilter,$parameter) ) return false;
524:                     break;
525: 
526:                 default:
527:                     dbg_error_log( 'myComponent', ' vProperty::TestFilter: unhandled tag "%s"', $tag );
528:                     break;
529:             }
530:         }
531:         return true;
532:     }
533: 
534:     function fill($sp, $en, $pe){
535: 
536:     }
537: 
538:     function TestParamFilter( $filters, $parameter_value ) {
539:         foreach( $filters AS $k => $v ) {
540:             $subtag = $v->GetNSTag();
541: //      dbg_error_log( 'vCalendar', "vProperty:TestParamFilter: '%s'='%s' => '%s'", $this->name, $subtag, $parameter_value );
542:             switch( $subtag ) {
543:                 case 'urn:ietf:params:xml:ns:caldav:is-defined':
544:                 case 'urn:ietf:params:xml:ns:carddav:is-defined':
545:                     if ( empty($parameter_value) ) return false;
546:                     break;
547: 
548:                 case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
549:                 case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
550:                     if ( ! empty($parameter_value) ) return false;
551:                     break;
552: 
553:                 case 'urn:ietf:params:xml:ns:caldav:time-range':
554:                     /** @todo: While this is unimplemented here at present, most time-range tests should occur at the SQL level. */
555:                     break;
556: 
557:                 case 'urn:ietf:params:xml:ns:carddav:text-match':
558:                 case 'urn:ietf:params:xml:ns:caldav:text-match':
559:                     $search = $v->GetContent();
560:                     $match = false;
561:                     if ( !empty($parameter_value) ) $match = strstr( $this->content, $search );
562:                     $negate = $v->GetAttribute("negate-condition");
563:                     if ( isset($negate) && strtolower($negate) == "yes" ) {
564:                         $match = !$match;
565:                     }
566:                     if ( ! $match ) return false;
567:                     break;
568: 
569:                 default:
570:                     dbg_error_log( 'myComponent', ' vProperty::TestParamFilter: unhandled tag "%s"', $tag );
571:                     break;
572:             }
573:         }
574:         return true;
575:     }
576: }
577: 
AWL API documentation generated by ApiGen 2.8.0