1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: require_once('AwlCache.php');
13:
14: 15: 16: 17: 18:
19: class Principal {
20:
21: 22: 23: 24:
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:
37: 38: 39: 40: 41:
42: private static $byUserno = array();
43: private static $byId = array();
44: private static $byEmail = array();
45:
46: 47: 48:
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:
72: 73: 74: 75:
76: protected $exists;
77:
78: 79: 80:
81: protected $url;
82:
83: 84: 85:
86: protected $original_request_url;
87:
88: 89: 90: 91:
92: protected $by_email;
93:
94: 95: 96: 97:
98: private $cacheNs;
99: private $cacheKey;
100:
101: protected $collections;
102: protected $dead_properties;
103: protected $default_calendar;
104:
105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121:
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:
141: 142: 143:
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:
246: 247: 248: 249: 250:
251: public function __get( $property ) {
252: return $this->{$property};
253: }
254:
255:
256: 257: 258: 259: 260:
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:
293: 294: 295: 296: 297:
298: private function usernameFromPath( $path ) {
299: global $session, $c;
300:
301: if ( $path == '/' || $path == '' ) {
302: dbg_error_log( 'Principal', 'No useful path split possible' );
303: return $session->username;
304: }
305:
306: $path_split = explode('/', $path );
307: @dbg_error_log( 'Principal', 'Path split into at least /// %s /// %s /// %s', $path_split[1], $path_split[2], $path_split[3] );
308:
309: $username = $path_split[1];
310: if ( $path_split[1] == 'principals' && isset($path_split[3]) ) {
311: $username = $path_split[3];
312: $this->original_request_url = $path;
313: }
314: if ( substr($username,0,1) == '~' ) {
315: $username = substr($username,1);
316: $this->original_request_url = $path;
317: }
318:
319: if ( isset($c->allow_by_email) && $c->allow_by_email && preg_match( '#^(\S+@\S+[.]\S+)$#', $username) ) {
320:
321: $p = new Principal('email',$username);
322: $username = $p->username;
323: $this->by_email = true;
324: }
325: return $username;
326: }
327:
328:
329: 330: 331: 332:
333: function username() {
334: return (isset($this->username)?$this->username:false);
335: }
336:
337:
338: 339: 340: 341:
342: function setUsername($new_username) {
343: if ( $this->exists && isset($this->username) ) return false;
344: $this->username = $new_username;
345: return $this->username;
346: }
347:
348:
349: 350: 351: 352:
353: function user_no() {
354: return (isset($this->user_no)?$this->user_no:false);
355: }
356:
357:
358: 359: 360: 361:
362: function principal_id() {
363: return (isset($this->principal_id)?$this->principal_id:false);
364: }
365:
366:
367: 368: 369: 370:
371: function email() {
372: return (isset($this->email)?$this->email:false);
373: }
374:
375:
376: 377: 378: 379:
380: function dav_name() {
381: if ( !isset($this->dav_name) ) {
382: if ( !isset($this->username) ) {
383: throw new Exception('Can\'t calculate dav_name for unknown username');
384: }
385: $this->dav_name = '/'.$this->username.'/';
386: }
387: return $this->dav_name;
388: }
389:
390:
391: 392: 393:
394: protected function FetchDeadProperties() {
395: if ( isset($this->dead_properties) ) return;
396:
397: $this->dead_properties = array();
398: $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name()) );
399: if ( $qry->Exec('Principal') ) {
400: while ( $property = $qry->Fetch() ) {
401: $this->dead_properties[$property->property_name] = DAVResource::BuildDeadPropertyXML($property->property_name,$property->property_value);
402: }
403: }
404: }
405:
406:
407: 408: 409: 410:
411: protected function FetchCollections() {
412: if ( isset($this->collections) ) return;
413:
414: $this->collections = array();
415: $qry = new AwlQuery('SELECT * FROM collection WHERE user_no= :user_no', array(':user_no' => $this->user_no()) );
416: if ( $qry->Exec('Principal') ) {
417: while ( $collection = $qry->Fetch() ) {
418: $this->collections[$collection->dav_name] = $collection;
419: }
420: }
421: }
422:
423:
424: 425: 426: 427:
428: function default_calendar() {
429: global $c;
430:
431: if ( !isset($this->default_calendar) ) {
432: $this->default_calendar = false;
433: if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
434: if ( isset($this->dead_properties['urn:ietf:params:xml:ns:caldav:schedule-default-calendar-URL']) ) {
435: $this->default_calendar = $this->dead_properties['urn:ietf:params:xml:ns:caldav:schedule-default-calendar-URL'];
436: }
437: else {
438: if ( !isset($this->collections) ) $this->FetchCollections();
439: $dav_name = $this->dav_name().$c->home_calendar_name.'/';
440: if ( isset($this->collections[$dav_name]) && ($this->collections[$dav_name]->is_calendar == 't') ) {
441: $this->default_calendar = $dav_name;
442: }
443: else {
444: $dav_name = $this->dav_name().'home/';
445: if ( isset($this->collections[$dav_name]) && ($this->collections[$dav_name]->is_calendar == 't') ) {
446: $this->default_calendar = $dav_name;
447: }
448: else {
449: foreach( $this->collections AS $dav_name => $collection ) {
450: if ( $collection->is_calendar == 't' ) {
451: $this->default_calendar = $dav_name;
452: }
453: }
454: }
455: }
456: }
457: }
458: return $this->default_calendar;
459: }
460:
461:
462: 463: 464: 465: 466: 467:
468: public function url($type = 'principal', $internal=false ) {
469: global $c;
470:
471: if ( $internal )
472: $result = $this->dav_name();
473: else {
474: if ( isset($this->original_request_url) && $type == 'principal' )
475: $result = $this->original_request_url;
476: else
477: $result = $this->url;
478: }
479:
480: switch( $type ) {
481: case 'principal': break;
482: case 'schedule-default-calendar': $result = $this->default_calendar(); break;
483: case 'schedule-inbox': $result .= '.in/'; break;
484: case 'schedule-outbox': $result .= '.out/'; break;
485: case 'dropbox': $result .= '.drop/'; break;
486: case 'notifications': $result .= '.notify/'; break;
487: default:
488: fatal('Unknown internal URL type "'.$type.'"');
489: }
490: return ConstructURL(DeconstructURL($result));
491: }
492:
493:
494: public function internal_url($type = 'principal' ) {
495: return $this->url($type,true);
496: }
497:
498:
499: public function unCache() {
500: if ( !isset($this->cacheNs) ) return;
501: $cache = getCacheInstance();
502: $cache->delete($this->cacheNs, null );
503: }
504:
505:
506: private function Write( $field_values, $inserting=true ) {
507: global $c;
508: if ( is_array($field_values) ) $field_values = (object) $field_values;
509:
510: if ( !isset($field_values->{'user_active'}) ) {
511: if ( isset($field_values->{'active'}) )
512: $field_values->{'user_active'} = $field_values->{'active'};
513: else if ( $inserting )
514: $field_values->{'user_active'} = true;
515: }
516: if ( !isset($field_values->{'modified'}) && isset($field_values->{'updated'}) )
517: $field_values->{'modified'} = $field_values->{'updated'};
518: if ( !isset($field_values->{'type_id'}) && $inserting )
519: $field_values->{'type_id'} = 1;
520: if ( !isset($field_values->{'default_privileges'}) && $inserting )
521: $field_values->{'default_privileges'} = sprintf('%024s',decbin(privilege_to_bits($c->default_privileges)));
522:
523:
524: $sql = '';
525: if ( $inserting ) {
526: $insert_fields = array();
527: $param_names = array();
528: }
529: else {
530: $update_list = array();
531: }
532: $sql_params = array();
533: foreach( self::updateableFields() AS $k ) {
534: if ( !isset($field_values->{$k}) && !isset($this->{$k}) ) continue;
535:
536: $param_name = ':'.$k;
537: $sql_params[$param_name] = (isset($field_values->{$k}) ? $field_values->{$k} : $this->{$k});
538: if ( $k == 'default_privileges' ) {
539: $sql_params[$param_name] = sprintf('%024s',$sql_params[$param_name]);
540: $param_name = 'cast('.$param_name.' as text)::BIT(24)';
541: }
542: else if ( $k == 'modified'
543: && isset($field_values->{$k})
544: && preg_match('{^([23]\d\d\d[01]\d[0123]\d)T?([012]\d[0-5]\d[0-5]\d)$}', $field_values->{$k}, $matches) ) {
545: $sql_params[$param_name] = $matches[1] . 'T' . $matches[2];
546: }
547:
548: if ( $inserting ) {
549: $param_names[] = $param_name;
550: $insert_fields[] = $k;
551: }
552: else {
553: $update_list[] = $k.'='.$param_name;
554: }
555: }
556:
557: if ( $inserting && isset(self::$db_mandatory_fields) ) {
558: foreach( self::$db_mandatory_fields AS $k ) {
559: if ( !isset($sql_params[':'.$k]) ) {
560: throw new Exception( get_class($this).'::Create: Mandatory field "'.$k.'" is not set.');
561: }
562: }
563: if ( isset($this->user_no) ) {
564: $param_names[] = ':user_no';
565: $insert_fields[] = 'user_no';
566: $sql_params[':user_no'] = $this->user_no;
567: }
568: if ( isset($this->created) ) {
569: $param_names[] = ':created';
570: $insert_fields[] = 'created';
571: $sql_params[':created'] = $this->created;
572: }
573: $sql = 'INSERT INTO '.self::$db_tablename.' ('.implode(',',$insert_fields).') VALUES('.implode(',',$param_names).')';
574: }
575: else {
576: $sql = 'UPDATE '.self::$db_tablename.' SET '.implode(',',$update_list);
577: $sql .= ' WHERE principal_id=:principal_id';
578: $sql_params[':principal_id'] = $this->principal_id;
579: }
580:
581: $qry = new AwlQuery($sql, $sql_params);
582: if ( $qry->Exec('Principal',__FILE__,__LINE__) ) {
583: $this->unCache();
584: $new_principal = new Principal('username', $sql_params[':username']);
585: foreach( $new_principal AS $k => $v ) {
586: $this->{$k} = $v;
587: }
588: }
589: }
590:
591:
592: public function Create( $field_values ) {
593: $this->Write($field_values, true);
594: }
595:
596: public function Update( $field_values ) {
597: if ( !$this->Exists() ) {
598: throw new Exception( get_class($this).'::Create: Attempting to update non-existent record.');
599: }
600: $this->Write($field_values, false);
601: }
602:
603: static public function cacheFlush( $where, $whereparams=array() ) {
604: $cache = getCacheInstance();
605: if ( !$cache->isActive() ) return;
606: $qry = new AwlQuery('SELECT dav_name FROM dav_principal WHERE '.$where, $whereparams );
607: if ( $qry->Exec('Principal',__FILE__,__LINE__) ) {
608: while( $row = $qry->Fetch() ) {
609: $cache->delete('principal-'.$row->dav_name, null);
610: }
611: }
612: }
613:
614: static public function cacheDelete( $type, $value ) {
615: $cache = getCacheInstance();
616: if ( !$cache->isActive() ) return;
617: if ( $type == 'username' ) {
618: $value = '/'.$value.'/';
619: }
620: $cache->delete('principal-'.$value, null);
621: }
622: }
623: