* @author Shohei Nakajima * @link http://www.netcommons.org NetCommons Project * @license http://www.netcommons.org/license.txt NetCommons License * @copyright Copyright 2014, NetCommons Project */ App::uses('UserSearchAppModel', 'Users.Model'); /** * UserSearch Model * * @author Shohei Nakajima * @package NetCommons\Users\Model * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class UserSearch extends UserSearchAppModel { /** * Alias name for model. * * @var string */ public $alias = 'User'; /** * Table name for this Model. * * @var string */ public $table = 'users'; /** * Custom database table name, or null/false if no table association is desired. * * @var string * @link http://book.cakephp.org/2.0/ja/models/model-attributes.html#usetable */ public $useTable = 'users'; /** * 閲覧できるフィールドリスト * * @var array */ public $readableFields = null; /** * 閲覧できるフィールドリスト * * @var array */ private static $__readableFields = null; /** * 事前準備を実行したかどうか * * @var bool */ private static $__doPrepare = false; /** * 閲覧できるフィールドリスト * * @var array */ public $convRealToFieldKey = null; /** * Constructor. Binds the model's database table to the object. * * @param bool|int|string|array $id Set this ID for this model on startup, * can also be an array of options, see above. * @param string $table Name of database table to use. * @param string $ds DataSource connection name. * @see Model::__construct() * @SuppressWarnings(PHPMD.BooleanArgumentFlag) */ public function __construct($id = false, $table = null, $ds = null) { parent::__construct($id, $table, $ds); $this->loadModels([ 'Group' => 'Groups.Group', 'GroupsUser' => 'Groups.GroupsUser', 'Role' => 'Roles.Role', 'RolesRoom' => 'Rooms.RolesRoom', 'RolesRoomsUser' => 'Rooms.RolesRoomsUser', 'Room' => 'Rooms.Room', 'RoomRole' => 'Rooms.RoomRole', 'UserRoleSetting' => 'UserRoles.UserRoleSetting', 'UsersLanguage' => 'Users.UsersLanguage', 'UserAttribute' => 'UserAttributes.UserAttribute', 'UserAttributesRole' => 'UserRoles.UserAttributesRole', 'UploadFile' => 'Files.UploadFile', 'UserAttribute' => 'UserAttributes.UserAttribute', 'UserAttributeSetting' => 'UserAttributes.UserAttributeSetting', 'DataType' => 'DataTypes.DataType', ]); if (Configure::read('NetCommons.installed')) { $this->__prepare(); } } /** * 事前準備 * * @return void */ private function __prepare() { if (self::$__doPrepare) { $this->readableFields = self::$__readableFields; return; } $results = $this->UserAttributesRole->find('list', array( 'recursive' => -1, 'fields' => array('user_attribute_key', 'user_attribute_key'), 'conditions' => array( 'role_key' => AuthComponent::user('role_key'), 'other_readable' => true, ) )); $userAttributes = $this->UserAttribute->getUserAttributesForLayout(); //通常フィールド $this->readableFields = array('id' => ['field' => 'id']); $this->readableFields = array('user_id' => ['field' => 'User.id']); foreach ($results as $field) { $this->_setReadableField($field, $userAttributes); } $this->readableFields['created_user']['field'] = 'TrackableCreator.handlename'; $this->readableFields['modified_user']['field'] = 'TrackableUpdater.handlename'; if (isset($this->readableFields['role_key'])) { $this->readableFields['role_key']['order'] = 'Role.id'; } $this->readableFields['origin_role_key']['field'] = 'UserRoleSetting.origin_role_key'; //参加ルーム $this->readableFields['room_id']['field'] = $this->Room->alias . '.id'; $this->readableFields['room_id']['label'] = __d('user_manager', 'Rooms'); $result = $this->Room->find('all', $this->Room->getReadableRoomsConditions(array( 'Room.space_id !=' => Space::PRIVATE_SPACE_ID ))); $this->readableFields['room_id']['options'] = []; foreach ($result as $room) { $roomId = $room['Room']['id']; $roomName = ''; foreach ($room['RoomsLanguage'] as $roomLanguage) { if ($roomLanguage['language_id'] === Current::read('Language.id')) { $roomName = $roomLanguage['name']; } } $this->readableFields['room_id']['options'][$roomId] = $roomName; } //自分自身のグループ $this->readableFields['group_id']['field'] = $this->GroupsUser->alias . '.group_id'; $this->readableFields['group_id']['label'] = __d('user_manager', 'Groups'); $result = $this->Group->find('list', array( 'recursive' => -1, 'fields' => array('id', 'name'), 'conditions' => array( 'created_user' => Current::read('User.id'), ), 'order' => array('id'), )); $this->readableFields['group_id']['options'] = $result; //ラベルなし $this->readableFields['role_id']['field'] = $this->Role->alias . '.id'; $this->readableFields['space_id']['field'] = $this->Room->alias . '.space_id'; $this->readableFields['room_role_key']['field'] = $this->RolesRoom->alias . '.role_key'; $this->readableFields['room_role_level']['field'] = $this->RoomRole->alias . '.level'; $this->readableFields['roles_room_id']['field'] = $this->RolesRoom->alias . '.id'; $this->readableFields['roles_room_room_id']['field'] = $this->RolesRoom->alias . '.room_id'; $this->readableFields['roles_room_role_key']['field'] = $this->RolesRoom->alias . '.role_key'; $this->readableFields['roles_rooms_user_id']['field'] = $this->RolesRoomsUser->alias . '.id'; $this->readableFields['roles_rooms_user_roles_room_id']['field'] = $this->RolesRoomsUser->alias . '.roles_room_id'; $this->readableFields['roles_rooms_user_user_id']['field'] = $this->RolesRoomsUser->alias . '.user_id'; $this->readableFields['roles_rooms_user_room_id']['field'] = $this->RolesRoomsUser->alias . '.room_id'; foreach ($this->readableFields as $key => $value) { $value['key'] = $key; $this->readableFields[$key] = $value; if (isset($this->readableFields[$key]['field'])) { $this->convRealToFieldKey[$this->readableFields[$key]['field']] = $value; } } self::$__readableFields = $this->readableFields; self::$__doPrepare = true; } /** * JOINテーブルを取得 * * @param array $joinModels JOINモデルリスト * @param array $conditions 条件(Conditions)リスト * @param array $fields 取得カラムリスト * @param string $join JOIN種別(INNER or LEFT) * @return array Findで使用するJOIN配列 */ public function getSearchJoinTables($joinModels, $conditions = [], $fields = [], $join = '') { if (!$join) { $join = 'INNER'; } $joinModels = Hash::merge( $joinModels, $this->_getSearchJoinTablesByConditions($conditions) ); $joins = $this->__getSearchJoinTablesForUser($joinModels, $join); //ルームの条件があるときのみルームに必要なJOINをする。 $joins = array_merge( $joins, $this->__getSearchJoinTablesForRoom($joinModels, $conditions, $fields, $join) ); if (Hash::get($joinModels, 'Group')) { $joins[] = array( 'table' => $this->GroupsUser->table, 'alias' => $this->GroupsUser->alias, 'type' => 'INNER', 'conditions' => array( $this->GroupsUser->alias . '.user_id' . ' = ' . $this->alias . '.id', $this->GroupsUser->alias . '.created_user' => Current::read('User.id'), ), ); } if (Hash::get($joinModels, 'TrackableCreator')) { $joins[] = array( 'table' => $this->table, 'alias' => 'TrackableCreator', 'type' => 'INNER', 'conditions' => array( $this->alias . '.created_user' . ' = ' . 'TrackableCreator.id', ), ); } if (Hash::get($joinModels, 'TrackableUpdater')) { $joins[] = array( 'table' => $this->table, 'alias' => 'TrackableUpdater', 'type' => 'INNER', 'conditions' => array( $this->alias . '.modified_user' . ' = ' . 'TrackableUpdater.id', ), ); } foreach ($joinModels as $joinModel) { if (isset($joinModel['table']) && $joinModel['table'] === $this->UploadFile->table) { $joins[] = $joinModel; } } return $joins; } /** * Roomを条件にする場合のJOINテーブルを取得 * * @param array $joinModels JOINモデルリスト * @param array $conditions 条件配列 * @param array $fields 取得カラムリスト * @param string $join JOIN種別(INNER or LEFT) * @return array Findで使用するJOIN配列 */ private function __getSearchJoinTablesForRoom($joinModels, $conditions, $fields, $join) { if ($fields !== true) { $fieldRooms = preg_grep('/^(RolesRoom|RoomRole|roles_room|room_role)/', $fields); } else { $fieldRooms = [$fields]; } $conditionRooms = preg_grep('/^(RolesRoom|RoomRole)/', array_keys($conditions)); if (count($fieldRooms) === 0 && count($conditionRooms) === 0) { return []; } $joins = array( Hash::merge(array( 'table' => $this->RolesRoom->table, 'alias' => $this->RolesRoom->alias, 'type' => $join, 'conditions' => array( $this->RolesRoomsUser->alias . '.roles_room_id' . ' = ' . $this->RolesRoom->alias . '.id', ), ), Hash::get($joinModels, 'RolesRoom', array())), Hash::merge(array( 'table' => $this->RoomRole->table, 'alias' => $this->RoomRole->alias, 'type' => $join, 'conditions' => array( $this->RolesRoom->alias . '.role_key' . ' = ' . $this->RoomRole->alias . '.role_key', ), ), Hash::get($joinModels, 'RoomRole', array())), ); return $joins; } /** * Userを条件にする場合のJOINテーブルを取得 * * @param array $joinModels JOINモデルリスト * @param string $join JOIN種別(INNER or LEFT) * @return array Findで使用するJOIN配列 */ private function __getSearchJoinTablesForUser($joinModels, $join) { $joins = array( array( 'table' => $this->UsersLanguage->table, 'alias' => $this->UsersLanguage->alias, 'type' => 'LEFT', 'conditions' => array( $this->UsersLanguage->alias . '.user_id' . ' = ' . $this->alias . '.id', $this->UsersLanguage->alias . '.language_id' => Current::read('Language.id'), ), ), Hash::merge(array( 'table' => $this->Role->table, 'alias' => $this->Role->alias, 'type' => 'INNER', 'conditions' => array( $this->alias . '.role_key' . ' = ' . $this->Role->alias . '.key', $this->Role->alias . '.language_id' => Current::read('Language.id'), ), ), Hash::get($joinModels, 'Role', array())), Hash::merge(array( 'table' => $this->UserRoleSetting->table, 'alias' => $this->UserRoleSetting->alias, 'type' => 'INNER', 'conditions' => array( $this->alias . '.role_key' . ' = ' . $this->UserRoleSetting->alias . '.role_key' ), ), Hash::get($joinModels, 'UserRoleSetting', array())), Hash::merge(array( 'table' => $this->RolesRoomsUser->table, 'alias' => $this->RolesRoomsUser->alias, 'type' => $join, 'conditions' => array( $this->RolesRoomsUser->alias . '.user_id' . ' = ' . $this->alias . '.id', ), ), Hash::get($joinModels, 'RolesRoomsUser', array())), Hash::merge(array( 'table' => $this->Room->table, 'alias' => $this->Room->alias, 'type' => $join, 'conditions' => array( $this->RolesRoomsUser->alias . '.room_id' . ' = ' . $this->Room->alias . '.id', ), ), Hash::get($joinModels, 'Room', array())), ); return $joins; } /** * 条件(Conditions)を取得 * * @param array $conditions 条件(Conditions)リスト * @return array 実際に条件を含められるリスト */ public function getSearchConditions($conditions = array()) { $fieldKeys = array_keys($conditions); foreach ($fieldKeys as $key) { list($field, $setting, $reqSign) = $this->_parseRequestKey($key); if (is_array($conditions[$key])) { $sign = $reqSign; $value = $conditions[$key]; } else { list($sign, $value) = $this->_creanSearchCondition( $field, $setting, $conditions[$key], $reqSign ); } unset($conditions[$key]); if (! isset($this->readableFields[$field])) { continue; } $sqlField = $this->readableFields[$field]['field']; $dataType = $this->readableFields[$field]['data_type'] ?? null; if ($dataType === DataType::DATA_TYPE_IMG) { $this->__setSearchConditionsByTypeImage($conditions, $sqlField, $sign, $value); } elseif ($dataType === DataType::DATA_TYPE_CHECKBOX) { $userAttribute = $this->_getUserAttribute($field); list($sign, $value) = $this->_makeSearchConditionByChoice($userAttribute, $field, $value, null); $this->__setSearchConditionsByTypeCheckbox($conditions, $sqlField, $sign, $value); } elseif ($setting === self::MORE_THAN_DAYS) { $conditions[count($conditions)]['OR'] = array( $sqlField => null, $sqlField . $sign => $value ); } else { $conditions[$sqlField . $sign] = $value; } } if (! isset($this->readableFields['role_key'])) { $conditions['User.status'] = '1'; } $conditions['User.is_deleted'] = false; return $conditions; } /** * imageタイプの条件(Conditions)を設定 * * @param array &$conditions 条件(Conditions)リスト * @param string $sqlField SQLのフィールド名 * @param string $sign 条件演算子 * @param int|string|array $value 値 * * @return void */ private function __setSearchConditionsByTypeImage(&$conditions, $sqlField, $sign, $value) { if ($sign) { $conditions[count($conditions)]['AND'] = array( $sqlField . $sign => $value, 'is_avatar_auto_created' => false, ); } else { $conditions[count($conditions)]['OR'] = array( $sqlField . $sign => $value, 'is_avatar_auto_created' => true, ); } } /** * checkboxタイプの条件(Conditions)を設定 * * @param array &$conditions 条件(Conditions)リスト * @param string $sqlField SQLのフィールド名 * @param string $sign 条件演算子 * @param array $values 値 * * @return void */ private function __setSearchConditionsByTypeCheckbox(&$conditions, $sqlField, $sign, $values) { if (! is_array($values)) { $values = [$values]; } foreach ($values as $value) { $conditions[count($conditions)]['AND'] = array( $sqlField . ' LIKE' => '%' . $value . '%' ); } } /** * paginate メソッド * * @param array $conditions 条件配列 * @param array $fields フィールド配列 * @param array $order ソート配列 * @param int $limit 取得件数 * @param int $page ページ番号 * @param int $recursive findのrecursive * @param array $extra findのオプション * @return array 検索結果 * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) { $displayRooms = Hash::get($extra, 'extra.plugin') === 'rooms'; if ($displayRooms) { $joins = $this->getSearchJoinTables(Hash::get($extra, 'joins', []), $conditions, true, 'LEFT'); } else { $joins = $this->getSearchJoinTables(Hash::get($extra, 'joins', []), $conditions, $fields); } $conditions = $this->getSearchConditions($conditions); $recursive = -1; $group = 'User.id'; $sort = Hash::get($extra, 'sort'); $direction = Hash::get($extra, 'direction'); if (! $order) { if ($sort && $direction) { $order = array($sort => $direction); } elseif ($displayRooms) { $order = array('room_role_level' => 'desc'); $sort = 'room_role_level'; } else { $order = array(); } } if ($displayRooms && $sort === 'room_role_level') { $order += Hash::get($extra, 'defaultOrder', array()) + array('user_id' => 'asc', 'role_id' => 'asc'); $convOrder = array(); foreach ($order as $key => $sort) { if (isset($this->convRealToFieldKey[$key])) { $convKey = $this->convRealToFieldKey[$key]['key']; } else { $convKey = $key; } $convOrder[$convKey] = $sort; } $order = $convOrder; $extra = Hash::insert( $extra, 'extra.isDifferenceCondition', $this->__isDifferenceConditionByRoomRoleKey( $conditions, $order, Hash::get($extra, 'extra.search', false) ) ); $result = $this->__paginateByRoomRoleKey( $conditions, $fields, $joins, $order, $limit, $page, $recursive, $group, $extra ); } else { $fields = $this->_getSearchFields($fields); $order += Hash::get($extra, 'defaultOrder', array()) + array('Role.id' => 'asc', 'User.id' => 'asc'); $result = $this->find( 'all', compact('conditions', 'fields', 'joins', 'order', 'limit', 'page', 'recursive', 'group') ); CakeSession::delete('paginateConditionsByRoomRoleKey'); } return $result; } /** * 条件に差があるかどうかチェック * * @param array $conditions 条件配列 * @param array $order ソート配列 * @param array $searched 検索されたかどうか * @return bool */ private function __isDifferenceConditionByRoomRoleKey($conditions, $order, $searched) { $sessConditions = CakeSession::read('paginateConditionsByRoomRoleKey.conditions'); $sessOrder = CakeSession::read('paginateConditionsByRoomRoleKey.order'); $sessSearched = CakeSession::read('paginateConditionsByRoomRoleKey.searched'); if ($sessConditions === serialize($conditions) && $sessOrder === serialize($order) && $sessSearched === serialize($searched)) { return true; } else { CakeSession::write('paginateConditionsByRoomRoleKey.conditions', serialize($conditions)); CakeSession::write('paginateConditionsByRoomRoleKey.order', serialize($order)); CakeSession::write('paginateConditionsByRoomRoleKey.searched', serialize($searched)); return false; } } /** * 条件に差があるかどうかチェック * * @param string $roleKey ロールキー * @param array $extra findのオプション * @return bool */ private function __getAddConditionByRoomRoleKey($roleKey, $extra) { $addConditions = array(); if (Hash::get($extra, 'extra.isDifferenceCondition')) { if (! $roleKey) { $roleKey = 'delete'; } $addConditions = CakeSession::read('paginateConditionsByRoomRoleKey.addConditions.' . $roleKey); } else { $selectedUsers = Hash::get($extra, 'extra.selectedUsers', array()); $allSelected = Hash::extract( $selectedUsers, '{n}.user_id' ); if ($roleKey) { $sessKey = $roleKey; $selectUserIds = Hash::extract( $selectedUsers, '{n}[role_key=' . $roleKey . '].user_id' ); } else { $sessKey = 'delete'; $selectUserIds = Hash::extract( $selectedUsers, '{n}[delete=true].user_id' ); } $addConditions['OR']['AND']['RolesRoom.role_key'] = $roleKey; if ($allSelected) { $addConditions['OR']['AND']['User.id NOT'] = array_diff($allSelected, $selectUserIds); } if ($selectUserIds) { $addConditions['OR']['User.id'] = $selectUserIds; } CakeSession::write('paginateConditionsByRoomRoleKey.addConditions.' . $sessKey, $addConditions); } return $addConditions; } /** * paginate メソッド * * @param array $conditions 条件配列 * @param array $fields フィールド配列 * @param array $joins JOINテーブル配列 * @param array $order ソート配列 * @param int $limit 取得件数 * @param int $page ページ番号 * @param int $recursive findのrecursive * @param string $group GROUP BY * @param array $extra findのオプション * @return array 検索結果 */ private function __paginateByRoomRoleKey($conditions, $fields, $joins, $order, $limit, $page, $recursive, $group, $extra) { $dbSource = $this->getDataSource(); $sql = ''; $roles = $this->_getRolesByRoomRoleKey($extra); $roomRoles = $this->RoomRole->find('list', array( 'recursive' => -1, 'fields' => array('role_key', 'level') )); //UNIONでデータ取得する $fields = $this->_getSearchFieldsByRoomRoleKey($fields); foreach ($roles as $roleKey) { $sql .= ' UNION '; $conditions[99] = $this->__getAddConditionByRoomRoleKey($roleKey, $extra); $fields['room_role_level'] = Hash::get($roomRoles, $roleKey, 0) . ' AS ' . 'room_role_level'; $query = $this->buildQuery('all', compact('conditions', 'fields', 'joins') ); $query['table'] = $dbSource->fullTableName($this); $query['alias'] = $this->alias; $sql .= $dbSource->buildStatement($query, $this); } $query = $this->buildQuery('all', compact('order', 'limit', 'page', 'recursive', 'group') ); $sql .= $dbSource->group($query['group'], $this); $sql .= $dbSource->order($query['order'], 'ASC', $this); $sql .= $dbSource->limit($query['limit'], $query['offset']); $queryResult = $this->query(substr($sql, 6)); $queryResult = Hash::extract($queryResult, '{n}.{n}'); $results = array(); foreach ($queryResult as $result) { $index = count($results); $results[$index] = array(); foreach ($result as $column => $value) { $results[$index] = Hash::insert($results[$index], $this->getOriginalField($column), $value); } } return $results; } /** * paginateCount メソッド * * @param array $conditions 条件配列 * @param int $recursive findのrecursive * @param array $extra findのオプション * @return array 検索結果の件数 */ public function paginateCount($conditions = null, $recursive = 0, $extra = array()) { $displayRooms = Hash::get($extra, 'extra.plugin') === 'rooms'; if ($displayRooms) { $joins = $this->getSearchJoinTables(Hash::get($extra, 'joins', []), $conditions, true, 'LEFT'); } else { $joins = $this->getSearchJoinTables(Hash::get($extra, 'joins', []), $conditions); } $recursive = -1; $group = 'User.id'; $sort = Hash::get($extra, 'sort'); if ($displayRooms && ($sort === 'room_role_level' || !$sort)) { $conditions = $this->getSearchConditions($conditions); $count = $this->__paginateCountByRoomRoleKey($conditions, $joins, $recursive, $group, $extra); } else { $conditions = $this->getSearchConditions($conditions); $count = $this->find('count', compact('conditions', 'joins', 'recursive', 'group')); } return $count; } /** * paginateCount メソッド * * @param array $conditions 条件配列 * @param array $joins JOINテーブル配列 * @param int $recursive findのrecursive * @param string $group GROUP BY * @param array $extra findのオプション * @return array 検索結果の件数 */ private function __paginateCountByRoomRoleKey($conditions, $joins, $recursive, $group, $extra) { $roles = $this->_getRolesByRoomRoleKey($extra); $count = 0; foreach ($roles as $roleKey) { $conditions['RolesRoom.role_key'] = $roleKey; $count += $this->find('count', compact('conditions', 'joins', 'recursive', 'group')); } return $count; } }