3 * Table Definition for Person
5 require_once 'DB/DataObject.php';
8 class Pman_Core_DataObjects_Person extends DB_DataObject
11 /* the code below is auto generated do not remove the above tag */
13 public $__table = 'Person'; // table name
14 public $id; // int(11) not_null primary_key auto_increment
15 public $email; // string(128) not_null
18 public $company_id; // int(11)
19 public $office_id; // int(11)
20 public $name; // string(128) not_null
21 public $firstname; // string(128) not_null
22 public $lastname; // string(128) not_null
23 public $phone; // string(32) not_null
24 public $fax; // string(32) not_null
26 public $role; // string(32) not_null
27 public $remarks; // blob(65535) not_null blob
28 public $passwd; // string(64) not_null
29 public $owner_id; // int(11) not_null
30 public $lang; // string(8)
31 public $no_reset_sent; // int(11)
32 public $action_type; // string(32)
33 public $project_id; // int(11)
36 public $active; // int(11) not_null
37 public $deleted_by; // int(11) not_null
38 public $deleted_dt; // datetime(19) binary
41 public $name_facebook; // VARCHAR(128) NULL;
42 public $url_blog; // VARCHAR(256) NULL ;
43 public $url_twitter; // VARCHAR(256) NULL ;
44 public $url_linkedin; // VARCHAR(256) NULL ;
46 public $phone_mobile; // varchar(32) NOT NULL DEFAULT '';
47 public $phone_direct; // varchar(32) NOT NULL DEFAULT '';
49 /* the code above is auto generated do not remove the tag below */
54 $p = DB_DataObject::Factory('Person');
55 $p->get($this->owner_id);
64 * FIXME !!!! -- USE Pman_Core_Mailer !!!!!
70 function buildMail($templateFile, $args)
73 $args = (array) $args;
74 $content = clone($this);
76 foreach((array)$args as $k=>$v) {
80 $ff = HTML_FlexyFramework::get();
83 //?? is this really the place for this???
86 empty($args['no_auth']) &&
87 !in_array($templateFile, array(
88 // templates that can be sent without authentication.
94 $content->authUser = $this->getAuthUser();
95 if (!$content->authUser) {
96 return PEAR::raiseError("Not authenticated");
100 // should handle x-forwarded...
102 $content->HTTP_HOST = isset($_SERVER["HTTP_HOST"]) ?
103 $_SERVER["HTTP_HOST"] :
104 (isset($ff->HTTP_HOST) ? $ff->HTTP_HOST : 'localhost');
106 /* use the regex compiler, as it doesnt parse <tags */
109 'compiler' => 'Flexy',
111 'filters' => array('SimpleTags','Mail'),
117 if (!empty($args['templateDir'])) {
118 $tops['templateDir'] = $args['templateDir'];
123 require_once 'HTML/Template/Flexy.php';
124 $template = new HTML_Template_Flexy( $tops );
125 $template->compile("mail/$templateFile.txt");
127 /* use variables from this object to ouput data. */
128 $mailtext = $template->bufferedOutputObject($content);
131 // if a html file with the same name exists, use that as the body
132 // I've no idea where this code went, it was here before..
133 if (false !== $template->resolvePath ( "mail/$templateFile.html" )) {
134 $tops['nonHTML'] = false;
135 $template = new HTML_Template_Flexy( $tops );
136 $template->compile("mail/$templateFile.html");
137 $htmlbody = $template->bufferedOutputObject($content);
143 //echo "<PRE>";print_R($mailtext);
144 //print_R($mailtext);exit;
145 /* With the output try and send an email, using a few tricks in Mail_MimeDecode. */
146 require_once 'Mail/mimeDecode.php';
147 require_once 'Mail.php';
149 $decoder = new Mail_mimeDecode($mailtext);
150 $parts = $decoder->getSendArray();
152 if (PEAR::isError($parts)) {
154 //echo "PROBLEM: {$parts->message}";
157 list($recipents,$headers,$body) = $parts;
158 $recipents = array($this->email);
159 if (!empty($content->bcc) && is_array($content->bcc)) {
160 $recipents =array_merge($recipents, $content->bcc);
162 $headers['Date'] = date('r');
164 if ($htmlbody !== false) {
165 require_once 'Mail/mime.php';
166 $mime = new Mail_mime(array('eol' => "\n"));
167 $mime->setTXTBody($body);
168 $mime->setHTMLBody($htmlbody);
169 // I think there might be code in mediaoutreach toEmail somewhere
170 // h embeds images here..
171 $body = $mime->get();
172 $headers = $mime->headers($headers);
179 'recipients' => $recipents,
180 'headers' => $headers,
190 * - user must be authenticate or args[no_auth] = true
191 * or template = password_[reset|welcome]
194 function sendTemplate($templateFile, $args)
197 $ar = $this->buildMail($templateFile, $args);
200 //print_r($recipents);exit;
201 $mailOptions = PEAR::getStaticProperty('Mail','options');
202 $mail = Mail::factory("SMTP",$mailOptions);
204 if (PEAR::isError($mail)) {
207 $oe = error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT);
208 $ret = $mail->send($ar['recipients'],$ar['headers'],$ar['body']);
209 error_reporting($oe);
218 function getEmailFrom()
220 return '"' . addslashes($this->name) . '" <' . $this->email . '>';
222 function toEventString()
224 return empty($this->name) ? $this->email : $this->name;
226 function verifyAuth()
228 $ff= HTML_FlexyFramework::get();
229 if (!empty($ff->Pman['auth_comptype']) &&
230 (!$this->company_id || ($ff->Pman['auth_comptype'] != $this->company()->comptype))
233 // force a logout - without a check on the isAuth - as this is called from there..
234 $db = $this->getDatabaseConnection();
235 $sesPrefix = $ff->appNameShort .'-'.get_class($this) .'-'.$db->dsn['database'] ;
236 $_SESSION[__CLASS__][$sesPrefix .'-auth'] = "";
239 $ff->page->jerr("Login not permited to outside companies");
246 // ---------------- authentication / passwords and keys stuff ----------------
249 $db = $this->getDatabaseConnection();
250 // we combine db + project names,
251 // otherwise if projects use different 'auth' objects
252 // then we get unserialize issues.
253 $ff= HTML_FlexyFramework::get();
254 $sesPrefix = $ff->appNameShort .'-' .get_class($this) .'-'.$db->dsn['database'] ;
259 if (!empty($_SESSION[__CLASS__][$sesPrefix .'-auth'])) {
261 $a = unserialize($_SESSION[__CLASS__][$sesPrefix .'-auth']);
263 $u = DB_DataObject::factory('Person');
264 if ($u->get($a->id)) { //&& strlen($u->passwd)) {
266 return $u->verifyAuth();
272 $_SESSION[__CLASS__][$sesPrefix .'-auth'] = '';
276 $u = DB_DataObject::factory('Person');
277 $ff = HTML_FlexyFramework::get();
278 if (!empty($ff->Pman['local_autoauth']) &&
279 (!empty($_SERVER['SERVER_ADDR'])) &&
280 ($_SERVER['SERVER_ADDR'] == '127.0.0.1') &&
281 ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') &&
282 $u->get('email', $ff->Pman['local_autoauth'])
284 $_SESSION[__CLASS__][$sesPrefix .'-auth'] = serialize($u);
289 $u = DB_DataObject::factory('Person');
291 if (!empty($_SERVER['PHP_AUTH_USER'])
293 !empty($_SERVER['PHP_AUTH_PW'])
295 $u->get('email', $_SERVER['PHP_AUTH_USER'])
297 $u->checkPassword($_SERVER['PHP_AUTH_PW'])
299 $_SESSION[__CLASS__][$sesPrefix .'-auth'] = serialize($u);
303 if (!empty( $_SESSION[__CLASS__][$sesPrefix .'-empty'] )) {
307 // not in session or not matched...
308 $u = DB_DataObject::factory('Person');
309 $u->whereAdd(' LENGTH(passwd) > 0');
311 $_SESSION[__CLASS__][$sesPrefix .'-empty'] = $n;
312 $error = PEAR::getStaticProperty('DB_DataObject','lastError');
314 die($error->toString()); // not really a good thing to do...
316 if (!$n){ // authenticated as there are no users in the system...
323 function getAuthUser()
325 if (!$this->isAuth()) {
328 $db = $this->getDatabaseConnection();
330 $ff= HTML_FlexyFramework::get();
331 $sesPrefix = $ff->appNameShort .'-' .get_class($this) .'-'.$db->dsn['database'] ;
335 if (!empty($_SESSION[__CLASS__][$sesPrefix .'-auth'])) {
336 $a = unserialize($_SESSION[__CLASS__][$sesPrefix .'-auth']);
338 $u = DB_DataObject::factory('Person');
339 if ($u->get($a->id)) { /// && strlen($u->passwd)) {
345 if (empty( $_SESSION[__CLASS__][$sesPrefix .'-empty'] )) {
346 $u = DB_DataObject::factory('Person');
347 $u->whereAdd(' LENGTH(passwd) > 0');
348 $_SESSION[__CLASS__][$sesPrefix .'-empty'] = $u->count();
352 if (empty( $_SESSION[__CLASS__][$sesPrefix .'-empty'] )) {
354 // fake person - open system..
355 //$ce = DB_DataObject::factory('core_enum');
359 $u = DB_DataObject::factory('Person');
368 $this->isAuth(); // force session start..
369 if (!$this->verifyAuth()) {
372 $db = $this->getDatabaseConnection();
375 // open up iptables at login..
376 $dbname = $this->database();
377 touch( '/tmp/run_pman_admin_iptables-'.$dbname);
379 // refresh admin group if we are logged in as one..
380 //DB_DataObject::debugLevel(1);
381 $g = DB_DataObject::factory('Groups');
383 $g->get('name', 'Administrators');
384 $gm = DB_DataObject::Factory('group_members');
385 if (in_array($g->id,$gm->listGroupMembership($this))) {
386 // refresh admin groups.
387 $gr = DB_DataObject::Factory('group_rights');
388 $gr->applyDefs($g, 0);
390 $ff= HTML_FlexyFramework::get();
391 $sesPrefix = $ff->appNameShort .'-' .get_class($this) .'-'.$db->dsn['database'] ;
394 $_SESSION[__CLASS__][$sesPrefix .'-auth'] = serialize($this);
399 $this->isAuth(); // force session start..
400 $db = $this->getDatabaseConnection();
401 $ff= HTML_FlexyFramework::get();
402 $sesPrefix = $ff->appNameShort .'-' .get_class($this) .'-'.$db->dsn['database'] ;
404 $_SESSION[__CLASS__][$sesPrefix .'-auth'] = "";
407 function genPassKey ($t)
409 return md5($this->email . $t. $this->passwd);
411 function simpleAuthKey($m = 0)
413 $month = $m > -1 ? date('Y-m') : date('Y-m', strtotime('LAST MONTH'));
415 return md5(implode(',' , array($month, $this->email , $this->passwd, $this->id)));
417 function checkPassword($val)
420 if (substr($this->passwd,0,1) == '$') {
421 return crypt($val,$this->passwd) == $this->passwd ;
423 // old style md5 passwords...- cant be used with courier....
424 return md5($val) == $this->passwd;
426 function setPassword($value)
429 while(strlen($salt)<9) {
430 $salt.=chr(rand(64,126));
431 //php -r var_dump(crypt('testpassword', '$1$'. (rand(64,126)). '$'));
433 $this->passwd = crypt($value, '$1$'. $salt. '$');
438 function generatePassword() // genearte a password (add set 'rawPasswd' to it's value)
440 require_once 'Text/Password.php';
441 $this->rawPasswd = strtr(ucfirst(Text_Password::create(5)).ucfirst(Text_Password::create(5)), array(
442 "a"=>"4", "e"=>"3", "i"=>"1", "o"=>"0", "s"=>"5", "t"=>"7"));
443 $this->setPassword($this->rawPasswd);
444 return $this->rawPasswd;
449 $x = DB_DataObject::factory('Companies');
451 $x->get($this->company_id);
454 function loadCompany()
456 $this->company = $this->company();
461 return $this->active;
463 function authUserName($n) // set username prior to acheck user exists query.
466 $this->whereAdd('LENGTH(passwd) > 1');
471 if (!func_num_args()) {
474 $val = array_shift(func_get_args());
475 if ($val == $this->lang) {
485 function authUserArray()
488 $aur = $this->toArray();
495 //DB_DataObject::debugLevel(1);
496 $c = DB_Dataobject::factory('Companies');
497 $im = DB_Dataobject::factory('Images');
498 $c->joinAdd($im, 'LEFT');
500 $c->selectAs($c, 'company_id_%s');
501 $c->selectAs($im, 'company_id_logo_id_%s');
502 $c->id = $this->company_id;
506 $aur = array_merge( $c->toArray(),$aur);
508 if (empty($c->company_id_logo_id_id)) {
510 $im = DB_Dataobject::factory('Images');
511 $im->ontable = 'Companies';
513 $im->imgtype = 'LOGO';
516 $im->selectAs($im, 'company_id_logo_id_%s');
517 if ($im->find(true)) {
519 foreach($im->toArray() as $k=>$v) {
526 $aur['perms'] = $this->getPerms();
527 $g = DB_DataObject::Factory('group_members');
528 $aur['groups'] = $g->listGroupMembership($this, 'name');
531 $aur['dailykey'] = '';
538 // ----------PERMS------ ----------------
541 //DB_DataObject::debugLevel(1);
542 // find out all the groups they are a member of.. + Default..
544 // ------ INIITIALIZE IF NO GROUPS ARE SET UP.
546 $g = DB_DataObject::Factory('group_rights');
552 return $g->adminRights(); // system is not set up - so they get full rights.
554 //DB_DataObject::debugLevel(1);
555 $g = DB_DataObject::Factory('group_members');
556 $g->whereAdd('group_id is NOT NULL AND user_id IS NOT NULL');
558 // add the current user to the admin group..
559 $g = DB_DataObject::Factory('Groups');
560 if ($g->get('name', 'Administrators')) {
561 $gm = DB_DataObject::Factory('group_members');
562 $gm->group_id = $g->id;
563 $gm->user_id = $this->id;
569 // ------ STANDARD PERMISSION HANDLING.
570 $isOwner = $this->company()->comptype == 'OWNER';
571 $g = DB_DataObject::Factory('group_members');
572 $grps = $g->listGroupMembership($this);
574 $isAdmin = $g->inAdmin;
575 //echo '<PRE>'; print_r($grps);var_dump($isAdmin);
576 // the load all the perms for those groups, and add them all together..
577 // then load all those
578 $g = DB_DataObject::Factory('group_rights');
579 $ret = $g->listPermsFromGroupIds($grps, $isAdmin, $isOwner);
580 //echo '<PRE>';print_r($ret);
586 *Basic group fetching - probably needs to filter by type eventually.
588 *@param String $what - fetchall() argument - eg. 'name' returns names of all groups that they are members of.
591 function groups($what=false)
593 $g = DB_DataObject::Factory('group_members');
594 $grps = $g->listGroupMembership($this);
595 $g = DB_DataObject::Factory('Groups');
596 $g->whereAddIn('id', $grps, 'int');
597 return $g->fetchAll($what);
603 function hasPerm($name, $lvl)
605 static $pcache = array();
607 if (!isset($pcache[$this->id])) {
608 $pcache[$this->id] = $this->getPerms();
610 // echo "<PRE>";print_r($pcache[$au->id]);
611 // var_dump($pcache[$au->id]);
612 if (empty($pcache[$this->id][$name])) {
616 return strpos($pcache[$this->id][$name], $lvl) > -1;
620 // ------------ROO HOOKS------------------------------------
621 function applyFilters($q, $au, $roo)
623 //DB_DataObject::DebugLevel(1);
624 if (!empty($q['query']['person_not_internal'])) {
625 $this->whereAdd(" join_company_id_id.isOwner = 0 ");
629 if (!empty($q['query']['person_internal_only_all'])) {
632 // must be internal and not current user (need for distribution list)
633 // user has a projectdirectory entry and role is not blank.
634 //DB_DataObject::DebugLevel(1);
635 $pd = DB_DataObject::factory('ProjectDirectory');
636 $pd->whereAdd("role != ''");
638 $pd->selectAdd('distinct(person_id) as person_id');
639 $roled = $pd->fetchAll('person_id');
641 {$this->tableName()}.id IN (".implode(',', $roled) . ")
643 $this->whereAdd(" join_company_id_id.comptype = 'OWNER' $rs ");
646 // -- for distribution
647 if (!empty($q['query']['person_internal_only'])) {
648 // must be internal and not current user (need for distribution list)
649 $this->whereAdd(" join_company_id_id.comptype = 'OWNER'");
651 //$this->whereAdd(($this->tableName() == 'Person' ? 'Person' : "join_person_id_id") .
652 // ".id != ".$au->id);
653 $this->whereAdd("Person.id != {$au->id}");
656 if (!empty($q['query']['comptype_or_company_id'])) {
657 // DB_DataObject::debugLevel(1);
658 $bits = explode(',', $q['query']['comptype_or_company_id']);
659 $id = (int) array_pop($bits);
660 $ct = $this->escape($bits[0]);
662 $this->whereAdd(" join_company_id_id.comptype = '$ct' OR Person.company_id = $id");
668 if (!empty($q['query']['person_inactive'])) {
669 // DB_Dataobject::debugLevel(1);
672 $tn_p = $this->tableName();
673 $tn_gm = DB_DataObject::Factory('group_members')->tableName();
674 $tn_g = DB_DataObject::Factory('Groups')->tableName();
676 ///---------------- Group views --------
677 if (!empty($q['query']['in_group'])) {
678 // DB_DataObject::debugLevel(1);
679 $ing = (int) $q['query']['in_group'];
680 if ($q['query']['in_group'] == -1) {
682 // list all staff who are not in a group.
683 $this->whereAdd("Person.id NOT IN (
684 SELECT distinct(user_id) FROM $tn_gm LEFT JOIN
685 $tn_g ON $tn_g.id = $tn_gm.group_id
686 WHERE $tn_g.type = ".$q['query']['type']."
692 $this->whereAdd("$tn_p.id IN (
693 SELECT distinct(user_id) FROM $tn_gm
694 WHERE group_id = $ing
700 if (!empty($q['query']['not_in_directory'])) {
701 // it's a Person list..
702 // DB_DATaobjecT::debugLevel(1);
704 // specific to project directory which is single comp. login
706 $owncomp = DB_DataObject::Factory('Companies');
707 $owncomp->get('comptype', 'OWNER');
708 if ($q['company_id'] == $owncomp->id) {
714 if ( $q['query']['not_in_directory'] > -1) {
715 $tn_pd = DB_DataObject::Factory('ProjectDirectory')->tableName();
716 // can list current - so that it does not break!!!
717 $this->whereAdd("$tn_p.id NOT IN
718 ( SELECT distinct person_id FROM $tn_pd WHERE
719 project_id = " . $q['query']['not_in_directory'] . " AND
720 company_id = " . $this->company_id . ')');
724 if (!empty($q['query']['role'])) {
725 // it's a Person list..
726 // DB_DATaobjecT::debugLevel(1);
728 // specific to project directory which is single comp. login
730 $tn_pd = DB_DataObject::Factory('ProjectDirectory')->tableName();
731 // can list current - so that it does not break!!!
732 $this->whereAdd("$tn_p.id IN
733 ( SELECT distinct person_id FROM $tn_pd WHERE
734 role = '". $this->escape($q['query']['role']) ."'
740 if (!empty($q['query']['project_member_of'])) {
741 // this is also a flag to return if they are a member..
742 //DB_DataObject::debugLevel(1);
743 $do = DB_DataObject::factory('ProjectDirectory');
744 $do->project_id = $q['query']['project_member_of'];
745 $tn_pd = DB_DataObject::Factory('ProjectDirectory')->tableName();
746 $this->joinAdd($do,array('joinType' => 'LEFT', 'useWhereAsOn' => true));
747 $this->selectAdd("IF($tn_pd.id IS NULL, 0, $tn_pd.id ) as is_member");
750 if (!empty($q['query']['project_member_filter'])) {
751 $this->having('is_member !=0');
757 if (!empty($q['query']['search'])) {
759 // use our magic search builder...
761 require_once 'Text/SearchParser.php';
762 $x = new Text_SearchParser($q['query']['search']);
770 "join_company_id_id.name"
773 $str = $x->toSQL(array(
776 'company' => 'join_company_id_id.name',
777 //'country' => 'Clipping.country',
778 // 'media' => 'Clipping.media_name',
780 'escape' => array($this->getDatabaseConnection(), 'escapeSimple'), /// pear db or mdb object..
785 $this->whereAdd($str); /*
786 $tn_p.name LIKE '%$s%' OR
787 $tn_p.email LIKE '%$s%' OR
788 $tn_p.role LIKE '%$s%' OR
789 $tn_p.phone LIKE '%$s%' OR
790 $tn_p.remarks LIKE '%$s%'
797 function setFromRoo($ar, $roo)
800 if (!empty($ar['passwd1'])) {
801 $this->setPassword($ar['passwd1']);
806 ($this->email == $roo->old->email)&&
807 ($this->company_id == $roo->old->company_id)
811 if (empty($this->email)) {
814 $xx = DB_Dataobject::factory('Person');
816 'email' => $this->email,
817 // 'company_id' => $x->company_id
821 return "Duplicate Email found";
827 * before Delete - delete significant dependancies..
828 * this is called after checkPerm..
831 function beforeDelete()
834 $e = DB_DataObject::Factory('Events');
835 $e->whereAdd('person_id = ' . $this->id);
844 * Check if the a user has access to modify this item.
845 * @param String $lvl Level (eg. Core.Projects)
846 * @param Pman_Core_DataObjects_Person $au The authenticated user.
847 * @param boolean $changes alllow changes???
849 * @return false if no access..
851 function checkPerm($lvl, $au, $changes=false) //heck who is trying to access this. false == access denied..
854 // do we have an empty system..
855 if ($au && $au->id == -1) {
859 // determine if it's staff!!!
861 if ($au->company()->comptype != 'OWNER') {
863 // - can not change company!!!
865 isset($changes['company_id']) &&
866 $changes['company_id'] != $au->company_id) {
869 // can only set new emails..
871 !empty($this->email) &&
872 isset($changes['email']) &&
873 $changes['email'] != $this->email) {
877 // edit self... - what about other staff members...
879 return $this->company_id == $au->company_id;
883 // yes, only owner company can mess with this...
884 $owncomp = DB_DataObject::Factory('Companies');
885 $owncomp->get('comptype', 'OWNER');
887 $isStaff = ($this->company_id == $owncomp->id);
891 // extra case change passwod?
892 case 'P': //??? password
893 // standard perms -- for editing + if the user is dowing them selves..
894 $ret = $isStaff ? $au->hasPerm("Core.Staff", "E") : $au->hasPerm("Core.Person", "E");
895 return $ret || $au->id == $this->id;
898 return $isStaff ? $au->hasPerm("Core.Staff", $lvl) : $au->hasPerm("Core.Person", $lvl);
903 function onInsert($req, $roo)
906 $p = DB_DataObject::factory('person');
907 if ($roo->authUser->id < 0 && $p->count() == 1) {
908 // this seems a bit risky...
910 $g = DB_DataObject::factory('Groups');
914 $g->get('name', 'Administrators');
916 $p = DB_DataObject::factory('group_members');
917 $p->group_id = $g->id;
918 $p->user_id = $this->id;
921 $roo->addEvent("ADD", $p, $g->toEventString(). " Added " . $this->toEventString());
925 if (!empty($req['project_id_addto'])) {
926 $pd = DB_DataObject::factory('ProjectDirectory');
927 $pd->project_id = $req['project_id_addto'];
928 $pd->person_id = $this->id;
930 $pd->office_id = $this->office_id;
931 $pd->company_id = $this->company_id;
937 function importFromArray($roo, $persons, $prefix)
939 if (!is_array($persons) || empty($persons)) {
940 $roo->jerr("error in the person data. - empty on not valid");
942 DB_DataObject::factory('groups')->initGroups();
944 foreach($persons as $person){
945 $p = DB_DataObject::factory('person');
946 if($p->get('name', $person['name'])){
949 $p->setFrom($person);
951 $companies = DB_DataObject::factory('companies');
952 if(!$companies->get('comptype', 'OWNER')){
953 $roo->jerr("Missing OWNER companies!");
955 $p->company_id = $companies->pid();
956 // strip the 'spaces etc.. make lowercase..
957 $name = strtolower(str_replace(' ', '', $person['name']));
958 $p->setPassword("$prefix$name");
961 // if $person->groups is set.. then
962 // add this person to that group eg. groups : [ 'Administrator' ]
963 if(!empty($person['groups'])){
964 $groups = DB_DataObject::factory('groups');
965 if(!$groups->get('name', $person['groups'])){
966 $roo->jerr("Missing groups : {$person['groups']}");
968 $gm = DB_DataObject::factory('group_members');
969 $gm->change($p, $groups, true);
972 $p->onInsert(array(), $roo);