DAViCal
Principal.php
1<?php
12require_once('AwlCache.php');
13
19class 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 $row = $cache->set($this->cacheNs, $this->cacheKey, $row, 864000 );
238 return $this;
239 }
240
241 if ( $type == 'username' && $value == 'unauthenticated' ) {
242 $this->assignGuestValues();
243 }
244 }
245
251 public function __get( $property ) {
252 return $this->{$property};
253 }
254
255
261 public function __isset( $property ) {
262 return isset($this->{$property});
263 }
264
265 private function assignGuestValues() {
266 $this->user_no = -1;
267 $this->exists = false;
268 if ( empty($this->username) ) $this->username = translate('unauthenticated');
269 $this->fullname = $this->displayname = translate('Unauthenticated User');
270 $this->email = false;
271 $this->is_principal = true;
272 $this->is_calendar = false;
273 $this->principal_id = -1;
274 $this->privileges = $this->default_privileges = 0;
275 }
276
277 private function assignRowValues( $db_row ) {
278 foreach( $db_row AS $k => $v ) {
279 $this->{$k} = $v;
280 }
281 }
282
283 public function Exists() {
284 return $this->exists;
285 }
286
287
288 public function byEmail() {
289 return $this->by_email;
290 }
291
292
297 private function usernameFromPath( $path ) {
298 global $session, $c;
299
300 if ( $path == '/' || $path == '' ) {
301 dbg_error_log( 'Principal', 'No useful path split possible' );
302 return $session->username;
303 }
304
305 $path_split = explode('/', $path );
306 @dbg_error_log( 'Principal', 'Path split into at least /// %s /// %s /// %s', $path_split[1], $path_split[2], $path_split[3] );
307
308 $username = $path_split[1];
309 if ( $path_split[1] == 'principals' && isset($path_split[3]) ) {
310 $username = $path_split[3];
311 $this->original_request_url = $path;
312 }
313 if ( substr($username,0,1) == '~' ) {
314 $username = substr($username,1);
315 $this->original_request_url = $path;
316 }
317
318 if ( isset($c->allow_by_email) && $c->allow_by_email && preg_match( '#^(\S+@\S+[.]\S+)$#', $username) ) {
319 // This might seem inefficient, but we cache the result, so the second time will not read from the DB
320 $p = new Principal('email',$username);
321 $username = $p->username;
322 $this->by_email = true;
323 }
324 return $username;
325 }
326
327
332 function username() {
333 return (isset($this->username)?$this->username:false);
334 }
335
336
341 function setUsername($new_username) {
342 if ( $this->exists && isset($this->username) ) return false;
343 $this->username = $new_username;
344 return $this->username;
345 }
346
347
352 function user_no() {
353 return (isset($this->user_no)?$this->user_no:false);
354 }
355
356
361 function principal_id() {
362 return (isset($this->principal_id)?$this->principal_id:false);
363 }
364
365
370 function email() {
371 return (isset($this->email)?$this->email:false);
372 }
373
374
379 function dav_name() {
380 if ( !isset($this->dav_name) ) {
381 if ( !isset($this->username) ) {
382 throw new Exception('Can\'t calculate dav_name for unknown username');
383 }
384 $this->dav_name = '/'.$this->username.'/';
385 }
386 return $this->dav_name;
387 }
388
389
393 protected function FetchDeadProperties() {
394 if ( isset($this->dead_properties) ) return;
395
396 $this->dead_properties = array();
397 $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name()) );
398 if ( $qry->Exec('Principal') ) {
399 while ( $property = $qry->Fetch() ) {
400 $this->dead_properties[$property->property_name] = DAVResource::BuildDeadPropertyXML($property->property_name,$property->property_value);
401 }
402 }
403 }
404
405
410 protected function FetchCollections() {
411 if ( isset($this->collections) ) return;
412
413 $this->collections = array();
414 $qry = new AwlQuery('SELECT * FROM collection WHERE user_no= :user_no', array(':user_no' => $this->user_no()) );
415 if ( $qry->Exec('Principal') ) {
416 while ( $collection = $qry->Fetch() ) {
417 $this->collections[$collection->dav_name] = $collection;
418 }
419 }
420 }
421
422
427 function default_calendar() {
428 global $c;
429
430 if ( !isset($this->default_calendar) ) {
431 $this->default_calendar = false;
432 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
433 if ( isset($this->dead_properties['urn:ietf:params:xml:ns:caldav:schedule-default-calendar-URL']) ) {
434 $this->default_calendar = $this->dead_properties['urn:ietf:params:xml:ns:caldav:schedule-default-calendar-URL'];
435 }
436 else {
437 if ( !isset($this->collections) ) $this->FetchCollections();
438 $dav_name = $this->dav_name().$c->home_calendar_name.'/';
439 if ( isset($this->collections[$dav_name]) && ($this->collections[$dav_name]->is_calendar == 't') ) {
440 $this->default_calendar = $dav_name;
441 }
442 else {
443 $dav_name = $this->dav_name().'home/';
444 if ( isset($this->collections[$dav_name]) && ($this->collections[$dav_name]->is_calendar == 't') ) {
445 $this->default_calendar = $dav_name;
446 }
447 else {
448 foreach( $this->collections AS $dav_name => $collection ) {
449 if ( $collection->is_calendar == 't' ) {
450 $this->default_calendar = $dav_name;
451 }
452 }
453 }
454 }
455 }
456 }
457 return $this->default_calendar;
458 }
459
460
467 public function url($type = 'principal', $internal=false ) {
468 global $c;
469
470 if ( $internal )
471 $result = $this->dav_name();
472 else {
473 if ( isset($this->original_request_url) && $type == 'principal' )
474 $result = $this->original_request_url;
475 else
476 $result = $this->url;
477 }
478
479 switch( $type ) {
480 case 'principal': break;
481 case 'schedule-default-calendar': $result = $this->default_calendar(); break;
482 case 'schedule-inbox': $result .= '.in/'; break;
483 case 'schedule-outbox': $result .= '.out/'; break;
484 case 'dropbox': $result .= '.drop/'; break;
485 case 'notifications': $result .= '.notify/'; break;
486 default:
487 fatal('Unknown internal URL type "'.$type.'"');
488 }
489 return ConstructURL(DeconstructURL($result));
490 }
491
492
493 public function internal_url($type = 'principal' ) {
494 return $this->url($type,true);
495 }
496
497
498 public function unCache() {
499 if ( !isset($this->cacheNs) ) return;
500 $cache = getCacheInstance();
501 $cache->delete($this->cacheNs, null );
502 }
503
504
505 private function Write( $field_values, $inserting=true ) {
506 global $c;
507 if ( is_array($field_values) ) $field_values = (object) $field_values;
508
509 if ( !isset($field_values->{'user_active'}) ) {
510 if ( isset($field_values->{'active'}) )
511 $field_values->{'user_active'} = $field_values->{'active'};
512 else if ( $inserting )
513 $field_values->{'user_active'} = true;
514 }
515 if ( !isset($field_values->{'modified'}) && isset($field_values->{'updated'}) )
516 $field_values->{'modified'} = $field_values->{'updated'};
517 if ( !isset($field_values->{'type_id'}) && $inserting )
518 $field_values->{'type_id'} = 1; // Default to 'person'
519 if ( !isset($field_values->{'default_privileges'}) && $inserting )
520 $field_values->{'default_privileges'} = sprintf('%024s',decbin(privilege_to_bits($c->default_privileges)));
521
522
523 $sql = '';
524 if ( $inserting ) {
525 $insert_fields = array();
526 $param_names = array();
527 }
528 else {
529 $update_list = array();
530 }
531 $sql_params = array();
532 foreach( self::updateableFields() AS $k ) {
533 if ( !isset($field_values->{$k}) && !isset($this->{$k}) ) continue;
534
535 $param_name = ':'.$k;
536 $sql_params[$param_name] = (isset($field_values->{$k}) ? $field_values->{$k} : $this->{$k});
537 if ( $k == 'default_privileges' ) {
538 $sql_params[$param_name] = sprintf('%024s',$sql_params[$param_name]);
539 $param_name = 'cast('.$param_name.' as text)::BIT(24)';
540 }
541 else if ( $k == 'modified'
542 && isset($field_values->{$k})
543 && preg_match('{^([23]\d\d\d[01]\d[0123]\d)T?([012]\d[0-5]\d[0-5]\d)$}', $field_values->{$k}, $matches) ) {
544 $sql_params[$param_name] = $matches[1] . 'T' . $matches[2];
545 }
546
547 if ( $inserting ) {
548 $param_names[] = $param_name;
549 $insert_fields[] = $k;
550 }
551 else {
552 $update_list[] = $k.'='.$param_name;
553 }
554 }
555
556 if ( $inserting && isset(self::$db_mandatory_fields) ) {
557 foreach( self::$db_mandatory_fields AS $k ) {
558 if ( !isset($sql_params[':'.$k]) ) {
559 throw new Exception( get_class($this).'::Create: Mandatory field "'.$k.'" is not set.');
560 }
561 }
562 if ( isset($this->user_no) ) {
563 $param_names[] = ':user_no';
564 $insert_fields[] = 'user_no';
565 $sql_params[':user_no'] = $this->user_no;
566 }
567 if ( isset($this->created) ) {
568 $param_names[] = ':created';
569 $insert_fields[] = 'created';
570 $sql_params[':created'] = $this->created;
571 }
572 $sql = 'INSERT INTO '.self::$db_tablename.' ('.implode(',',$insert_fields).') VALUES('.implode(',',$param_names).')';
573 }
574 else {
575 $sql = 'UPDATE '.self::$db_tablename.' SET '.implode(',',$update_list);
576 $sql .= ' WHERE principal_id=:principal_id';
577 $sql_params[':principal_id'] = $this->principal_id;
578 }
579
580 $qry = new AwlQuery($sql, $sql_params);
581 if ( $qry->Exec('Principal',__FILE__,__LINE__) ) {
582 $this->unCache();
583 $new_principal = new Principal('username', $sql_params[':username']);
584 foreach( $new_principal AS $k => $v ) {
585 $this->{$k} = $v;
586 }
587 }
588 }
589
590
591 public function Create( $field_values ) {
592 $this->Write($field_values, true);
593 }
594
595 public function Update( $field_values ) {
596 if ( !$this->Exists() ) {
597 throw new Exception( get_class($this).'::Create: Attempting to update non-existent record.');
598 }
599 $this->Write($field_values, false);
600 }
601
602 static public function cacheFlush( $where, $whereparams=array() ) {
603 $cache = getCacheInstance();
604 if ( !$cache->isActive() ) return;
605 $qry = new AwlQuery('SELECT dav_name FROM dav_principal WHERE '.$where, $whereparams );
606 if ( $qry->Exec('Principal',__FILE__,__LINE__) ) {
607 while( $row = $qry->Fetch() ) {
608 $cache->delete('principal-'.$row->dav_name, null);
609 }
610 }
611 }
612
613 static public function cacheDelete( $type, $value ) {
614 $cache = getCacheInstance();
615 if ( !$cache->isActive() ) return;
616 if ( $type == 'username' ) {
617 $value = '/'.$value.'/';
618 }
619 $cache->delete('principal-'.$value, null);
620 }
621}
static BuildDeadPropertyXML($property_name, $raw_string)
__isset( $property)
Definition: Principal.php:261
__construct( $type, $value, $use_cache=true)
Definition: Principal.php:122
__get( $property)
Definition: Principal.php:251
url($type='principal', $internal=false)
Definition: Principal.php:467
usernameFromPath( $path)
Definition: Principal.php:297
FetchDeadProperties()
Definition: Principal.php:393
FetchCollections()
Definition: Principal.php:410
principal_id()
Definition: Principal.php:361
default_calendar()
Definition: Principal.php:427
setUsername($new_username)
Definition: Principal.php:341