Andrew's Web Libraries (AWL)
Loading...
Searching...
No Matches
vProperty.php
1<?php
2
3require_once('XMLElement.php');
9class vProperty extends vObject {
19 protected $name;
20
26 protected $parameters;
27
33 protected $content;
34
40 protected $iterator;
41
46 protected $seek;
47
48 protected $line;
49
50 //protected $rendered;
51
52
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
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 ( isset($value) && 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
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
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
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
263 function TextMatch( $search ) {
264 if ( isset($this->content) ) return strstr( $this->content, $search );
265 return false;
266 }
267
268
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
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
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
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
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 ) {
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
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;
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
443 function TestFilter( $filters, $matchAll = true ) {
444 foreach( $filters AS $k => $v ) {
445 $tag = $v->GetNSTag();
446// dbg_error_log( 'vCalendar', "vProperty:TestFilter: '%s'='%s' => '%s'", $this->name, $tag, $this->content );
447 switch( $tag ) {
448 case 'urn:ietf:params:xml:ns:caldav:is-defined':
449 case 'urn:ietf:params:xml:ns:carddav:is-defined':
450 if ( empty($this->content) ) return false;
451 break;
452
453 case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
454 case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
455 if ( ! empty($this->content) ) return false;
456 break;
457
458 case 'urn:ietf:params:xml:ns:caldav:time-range':
460 break;
461
462 case 'urn:ietf:params:xml:ns:carddav:text-match':
463 case 'urn:ietf:params:xml:ns:caldav:text-match':
464 // Call the Value() getter method to get hold of the vProperty content - need to ensure parsing has occurred
465 $haystack = $this->Value();
466 $match = $this->TestTextMatch($v, $haystack);
467 // Early exit: OR match can stop if we have on success, AND match can stop if we have a fail
468 if ( $match != $matchAll ) return $match;
469 break;
470
471 case 'urn:ietf:params:xml:ns:carddav:param-filter':
472 case 'urn:ietf:params:xml:ns:caldav:param-filter':
473 $subfilter = $v->GetContent();
474 $parameter = $this->GetParameterValue($v->GetAttribute("name"));
475
476 if (empty($subfilter)) {
477 // param-filter without subfilter matches if the parameter is defined
478 $match = isset($parameter);
479 } else {
480 if (!isset($parameter)) {
481 $parameter = [ null ];
482 } elseif (!is_array($parameter)) {
483 $parameter = explode(',', $parameter);
484 }
485
486 $match = false;
487 foreach ($parameter as $param) {
488 $match = $this->TestParamFilter($subfilter,$param);
489 if ($match) break;
490 }
491 }
492
493 // Early exit: OR match can stop if we have on success, AND match can stop if we have a fail
494 if ( $match != $matchAll ) return $match;
495 break;
496
497 default:
498 dbg_error_log( 'myComponent', ' vProperty::TestFilter: unhandled tag "%s"', $tag );
499 break;
500 }
501 }
502
503 // AND-case: all matched; OR-case: all failed
504 return $matchAll;
505 }
506
507 function fill($sp, $en, $pe){
508
509 }
510
511 function TestTextMatch( $filter, $haystack ) {
512 // if the property / parameter is not defined, the prop-filter/param-filter fails
513 if (!isset($haystack)) {
514 return false;
515 }
516
517 $search = $filter->GetContent();
518 $collation = $filter->GetAttribute("collation");
519 if (! isset($collation)) {
520 $collation = '';
521 }
522 switch( strtolower($collation) ) {
523 case 'i;octet':
524 // don't change search and haystack
525 break;
526 case 'i;ascii-casemap':
527 case 'i;unicode-casemap':
528 default:
529 // for ignore case search we transform
530 // search and haystack to lowercase
531 $search = strtolower( $search );
532 $haystack = strtolower( $haystack );
533 break;
534 }
535
536 $matchType = $filter->GetAttribute("match-type");
537 if (! isset($matchType)) {
538 $matchType = '';
539 }
540 switch( strtolower($matchType) ) {
541 case 'equals':
542 $match = ( $haystack === $search );
543 break;
544 case 'starts-with':
545 $length = strlen($search);
546 if ($length == 0) {
547 $match = true;
548 } else {
549 $match = !strncmp($haystack, $search, $length);
550 }
551 break;
552 case 'ends-with':
553 $length = strlen($search);
554 if ($length == 0) {
555 $match = true;
556 } else {
557 $match = ( substr($haystack, -$length) === $search );
558 }
559 break;
560 default: // contains
561 $length = strlen($search);
562 if ($length == 0) {
563 $match = true;
564 } else {
565 $match = strstr( $haystack, $search );
566 }
567 break;
568 }
569
570 $negate = $filter->GetAttribute("negate-condition");
571 if ( isset($negate) && strtolower($negate) == "yes" ) {
572 $match = !$match;
573 }
574
575 return $match;
576 }
577
578 function TestParamFilter( $filters, $parameter_value ) {
579 foreach( $filters AS $k => $v ) {
580 $subtag = $v->GetNSTag();
581// dbg_error_log( 'vCalendar', "vProperty:TestParamFilter: '%s'='%s' => '%s'", $this->name, $subtag, $parameter_value );
582 switch( $subtag ) {
583 case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
584 case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
585 if ( ! empty($parameter_value) ) return false;
586 break;
587
588 case 'urn:ietf:params:xml:ns:caldav:time-range':
590 break;
591
592 case 'urn:ietf:params:xml:ns:carddav:text-match':
593 case 'urn:ietf:params:xml:ns:caldav:text-match':
594 $match = $this->TestTextMatch($v, $parameter_value);
595 if ( ! $match ) return false;
596 break;
597
598 default:
599 dbg_error_log( 'myComponent', ' vProperty::TestParamFilter: unhandled tag "%s"', $tag );
600 break;
601 }
602 }
603 return true;
604 }
605}
Render( $force=false)
ClearParameters( $type=null)
__construct( $name=null, &$master=null, &$refData=null, $seek=null)
Definition vProperty.php:61
Name( $newname=null)
TextMatch( $search)
TestParamFilter( $filters, $parameter_value)
Value( $newvalue=null)
SetParameterValue( $name, $value)
RenderParameters()
ParseFromIterator()
GetParameterValue( $name)
TestFilter( $filters, $matchAll=true)
Parameters( $newparams=null)