<?php

/**
 * @file classes/security/RoleDAO.inc.php
 *
 * Copyright (c) 2014-2019 Simon Fraser University
 * Copyright (c) 2003-2019 John Willinsky
 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
 *
 * @class RoleDAO
 * @ingroup security
 *
 * @brief Operations for retrieving and modifying Role objects.
 */

import('lib.pkp.classes.security.Role');
import('lib.pkp.classes.security.UserGroupAssignment');

class RoleDAO extends DAO {
	/** @var The User DAO to return User objects when necessary **/
	var $userDao;

	/**
	 * Constructor.
	 */
	function __construct() {
		parent::__construct();
		$this->userDao = DAORegistry::getDAO('UserDAO');
	}

	/**
	 * Create new data object
	 * @return Role
	 */
	function newDataObject() {
		return new Role();
	}

	/**
	 * Retrieve a list of users in a specified role.
	 * @param $roleId int optional (can leave as null to get all users in context)
	 * @param $contextId int optional, include users only in this context
	 * @param $searchType int optional, which field to search
	 * @param $search string optional, string to match
	 * @param $searchMatch string optional, type of match ('is' vs. 'contains' vs. 'startsWith')
	 * @param $dbResultRange object DBRangeInfo object describing range of results to return
	 * @return array matching Users
	 */
	function getUsersByRoleId($roleId = null, $contextId = null, $searchType = null, $search = null, $searchMatch = null, $dbResultRange = null) {
		$paramArray = array(ASSOC_TYPE_USER, 'interest', IDENTITY_SETTING_GIVENNAME, IDENTITY_SETTING_FAMILYNAME);
		$paramArray = array_merge($paramArray, $this->userDao->getFetchParameters());
		if (isset($roleId)) $paramArray[] = (int) $roleId;
		if (isset($contextId)) $paramArray[] = (int) $contextId;
		// For security / resource usage reasons, a role or context ID
		// must be specified. Don't allow calls supplying neither.
		if ($contextId === null && $roleId === null) return null;

		$searchSql = '';

		$searchTypeMap = array(
			IDENTITY_SETTING_GIVENNAME => 'usgs.setting_value',
			IDENTITY_SETTING_FAMILYNAME => 'usfs.setting_value',
			USER_FIELD_USERNAME => 'u.username',
			USER_FIELD_EMAIL => 'u.email',
			USER_FIELD_INTERESTS => 'cves.setting_value'
		);

		if (!empty($search) && isset($searchTypeMap[$searchType])) {
			$fieldName = $searchTypeMap[$searchType];
			switch ($searchMatch) {
				case 'is':
					$searchSql = "AND LOWER($fieldName) = LOWER(?)";
					$paramArray[] = $search;
					break;
				case 'contains':
					$searchSql = "AND LOWER($fieldName) LIKE LOWER(?)";
					$paramArray[] = '%' . $search . '%';
					break;
				case 'startsWith':
					$searchSql = "AND LOWER($fieldName) LIKE LOWER(?)";
					$paramArray[] = $search . '%';
					break;
			}
		} elseif (!empty($search)) switch ($searchType) {
			case USER_FIELD_USERID:
				$searchSql = 'AND u.user_id=?';
				$paramArray[] = $search;
				break;
		}

		$searchSql .= ' ' . $this->userDao->getOrderBy(); // FIXME Add "sort field" parameter?

		$result = $this->retrieveRange(
			'SELECT DISTINCT u.*,
			' . $this->userDao->getFetchColumns() . '
			FROM users AS u
			LEFT JOIN user_user_groups uug ON (uug.user_id = u.user_id)
			LEFT JOIN user_groups ug ON (ug.user_group_id = uug.user_group_id)
			LEFT JOIN controlled_vocabs cv ON (cv.assoc_type = ? AND cv.assoc_id = u.user_id AND cv.symbolic = ?)
			LEFT JOIN user_settings usgs ON (usgs.user_id = u.user_id AND usgs.setting_name = ?)
			LEFT JOIN user_settings usfs ON (usfs.user_id = u.user_id AND usfs.setting_name = ?)
			LEFT JOIN controlled_vocab_entries cve ON (cve.controlled_vocab_id = cv.controlled_vocab_id)
			LEFT JOIN controlled_vocab_entry_settings cves ON (cves.controlled_vocab_entry_id = cve.controlled_vocab_entry_id)
			' . $this->userDao->getFetchJoins() . '
			WHERE ' . (isset($roleId) ? 'ug.role_id = ?' : '') . (isset($contextId) ? ' AND ug.context_id = ?' : '') . ' ' . $searchSql,
			$paramArray,
			$dbResultRange
		);

		return new DAOResultFactory($result, $this->userDao, '_returnUserFromRowWithData');
	}

