1: <?php
2:
3: require_once('XMLElement.php');
4: 5: 6: 7: 8:
9: class vProperty extends vObject {
10: 11: 12:
13:
14: 15: 16: 17: 18:
19: protected $name;
20:
21: 22: 23: 24: 25:
26: protected $parameters;
27:
28: 29: 30: 31: 32:
33: protected $content;
34:
35: 36: 37: 38: 39:
40: protected $iterator;
41:
42: 43: 44: 45:
46: protected $seek;
47:
48: protected $line;
49:
50:
51:
52:
53:
54:
55: 56: 57: 58: 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: 95: 96: 97: 98: 99: 100: 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:
121: $unescaped = preg_replace( array('{\\\\[nN]}', '{\\\\[rR]}'), array("\n", "\r"), $unescaped);
122:
123:
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:
144:
145: if(!isset($this->content)){
146:
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:
157: $parameters = preg_split( '{(?<!\\\\);}', $start);
158:
159:
160: $possiblename = strtoupper(array_shift( $parameters ));
161:
162:
163: if(!isset($this->name)){
164: $this->name = $possiblename;
165: }
166:
167:
168:
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:
195: }
196:
197:
198: 199: 200: 201: 202: 203: 204:
205: function Name( $newname = null ) {
206: if ( $newname != null ) {
207: $this->name = strtoupper($newname);
208: if ( $this->isValid() ) $this->invalidate();
209:
210: } else if(!isset($this->name)){
211: $this->ParseFromIterator();
212: }
213: return $this->name;
214: }
215:
216:
217: 218: 219: 220: 221: 222: 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: 237: 238: 239: 240: 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: 258: 259: 260: 261: 262:
263: function TextMatch( $search ) {
264: if ( isset($this->content) ) return strstr( $this->content, $search );
265: return false;
266: }
267:
268:
269: 270: 271: 272: 273: 274: 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: 291: 292: 293: 294: 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:
305:
306: $this->parameters[strtoupper($name)] = $value;
307:
308: }
309:
310: 311: 312: 313: 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:
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: 342: 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: 370:
371: function Render( $force = false ) {
372:
373:
374:
375:
376:
377:
378:
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 );
386: switch( $property ) {
387:
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:
399: case 'ADR': case 'N': case 'ORG':
400:
401: $escaped = str_replace( '\\', '\\\\', $escaped);
402: $escaped = preg_replace( '/\r?\n/', '\\n', $escaped);
403: $escaped = str_replace( ',', '\\,', $escaped);
404: break;
405:
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:
424: return $rendered;
425: }
426:
427:
428: public function __toString() {
429: return $this->Render();
430: }
431:
432:
433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443:
444: function TestFilter( $filters ) {
445: foreach( $filters AS $k => $v ) {
446: $tag = $v->GetNSTag();
447:
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:
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:
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:
474: break;
475: case 'i;ascii-casemap':
476: case 'i;unicode-casemap':
477: default:
478:
479:
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:
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:
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:
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: