Admidio/admidio

View on GitHub
adm_program/system/classes/TableMembers.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?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;
    }
}