false, self::CANADDRECORD => false, self::CANPUTINALBUM => false, self::CANDWNLDHD => true, self::CANDWNLDPREVIEW => true, self::CHGSTATUS => false, self::COLL_MANAGE => false, self::COLL_MODIFY_STRUCT => false, self::CANDELETERECORD => false, self::IMGTOOLS => false, self::CANADMIN => false, self::CANMODIFRECORD => false, self::CANCMD => false, self::ORDER_MASTER => false, self::CANPUSH => false, self::CANREPORT => false, self::NOWATERMARK => false, self::RESTRICT_DWNLD => false, self::BAS_CHUPUB => false, self::BAS_MANAGE => false, self::BAS_MODIF_TH => false, self::BAS_MODIFY_STRUCT => false, self::TASKMANAGER => false, ]; /** * @var Application */ protected $app; const CACHE_IS_ADMIN = 'is_admin'; const CACHE_RIGHTS_BAS = 'rights_bas'; const CACHE_LIMITS_BAS = 'limits_bas'; const CACHE_RIGHTS_SBAS = 'rights_sbas'; const CACHE_RIGHTS_RECORDS = 'rights_records'; const CACHE_GLOBAL_RIGHTS = 'global_rights'; const GRANT_ACTION_PUSH = 'push'; const GRANT_ACTION_VALIDATE = 'validate'; const GRANT_ACTION_ORDER = 'order'; /** * Constructor * * @param User $user * @param Application $app */ public function __construct(User $user, Application $app) { $this->user = $user; $this->app = $app; } /** * Returns the list of available rights for collections * * @return string[] */ public function get_bas_rights() { return self::$bas_rights; } /** * Returns the list of available rights by databox for the current user * * @return array */ public function get_sbas_rights() { $this->load_rights_sbas(); return $this->_rights_sbas; } public function update_expire_grant_hd(RecordReferenceInterface $record, $action, $expireOn = null) { if (!empty($expireOn)) { try { $expireOn = (new DateTime($expireOn))->format(DATE_ATOM); } catch (\Exception $e) { $expireOn = null; } } $sql = "UPDATE records_rights SET `case`=:act, `expire_on`=:expire_on WHERE `usr_id` = :usr_id AND `sbas_id`=:sbas_id AND `record_id`=:record_id AND `document`=1"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':act' => $action, ':expire_on'=> $expireOn ]); return $this; } /** * Check if a hd grant has been received for a record * * @param RecordReferenceInterface $record * @return bool */ public function has_hd_grant(RecordReferenceInterface $record) { $this->load_hd_grant(); if (array_key_exists($record->getId(), $this->_rights_records_document)) { return true; } return false; } public function grant_hd_on(RecordReferenceInterface $record, User $pusher, $action, $expireOn = null) { static $stmt = null; if(!$stmt) { $sql = "REPLACE INTO records_rights\n" . " (id, usr_id, sbas_id, record_id, document, `case`, pusher_usr_id, expire_on)\n" . " VALUES (null, :usr_id, :sbas_id, :record_id, 1, :case, :pusher, :expireOn)"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); } if (!empty($expireOn)) { try { $expireOn = (new DateTime($expireOn))->format(DATE_ATOM); } catch (\Exception $e) { $expireOn = null; } } $params = [ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId(), ':expireOn' => $expireOn ]; $stmt->execute($params); $stmt->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_RECORDS); return $this; } public function nono_grant_preview_on(RecordReferenceInterface $record, User $pusher, $action) { static $stmt_upd = null; static $stmt_ins = null; static $sql_upd = null; $cnx = $this->app->getApplicationBox()->get_connection(); if(!$stmt_upd) { $sql_upd = "UPDATE records_rights SET\n" . " document = 0, preview = 1, `case` = :case, pusher_usr_id = :pusher\n" . " WHERE usr_id = :usr_id AND sbas_id = :sbas_id AND record_id = :record_id"; $stmt_upd = $cnx->prepare($sql_upd); } if(!$stmt_ins) { $sql = "INSERT INTO records_rights\n" . " (usr_id, sbas_id, record_id, preview, `case`, pusher_usr_id)\n" . " VALUES\n" . " (:usr_id, :sbas_id, :record_id, 1, :case, :pusher)"; $stmt_ins = $cnx->prepare($sql); } $cnx->exec("START TRANSACTION"); try { $n0 = $stmt_upd->execute([ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId() ]); // $n = $cnx->executeUpdate( // $sql_upd, // [ // ':usr_id' => $this->user->getId(), // ':sbas_id' => $record->getDataboxId(), // ':record_id' => $record->getRecordId(), // ':case' => $action, // ':pusher' => $pusher->getId() // ] // ); // $n1 = $stmt_upd->rowCount(); $n2 = $cnx->executeQuery("SELECT ROW_COUNT()")->fetchColumn(0); file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", 'u'.$n0.$n1.$n2."\n", FILE_APPEND); // if($n != 1) { // throw new \Exception("no update"); // } $stmt_ins->execute([ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId() ]); // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", "i", FILE_APPEND); } catch (\Exception $e) { // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", sprintf("%s\n", $e->getMessage()), FILE_APPEND); $stmt_upd->execute([ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId() ]); // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", 'u', FILE_APPEND); } // $stmt->closeCursor(); $cnx->exec("COMMIT"); $this->delete_data_from_cache(self::CACHE_RIGHTS_RECORDS); return $this; } public function no_grant_preview_on(RecordReferenceInterface $record, User $pusher, $action) { static $stmt_del = null; static $stmt_upd = null; static $stmt_ins = null; static $sql_upd = null; $cnx = $this->app->getApplicationBox()->get_connection(); if(!$stmt_del) { $sql = "DELETE FROM records_rights\n" . " WHERE usr_id = :usr_id AND sbas_id = :sbas_id AND record_id = :record_id"; $stmt_del = $cnx->prepare($sql); } if(!$stmt_upd) { $sql_upd = "UPDATE records_rights SET\n" . " document = 0, preview = 1, `case` = :case, pusher_usr_id = :pusher\n" . " WHERE usr_id = :usr_id AND sbas_id = :sbas_id AND record_id = :record_id"; $stmt_upd = $cnx->prepare($sql_upd); } if(!$stmt_ins) { $sql = "INSERT INTO records_rights\n" . " (usr_id, sbas_id, record_id, preview, `case`, pusher_usr_id)\n" . " VALUES\n" . " (:usr_id, :sbas_id, :record_id, 1, :case, :pusher)"; $stmt_ins = $cnx->prepare($sql); } $cnx->exec("START TRANSACTION"); // $stmt_del->execute([ // ':usr_id' => $this->user->getId(), // ':sbas_id' => $record->getDataboxId(), // ':record_id' => $record->getRecordId() // ]); // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", "d", FILE_APPEND); try { // $n = $stmt_upd->execute([ // ':usr_id' => $this->user->getId(), // ':sbas_id' => $record->getDataboxId(), // ':record_id' => $record->getRecordId(), // ':case' => $action, // ':pusher' => $pusher->getId() // ]); // $n = $cnx->executeUpdate( // $sql_upd, // [ // ':usr_id' => $this->user->getId(), // ':sbas_id' => $record->getDataboxId(), // ':record_id' => $record->getRecordId(), // ':case' => $action, // ':pusher' => $pusher->getId() // ] // ); // // // $n = $stmt_upd->rowCount(); // $n = $cnx->executeQuery("SELECT ROW_COUNT()")->fetchColumn(0); // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", 'u'.$n, FILE_APPEND); // if($n != 1) { // throw new \Exception("no update"); // } $stmt_ins->execute([ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId() ]); // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", "i", FILE_APPEND); } catch (\Exception $e) { // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", sprintf("%s\n", $e->getMessage()), FILE_APPEND); $stmt_upd->execute([ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId() ]); // file_put_contents("/var/alchemy/Phraseanet/tmp/phraseanet-log.txt", 'u', FILE_APPEND); } // $stmt->closeCursor(); $cnx->exec("COMMIT"); $this->delete_data_from_cache(self::CACHE_RIGHTS_RECORDS); return $this; } public function grant_preview_on(RecordReferenceInterface $record, User $pusher, $action) { static $stmt = null; if(!$stmt) { $sql = "REPLACE INTO records_rights\n" . " (usr_id, sbas_id, record_id, preview, `case`, pusher_usr_id)\n" . " VALUES\n" . " (:usr_id, :sbas_id, :record_id, 1, :case, :pusher)"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); } $params = [ ':usr_id' => $this->user->getId(), ':sbas_id' => $record->getDataboxId(), ':record_id' => $record->getRecordId(), ':case' => $action, ':pusher' => $pusher->getId() ]; $stmt->execute($params); $stmt->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_RECORDS); return $this; } /** * Check if a hd grant has been received for a record * * @param RecordReferenceInterface $record * @return bool */ public function has_preview_grant(RecordReferenceInterface $record) { $this->load_hd_grant(); if (array_key_exists($record->getId(), $this->_rights_records_preview)) { return true; } return false; } public function has_access_to_record(RecordInterface $record) { if ($this->has_access_to_base($record->getBaseId()) && $this->has_status_access_to_record($record)) { return true; } return $this->has_preview_grant($record) || $this->has_hd_grant($record); } public function has_status_access_to_record(RecordInterface $record) { return 0 === (($record->getStatusBitField() ^ $this->get_mask_xor($record->getBaseId())) & $this->get_mask_and($record->getBaseId())); } public function has_access_to_subdef(RecordInterface $record, $subdef_name) { if ($subdef_name == 'thumbnail') { return true; } if ($record->isStory()) { return true; } $databox = $this->app->findDataboxById($record->getDataboxId()); try { $subdef_class = $databox->get_subdef_structure() ->get_subdef($record->getType(), $subdef_name) ->get_class(); } catch (\Exception $e) { return false; } $granted = false; if ($subdef_class == databox_subdef::CLASS_THUMBNAIL) { $granted = true; } elseif ($subdef_class == databox_subdef::CLASS_PREVIEW && $this->has_right_on_base($record->getBaseId(), self::CANDWNLDPREVIEW)) { $granted = true; } elseif ($subdef_class == databox_subdef::CLASS_PREVIEW && $this->has_preview_grant($record)) { $granted = true; } elseif ($subdef_class == databox_subdef::CLASS_DOCUMENT && $this->has_right_on_base($record->getBaseId(), self::CANDWNLDHD)) { $granted = true; } elseif ($subdef_class == databox_subdef::CLASS_DOCUMENT && $this->has_hd_grant($record)) { $granted = true; } if (false === $granted && $this->app['repo.feed-items']->isRecordInPublicFeed($record->getDataboxId(), $record->getRecordId())) { $granted = true; } return $granted; } /** * Apply a template on user * * @param User $template_user * @param array $base_ids * @return ACL */ public function apply_model(User $template_user, Array $base_ids) { if (count($base_ids) == 0) { return $this; } $sbas_ids = []; foreach ($base_ids as $base_id) { $sbas_ids[] = phrasea::sbasFromBas($this->app, $base_id); } $sbas_ids = array_unique($sbas_ids); $sbas_to_acces = []; $rights_to_give = []; foreach ($this->app->getAclForUser($template_user)->get_granted_sbas() as $databox) { $sbas_id = $databox->get_sbas_id(); if (!in_array($sbas_id, $sbas_ids)) continue; if (!$this->has_access_to_sbas($sbas_id)) { $sbas_to_acces[] = $sbas_id; } foreach (self::$sbas_rights as $right) { if ($this->app->getAclForUser($template_user)->has_right_on_sbas($sbas_id, $right)) { $rights_to_give[$sbas_id][$right] = true; } } } $this->give_access_to_sbas($sbas_to_acces); foreach ($rights_to_give as $sbas_id => $rights) { $this->update_rights_to_sbas($sbas_id, $rights); } $bas_rights = $this->get_bas_rights(); $bas_to_acces = $masks_to_give = $rights_to_give = []; // todo ? wtf simplify this sb manipulation, now it is 32 bits /** * map masks (and+xor) of template to masks to apply to user on base * (and_and, and_or, xor_and, xor_or) */ $sbmap = [ '00' => ['aa' => '1', 'ao' => '0', 'xa' => '1', 'xo' => '0'], '01' => ['aa' => '1', 'ao' => '0', 'xa' => '1', 'xo' => '0'], '10' => ['aa' => '1', 'ao' => '1', 'xa' => '0', 'xo' => '0'], '11' => ['aa' => '1', 'ao' => '1', 'xa' => '1', 'xo' => '1'] ]; foreach ($this->app->getAclForUser($template_user)->get_granted_base() as $collection) { $base_id = $collection->get_base_id(); if (!in_array($base_id, $base_ids)) { continue; } if (!$this->has_access_to_base($base_id)) { $bas_to_acces[] = $base_id; } foreach ($bas_rights as $right) { if ($this->app->getAclForUser($template_user)->has_right_on_base($base_id, $right)) { $rights_to_give[$base_id][$right] = '1'; } } $mask_and = $this->app->getAclForUser($template_user)->get_mask_and($base_id); $mask_xor = $this->app->getAclForUser($template_user)->get_mask_xor($base_id); /** * apply sb is substractive */ $mand = substr( str_repeat('0', 32) . decbin($mask_and) , -32 ); $mxor = substr( str_repeat('0', 32) . decbin($mask_xor) , -32 ); $m = ['aa' => '', 'ao' => '', 'xa' => '', 'xo' => '']; for ($i = 0; $i < 32; $i++) { $ax = $mand[$i] . $mxor[$i]; foreach ($m as $k => $v) { $m[$k] .= $sbmap[$ax][$k]; } } $masks_to_give[$base_id] = [ 'aa' => $m['aa'], 'ao' => $m['ao'], 'xa' => $m['xa'], 'xo' => $m['xo'] ]; } $this->give_access_to_base($bas_to_acces); foreach ($masks_to_give as $base_id => $mask) { $this->set_masks_on_base($base_id, $mask['aa'], $mask['ao'], $mask['xa'], $mask['xo']); } foreach ($rights_to_give as $base_id => $rights) { $this->update_rights_to_base($base_id, $rights); } $this->apply_template_time_limits($template_user, $base_ids); $this->user->setLastAppliedTemplate($template_user); return $this; } private function apply_template_time_limits(User $template_user, Array $base_ids) { foreach ($base_ids as $base_id) { $limited = $this->app->getAclForUser($template_user)->get_limits($base_id); if (null !== $limited) { $this->set_limits($base_id, '1', $limited['dmin'], $limited['dmax']); } else { $this->set_limits($base_id, '0', $limited['dmin'], $limited['dmax']); } } } /** * * @return boolean */ public function is_phantom() { return count($this->get_granted_base()) === 0; } /** * @param $base_id * @param $right * @return bool * @throws Exception */ public function has_right_on_base($base_id, $right) { $this->load_rights_bas(); if (!$this->has_access_to_base($base_id)) { return false; } if ($this->is_limited($base_id)) { return false; } if (!isset($this->_rights_bas[$base_id][$right])) { throw new Exception('right ' . $right . ' does not exists'); } return ($this->_rights_bas[$base_id][$right] === true); } /** * @param string|null $option * @return string */ public function get_cache_key($option = null) { return '_ACL_' . $this->user->getId() . ($option ? '_' . $option : ''); } /** * @param string|null $option */ public function delete_data_from_cache($option = null) { switch ($option) { case self::CACHE_GLOBAL_RIGHTS: $this->_global_rights = null; break; case self::CACHE_RIGHTS_BAS: case self::CACHE_LIMITS_BAS: $this->_rights_bas = null; $this->_limited = null; break; case self::CACHE_RIGHTS_RECORDS: $this->_rights_records_document = null; $this->_rights_records_preview = null; break; case self::CACHE_RIGHTS_SBAS: $this->_rights_sbas = null; break; default: break; } $this->app->getApplicationBox()->delete_data_from_cache($this->get_cache_key($option)); } /** * @param string|null $option * @return array */ public function get_data_from_cache($option = null) { return $this->app->getApplicationBox()->get_data_from_cache($this->get_cache_key($option)); } /** * @param $value * @param string|null $option * @param int $duration * @return bool */ public function set_data_to_cache($value, $option = null, $duration = 0) { return $this->app->getApplicationBox()->set_data_to_cache($value, $this->get_cache_key($option), $duration); } /** * Return true if user is restricted in download on the collection * * @param int $base_id * @return boolean */ public function is_restricted_download($base_id) { $this->load_rights_bas(); if (!$this->has_access_to_base($base_id)) { return false; } return $this->_rights_bas[$base_id][self::RESTRICT_DWNLD]; } /** * Return the number of remaining downloads on the collection * * @param int $base_id * @return int */ public function remaining_download($base_id) { $this->load_rights_bas(); if (!$this->has_access_to_base($base_id)) { return false; } return (int) $this->_rights_bas[$base_id]['remain_dwnld']; } /** * Remove n download from the remainings * * @param int $base_id * @param int $n * @return ACL * * todo : wtf direct cache modification, where is sql ? */ public function remove_remaining($base_id, $n = 1) { $this->load_rights_bas(); if (!$this->has_access_to_base($base_id)) { return false; } $this->_rights_bas[$base_id]['remain_dwnld'] = max(0, $this->_rights_bas[$base_id]['remain_dwnld'] - (int) $n); return $this; } /** * Check if the user has the right, on at least one collection * * @param string $right * @return bool * @throws Exception */ public function has_right($right) { $this->load_global_rights(); if (!isset($this->_global_rights[$right])) { throw new Exception('This right does not exists'); } return $this->_global_rights[$right]; } /** * Check if the user has the required right on a database * * @param int $sbas_id * @param string $right * @return bool * @throws Exception */ public function has_right_on_sbas($sbas_id, $right) { $this->load_rights_sbas(); if (!isset($this->_rights_sbas[$sbas_id])) { return false; } if (!isset($this->_rights_sbas[$sbas_id][$right])) { throw new Exception('This right does not exists'); } if ($this->_rights_sbas[$sbas_id][$right] === true) { return true; } return false; } /** * Retrieve mask AND for user on specified base_id * * @param int $base_id * @return int */ public function get_mask_and($base_id) { $this->load_rights_bas(); if (!$this->has_access_to_base($base_id)) { return false; } return $this->_rights_bas[$base_id]['mask_and']; } /** * Retrieve mask XOR for user on specified base_id * * @param int $base_id * @return int */ public function get_mask_xor($base_id) { $this->load_rights_bas(); if (!$this->has_access_to_base($base_id)) { return false; } return $this->_rights_bas[$base_id]['mask_xor']; } /** * Return true if access to base_id is granted * * @param int $base_id * @return boolean */ public function has_access_to_base($base_id) { $this->load_rights_bas(); return (isset($this->_rights_bas[$base_id]) && $this->_rights_bas[$base_id][self::ACTIF] === true); } /** * Return true if access to sbas_id is granted * * @param int $sbas_id * @return boolean */ public function has_access_to_sbas($sbas_id) { $this->load_rights_sbas(); return (isset($this->_rights_sbas[$sbas_id])); } /** * Return an array of base_id which are granted, with * optionnal filter by rights * * @param array $rights * @param array|null $sbas_ids Optionnal sbas_id to restrict the query on * @return collection[] An array of collection */ public function get_granted_base(Array $rights = [], array $sbas_ids = null) { $this->load_rights_bas(); $ret = []; foreach ($this->app->getDataboxes() as $databox) { if ($sbas_ids && !in_array($databox->get_sbas_id(), $sbas_ids)) { continue; } foreach ($databox->get_collections() as $collection) { $continue = false; if (!array_key_exists($collection->get_base_id(), $this->_rights_bas)) { continue; } $base_id = $collection->get_base_id(); foreach ($rights as $right) { if (!$this->has_right_on_base($base_id, $right)) { $continue = true; break; } } if ($continue || $this->is_limited($base_id)) { continue; } $ret[$base_id] = $collection; } } return $ret; } /** * @return array baseIds where user can search */ public function getSearchableBasesIds() { static $ret = null; if($ret === null) { $this->load_rights_bas(); foreach ($this->_rights_bas as $baseId => $rights) { if ($this->has_access_to_base($baseId) && !$this->is_limited($baseId)) { $ret[] = $baseId; } } } return $ret; } /** * @return CollectionReference[] CollectionsReferences where the user can search; */ public function getSearchableBasesReferences() { static $ret = null; if($ret == null) { /** @var DbalCollectionReferenceRepository $dbalCollectionReferenceRepository */ $dbalCollectionReferenceRepository = $this->app['repo.collection-references']; $ret = $dbalCollectionReferenceRepository->findMany($this->getSearchableBasesIds()); } return $ret; } /** * Return an array of databox (key=sbas_id) which are granted, with * optionnal filter by rights * * @param Array $rights * @return \databox[] */ public function get_granted_sbas($rights = []) { if (is_string($rights)) { $rights = [$rights]; } assert(is_array($rights)); $this->load_rights_sbas(); $ret = []; foreach ($this->_rights_sbas as $sbas_id => $datas) { $continue = false; foreach ($rights as $right) { if (!$this->has_right_on_sbas($sbas_id, $right)) { $continue = true; break; } } if ($continue) { continue; } try { $ret[$sbas_id] = $this->app->findDataboxById((int) $sbas_id); } catch (\Exception $e) { // no-op } } return $ret; } public function is_admin() { return $this->user->isAdmin(); } public function set_admin($boolean) { if ($boolean) { $this->app['manipulator.user']->promote($this->user); } else { $this->app['manipulator.user']->demote($this->user); } $this->app['dispatcher']->dispatch( AclEvents::SYSADMIN_CHANGED, new SysadminChangedEvent( $this, array( 'is_sysadmin'=>$boolean ) ) ); return $this; } /** * Load if needed the elements which have a HD grant * * @return ACL */ protected function load_hd_grant() { if ($this->_rights_records_preview) { return $this; } try { $tmp_rights = $this->get_data_from_cache(self::CACHE_RIGHTS_RECORDS); $this->_rights_records_preview = $tmp_rights['preview']; $this->_rights_records_document = $tmp_rights['document']; return $this; } catch (\Exception $e) { } $sql = "SELECT sbas_id, record_id, preview, document, expire_on FROM records_rights WHERE usr_id = :usr_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId()]); $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); unset($stmt); $this->_rights_records_preview = []; $this->_rights_records_document = []; $dateNow = new DateTime("now"); foreach ($rs as $row) { $currentid = $row["sbas_id"] . "_" . $row["record_id"]; if (!empty($row['expire_on'])) { try { $expireOn = new DateTime($row['expire_on']); if ($expireOn < $dateNow) { continue; } } catch (\Exception $e) { } } if ($row['document'] == '1') { $this->_rights_records_document[$currentid] = $currentid; } $this->_rights_records_preview[$currentid] = $currentid; } $datas = [ 'preview' => $this->_rights_records_preview, 'document' => $this->_rights_records_document ]; $this->set_data_to_cache($datas, self::CACHE_RIGHTS_RECORDS); return $this; } /** * Loads rights of specified user for all sbas * * @return ACL */ protected function load_rights_sbas() { if ($this->_rights_sbas && $this->_global_rights) { return $this; } try { $global_rights = $this->get_data_from_cache(self::CACHE_GLOBAL_RIGHTS); if (!is_array($global_rights)) { throw new Exception('global rights were not properly retrieved'); } $sbas_rights = $this->get_data_from_cache(self::CACHE_RIGHTS_SBAS); if (!is_array($sbas_rights)) { throw new Exception('sbas rights were not properly retrieved'); } $this->_global_rights = $global_rights; $this->_rights_sbas = $sbas_rights; return $this; } catch (\Exception $e) { // no-op } $sql = "SELECT sbasusr.* FROM sbasusr INNER JOIN sbas USING(sbas_id) WHERE usr_id= :usr_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId()]); $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); $this->_rights_sbas = []; foreach(self::$sbas_rights as $b) { $this->_global_rights[$b] = false; } foreach ($rs as $row) { $sbid = $row['sbas_id']; $this->_rights_sbas[$sbid] = []; foreach (self::$sbas_rights as $b) { $this->_global_rights[$b] = ($this->_rights_sbas[$sbid][$b] = ($row[$b] == '1')) || $this->_global_rights[$b]; } } $this->set_data_to_cache($this->_rights_sbas, self::CACHE_RIGHTS_SBAS); $this->set_data_to_cache($this->_global_rights, self::CACHE_GLOBAL_RIGHTS); return $this; } /** * Loads rights of specified user for all bas * * @return ACL */ protected function load_rights_bas() { if ($this->_rights_bas && $this->_global_rights && is_array($this->_limited)) { return $this; } try { $data = $this->get_data_from_cache(self::CACHE_GLOBAL_RIGHTS); if (!is_array($data)) { throw new Exception('Unable to retrieve global rights'); } $this->_global_rights = $data; $data = $this->get_data_from_cache(self::CACHE_RIGHTS_BAS); if (!is_array($data)) { throw new Exception('Unable to retrieve base rights'); } $this->_rights_bas = $data; $data = $this->get_data_from_cache(self::CACHE_LIMITS_BAS); if (!is_array($data)) { throw new Exception('Unable to retrieve limits rights'); } $this->_limited = $data; return $this; } catch (\Exception $e) { // no-op } $sql = "SELECT u.* FROM basusr u, bas b, sbas s\n" . " WHERE usr_id= :usr_id\n" . " AND b.base_id = u.base_id\n" . " AND s.sbas_id = b.sbas_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId()]); $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt->closeCursor(); $this->_rights_bas = $this->_limited = []; foreach(self::$bas_rights as $b) { $this->_global_rights[$b] = false; } foreach ($rs as $row) { $bid = $row['base_id']; foreach(self::$bas_rights as $b) { $this->_global_rights[$b] = ($this->_rights_bas[$bid][$b] = ($row[$b] == '1')) || $this->_global_rights[$b]; } $this->_rights_bas[$bid]['remain_dwnld'] = (int) $row['remain_dwnld']; $this->_rights_bas[$bid]['mask_and'] = (int) $row['mask_and']; $this->_rights_bas[$bid]['mask_xor'] = (int) $row['mask_xor']; $row['limited_from'] = $row['limited_from'] == '0000-00-00 00:00:00' ? '' : trim($row['limited_from']); $row['limited_to'] = $row['limited_to'] == '0000-00-00 00:00:00' ? '' : trim($row['limited_to']); if ($row['time_limited'] == '1' && ($row['limited_from'] !== '' || $row['limited_to'] !== '')) { $this->_limited[$bid] = [ 'dmin' => $row['limited_from'] ? new DateTime($row['limited_from']) : null, 'dmax' => $row['limited_to'] ? new DateTime($row['limited_to']) : null ]; } } $this->set_data_to_cache($this->_global_rights, self::CACHE_GLOBAL_RIGHTS); $this->set_data_to_cache($this->_rights_bas, self::CACHE_RIGHTS_BAS); $this->set_data_to_cache($this->_limited, self::CACHE_LIMITS_BAS); return $this; } /** * Loads global rights for user * * @return ACL */ protected function load_global_rights() { $this->load_rights_bas(); $this->load_rights_sbas(); $this->_global_rights[self::TASKMANAGER] = $this->is_admin(); return $this; } /** * Return whether or not the acces to the specified module is OK * * @param String $module_name * @return boolean */ public function has_access_to_module($module_name) { switch ($module_name) { case 'admin': return ( ($this->has_right(self::BAS_MODIFY_STRUCT) || $this->has_right(self::COLL_MODIFY_STRUCT) || $this->has_right(self::BAS_MANAGE) || $this->has_right(self::COLL_MANAGE) || $this->has_right(self::CANADMIN) || $this->is_admin()) ); break; case 'thesaurus': return ($this->has_right(self::BAS_MODIF_TH) === true ); break; case 'upload': return ($this->has_right(self::CANADDRECORD) === true); break; case 'report': return ($this->has_right(self::CANREPORT) === true); break; default: break; } return true; } /** * @param array $base_ids * @return $this * @throws Exception */ public function revoke_access_from_bases(Array $base_ids) { $sql_del = 'DELETE FROM basusr WHERE base_id = :base_id AND usr_id = :usr_id'; $stmt_del = $this->app->getApplicationBox()->get_connection()->prepare($sql_del); $usr_id = $this->user->getId(); $errors = 0; foreach ($base_ids as $base_id) { if ($stmt_del->execute([':base_id' => $base_id, ':usr_id' => $usr_id])) { $this->app['dispatcher']->dispatch( AclEvents::ACCESS_TO_BASE_REVOKED, new AccessToBaseRevokedEvent( $this, [ 'base_id' => $base_id ] ) ); } else { $errors++; } } $stmt_del->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); if($errors > 0) { throw new Exception('Error while deleting some rights'); } return $this; } /** * * @param array $base_ids * @return ACL */ public function give_access_to_base(Array $base_ids) { $this->load_rights_bas(); $usr_id = $this->user->getId(); $sql_i = "INSERT INTO basusr (base_id, usr_id, actif) VALUES (:base_id, :usr_id, '1')"; $stmt_i = $this->app->getApplicationBox()->get_connection()->prepare($sql_i); $sql_u = "UPDATE basusr SET actif='1' WHERE base_id = :base_id AND usr_id = :usr_id"; $stmt_u = $this->app->getApplicationBox()->get_connection()->prepare($sql_u); foreach ($base_ids as $base_id) { if (isset($this->_rights_bas[$base_id]) && $this->_rights_bas[$base_id][self::ACTIF] == true) { continue; } if($this->try_give_access_to_base_insert($stmt_i, $base_id, $usr_id) == true) { $this->app['dispatcher']->dispatch( AclEvents::ACCESS_TO_BASE_GRANTED, new AccessToBaseGrantedEvent( $this, array( 'base_id'=>$base_id ) ) ); } else { $this->try_give_access_to_base_update($stmt_u, $base_id, $usr_id); } } $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); $this->inject_rights(); return $this; } private function try_give_access_to_base_insert(Statement $stmt, $base_id, $usr_id) { $inserted = false; try { $stmt->execute([':base_id' => $base_id, ':usr_id' => $usr_id]); if ($stmt->rowCount() > 0) { $inserted = true; } $stmt->closeCursor(); } catch(DBALException $e) { // no-op, mostly the row did exist } return $inserted; } private function try_give_access_to_base_update(Statement $stmt, $base_id, $usr_id) { $stmt->execute([':base_id' => $base_id, ':usr_id' => $usr_id]); $stmt->closeCursor(); } /** * * @param array $sbas_ids * @return ACL */ public function give_access_to_sbas(Array $sbas_ids) { $sql_ins = 'INSERT INTO sbasusr (sbasusr_id, sbas_id, usr_id) VALUES (null, :sbas_id, :usr_id)'; $stmt_ins = $this->app->getApplicationBox()->get_connection()->prepare($sql_ins); $usr_id = $this->user->getId(); foreach ($sbas_ids as $sbas_id) { if (!$this->has_access_to_sbas($sbas_id)) { try { $stmt_ins->execute([':sbas_id' => $sbas_id, ':usr_id' => $usr_id]); $this->app['dispatcher']->dispatch( AclEvents::ACCESS_TO_SBAS_GRANTED, new AccessToSbasGrantedEvent( $this, array( 'sbas_id'=>$sbas_id ) ) ); } catch (DBALException $e) { } } $this->app->getApplicationBox()->get_databox($sbas_id)->clearCache(databox::CACHE_COLLECTIONS); } $stmt_ins->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS); // $this->delete_data_from_cache(self::CACHE_GLOBAL_RIGHTS); return $this; } /** * @todo Create special toggle 'actif' / not a right like others * => nested loops when updating right to actif on an inactif account * * @param $base_id * @param $rights * @return ACL */ public function update_rights_to_base($base_id, $rights) { if (!$this->has_access_to_base($base_id) && (!isset($rights[self::ACTIF]) || $rights[self::ACTIF] == true)) { $this->give_access_to_base([$base_id]); } $conn = $this->app->getApplicationBox()->get_connection(); $sql_args = []; foreach ($rights as $right => $v) { if(is_bool($v)) { $v = $v ? 1 : 0; } $sql_args[] = " " . $conn->quoteIdentifier($right) . "=" . $conn->quote($v) . "\n"; } if (count($sql_args) == 0) { return $this; } $sql = "UPDATE basusr SET\n" . implode(',', $sql_args) . " WHERE base_id = :base_id AND usr_id = :usr_id"; $stmt = $conn->prepare($sql); $stmt->execute([':base_id' => $base_id, ':usr_id' => $this->user->getId()]); $stmt->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); $this->app['dispatcher']->dispatch( AclEvents::RIGHTS_TO_BASE_CHANGED, new RightsToBaseChangedEvent( $this, [ 'base_id' => $base_id, 'rights' => $rights ] ) ); return $this; } /** * * @return ACL */ public function revoke_unused_sbas_rights() { $sql = "DELETE FROM sbasusr\n" . " WHERE usr_id = :usr_id_1\n" . " AND sbas_id NOT IN\n" . "(SELECT distinct sbas_id FROM basusr bu, bas b WHERE usr_id = :usr_id_2 AND b.base_id = bu.base_id)"; $usr_id = $this->user->getId(); $params = [':usr_id_1' => $usr_id, ':usr_id_2' => $usr_id]; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute($params); $stmt->closeCursor(); $this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS); return $this; } /** * @param $sbas_id * @param $rights * @return $this * @throws DBALException * @throws Exception */ public function update_rights_to_sbas($sbas_id, $rights) { if (!$this->has_access_to_sbas($sbas_id)) { $this->give_access_to_sbas([$sbas_id]); } $sql_args = []; $conn = $this->app->getApplicationBox()->get_connection(); foreach ($rights as $right => $v) { if(is_bool($v)) { $v = $v ? 1 : 0; } $sql_args[] = " " . $conn->quoteIdentifier($right) . "=" . $conn->quote($v) . "\n"; } if (count($sql_args) == 0) { return $this; } $sql = "UPDATE sbasusr SET\n" . implode(',', $sql_args) . " WHERE sbas_id = :sbas_id AND usr_id = :usr_id"; $stmt = $conn->prepare($sql); $stmt->execute([':sbas_id' => $sbas_id, ':usr_id' => $this->user->getId()]); $stmt->closeCursor(); $this->app->getApplicationBox()->get_databox($sbas_id)->clearCache(databox::CACHE_COLLECTIONS); $this->delete_data_from_cache(self::CACHE_RIGHTS_SBAS); // $this->delete_data_from_cache(self::CACHE_GLOBAL_RIGHTS); $this->app['dispatcher']->dispatch( AclEvents::RIGHTS_TO_SBAS_CHANGED, new RightsToSbasChangedEvent( $this, [ 'sbas_id' => $sbas_id, 'rights' => $rights ] ) ); return $this; } /** * * @param $base_id * @return ACL */ public function remove_quotas_on_base($base_id) { $sql = "UPDATE basusr SET remain_dwnld = 0, restrict_dwnld = 0, month_dwnld_max = 0\n" . " WHERE usr_id = :usr_id AND base_id = :base_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId(), ':base_id' => $base_id]); $stmt->closeCursor(); unset($stmt); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); $this->app['dispatcher']->dispatch( AclEvents::DOWNLOAD_QUOTAS_ON_BASE_REMOVED, new DownloadQuotasOnBaseRemovedEvent( $this, array( 'base_id'=>$base_id ) ) ); return $this; } public function update_download_restrictions() { $sql = "UPDATE basusr SET remain_dwnld = month_dwnld_max\n" . " WHERE actif = 1" . " AND usr_id = :usr_id" . " AND MONTH(lastconn) != MONTH(NOW()) AND restrict_dwnld = 1"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId()]); $stmt->closeCursor(); $sql = "UPDATE basusr SET lastconn=NOW() WHERE usr_id = :usr_id AND actif = 1"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':usr_id' => $this->user->getId()]); $stmt->closeCursor(); unset($stmt); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); $this->app['dispatcher']->dispatch( AclEvents::DOWNLOAD_QUOTAS_RESET, new DownloadQuotasResetEvent( $this ) ); return $this; } /** * * @param $base_id * @param $droits * @param $restes * @return ACL */ public function set_quotas_on_base($base_id, $droits, $restes) { $sql = "UPDATE basusr SET remain_dwnld = :restes, restrict_dwnld = 1, month_dwnld_max = :droits\n" . " WHERE usr_id = :usr_id AND base_id = :base_id"; $params = [ ':usr_id' => $this->user->getId(), ':base_id' => $base_id, ':restes' => $restes, ':droits' => $droits ]; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute($params); $stmt->closeCursor(); unset($stmt); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); $this->app['dispatcher']->dispatch( AclEvents::DOWNLOAD_QUOTAS_ON_BASE_CHANGED, new DownloadQuotasOnBaseChangedEvent( $this, array( 'base_id'=>$base_id, 'remain_dwnld'=>$restes, 'month_dwnld_max'=>$droits ) ) ); return $this; } public function duplicate_right_from_bas($base_id_from, $base_id_dest) { $sql = "SELECT * FROM basusr WHERE base_id = :base_from AND usr_id = :usr_id"; $params = [ ':base_from' => $base_id_from, ':usr_id' => $this->user->getId() ]; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(PDO::FETCH_ASSOC); $stmt->closeCursor(); if (!$row) { return $this; } $this->give_access_to_base([$base_id_dest]); $this->update_rights_to_base( $base_id_dest, [ 'mask_and' => $row['mask_and'], 'mask_xor' => $row['mask_xor'], self::CANPUTINALBUM => ($row[self::CANPUTINALBUM] == '1'), self::CANDWNLDHD => ($row[self::CANDWNLDHD] == '1'), self::CANDWNLDPREVIEW => ($row[self::CANDWNLDPREVIEW] == '1'), self::CANCMD => ($row[self::CANCMD] == '1'), self::CANADMIN => ($row[self::CANADMIN] == '1'), self::CANREPORT => ($row[self::CANREPORT] == '1'), self::CANPUSH => ($row[self::CANPUSH] == '1'), self::NOWATERMARK => ($row[self::NOWATERMARK] == '1'), self::CANADDRECORD => ($row[self::CANADDRECORD] == '1'), self::CANMODIFRECORD => ($row[self::CANMODIFRECORD] == '1'), self::CANDELETERECORD => ($row[self::CANDELETERECORD] == '1'), self::CHGSTATUS => ($row[self::CHGSTATUS] == '1'), self::IMGTOOLS => ($row[self::IMGTOOLS] == '1'), self::COLL_MANAGE => ($row[self::COLL_MANAGE] == '1'), self::COLL_MODIFY_STRUCT => ($row[self::COLL_MODIFY_STRUCT] == '1') ] ); if ($row['time_limited']) { $this->set_limits($base_id_dest, $row['time_limited'], new \DateTime($row['limited_from']), new \DateTime($row['limited_to'])); } if ($row['restrict_dwnld']) { $this->set_quotas_on_base($base_id_dest, $row['month_dwnld_max'], $row['remain_dwnld']); } return $this; } public function inject_rights() { $this->update_download_restrictions(); foreach ($this->get_granted_sbas() as $databox) { $this->inject_rights_sbas($databox); } return $this; } protected function inject_rights_sbas(databox $databox) { $this->delete_injected_rights_sbas($databox); $sql = "INSERT INTO collusr (site, usr_id, coll_id, mask_and, mask_xor, ord) VALUES (:site_id, :usr_id, :coll_id, :mask_and, :mask_xor, :ord)"; $stmt = $databox->get_connection()->prepare($sql); $iord = 0; foreach ($this->get_granted_base([], [$databox->get_sbas_id()]) as $collection) { try { $stmt->execute([ ':site_id' => $this->app['conf']->get(['main', 'key']), ':usr_id' => $this->user->getId(), ':coll_id' => $collection->get_coll_id(), ':mask_and' => $this->get_mask_and($collection->get_base_id()), ':mask_xor' => $this->get_mask_xor($collection->get_base_id()), ':ord' => $iord++ ]); } catch (DBALException $e) { } } $stmt->closeCursor(); return $this; } public function delete_injected_rights() { foreach ($this->get_granted_sbas() as $databox) { $this->delete_injected_rights_sbas($databox); } return $this; } public function delete_injected_rights_sbas(databox $databox) { $sql = 'DELETE FROM collusr WHERE usr_id = :usr_id AND site = :site'; $stmt = $databox->get_connection()->prepare($sql); $stmt->execute([ ':usr_id' => $this->user->getId(), ':site' => $this->app['conf']->get(['main', 'key']) ]); $stmt->closeCursor(); return $this; } public function set_masks_on_base($base_id, $and_and, $and_or, $xor_and, $xor_or) { $vhex = []; $datas = [ 'and_and' => $and_and, 'and_or' => $and_or, 'xor_and' => $xor_and, 'xor_or' => $xor_or ]; foreach ($datas as $name => $f) { $vhex[$name] = "0x"; while (strlen($datas[$name]) < 32) { $datas[$name] = "0" . $datas[$name]; } } foreach ($datas as $name => $f) { while (strlen($datas[$name]) > 0) { $valtmp = substr($datas[$name], 0, 4); $datas[$name] = substr($datas[$name], 4); $vhex[$name] .= dechex(bindec($valtmp)); } } $sql = "UPDATE basusr\n" . " SET mask_and=((mask_and & " . $vhex['and_and'] . ") | " . $vhex['and_or'] . "),\n" . " mask_xor=((mask_xor & " . $vhex['xor_and'] . ") | " . $vhex['xor_or'] . ")\n" . " WHERE usr_id = :usr_id and base_id = :base_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([':base_id' => $base_id, ':usr_id' => $this->user->getId()]); $stmt->closeCursor(); unset($stmt); $this->delete_data_from_cache(self::CACHE_RIGHTS_BAS); $this->app['dispatcher']->dispatch( AclEvents::MASKS_ON_BASE_CHANGED, new MasksOnBaseChangedEvent( $this, array( 'base_id'=>$base_id ) ) ); return $this; } public function is_limited($base_id) { $this->load_rights_bas(); $datetime = new DateTime(); if (!isset($this->_limited[$base_id])) { return false; } $lim_min = $this->_limited[$base_id]['dmin'] && $this->_limited[$base_id]['dmin'] > $datetime; $lim_max = $this->_limited[$base_id]['dmax'] && $this->_limited[$base_id]['dmax'] < $datetime; return ($lim_max || $lim_min); } /** * returns date limits ['dmin'=>x, 'dmax'=>y] with x,y : NullableDateTime * * * @param $base_id * @return array|null */ public function get_limits($base_id) { $this->load_rights_bas(); if (!isset($this->_limited[$base_id])) { return null; } return $this->_limited[$base_id]; } public function set_limits($base_id, $limit, DateTime $limit_from = null, DateTime $limit_to = null) { $sql = "UPDATE basusr\n" . " SET time_limited = :time_limited, limited_from = :limited_from, limited_to = :limited_to\n" . " WHERE base_id = :base_id AND usr_id = :usr_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([ ':time_limited' => $limit ? 1 : 0, ':usr_id' => $this->user->getId(), ':base_id' => $base_id, ':limited_from' => NullableDateTime::format($limit_from), ':limited_to' => NullableDateTime::format($limit_to), ]); $stmt->closeCursor(); $this->delete_data_from_cache(self::CACHE_LIMITS_BAS); $this->app['dispatcher']->dispatch( AclEvents::ACCESS_PERIOD_CHANGED, new AccessPeriodChangedEvent( $this, array( 'base_id'=>$base_id ) ) ); return $this; } public function can_see_business_fields(\databox $databox) { // a user can see the business fields if he has at least the right on one collection to edit a record foreach($databox->get_collections() as $collection) { if ($this->has_access_to_base($collection->get_base_id()) && $this->has_right_on_base($collection->get_base_id(), self::CANMODIFRECORD)) { return true; } } return false; } /** * Returns base ids on which user is 'order master' * * @return array */ public function getOrderMasterCollectionsBaseIds() { $sql = "SELECT base_id FROM basusr WHERE order_master='1' AND usr_id= :usr_id"; $result = $this->app->getApplicationBox() ->get_connection() ->executeQuery($sql, [':usr_id' => $this->user->getId()]) ->fetchAll(\PDO::FETCH_ASSOC); $baseIds = []; foreach ($result as $item) { $baseIds[] = $item['base_id']; } return $baseIds; } /** * Returns an array of collections on which the user is 'order master' * * @return collection[] */ public function get_order_master_collections() { $baseIds = $this->getOrderMasterCollectionsBaseIds(); $collectionReferences = $this->app['repo.collection-references']->findHavingOrderMaster($baseIds); $groups = new CollectionReferenceCollection($collectionReferences); $collections = []; foreach ($groups->groupByDataboxIdAndCollectionId() as $databoxId => $group) { foreach ($group as $collectionId => $index) { $collections[$index] = \collection::getByCollectionId($this->app, $databoxId, $collectionId); } } ksort($collections); return $collections; } /** * Sets the user as "order_master" on a collection * * @param \collection $collection The collection to apply * @param Boolean $bool Wheter the user is order master or not * * @return ACL */ public function set_order_master(\collection $collection, $bool) { $sql = "UPDATE basusr SET order_master = :master WHERE usr_id = :usr_id AND base_id = :base_id"; $stmt = $this->app->getApplicationBox()->get_connection()->prepare($sql); $stmt->execute([ ':master' => $bool ? 1 : 0, ':usr_id' => $this->user->getId(), ':base_id' => $collection->get_base_id() ]); $stmt->closeCursor(); return $this; } }