DAViCal
Principal.php
1 <?php
12 require_once('AwlCache.php');
13 
19 class Principal {
20 
25  private static $db_tablename = 'dav_principal';
26  private static $db_mandatory_fields = array(
27  'username',
28  );
29 
30  public static function updateableFields() {
31  return array(
32  'username', 'email', 'user_active', 'modified', 'password', 'fullname',
33  'email_ok', 'date_format_type', 'locale', 'type_id', 'displayname', 'default_privileges'
34  );
35  }
36 
42  private static $byUserno = array();
43  private static $byId = array();
44  private static $byEmail = array();
45 
49  protected $username;
50  protected $user_no;
51  protected $principal_id;
52  protected $email;
53  protected $dav_name;
54  public $user_active;
55  public $created;
56  public $modified;
57  public $password;
58  public $fullname;
59  public $email_ok;
60  public $date_format_type;
61  public $locale;
62  public $type_id;
63  public $displayname;
64  public $default_privileges;
65  public $is_principal;
66  public $is_calendar;
67  public $collection_id;
68  public $is_addressbook;
69  public $resourcetypes;
70  public $privileges;
71 
76  protected $exists;
77 
81  protected $url;
82 
86  protected $original_request_url;
87 
92  protected $by_email;
93 
98  private $cacheNs;
99  private $cacheKey;
100 
101  protected $collections;
102  protected $dead_properties;
103  protected $default_calendar;
104 
122  function __construct( $type, $value, $use_cache=true ) {
123  global $c, $session;
124 
125  $this->exists = false;
126  $this->by_email = false;
127  $this->original_request_url = null;
128 
129  switch( $type ) {
130  case 'path':
131  $type = 'username';
132  $value = $this->usernameFromPath($value);
133  break;
134  case 'dav_name':
135  $type = 'username';
136  $value = substr($value, 1, -1);
137  break;
138  }
139 
140 
144  switch ( $type ) {
145  case 'user_no': $this->user_no = $value; break;
146  case 'principal_id': $this->principal_id = $value; break;
147  case 'email': $this->email = $value; break;
148  case 'username': $this->username = $value; break;
149  default:
150  throw new Exception('Can only retrieve a Principal by user_no,principal_id,username or email address');
151  }
152 
153  $cache = getCacheInstance();
154  if ( $use_cache && isset($session->principal_id) ) {
155  switch ( $type ) {
156  case 'user_no':
157  if ( isset(self::$byUserno[$value]) ) {
158  $type = 'username';
159  $value = self::$byUserno[$value];
160  }
161  break;
162  case 'principal_id':
163  if ( isset(self::$byId[$value]) ) {
164  $type = 'username';
165  $value = self::$byId[$value];
166  }
167  break;
168  case 'email':
169  $this->by_email = true;
170  if ( isset(self::$byEmail[$value]) ) {
171  $type = 'username';
172  $value = self::$byEmail[$value];
173  }
174  break;
175  }
176 
177  if ( $type == 'username' ) {
178  $this->username = $value;
179  $this->dav_name = '/'.$value.'/';
180  $this->url = ConstructURL( $this->dav_name, true );
181  $this->cacheNs = 'principal-/'.$value.'/';
182  $this->cacheKey = 'p-'.$session->principal_id;
183  $row = $cache->get('principal-/'.$value.'/', 'p-'.$session->principal_id );
184  if ( $row !== false ) {
185  self::$byId[$row->principal_id] = $row->username;
186  self::$byUserno[$row->user_no] = $row->username;
187  self::$byEmail[$row->email] = $row->username;
188  $this->assignRowValues($row);
189  $this->url = ConstructURL( $this->dav_name, true );
190  $this->exists = true;
191  return $this;
192  }
193  }
194  }
195 
196  $sql = 'SELECT *, ';
197  if ( isset($session->principal_id) && $session->principal_id !== false ) {
198  $sql .= 'pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS privileges ';
199  $params = array( ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
200  }
201  else {
202  $sql .= '0::BIT(24) AS privileges ';
203  $params = array( );
204  }
205  $sql .= 'FROM dav_principal WHERE ';
206  switch ( $type ) {
207  case 'username':
208  $sql .= 'lower(username)=lower(text(:param))';
209  break;
210  case 'user_no':
211  $sql .= 'user_no=:param';
212  break;
213  case 'principal_id':
214  $sql .= 'principal_id=:param';
215  break;
216  case 'email':
217  $this->by_email = true;
218  $sql .= 'lower(email)=lower(:param)';
219  break;
220  }
221  $params[':param'] = $value;
222 
223  $qry = new AwlQuery( $sql, $params );
224  if ( $qry->Exec('Principal',__LINE__,__FILE__) && $qry->rows() == 1 && $row = $qry->Fetch() ) {
225  $this->exists = true;
226  if ( isset($session->principal_id) ) {
227  self::$byId[$row->principal_id] = $row->username;
228  self::$byUserno[$row->user_no] = $row->username;
229  self::$byEmail[$row->email] = $row->username;
230  if ( !isset($this->cacheNs) ) {
231  $this->cacheNs = 'principal-'.$row->dav_name;
232  $this->cacheKey = 'p-'.$session->principal_id;
233  }
234  }
235  $this->assignRowValues($row);
236  $this->url = ConstructURL( $this->dav_name, true );
237 
238  if (isset($this->cacheNs))
239  $cache->set($this->cacheNs, $this->cacheKey, $row, 864000 );
240 
241  return $this;
242  }
243 
244  if ( $type == 'username' && $value == 'unauthenticated' ) {
245  $this->assignGuestValues();
246  }
247  }
248 
254  public function __get( $property ) {
255  return $this->{$property};
256  }
257 
258 
264  public function __isset( $property ) {
265  return isset($this->{$property});
266  }
267 
268  private function assignGuestValues() {
269  $this->user_no = -1;
270  $this->exists = false;
271  if ( empty($this->username) ) $this->username = translate('unauthenticated');
272  $this->fullname = $this->displayname = translate('Unauthenticated User');
273  $this->email = false;
274  $this->is_principal = true;
275  $this->is_calendar = false;
276  $this->principal_id = -1;
277  $this->privileges = $this->default_privileges = 0;
278  }
279 
280  private function assignRowValues( $db_row ) {
281  foreach( $db_row AS $k => $v ) {
282  $this->{$k} = $v;
283  }
284  }
285 
286  public function Exists() {
287  return $this->exists;
288  }
289 
290 
291  public function byEmail() {
292  return $this->by_email;
293  }
294 
295 
300  private function usernameFromPath( $path ) {
301  global $session, $c;
302 
303  if ( $path == '/' || $path == '' ) {
304  dbg_error_log( 'Principal', 'No useful path split possible' );
305  return $session->username;
306  }
307 
308  $path_split = explode('/', $path );
309  @dbg_error_log( 'Principal', 'Path split into at least /// %s /// %s /// %s', $path_split[1], $path_split[2], $path_split[3] );
310 
311  $username = $path_split[1];
312  if ( $path_split[1] == 'principals' && isset($path_split[3]) ) {
313  $username = $path_split[3];
314  $this->original_request_url = $path;
315  }
316  if ( substr($username,0,1) == '~' ) {
317  $username = substr($username,1);
318  $this->original_request_url = $path;
319  }
320 
321  if ( isset($c->allow_by_email) && $c->allow_by_email && preg_match( '#^(\S+@\S+[.]\S+)$#', $username) ) {
322  // This might seem inefficient, but we cache the result, so the second time will not read from the DB
323  $p = new Principal('email',$username);
324  $username = $p->username;
325  $this->by_email = true;
326  }
327  return $username;
328  }
329 
330 
335  function username() {
336  return (isset($this->username)?$this->username:false);
337  }
338 
339 
344  function setUsername($new_username) {
345  if ( $this->exists && isset($this->username) ) return false;
346  $this->username = $new_username;
347  return $this->username;
348  }
349 
350 
355  function user_no() {
356  return (isset($this->user_no)?$this->user_no:false);
357  }
358 
359 
364  function principal_id() {
365  return (isset($this->principal_id)?$this->principal_id:false);
366  }
367 
368 
373  function email() {
374  return (isset($this->email)?$this->email:false);
375  }
376 
377 
382  function dav_name() {
383  if ( !isset($this->dav_name) ) {
384  if ( !isset($this->username) ) {
385  throw new Exception('Can\'t calculate dav_name for unknown username');
386  }
387  $this->dav_name = '/'.$this->username.'/';
388  }
389  return $this->dav_name;
390  }
391 
392 
396  protected function FetchDeadProperties() {
397  if ( isset($this->dead_properties) ) return;
398 
399  $this->dead_properties = array();
400  $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name()) );
401  if ( $qry->Exec('Principal') ) {
402  while ( $property = $qry->Fetch() ) {
403  $this->dead_properties[$property->property_name] = DAVResource::BuildDeadPropertyXML($property->property_name,$property->property_value);
404  }
405  }
406  }
407 
408 
413  protected function FetchCollections() {
414  if ( isset($this->collections) ) return;
415 
416  $this->collections = array();
417  $qry = new AwlQuery('SELECT * FROM collection WHERE user_no= :user_no', array(':user_no' => $this->user_no()) );
418  if ( $qry->Exec('Principal') ) {
419  while ( $collection = $qry->Fetch() ) {
420  $this->collections[$collection->dav_name] = $collection;
421  }
422  }
423  }
424 
425 
430  function default_calendar() {
431  global $c;
432 
433  if ( !isset($this->default_calendar) ) {
434  $this->default_calendar = false;
435  if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
436  if ( isset($this->dead_properties['urn:ietf:params:xml:ns:caldav:schedule-default-calendar-URL']) ) {
437  $this->default_calendar = $this->dead_properties['urn:ietf:params:xml:ns:caldav:schedule-default-calendar-URL'];
438  }
439  else {
440  if ( !isset($this->collections) ) $this->FetchCollections();
441  $dav_name = $this->dav_name().$c->home_calendar_name.'/';
442  if ( isset($this->collections[$dav_name]) && ($this->collections[$dav_name]->is_calendar == 't') ) {
443  $this->default_calendar = $dav_name;
444  }
445  else {
446  $dav_name = $this->dav_name().'home/';
447  if ( isset($this->collections[$dav_name]) && ($this->collections[$dav_name]->is_calendar == 't') ) {
448  $this->default_calendar = $dav_name;
449  }
450  else {
451  foreach( $this->collections AS $dav_name => $collection ) {
452  if ( $collection->is_calendar == 't' ) {
453  $this->default_calendar = $dav_name;
454  }
455  }
456  }
457  }
458  }
459  }
460  return $this->default_calendar;
461  }
462 
463 
470  public function url($type = 'principal', $internal=false ) {
471  global $c;
472 
473  if ( $internal )
474  $result = $this->dav_name();
475  else {
476  if ( isset($this->original_request_url) && $type == 'principal' )
477  $result = $this->original_request_url;
478  else
479  $result = $this->url;
480  }
481 
482  switch( $type ) {
483  case 'principal': break;
484  case 'schedule-default-calendar': $result = $this->default_calendar(); break;
485  case 'schedule-inbox': $result .= '.in/'; break;
486  case 'schedule-outbox': $result .= '.out/'; break;
487  case 'dropbox': $result .= '.drop/'; break;
488  case 'notifications': $result .= '.notify/'; break;
489  default:
490  fatal('Unknown internal URL type "'.$type.'"');
491  }
492  return ConstructURL(DeconstructURL($result));
493  }
494 
495 
496  public function internal_url($type = 'principal' ) {
497  return $this->url($type,true);
498  }
499 
500 
501  public function unCache() {
502  if ( !isset($this->cacheNs) ) return;
503  $cache = getCacheInstance();
504  $cache->delete($this->cacheNs, null );
505  }
506 
507 
508  private function Write( $field_values, $inserting=true ) {
509  global $c;
510  if ( is_array($field_values) ) $field_values = (object) $field_values;
511 
512  if ( !isset($field_values->{'user_active'}) ) {
513  if ( isset($field_values->{'active'}) )
514  $field_values->{'user_active'} = $field_values->{'active'};
515  else if ( $inserting )
516  $field_values->{'user_active'} = true;
517  }
518  if ( !isset($field_values->{'modified'}) && isset($field_values->{'updated'}) )
519  $field_values->{'modified'} = $field_values->{'updated'};
520  if ( !isset($field_values->{'type_id'}) && $inserting )
521  $field_values->{'type_id'} = 1; // Default to 'person'
522  if ( !isset($field_values->{'default_privileges'}) && $inserting )
523  $field_values->{'default_privileges'} = sprintf('%024s',decbin(privilege_to_bits($c->default_privileges)));
524 
525 
526  $sql = '';
527  if ( $inserting ) {
528  $insert_fields = array();
529  $param_names = array();
530  }
531  else {
532  $update_list = array();
533  }
534  $sql_params = array();
535  foreach( self::updateableFields() AS $k ) {
536  if ( !isset($field_values->{$k}) && !isset($this->{$k}) ) continue;
537 
538  $param_name = ':'.$k;
539  $sql_params[$param_name] = (isset($field_values->{$k}) ? $field_values->{$k} : $this->{$k});
540  if ( $k == 'default_privileges' ) {
541  $sql_params[$param_name] = sprintf('%024s',$sql_params[$param_name]);
542  $param_name = 'cast('.$param_name.' as text)::BIT(24)';
543  }
544  else if ( $k == 'modified'
545  && isset($field_values->{$k})
546  && preg_match('{^([23]\d\d\d[01]\d[0123]\d)T?([012]\d[0-5]\d[0-5]\d)$}', $field_values->{$k}, $matches) ) {
547  $sql_params[$param_name] = $matches[1] . 'T' . $matches[2];
548  }
549 
550  if ( $inserting ) {
551  $param_names[] = $param_name;
552  $insert_fields[] = $k;
553  }
554  else {
555  $update_list[] = $k.'='.$param_name;
556  }
557  }
558 
559  if ( $inserting && isset(self::$db_mandatory_fields) ) {
560  foreach( self::$db_mandatory_fields AS $k ) {
561  if ( !isset($sql_params[':'.$k]) ) {
562  throw new Exception( get_class($this).'::Create: Mandatory field "'.$k.'" is not set.');
563  }
564  }
565  if ( isset($this->user_no) ) {
566  $param_names[] = ':user_no';
567  $insert_fields[] = 'user_no';
568  $sql_params[':user_no'] = $this->user_no;
569  }
570  if ( isset($this->created) ) {
571  $param_names[] = ':created';
572  $insert_fields[] = 'created';
573  $sql_params[':created'] = $this->created;
574  }
575  $sql = 'INSERT INTO '.self::$db_tablename.' ('.implode(',',$insert_fields).') VALUES('.implode(',',$param_names).')';
576  }
577  else {
578  $sql = 'UPDATE '.self::$db_tablename.' SET '.implode(',',$update_list);
579  $sql .= ' WHERE principal_id=:principal_id';
580  $sql_params[':principal_id'] = $this->principal_id;
581  }
582 
583  $qry = new AwlQuery($sql, $sql_params);
584  if ( $qry->Exec('Principal',__FILE__,__LINE__) ) {
585  $this->unCache();
586  $new_principal = new Principal('username', $sql_params[':username']);
587  foreach( $new_principal AS $k => $v ) {
588  $this->{$k} = $v;
589  }
590  }
591  }
592 
593 
594  public function Create( $field_values ) {
595  $this->Write($field_values, true);
596  }
597 
598  public function Update( $field_values ) {
599  if ( !$this->Exists() ) {
600  throw new Exception( get_class($this).'::Create: Attempting to update non-existent record.');
601  }
602  $this->Write($field_values, false);
603  }
604 
605  static public function cacheFlush( $where, $whereparams=array() ) {
606  $cache = getCacheInstance();
607  if ( !$cache->isActive() ) return;
608  $qry = new AwlQuery('SELECT dav_name FROM dav_principal WHERE '.$where, $whereparams );
609  if ( $qry->Exec('Principal',__FILE__,__LINE__) ) {
610  while( $row = $qry->Fetch() ) {
611  $cache->delete('principal-'.$row->dav_name, null);
612  }
613  }
614  }
615 
616  static public function cacheDelete( $type, $value ) {
617  $cache = getCacheInstance();
618  if ( !$cache->isActive() ) return;
619  if ( $type == 'username' ) {
620  $value = '/'.$value.'/';
621  }
622  $cache->delete('principal-'.$value, null);
623  }
624 }
__isset( $property)
Definition: Principal.php:264
__get( $property)
Definition: Principal.php:254
static BuildDeadPropertyXML($property_name, $raw_string)
FetchDeadProperties()
Definition: Principal.php:396
url($type='principal', $internal=false)
Definition: Principal.php:470
__construct( $type, $value, $use_cache=true)
Definition: Principal.php:122
usernameFromPath( $path)
Definition: Principal.php:300
default_calendar()
Definition: Principal.php:430
setUsername($new_username)
Definition: Principal.php:344
principal_id()
Definition: Principal.php:364
FetchCollections()
Definition: Principal.php:413