Core  3.2
PHP API documentation
 All Data Structures Namespaces Files Functions Variables Pages
ImportAccounts.php
Go to the documentation of this file.
1 <?php
2 /*
3  * @author Anakeen
4  * @package FDL
5 */
6 
7 namespace Dcp\Core;
8 
9 include_once ("FDL/freedom_util.php");
10 
12 {
13  protected $file;
14  /**
15  * @var \DOMDocument
16  */
17  protected $xml;
18  /**
19  * @var \DOMXPath
20  */
21  protected $xpath;
22  /**
23  * @var bool
24  */
25  protected $analyzeOnly = false;
26  /**
27  * @var bool commit only if no one error detected
28  */
29  protected $transactionMode = true;
30  /**
31  * @var array list all actions done
32  */
33  protected $report = array();
34 
35  protected $xsd = "USERCARD/Layout/accounts.xsd";
36 
37  protected $familiesXsd = array();
38 
39  protected $stopOnError = false;
40 
41  const ABORTORDER = "::ABORT::";
42 
43  protected $sessionKey = '';
44 
45  private $needSyncAccounts = false;
46  /**
47  * @var \Account
48  */
49  private $workAccount = null;
50 
51  public function import()
52  {
53  $this->setSessionMessage(___("Load XML file", "fuserimport"));
54  $this->xml = new \DOMDocument();
55  $this->xml->load($this->file);
56  $this->xml->preserveWhiteSpace = false;
57  $this->xml->formatOutput = true;
58  $this->xpath = new \DOMXPath($this->xml);
59 
60  try {
61  $this->validateShema();
62  $this->workAccount = new \Account();
63 
64  if ($this->transactionMode || $this->analyzeOnly) {
65  $this->workAccount->savePoint("AccountsExport");
66  // Use a master lock because can be numerous accounts to import
67  $this->workAccount->setMasterLock(true);
68  }
69  $this->importRoles();
70  $this->importGroups();
71  $this->importUsers();
72 
73  if ($this->transactionMode && !$this->hasErrors() && !$this->analyzeOnly) {
74  $this->workAccount->commitPoint("AccountsExport");
75  if ($this->needSyncAccounts) {
76  $g = new \Group();
77  // send order to recompute memberOf
78  $g->resetAccountMemberOf(false);
79  }
80  }
81  $this->setSessionMessage(___("Import end", "fuserimport"));
82  }
83  catch(Exception $e) {
84  if ($e->getDcpCode() === "ACCT0204") {
85  $this->setSessionMessage(___("Import Aborted", "fuserimport"));
86  $this->addToReport("", "userAbort", ___("Import Aborted", "fuserimport") , "", null);
87  } elseif ($e->getDcpCode() === "ACCT0206") {
88  $this->setSessionMessage(___("Import Aborted", "fuserimport"));
89  $this->addToReport("", "stopOnError", "", "", null);
90  } else {
91  $this->setSessionMessage("::END::");
92  throw $e;
93  }
94  }
95  $this->setSessionMessage("::END::");
96  }
97  /**
98  * @param string $file XML file path to import
99  */
100  public function setFile($file)
101  {
102  $this->file = $file;
103  }
104  /**
105  * @param boolean $transactionMode
106  */
108  {
109  $this->transactionMode = $transactionMode;
110  }
111  /**
112  * @param string $sessionKey
113  */
114  public function setSessionKey($sessionKey)
115  {
116  $this->sessionKey = $sessionKey;
117  }
118  /**
119  * @param boolean $stopOnError
120  */
121  public function setStopOnError($stopOnError)
122  {
123  $this->stopOnError = $stopOnError;
124  }
125  /**
126  * Abort current import session
127  */
128  public function abortSession()
129  {
130  if ($this->sessionKey) {
131  global $action;
132  $action->session->register($this->sessionKey . "::ABORT", self::ABORTORDER);
133  }
134  }
135  protected function setSessionMessage($text)
136  {
137  if ($this->sessionKey) {
138  global $action;
139 
140  $action->session->register($this->sessionKey, $text);
141  $msg = $action->session->read($this->sessionKey . "::ABORT");
142  if ($msg === self::ABORTORDER) {
143  $this->stopOnError = false;
144  $action->session->register($this->sessionKey . "::ABORT", "CATCHED");
145 
146  throw new Exception("ACCT0204");
147  }
148  }
149  }
150 
151  public function getSessionMessage()
152  {
153  if ($this->sessionKey) {
154  global $action;
155  return $action->session->read($this->sessionKey);
156  }
157  return null;
158  }
159 
160  protected function libxml_display_error($error)
161  {
162  $return = "";
163  switch ($error->level) {
164  case LIBXML_ERR_WARNING:
165  $return.= "Warning $error->code: ";
166  break;
167 
168  case LIBXML_ERR_ERROR:
169  $return.= "Error $error->code: ";
170  break;
171 
172  case LIBXML_ERR_FATAL:
173  $return.= "Fatal Error $error->code: ";
174  break;
175  }
176  $return.= trim($error->message);
177  if ($error->file) {
178  $return.= " in $error->file";
179  }
180  $return.= " on line $error->line\n";
181 
182  return $return;
183  }
184 
185  protected function getXmlError()
186  {
187  $errors = libxml_get_errors();
188  $humanError = "";
189  foreach ($errors as $error) {
190  $humanError.= $this->libxml_display_error($error);
191  }
192  libxml_clear_errors();
193  return $humanError;
194  }
195  /**
196  * Validate XML file with accounts schema
197  * Document parts are validated by families schemas
198  * @throws Exception
199  */
200  protected function validateShema()
201  {
202  libxml_use_internal_errors(true);
203  $xmlWithoutDocument = new \DOMDocument();
204 
205  $xmlWithoutDocument->load($this->file);
206  $xmlWithoutDocument->preserveWhiteSpace = false;
207  $xpath = new \DOMXPath($xmlWithoutDocument);
208  // Delete document tag childs because if not a direct part of accounts xsd
209  $documents = $xpath->query("//document");
210  /**
211  * @var \DOMElement $nodeDocument
212  */
213  foreach ($documents as $nodeDocument) {
214  while ($nodeDocument->hasChildNodes()) {
215  $nodeDocument->removeChild($nodeDocument->firstChild);
216  }
217  }
218 
219  if (!$xmlWithoutDocument->schemaValidate($this->xsd)) {
220  throw new Exception("ACCT0201", $this->getXmlError());
221  }
222  // Now validate document part
223  $documents = $this->xpath->query("//document/*");
224  $docDOM = new \DOMDocument();
225  foreach ($documents as $nodeDocument) {
226  $rootTag = $nodeDocument->tagName;
227  $docDOM->loadXML($this->xml->saveXML($nodeDocument));
228  if (!$docDOM->schemaValidateSource($this->getFamilyXsd($rootTag))) {
229  throw new Exception("ACCT0201", $this->getXmlError());
230  }
231  }
232  $this->xml->normalize();
233  }
234  /**
235  * @param boolean $analyzeOnly
236  */
237  public function setAnalyzeOnly($analyzeOnly)
238  {
239  $this->analyzeOnly = $analyzeOnly;
240  }
241  /**
242  * Return family xsd
243  * @param string $familyName family name
244  * @return string
245  * @throws Exception
246  */
247  protected function getFamilyXsd($familyName)
248  {
249  if (!isset($this->familiesXsd[$familyName])) {
250  /**
251  * @var \DocFam $family
252  */
253  $family = new_doc("", getFamIdFromName("", $familyName));
254  if (!$family->isAlive()) {
255  throw new Exception("ACCT0202", $familyName);
256  }
257  $this->familiesXsd[$familyName] = $family->getXmlSchema();
258  }
259 
260  return $this->familiesXsd[$familyName];
261  }
262 
263  protected function importRoles()
264  {
265  $roles = $this->xpath->query("/accounts/roles/role");
266  $count = $roles->length;
267  foreach ($roles as $k => $role) {
268  $this->setSessionMessage(sprintf(___("Import role (%d/%d)", "fuserimport") , $k, $count));
269  $this->importRole($role);
270  }
271  }
272  protected function importGroups()
273  {
274  $groups = $this->xpath->query("/accounts/groups/group");
275  $count = $groups->length;
276  foreach ($groups as $k => $group) {
277  $this->setSessionMessage(sprintf(___("Import group (%d/%d)", "fuserimport") , $k, $count));
278  $this->importGroup($group);
279  }
280  }
281  protected function importUsers()
282  {
283  $users = $this->xpath->query("/accounts/users/user");
284 
285  $count = $users->length;
286  foreach ($users as $k => $user) {
287  $this->setSessionMessage(sprintf(___("Import user (%d/%d)", "fuserimport") , $k, $count));
288  $this->importUser($user);
289  }
290  }
291  /**
292  * @param \DOMElement $node
293  * @throws Exception
294  */
295  protected function importUser($node)
296  {
297  $values = array();
298  $matchings = array(
299  "login" => "login",
300  "firstname" => "firstname",
301  "lastname" => "lastname",
302  "mail" => "mail",
303  "substitute" => "substitute",
304  "password" => "password",
305  "status" => "status"
306  );
307  foreach ($matchings as $xmlTag => $varId) {
308  /**
309  * @var \DOMElement $nodeInfo
310  */
311  $nodeInfo = $this->xpath->query($xmlTag, $node)->item(0);
312  if ($nodeInfo) {
313  switch ($varId) {
314  case "substitute":
315  $substituteLogin = $nodeInfo->getAttribute("reference");
316 
317  $subs = $this->getWorkingAccount();
318  $subs->setLoginName($substituteLogin);
319  if (!$subs->id) {
320  throw new Exception("ACCT0200", $substituteLogin, $values["login"]);
321  }
322 
323  $values[$varId] = $subs->id;
324  break;
325 
326  case "status":
327 
328  $status = $nodeInfo->getAttribute("activated");
329  if ($status === "true") {
330  $values[$varId] = "A";
331  } elseif ($status === "false") {
332  $values[$varId] = "D";
333  }
334 
335  break;
336 
337  case "password":
338  $crypted = $nodeInfo->getAttribute("crypted") === "true";
339  if ($crypted) {
340  if (substr($nodeInfo->nodeValue, 0, 3) !== '$5$') {
341  $this->addToReport($values["login"], "changePassword", "Not a SHA256 crypt", "", $nodeInfo);
342  } else {
343  $values["password"] = $nodeInfo->nodeValue;
344  }
345  } else {
346  $values["password_new"] = $nodeInfo->nodeValue;
347  $this->addToReport($values["login"], "changePassword", "", "", $nodeInfo);
348  }
349  break;
350 
351  case "login":
352  if (mb_strtolower($nodeInfo->nodeValue) !== $nodeInfo->nodeValue) {
353  $this->addToReport($nodeInfo->nodeValue, "users update", "Login must not contains uppercase characters", "", $nodeInfo);
354  }
355  $values[$varId] = $nodeInfo->nodeValue;
356  break;
357 
358  default:
359  $values[$varId] = $nodeInfo->nodeValue;
360  }
361  }
362  }
363  $account = $this->importAccount($node, "user", "IUSER", $values);
364  if (isset($subs) && $subs->id) {
365  $account->setSubstitute($subs->id);
366  }
367  $this->importParent($node, "associatedRole", $account);
368  $this->importParent($node, "parentGroup", $account);
369  }
370  /**
371  * @param \DOMElement $node
372  */
373  protected function importGroup($node)
374  {
375 
376  $values = array();
377  $matchings = array(
378  "reference" => "login",
379  "displayName" => "lastname"
380  );
381  foreach ($matchings as $xmlTag => $varId) {
382  /**
383  * @var \DOMNodeList $value
384  */
385  $value = $this->xpath->query($xmlTag, $node);
386  $values[$varId] = $value->item(0)->nodeValue;
387  if ($varId === "login" && mb_strtolower($values[$varId]) !== $values[$varId]) {
388  $this->addToReport($values[$varId], "group update", "Reference must not contains uppercase characters", "", $value->item(0));
389  }
390  }
391 
392  $account = $this->importAccount($node, "group", "IGROUP", $values);
393  $this->importParent($node, "associatedRole", $account);
394  $this->importParent($node, "parentGroup", $account);
395  }
396  /**
397  * @param \DOMElement $node
398  * @param string $tagName "group" or "role"
399  * @param \Account $account
400  */
401  protected function importParent($node, $tagName, \Account $account)
402  {
403  $listNode = $this->xpath->query(sprintf("%ss", $tagName) , $node);
404  if ($listNode->length > 0) {
405  $parents = $this->xpath->query(sprintf("%ss/%s", $tagName, $tagName) , $node);
406  /**
407  * @var \DOMElement $listNodeItem
408  */
409  $listNodeItem = $listNode->item(0);
410  $reset = $listNodeItem->getAttribute("reset") === "true";
411 
412  if ($reset) {
413  $type = "";
414  if ($tagName === "parentGroup") {
416  } elseif ($tagName === "associatedRole") {
418  }
419  $sql = sprintf("delete from groups using users where iduser=%d and users.id=groups.idgroup and users.accounttype= %s", $account->id, pg_escape_literal($type));
420  simpleQuery("", $sql);
421  $this->addToReport($account->login, "reset$tagName", "", "", $listNodeItem);
422  $this->needSyncAccounts = true;
423  $account->updateMemberOf();
424  $account->synchroAccountDocument();
425  }
426  $needUpdate = array();
427  /**
428  * @var \DOMElement $parentNode
429  */
430  foreach ($parents as $parentNode) {
431  $parentLogin = $parentNode->getAttribute("reference");
432  $groupAccount = $this->getWorkingAccount();
433 
434  if ($groupAccount->setLoginName($parentLogin)) {
435  $group = new \Group();
436  $group->setSyncAccount(false); // No sync for each grou, sync done at the end
437  $group->idgroup = $groupAccount->id;
438  $group->iduser = $account->id;
439  $alreadyExists = ($group->preInsert() === "OK");
440 
441  if (!$alreadyExists) {
442  $err = $group->add();
443  $this->needSyncAccounts = true;
444  $this->addToReport($account->login, "add$tagName", $err, $groupAccount->login, $parentNode);
445  if (!$err) {
446  $needUpdate[] = $groupAccount->fid;
447  }
448  } else {
449  $this->addToReport($account->login, "already$tagName", "", $groupAccount->login, $parentNode);
450  }
451  } else {
452  $this->addToReport($account->login, "add $tagName", sprintf("$tagName reference %s not exists", $parentLogin) , "", $parentNode);
453  }
454  }
455  if ($needUpdate) {
456  $account->synchroAccountDocument();
457  if ($tagName === "parentGroup") {
458  $dl = new \DocumentList();
459  if ($account->accounttype === \Account::GROUP_TYPE) {
460  $needUpdate[] = $account->fid;
461  }
462  $dl->addDocumentIdentifiers($needUpdate);
463  /**
464  * @var \Dcp\Family\Igroup $iGroup
465  */
466  foreach ($dl as $iGroup) {
467  $iGroup->refreshGroup();
468  }
469  }
470  }
471  }
472  }
473  /**
474  * @param \DOMElement $node
475  */
476  protected function importRole($node)
477  {
478  $values = array();
479  $matchings = array(
480  "reference" => "login",
481  "displayName" => "lastname"
482  );
483  foreach ($matchings as $xmlTag => $varId) {
484  /**
485  * @var \DOMNodeList $value
486  */
487  $value = $this->xpath->query($xmlTag, $node);
488  $values[$varId] = $value->item(0)->nodeValue;
489  if ($varId === "login" && mb_strtolower($values[$varId]) !== $values[$varId]) {
490  $this->addToReport($values[$varId], "role update", "Reference must not contains uppercase characters", "", $value->item(0));
491  }
492  }
493  $this->importAccount($node, "role", "ROLE", $values);
494  }
495  /**
496  * @param \DOMElement $node node to import
497  * @param string $tag node tag
498  * @param string $defaultFamily default family for account in case of document tag not exists
499  * @param array $values system values to update account
500  * @return \Account
501  * @throws Exception
502  */
503  protected function importAccount($node, $tag, $defaultFamily, array $values)
504  {
505  $newDocAccount = null;
506  $err = '';
507  /**
508  * @var \DOMElement $documentNode
509  */
510  $documentNode = $this->xpath->query("document", $node)->item(0);
511  $family = $defaultFamily;
512  if ($documentNode) {
513  $family = $documentNode->getAttribute("family");
514  }
515  $account = new \Account();
516 
517  $msg = "";
518  if ($values) {
519  $msg = ___("Updated values", "dcp:import") . " :\n" . substr(print_r($values, true) , 7, -2);
520  }
521 
522  if ($account->setLoginName($values["login"])) {
523  // Already exists : update role
524 
525  } else {
526  if ($tag === "role") {
527  $account->accounttype = \Account::ROLE_TYPE;
528  } elseif ($tag === "group") {
529  $account->accounttype = \Account::GROUP_TYPE;
530  }
531  // New account
532  $famId = getFamIdFromName("", $family);
533  if (!$famId) {
534  $err = "Not found family $family";
535  $this->addToReport($values["login"], "documentCreation", $err, "", $documentNode);
536  $famId = $defaultFamily;
537  }
538  $newDocAccount = \createDoc("", $famId);
539  if ($newDocAccount) {
540  $err = $newDocAccount->add();
541  if (!$err) {
542  $account->fid = $newDocAccount->id;
543  $this->addToReport($values["login"], "documentCreation", "", sprintf(___("Family %s", "fusersimport") , $newDocAccount->getFamilyDocument()->getTitle()) , $documentNode ? ($documentNode->cloneNode(false)) : null);
544  } else {
545  $this->addToReport($values["login"], "documentCreation", $err, "", $documentNode);
546  }
547  } else {
548  $this->addToReport($values["login"], "documentCreation", "Cannot create $family", "", $documentNode);
549  }
550  };
551  /**
552  * @var \DOMElement $roleDocumentNode
553  */
554  $roleDocumentNode = $this->xpath->query("document/" . strtolower($family) , $node)->item(0);
555  if (!$err) {
556  $account->affect($values);
557  /**
558  * @var \DOMElement $uNode
559  */
560  $uNode = $node->cloneNode(true);
561  foreach (array(
562  "document",
563  "groups",
564  "roles"
565  ) as $delTag) {
566  $delNode = $uNode->getElementsByTagName($delTag);
567  if ($delNode->length > 0) {
568  $uNode->removeChild($delNode->item(0));
569  }
570  }
571  $msg = sprintf(___("Account Type \"%s\"") , $tag) . "\n" . $msg;
572  if ($account->id > 0) {
573  $err = $account->modify();
574 
575  $this->addToReport($account->login, "updateAccount", $err, $msg, $uNode);
576  if ($roleDocumentNode) {
577  $docName = $roleDocumentNode->getAttribute("name");
578  if ($docName) {
579  $docAccount = \new_doc("", $account->fid);
580  if (!$docAccount->name) {
581  $docAccount->setLogicalName($docName);
582  } else {
583  if ($docAccount->name != $docName) {
584  throw new Exception("ACCT0209", $docName, $docAccount);
585  }
586  }
587  }
588  }
589  } else {
590  if (!$account->password) {
591  $account->password = "-";
592  }
593  $err = $account->add();
594  $this->addToReport($account->login, "addAccount", $err, $msg, $uNode);
595  // Connect document and account
596  $newDocAccount->setValue(\Dcp\AttributeIdentifiers\Role::us_whatid, $account->id);
597  $newDocAccount->modify();
598  if ($roleDocumentNode) {
599  $docName = $roleDocumentNode->getAttribute("name");
600  if ($docName) {
601  $newDocAccount->setLogicalName($docName);
602  }
603  }
604  $account->synchroAccountDocument();
605  }
606  }
607  if ($roleDocumentNode) {
608  $this->importXMLDocument($roleDocumentNode, $account);
609  }
610 
611  return $account;
612  }
613 
614  protected function importXMLDocument(\DOMElement $node, \Account $account)
615  {
616  $node->setAttribute("id", $account->fid);
617  $importXml = new importXml();
618  $tmpFile = $this->getTmpFile();
619 
620  file_put_contents($tmpFile, $this->xml->saveXML($node));
621 
622  $importXml->importXmlFileDocument($tmpFile, $log);
623  $msg = "";
624  if ($log["values"]) {
625  $msg = ___("Updated values", "dcp:import") . " :\n" . substr(print_r($log["values"], true) , 7, -2);
626  }
627 
628  $this->addToReport($account->login, "documentUpdate", $log["err"], $msg, $node);
629  }
630 
631  protected function getTmpFile()
632  {
633  return \LibSystem::tempnam(null, "importXml");
634  }
635 
636  protected function addToReport($login, $actionType, $error, $msg = "", $node = null)
637  {
638  switch ($actionType) {
639  case "documentCreation":
640  $msgType = ___("Document creation", "fusersimport");
641  break;
642 
643  case "documentUpdate":
644  $msgType = ___("Document update", "fusersimport");
645  break;
646 
647  case "updateAccount":
648  $msgType = ___("Update account", "fusersimport");
649  break;
650 
651  case "addAccount":
652  $msgType = ___("Create account", "fusersimport");
653  break;
654 
655  case "addparentGroup":
656  $msgType = ___("Add group reference", "fusersimport");
657  break;
658 
659  case "addassociatedRole":
660  $msgType = ___("Add role reference", "fusersimport");
661  break;
662 
663  case "alreadyparentGroup":
664  $msgType = ___("Group reference already added", "fusersimport");
665  break;
666 
667  case "alreadyassociatedRole":
668  $msgType = ___("Role reference already added", "fusersimport");
669  break;
670 
671  case "resetgroup":
672  $msgType = ___("Reset all group attachment", "fusersimport");
673  break;
674 
675  case "resetrole":
676  $msgType = ___("Reset all direct associated roles", "fusersimport");
677  break;
678 
679  case "changePassword":
680  $msgType = ___("New password", "fusersimport");
681  break;
682 
683  case "stopOnError":
684  $msgType = ___("Stopped on first error", "fusersimport");
685  break;
686 
687  default:
688  $msgType = $actionType;
689  }
690 
691  if ($msg) {
692  $msgType.= " : \n" . $msg;
693  }
694 
695  $this->report[] = array(
696  "login" => $login,
697  "action" => $actionType,
698  "error" => $error,
699  "message" => $msgType,
700  "node" => ($node) ? $this->xml->saveXML($node) : ""
701  );
702 
703  if ($error && $this->stopOnError) {
704  throw new Exception("ACCT0206");
705  }
706  }
707 
708  public function getReport()
709  {
710  return $this->report;
711  }
712 
713  protected function hasErrors()
714  {
715  foreach ($this->report as $report) {
716  if ($report["error"]) {
717  return true;
718  }
719  }
720  return false;
721  }
722  /**
723  * @return \Account
724  */
725  private function getWorkingAccount()
726  {
727  foreach ($this->workAccount->fields as $field) {
728  $this->workAccount->$field = "";
729  }
730  $this->workAccount->isset = false;
731  return $this->workAccount;
732  }
733 }
setAnalyzeOnly($analyzeOnly)
$status
Definition: index.php:30
global $action
setLoginName($login)
updateMemberOf($updateSubstitute=true)
if(!function_exists('pgettext')) ___($message, $context="")
Definition: Lib.Common.php:46
const GROUP_TYPE
$log
Definition: wsh.php:33
modify($nopost=false, $sfields="", $nopre=false)
setTransactionMode($transactionMode)
createDoc($dbaccess, $fromid, $control=true, $defaultvalues=true, $temporary=false)
$login
Definition: dav.php:40
getFamIdFromName($dbaccess, $name)
importXMLDocument(\DOMElement $node,\Account $account)
setStopOnError($stopOnError)
$account
Definition: guest.php:36
simpleQuery($dbaccess, $query, &$result=array(), $singlecolumn=false, $singleresult=false, $useStrict=null)
Definition: Lib.Common.php:484
if($file) if($subject==""&&$file) if($subject=="") $err
affect($array, $more=false, $reset=true)
$value
addToReport($login, $actionType, $error, $msg="", $node=null)
const ROLE_TYPE
← centre documentaire © anakeen