Andrew's Web Libraries (AWL)
vProperty.php
1 <?php
2 
3 require_once('XMLElement.php');
9 class 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 
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 
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 
442  function TestFilter( $filters ) {
443  foreach( $filters AS $k => $v ) {
444  $tag = $v->GetNSTag();
445 // dbg_error_log( 'vCalendar', "vProperty:TestFilter: '%s'='%s' => '%s'", $this->name, $tag, $this->content );
446  switch( $tag ) {
447  case 'urn:ietf:params:xml:ns:caldav:is-defined':
448  case 'urn:ietf:params:xml:ns:carddav:is-defined':
449  if ( empty($this->content) ) return false;
450  break;
451 
452  case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
453  case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
454  if ( ! empty($this->content) ) return false;
455  break;
456 
457  case 'urn:ietf:params:xml:ns:caldav:time-range':
459  break;
460 
461  case 'urn:ietf:params:xml:ns:carddav:text-match':
462  case 'urn:ietf:params:xml:ns:caldav:text-match':
463  $search = $v->GetContent();
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 = isset($haystack);
467  if ( $match ) {
468  $collation = $v->GetAttribute("collation");
469  switch( strtolower($collation) ) {
470  case 'i;octet':
471  // don't change search and haystack
472  break;
473  case 'i;ascii-casemap':
474  case 'i;unicode-casemap':
475  default:
476  // for ignore case search we transform
477  // search and haystack to lowercase
478  $search = strtolower( $search );
479  $haystack = strtolower( $haystack );
480  break;
481  }
482 
483  $matchType = $v->GetAttribute("match-type");
484  switch( strtolower($matchType) ) {
485  case 'equals':
486  $match = ( $haystack === $search );
487  break;
488  case 'starts-with':
489  $length = strlen($search);
490  if ($length == 0) {
491  $match = true;
492  } else {
493  $match = !strncmp($haystack, $search, $length);
494  }
495  break;
496  case 'ends-with':
497  $length = strlen($search);
498  if ($length == 0) {
499  $match = true;
500  } else {
501  $match = ( substr($haystack, -$length) === $search );
502  }
503  break;
504  default: // contains
505  $match = strstr( $haystack, $search );
506  break;
507  }
508  }
509 
510  $negate = $v->GetAttribute("negate-condition");
511  if ( isset($negate) && strtolower($negate) == "yes" ) {
512  $match = !$match;
513  }
514  if ( ! $match ) return false;
515  break;
516 
517  case 'urn:ietf:params:xml:ns:carddav:param-filter':
518  case 'urn:ietf:params:xml:ns:caldav:param-filter':
519  $subfilter = $v->GetContent();
520  $parameter = $this->GetParameterValue($v->GetAttribute("name"));
521  if ( ! $this->TestParamFilter($subfilter,$parameter) ) return false;
522  break;
523 
524  default:
525  dbg_error_log( 'myComponent', ' vProperty::TestFilter: unhandled tag "%s"', $tag );
526  break;
527  }
528  }
529  return true;
530  }
531 
532  function fill($sp, $en, $pe){
533 
534  }
535 
536  function TestParamFilter( $filters, $parameter_value ) {
537  foreach( $filters AS $k => $v ) {
538  $subtag = $v->GetNSTag();
539 // dbg_error_log( 'vCalendar', "vProperty:TestParamFilter: '%s'='%s' => '%s'", $this->name, $subtag, $parameter_value );
540  switch( $subtag ) {
541  case 'urn:ietf:params:xml:ns:caldav:is-defined':
542  case 'urn:ietf:params:xml:ns:carddav:is-defined':
543  if ( empty($parameter_value) ) return false;
544  break;
545 
546  case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
547  case 'urn:ietf:params:xml:ns:carddav:is-not-defined':
548  if ( ! empty($parameter_value) ) return false;
549  break;
550 
551  case 'urn:ietf:params:xml:ns:caldav:time-range':
553  break;
554 
555  case 'urn:ietf:params:xml:ns:carddav:text-match':
556  case 'urn:ietf:params:xml:ns:caldav:text-match':
557  $search = $v->GetContent();
558  $match = false;
559  if ( !empty($parameter_value) ) $match = strstr( $this->content, $search );
560  $negate = $v->GetAttribute("negate-condition");
561  if ( isset($negate) && strtolower($negate) == "yes" ) {
562  $match = !$match;
563  }
564  if ( ! $match ) return false;
565  break;
566 
567  default:
568  dbg_error_log( 'myComponent', ' vProperty::TestParamFilter: unhandled tag "%s"', $tag );
569  break;
570  }
571  }
572  return true;
573  }
574 }
vProperty\TextMatch
TextMatch( $search)
Definition: vProperty.php:263
vProperty\TestParamFilter
TestParamFilter( $filters, $parameter_value)
Definition: vProperty.php:536
vProperty\__construct
__construct( $name=null, &$master=null, &$refData=null, $seek=null)
Definition: vProperty.php:61
vProperty\ClearParameters
ClearParameters( $type=null)
Definition: vProperty.php:315
vProperty\Parameters
Parameters( $newparams=null)
Definition: vProperty.php:242
vProperty\Render
Render( $force=false)
Definition: vProperty.php:371
vProperty\Value
Value( $newvalue=null)
Definition: vProperty.php:224
vProperty\SetParameterValue
SetParameterValue( $name, $value)
Definition: vProperty.php:296
vProperty\TestFilter
TestFilter( $filters)
Definition: vProperty.php:442
vProperty\Name
Name( $newname=null)
Definition: vProperty.php:205
vProperty\ParseFromIterator
ParseFromIterator()
Definition: vProperty.php:102
vProperty\RenderParameters
RenderParameters()
Definition: vProperty.php:344
vProperty
Definition: vProperty.php:9
vProperty\GetParameterValue
GetParameterValue( $name)
Definition: vProperty.php:276
vObject
Definition: vObject.php:10