adm_program/system/classes/TableMembers.php
<?php
use Admidio\Exception;
/**
* @brief Handle memberships of roles and manage it in the database table adm_members
*
* The class search in the database table **adm_members** for role memberships of
* users. It has easy methods to start or stop a membership.
*
* **Code example**
* ```
* // start membership without read data before
* $membership = new TableMembers($gDb);
* $membership->startMembership($roleId, $userId);
*
* // read membership data and then stop membership
* $membership = new TableMembers($gDb);
* $membership->readDataByColumns(array('mem_rol_id' => $roleId, 'mem_usr_id' => $userId));
* $membership->stopMembership();
* ```
* @copyright The Admidio Team
* @see https://www.admidio.org/
* @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License v2.0 only
*/
class TableMembers extends TableAccess
{
/**
* Constructor that will create an object of a recordset of the table adm_members.
* If the id is set than the specific membership will be loaded.
* @param Database $database Object of the class Database. This should be the default global object **$gDb**.
* @param int $memId The recordset of the membership with this id will be loaded. If id isn't set than an empty object of the table is created.
* @throws Exception
*/
public function __construct(Database $database, $memId = 0)
{
// read also data of assigned category
$this->connectAdditionalTable(TBL_ROLES, 'rol_id', 'mem_rol_id');
parent::__construct($database, TBL_MEMBERS, 'mem', $memId);
}
/**
* Set a new value for a column of the database table. The value is only saved in the object.
* You must call the method **save** to store the new value to the database. If the unique key
* column is set to 0 than this record will be a new record and all other columns are marked as changed.
* This method also queues the changes to the field for admin notification
* messages. Apart from this, the parent's setValue is used to set the new value.
* @param string $columnName The name of the database column whose value should get a new value
* @param mixed $newValue The new value that should be stored in the database field
* @param bool $checkValue The value will be checked if it's valid. If set to **false** than the value will not be checked.
* @return bool Returns **true** if the value is stored in the current object and **false** if a check failed
* @throws Exception
* @see TableAccess#getValue
*/
public function setValue(string $columnName, $newValue, bool $checkValue = true): bool
{
global $gChangeNotification, $gCurrentSession;
// New records will be logged in ::save, because their ID is only generated during first save
if (!$this->newRecord && $gCurrentSession instanceof Session) {
if (in_array($columnName, array('mem_begin', 'mem_end'))) {
$oldValue = $this->getValue($columnName, 'Y-m-d');
} else {
$oldValue = $this->getValue($columnName);
}
if ($oldValue != $newValue) {
$gChangeNotification->logRoleChange(
$this->getValue('mem_usr_id'),
$this->getValue('rol_name'),
$columnName,
(string) $oldValue,
(string) $newValue
);
}
}
return parent::setValue($columnName, $newValue, $checkValue);
}
/**
* Deletes a membership for the assigned role and user. In opposite to removeMembership
* this method will delete the entry, and you can't see any history assignment.
* If the user is the current user then initiate a refresh of his role cache.
* @param int $roleId Stops the membership of this role
* @param int $userId The user who should lose the member of the role.
* @return bool Return **true** if the membership was successfully deleted.
* @throws Exception
*/
public function deleteMembership(int $roleId = 0, int $userId = 0): bool
{
global $gCurrentUser;
// if role and user is set, then search for this membership and load data into class
if ($roleId > 0 && $userId > 0) {
$this->readDataByColumns(array('mem_rol_id' => $roleId, 'mem_usr_id' => $userId));
}
if ($this->getValue('mem_rol_id') > 0 && $this->getValue('mem_usr_id') > 0) {
$this->delete();
// if role membership of current user will be changed then renew his rights arrays
if ($userId === $GLOBALS['gCurrentUserId']) {
$gCurrentUser->renewRoleData();
}
return true;
}
return false;
}
/**
* Deletes the selected record of the table and optionally sends an admin notification if configured
* @return true Returns **true** if no error occurred
* @throws Exception
*/
public function delete(): bool
{
// Queue admin notification about membership deletion
global $gChangeNotification, $gCurrentSession;
// If this is a new record that hasn't been written to the database, simply ignore it
if (!$this->newRecord && is_object($gChangeNotification)) {
// Log begin, end and leader as changed (set to NULL)
$usrId = $this->getValue('mem_usr_id');
$membership = $this->getValue('rol_name');
$gChangeNotification->logRoleChange(
$usrId,
$membership,
'mem_begin',
$this->getValue('mem_begin', 'Y-m-d'),
'',
null,
true
);
$gChangeNotification->logRoleChange(
$usrId,
$membership,
'mem_end',
$this->getValue('mem_end', 'Y-m-d'),
'',
null,
true
);
if ($this->getValue('mem_leader')) {
$gChangeNotification->logRoleChange(
$usrId,
$membership,
'mem_leader',
$this->getValue('mem_leader'),
'',
null,
true
);
}
}
// renew user object of the affected user because of edited role assignment
$gCurrentSession->reload((int) $this->getValue('mem_usr_id'));
return parent::delete();
}
/**
* Save all changed columns of the recordset in table of database. Therefore, the class remembers if it's
* a new record or if only an update is necessary. The update statement will only update
* the changed columns. If the table has columns for creator or editor than these column
* with their timestamp will be updated.
* @param bool $updateFingerPrint Default **true**. Will update the creator or editor of the recordset if table has columns like **usr_id_create** or **usr_id_changed**
* @return bool If an update or insert into the database was done then return true, otherwise false.
* @throws Exception
*/
public function save(bool $updateFingerPrint = true): bool
{
global $gCurrentSession, $gChangeNotification, $gCurrentUser;
// if role is administrator than only administrator can add new user,
// but don't change their own membership, because there must be at least one administrator
if ($this->getValue('rol_administrator') && !$gCurrentUser->isAdministrator()) {
throw new Exception('SYS_NO_RIGHTS');
}
$newRecord = $this->newRecord;
$returnStatus = parent::save($updateFingerPrint);
if ($returnStatus && $gCurrentSession instanceof Session) {
// renew user object of the affected user because of edited role assignment
$gCurrentSession->reload((int) $this->getValue('mem_usr_id'));
}
if ($newRecord && is_object($gChangeNotification)) {
// Queue admin notification about membership deletion
// storing a record for the first time does NOT update the fields from
// the roles table => need to create a new object that loads the
// role name from the database, too!
$memId = $this->getValue('mem_id');
$obj = new self($this->db, $memId);
// Log begin, end and leader as changed (set to NULL)
$usrId = $obj->getValue('mem_usr_id');
$membership = $obj->getValue('rol_name');
$gChangeNotification->logRoleChange(
$usrId,
$membership,
'mem_begin',
'',
$obj->getValue('mem_begin', 'Y-m-d'), // user=
null,
true
);
$gChangeNotification->logRoleChange(
$usrId,
$membership,
'mem_end',
'',
$obj->getValue('mem_end', 'Y-m-d'), // user=
null,
true
);
if ($obj->getValue('mem_leader')) {
$gChangeNotification->logRoleChange(
$usrId,
$membership,
'mem_leader',
'',
$obj->getValue('mem_leader'), // user=
null,
true
);
}
}
return $returnStatus;
}
/**
* Starts a membership for the assigned role and user from now until 31.12.9999.
* An existing membership will be extended if necessary. If the user is the
* current user then initiate a refresh of his role cache.
* @param int $roleId Assign the membership to this role
* @param int $userId The user who should get a member of the role.
* @param bool|null $leader If value **1** then the user will be a leader of the role and get more rights.
* @param int|null $approvalState Option for User to confirm and adjust the membership ( **1** = User confirmed membership but maybe disagreed, **2** = user accepted membership
* @return bool Return **true** if the assignment was successful.
* @throws Exception
*/
public function startMembership(int $roleId = 0, int $userId = 0, bool $leader = null, int $approvalState = null): bool
{
global $gCurrentUser, $gCurrentUserId;
// if role and user is set, then search for this membership and load data into class
if ($roleId > 0 && $userId > 0) {
$this->readDataByColumns(array('mem_rol_id' => $roleId, 'mem_usr_id' => $userId));
}
if ($this->getValue('mem_rol_id') > 0 && $this->getValue('mem_usr_id') > 0) {
// Do not overwrite start date if already exists
if ($this->newRecord || strcmp($this->getValue('mem_begin', 'Y-m-d'), DATE_NOW) > 0) {
$this->setValue('mem_begin', DATE_NOW);
}
// Leaders should not be overwritten if parameter not set
if ($leader === null) {
if ($this->newRecord) {
$this->setValue('mem_leader', false);
}
} else {
$this->setValue('mem_leader', $leader);
}
$this->setValue('mem_end', DATE_MAX);
// User has confirmed or adjusted role membership
if ($approvalState > 0) {
$this->setValue('mem_approved', $approvalState);
}
if ($this->columnsValueChanged) {
$this->save();
// if role membership of current user will be changed then renew his rights arrays
if ($gCurrentUserId === $userId) {
$gCurrentUser->renewRoleData();
}
return true;
}
}
return false;
}
}