	/**
	 * Validation check to see if a user belongs to any group that has a given role
	 * @param $contextId int
	 * @param $userId int
	 * @param $roleId int|array ROLE_ID_...
	 * @return bool True iff at least one such role exists
	 */
	function userHasRole($contextId, $userId, $roleId) {
		$roleId = is_array($roleId) ? join(',', array_map('intval', $roleId)) : (int) $roleId;
		$result = $this->retrieve(
			'SELECT count(*) FROM user_groups ug JOIN user_user_groups uug ON ug.user_group_id = uug.user_group_id
			WHERE ug.context_id = ? AND uug.user_id = ? AND ug.role_id IN (' . $roleId . ')',
			array((int) $contextId, (int) $userId)
		);

		// > 0 because user could belong to more than one user group with this role
		$returner = isset($result->fields[0]) && $result->fields[0] > 0 ? true : false;

		$result->Close();
		return $returner;
	}

	/**
	 * Return an array of row objects corresponding to the roles a given use has
	 * @param $userId
	 * @param $contextId
	 * @return array of Roles
	 */
	function getByUserId($userId, $contextId = null) {
		$params = array((int) $userId);
		if ($contextId !== null) $params[] = (int) $contextId;
		$result = $this->retrieve(
			'SELECT	DISTINCT ug.role_id
			FROM	user_groups ug
				JOIN user_user_groups uug ON ug.user_group_id = uug.user_group_id
			WHERE	uug.user_id = ?' . ($contextId !== null ? ' AND ug.context_id = ?' : ''),
			$params
		);

		$roles = array();
		while ( !$result->EOF ) {
			$role = $this->newDataObject();
			$role->setRoleId($result->fields[0]);
			$roles[] = $role;
			$result->MoveNext();
		}
		$result->Close();
		return $roles;
	}

	/**
	 * Return an array of objects corresponding to the roles a given user has,
	 * grouped by context id.
	 * @param $userId int
	 * @return array
	 */
	function getByUserIdGroupedByContext($userId) {
		$userGroupDao = DAORegistry::getDAO('UserGroupDAO');
		$roleDao = DAORegistry::getDAO('RoleDAO');
		$userGroupsFactory = $userGroupDao->getByUserId($userId);

		$roles = array();
		while ($userGroup = $userGroupsFactory->next()) {
			$role = $roleDao->newDataObject();
			$role->setRoleId($userGroup->getRoleId());
			$roles[$userGroup->getContextId()][$userGroup->getRoleId()] = $role;
		}

		return $roles;
	}

	/**
	 * Get role forbidden stages.
	 * @param $roleId int Specific role ID to fetch stages for, if any
	 * @return array With $roleId, array(WORKFLOW_STAGE_ID_...); without,
	 *  array(ROLE_ID_... => array(WORKFLOW_STAGE_ID_...))
	 */
	function getForbiddenStages($roleId = null) {
		$forbiddenStages = array(
			ROLE_ID_MANAGER => array(
				// Journal managers should always have all stage selections locked by default.
				WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION,
			),
			ROLE_ID_REVIEWER => array(
				// Reviewer user groups should only have review stage assignments.
				WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION,
			),
			ROLE_ID_READER => array(
				// Reader user groups should have no stage assignments.
				WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION,
			),
		);

		if ($roleId) {
			if (isset($forbiddenStages[$roleId])) {
				return $forbiddenStages[$roleId];
			} else {
				return array();
			}
		} else {
			return $forbiddenStages;
		}
	}

	/**
	 *  All stages are always active for these permission levels.
	 * @return array array(ROLE_ID_MANAGER...);
	 */
	function getAlwaysActiveStages() {
		$alwaysActiveStages = array(ROLE_ID_MANAGER);
		return $alwaysActiveStages;
	}
}


