Overview

Packages

  • awl
    • caldav-client-v2
    • RRule
  • davical
    • authentication
      • drivers
    • caldav
    • DAViCalSession
    • DAVTicket
    • external-bind
    • feed
    • HTTPAuthSession
    • iSchedule
    • iSchedule-POST
    • logging
    • metrics
    • Principal
    • propfind
    • PublicSession
    • Request
    • Resource
    • tzservice
  • None
  • PHP

Classes

  • DAVPrincipal
  • Principal
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: /**
  3: * An object representing a 'Principal' read from the database
  4: *
  5: * @package   davical
  6: * @subpackage   Principal
  7: * @author    Andrew McMillan <andrew@mcmillan.net.nz>
  8: * @copyright Morphoss Ltd <http://www.morhposs.com/>
  9: * @license   http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
 10: */
 11: 
 12: require_once('AwlCache.php');
 13: 
 14: /**
 15: * A class for things to do with a Principal
 16: *
 17: * @package   davical
 18: */
 19: class Principal {
 20: 
 21:   /**
 22:    * Some control over our DB
 23:    * @var unknown_type
 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:    * We cache these so if we try and access a row by principal_id/user_no/e_mail that we've
 39:    * already read we don't read it again.
 40:    * @var unknown_type
 41:    */
 42:   private static $byUserno = array();
 43:   private static $byId     = array();
 44:   private static $byEmail  = array();
 45: 
 46:   /**
 47:    * Columns from the database
 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:    * Whether this Principal actually exists in the database yet.
 74:    * @var boolean
 75:    */
 76:   protected $exists;
 77: 
 78:   /**
 79:   * @var The home URL of the principal
 80:   */
 81:   protected $url;
 82: 
 83:   /**
 84:   * @var The actual requested URL for this principal, when the request was for /principals/... or such
 85:   */
 86:   protected $original_request_url;
 87: 
 88:   /**
 89:    * Whether this was retrieved using an e-mail address
 90:    * @var boolean
 91:    */
 92:   protected $by_email;
 93: 
 94:   /**
 95:    * If we're using memcached this is the namespace we'll put stuff in
 96:    * @var unknown_type
 97:    */
 98:   private $cacheNs;
 99:   private $cacheKey;
100: 
101:   protected $collections;
102:   protected $dead_properties;
103:   protected $default_calendar;
104: 
105:   /**
106:    * Construct a new Principal object.  The principal record will be retrieved from the database, or (if not found) initialised to a new record.  You can test for whether the Principal exists by calling the Exists() method on the returned object.
107:    *
108:    * Depending on the supplied $type, the following behaviour will occur:
109:    *  path:          Will attempt to extract a username or email from the supplied path, and then do what those do.
110:    *  dav_name:      Expects the dav_name of a <em>principal</em>, exactly, like: /principal/ and will use that as for username.
111:    *  user_no:       Expects an integer which is the usr.user_no (deprecated)
112:    *  principal_id:  Expects an integer which is the principal.principal_id
113:    *  email:         Will try and retrieve a unique principal by using the email address.  Will fail (subsequent call to Exists() will be false) if there is not a unique match.
114:    *  username:      Will retrieve based on strtolower($value) = lower(usr.username)
115:    *
116:    * @param string $type One of 'path', 'dav_name', 'user_no', 'principal_id', 'email' or 'username'
117:    * @param mixed $value A value appropriate to the $type requested.
118:    * @param boolean $use_cache Whether to use an available cache source (default true)
119:    * @throws Exception When provided with an invalid $type parameter.
120:    * @return Principal
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:      * There are some values we can construct on the basis of the constructor value.
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:    * This will allow protected properties to be referenced for retrieval, but not
248:    * referenced for update.
249:    * @param $property
250:    */
251:   public function __get( $property ) {
252:     return $this->{$property};
253:   }
254: 
255: 
256:   /**
257:    * This will allow protected properties to be examined for whether they are set
258:    * without making them writable.  PHP 5.1 or later only.
259:    * @param $property
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:   * Work out the username, based on elements of the path.
295:   * @param string $path The path to be used.
296:   * @param array $options The request options, controlling whether e-mail paths are allowed.
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:       // This might seem inefficient, but we cache the result, so the second time will not read from the DB
321:       $p = new Principal('email',$username);
322:       $username = $p->username;
323:       $this->by_email = true;
324:     }
325:     return $username;
326:   }
327: 
328: 
329:   /**
330:   * Return the username
331:   * @return string The username
332:   */
333:   function username() {
334:     return (isset($this->username)?$this->username:false);
335:   }
336: 
337: 
338:   /**
339:   * Set the username - but only if the record does not yet exist!
340:   * @return string The username
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:   * Return the user_no
351:   * @return int The user_no
352:   */
353:   function user_no() {
354:     return (isset($this->user_no)?$this->user_no:false);
355:   }
356: 
357: 
358:   /**
359:   * Return the principal_id
360:   * @return string The principal_id
361:   */
362:   function principal_id() {
363:     return (isset($this->principal_id)?$this->principal_id:false);
364:   }
365: 
366: 
367:   /**
368:   * Return the email
369:   * @return string The email
370:   */
371:   function email() {
372:     return (isset($this->email)?$this->email:false);
373:   }
374: 
375: 
376:   /**
377:   * Return the partial path representing this principal
378:   * @return string The dav_name
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:   * Ensure the principal's dead properties are loaded
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:   * Fetch the list of collections for this principal
409:   * @return string The internal dav_name for the home_calendar, or null if there is none
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:   * Return the default calendar for this principal
426:   * @return string The internal dav_name for the home_calendar, or false if there is none
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:   * Return the URL for this principal
464:   * @param string $type The type of URL we want (the principal, by default)
465:   * @param boolean $internal Whether an internal reference is requested
466:   * @return string The principal-URL
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; // Default to 'person'
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: 
DAViCal API documentation generated by ApiGen 2.8.0