diff --git a/Controller/Component/UserManagerBulkComponent.php b/Controller/Component/UserManagerBulkComponent.php new file mode 100644 index 0000000..8fff260 --- /dev/null +++ b/Controller/Component/UserManagerBulkComponent.php @@ -0,0 +1,282 @@ + + * @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('Component', 'Controller'); +\App::uses('UserSearchCompComponent', 'Users.Controller/Component'); +\App::uses('UserManagerSearchLib', 'UserManager.Lib'); +\App::uses('RoomsLibCommandExec', 'Rooms.Lib'); +\App::uses('UserAttributeChoice', 'UserAttributes.Model'); +\App::uses('RoomsLibCommandExec', 'Rooms.Lib'); + +/** + * UserManager Component + * + * @author Shohei Nakajima + * @package NetCommons\UserManager\Controller\Component + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class UserManagerBulkComponent extends Component { + +/** + * コントローラ + * + * @var UserManagerController|Controller + */ + public $controller; + +/** + * Userモデル + * + * @var User + */ + public $User; + +/** + * UserRoleSettingモデル + * + * @var UserRoleSetting + */ + public $UserRoleSetting; + +/** + * Called after the Controller::beforeFilter() and before the controller action + * + * @param Controller $controller Controller with components to startup + * @return void + */ + public function startup(Controller $controller) { + $this->controller = $controller; + $this->User = ClassRegistry::init('Users.User'); + $this->UserRoleSetting = ClassRegistry::init('UserRoles.UserRoleSetting'); + } + +/** + * 選択した会員を一括で利用不可に設定する + * + * @return CakeResponse|null + * @throws BadRequestException + */ + public function bulkNonactive() { + $controller = $this->controller; + $data = $controller->request->data['UserManagerBulk']; + + $displayIds = explode(',', $data['displayIds']); + $checkedIds = explode(',', $data['checkedIds']); + + //バリデーション + try { + if (! $this->__validateBulk($checkedIds, $displayIds)) { + //通常この条件に来ない。 + //ただし、jsonで処理された場合、当処理に入ってくるため、念のため抜けておく。 + return; + } + } catch (Exception $ex) { + throw $ex; + } + + //登録処理 + $users = $this->__findBulkUsers($checkedIds); + try { + //トランザクションBegin + $this->User->begin(); + + foreach ($users as $user) { + if (! UserManagerSearchLib::hasEditableBulkUser($user)) { + throw new BadRequestException(__d('net_commons', 'Bad Request')); + } + + //ステータス更新処理 + $this->User->updateStatus($user['User']['id'], \UserAttributeChoice::STATUS_CODE_NONACTIVE); + } + + //トランザクションCommit + $this->User->commit(); + } catch (Exception $ex) { + //トランザクションRollback + $this->User->rollback($ex); + } + + //リダイレクト + $controller->NetCommons->setFlashNotification( + __d('net_commons', 'Successfully saved.'), array('class' => 'success') + ); + return $controller->redirect($controller->referer('/user_manager/user_manager/index/')); + } + +/** + * 選択した会員を一括で削除する + * + * @return CakeResponse|null + * @throws BadRequestException + * @throws InternalErrorException + */ + public function bulkDelete() { + $controller = $this->controller; + $data = $controller->request->data['UserManagerBulk']; + + $displayIds = explode(',', $data['displayIds']); + $checkedIds = explode(',', $data['checkedIds']); + + //バリデーション + if (! $this->__validateBulk($checkedIds, $displayIds)) { + //通常この条件に来ない。 + //ただし、jsonで処理された場合、当処理に入ってくるため、念のため抜けておく。 + return; + } + + //削除処理 + $users = $this->__findBulkUsers($checkedIds); + try { + //トランザクションBegin + $this->User->begin(); + + foreach ($users as $user) { + if (! UserManagerSearchLib::hasEditableBulkUser($user)) { + throw new BadRequestException(__d('net_commons', 'Bad Request')); + } + + //削除処理 + if (! $this->User->deleteUser($user, false)) { + //本来あり得ないが、この処理に入ってきたら、エラーログに出力して、 + //throwを投げる + $error = [ + 'user' => $user, + 'validationErrors' => $this->User->validationErrors, + ]; + \CakeLog::error(__METHOD__ . '(' . __LINE__ . ') ' . var_export($error, true)); + throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); + } + } + + //トランザクションCommit + $this->User->commit(); + + //全てが削除されたら、シェルを起動 + RoomsLibCommandExec::deleteRelatedRooms(); + } catch (Exception $ex) { + //トランザクションRollback + $this->User->rollback($ex); + } + + //リダイレクト + $controller->NetCommons->setFlashNotification( + __d('net_commons', 'Successfully deleted.'), array('class' => 'success') + ); + + return $controller->redirect($this->__makeDeleteRedirectUrl($data)); + } + +/** + * 一括削除のリダイレクトURLを生成する + * + * @param array $data リクエストデータ + * @return string + */ + private function __makeDeleteRedirectUrl($data) { + $controller = $this->controller; + + $referer = $controller->referer('/user_manager/user_manager/index/'); + $hasPrev = $data['hasPrev'] ?? false; + $hasNext = $data['hasNext'] ?? false; + $hasAdminUser = $data['hasAdminUser'] ?? false; + if (! $hasPrev || + $hasNext || + $hasAdminUser || + $data['displayIds'] !== $data['checkedIds']) { + //先頭ページか、最終ページではない、全選択されていない場合 + return $referer; + } else { + //先頭ページではなく、最終ページで、全選択されている場合 + $urlPath = parse_url($referer, PHP_URL_PATH); + if (! $urlPath) { + $urlPath = $referer; + } + $match = []; + if (preg_match('#/page:([0-9]+)#iu', $urlPath, $match)) { + $urlPath = preg_replace('#/page:([0-9]+)#iu', '', $urlPath); + if (substr($urlPath, -1) !== '/') { + $urlPath .= '/'; + } + $urlPath .= 'page:' . ((int)$match[1] - 1); + } + + $urlQuery = parse_url($referer, PHP_URL_QUERY); + return $urlPath . ($urlQuery ? '?' . $urlQuery : ''); + } + } + +/** + * 選択した会員を一括処理できるかどうかチェックする + * + * @param array $checkedIds チェックしているユーザIDリスト + * @param array $displayIds 画面に表示しているユーザIDリスト + * + * @return bool + */ + private function __validateBulk($checkedIds, $displayIds) { + if (count(array_diff($checkedIds, $displayIds)) > 0) { + $this->controller->throwBadRequest(); + return false; + } + + $count = $this->User->find('count', [ + 'recursive' => -1, + 'conditions' => [ + 'id' => $checkedIds + ], + ]); + if ($count !== count($checkedIds)) { + $this->controller->throwBadRequest(); + return false; + } + + return true; + } + +/** + * 一括処理するユーザを取得 + * + * @param array $checkedIds チェックしているユーザIDリスト + * + * @return array + */ + private function __findBulkUsers($checkedIds) { + $users = $this->User->find('all', [ + 'recursive' => -1, + 'fields' => [ + $this->User->alias . '.id', + $this->User->alias . '.handlename', + $this->User->alias . '.role_key', + $this->UserRoleSetting->alias . '.origin_role_key', + ], + 'conditions' => [ + $this->User->alias . '.id' => $checkedIds, + ], + 'joins' => [ + [ + 'table' => $this->UserRoleSetting->table, + 'alias' => $this->UserRoleSetting->alias, + 'type' => 'INNER', + 'conditions' => [ + $this->User->alias . '.role_key' . ' = ' . $this->UserRoleSetting->alias . '.role_key' + ], + ] + ] + ]); + if (empty($users)) { + $this->controller->throwBadRequest(); + return false; + } + + return $users; + } + +} diff --git a/Controller/UserManagerController.php b/Controller/UserManagerController.php index 38c5427..eb91096 100644 --- a/Controller/UserManagerController.php +++ b/Controller/UserManagerController.php @@ -17,6 +17,19 @@ /** * UserManager Controller * + * @property AutoUserRegist $AutoUserRegist + * @property AutoUserRegistMail $AutoUserRegistMail + * @property User $User + * @property UserSearch $UserSearch + * @property DownloadComponent $Download + * @property FileUploadComponent $FileUpload + * @property SwitchLanguageComponent $SwitchLanguage + * @property RoomsComponent $Rooms + * @property UserAttributeLayoutComponent $UserAttributeLayout + * @property UserManagerComponent $UserManager + * @property UserManagerBulkComponent $UserManagerBulk + * @property UserSearchCompComponent $UserSearchComp + * * @author Shohei Nakajima * @package NetCommons\UserManager\Controller * @SuppressWarnings(PHPMD.TooManyPublicMethods) @@ -62,6 +75,7 @@ class UserManagerController extends UserManagerAppController { 'Rooms.Rooms', 'UserAttributes.UserAttributeLayout', 'UserManager.UserManager', + 'UserManager.UserManagerBulk', 'Users.UserSearchComp', ); @@ -86,7 +100,8 @@ public function index() { //ユーザ一覧データ取得 $this->UserSearchComp->search([ - 'fields' => self::$displaFields, + 'fields' => array_merge(['origin_role_key'], self::$displaFields), + 'displayFields' => self::$displaFields, 'conditions' => array('space_id !=' => Space::PRIVATE_SPACE_ID), 'joins' => array('Room' => array( 'conditions' => array( @@ -360,4 +375,28 @@ public function export() { } } +/** + * bulkアクション + * + * @return void + */ + public function bulk() { + //タイムアウト発生するなら適宜設定 + set_time_limit(1800); + if (! $this->request->is('post')) { + return $this->throwBadRequest(); + } + + if ($this->request->data['UserManagerBulk']['submit'] === 'nonactive') { + //利用不可に設定 + return $this->UserManagerBulk->bulkNonactive(); + } elseif ($this->request->data['UserManagerBulk']['submit'] === 'delete') { + //削除する + return $this->UserManagerBulk->bulkDelete(); + } else { + //それ以外 + return $this->throwBadRequest(); + } + } + } diff --git a/Lib/UserManagerSearchLib.php b/Lib/UserManagerSearchLib.php new file mode 100644 index 0000000..5fb8c9d --- /dev/null +++ b/Lib/UserManagerSearchLib.php @@ -0,0 +1,32 @@ + + * @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('UserRole', 'UserRoles.Model'); + +/** + * 会員管理の一覧に関するライブラリ + * + * @package NetCommons\UserManager\Lib + */ +class UserManagerSearchLib { + +/** + * 一括操作できるユーザかどうか + * + * @param array $user ユーザデータ + * @return bool + */ + public static function hasEditableBulkUser($user) { + $roleKey = $user['UserRoleSetting']['origin_role_key'] ?? null; + return ! in_array($roleKey, \UserRole::$systemRoles, true); + } + +} diff --git a/Locale/eng/LC_MESSAGES/user_manager.po b/Locale/eng/LC_MESSAGES/user_manager.po index cc1b7aa..91fe256 100644 --- a/Locale/eng/LC_MESSAGES/user_manager.po +++ b/Locale/eng/LC_MESSAGES/user_manager.po @@ -291,6 +291,28 @@ msgstr "" msgid "Do you approve?" msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Change to nonactive" +msgstr "" + +msgid "Selected row ..." +msgstr "" + +msgid "Selected members" +msgstr "" + +msgid "Is it really okay to delete it?" +msgstr "" + +msgid "Nonactive the %s. Are you sure to proceed?" +msgstr "" + +msgid "Not found the select user." +msgstr "" + #: Users/View/Helper/UserSearchHelper.php:267 msgid "Approval" msgstr "" diff --git a/Locale/jpn/LC_MESSAGES/user_manager.po b/Locale/jpn/LC_MESSAGES/user_manager.po index adef55d..fe78cf4 100644 --- a/Locale/jpn/LC_MESSAGES/user_manager.po +++ b/Locale/jpn/LC_MESSAGES/user_manager.po @@ -79,6 +79,28 @@ msgid "Do you approve?" msgstr "承認しますか。" +msgid "Delete" +msgstr "削除する" + +msgid "Change to nonactive" +msgstr "利用不可にする" + +msgid "Selected row ..." +msgstr "選択した行を・・・" + +msgid "Selected members" +msgstr "選択した会員" + +msgid "Is it really okay to delete it?" +msgstr "削除すると元に戻すことは出来ません。本当に削除して良いですか。" + +msgid "Nonactive the %s. Are you sure to proceed?" +msgstr "%sを利用不可に設定します。本当によろしいですか。" + +msgid "Not found the select user." +msgstr "一つも選択されていません。" + + # # 入力画面 # diff --git a/Test/Case/Controller/UserManagerController/IndexTest.php b/Test/Case/Controller/UserManagerController/IndexTest.php index b28ce5e..1b261ff 100644 --- a/Test/Case/Controller/UserManagerController/IndexTest.php +++ b/Test/Case/Controller/UserManagerController/IndexTest.php @@ -54,6 +54,7 @@ public function tearDown() { * index()アクションのGetリクエストテスト * * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testIndexGet() { //テスト実行 @@ -85,6 +86,9 @@ public function testIndexGet() { 'modified' => '2015-08-15 06:12:30', 'last_login' => '2016-09-14 12:18:45', ), + 'UserRoleSetting' => [ + 'origin_role_key' => 'system_administrator' + ], 'UsersLanguage' => array( 'name' => 'System Administrator Name', ), @@ -96,6 +100,9 @@ public function testIndexGet() { 'modified' => '2015-08-15 06:12:30', 'last_login' => null, ), + 'UserRoleSetting' => [ + 'origin_role_key' => 'administrator' + ], 'UsersLanguage' => array( 'name' => 'Site Manager Name', ), @@ -107,6 +114,9 @@ public function testIndexGet() { 'modified' => '2015-08-15 06:12:30', 'last_login' => null, ), + 'UserRoleSetting' => [ + 'origin_role_key' => 'common_user' + ], 'UsersLanguage' => array( 'name' => 'Chief Editor Name', ), @@ -118,6 +128,9 @@ public function testIndexGet() { 'modified' => '2015-08-15 06:12:30', 'last_login' => null, ), + 'UserRoleSetting' => [ + 'origin_role_key' => 'common_user' + ], 'UsersLanguage' => array( 'name' => 'Editor Name', ), @@ -129,6 +142,9 @@ public function testIndexGet() { 'modified' => '2015-08-15 06:12:30', 'last_login' => null, ), + 'UserRoleSetting' => [ + 'origin_role_key' => 'common_user' + ], 'UsersLanguage' => array( 'name' => 'General User Name', ), @@ -140,6 +156,9 @@ public function testIndexGet() { 'modified' => '2015-08-15 06:12:30', 'last_login' => null, ), + 'UserRoleSetting' => [ + 'origin_role_key' => 'common_user' + ], 'UsersLanguage' => array( 'name' => 'Visitor Name', ), diff --git a/View/UserManager/index.ctp b/View/UserManager/index.ctp index 0f46547..41662d2 100644 --- a/View/UserManager/index.ctp +++ b/View/UserManager/index.ctp @@ -9,6 +9,8 @@ * @copyright Copyright 2014, NetCommons Project */ +\App::uses('UserManagerSearchLib', 'UserManager.Lib'); + echo $this->NetCommonsHtml->css(array( '/user_manager/css/style.css', '/users/css/style.css', @@ -39,34 +41,140 @@ echo $this->NetCommonsHtml->script('/user_manager/js/user_manager.js'); end(); ?> -MessageFlash->description( - __d('user_manager', 'Click the handle name to read his/her data. And to edit the user data. And delete user data, please go from editing screen.') -); ?> +
+ MessageFlash->description( + __d('user_manager', 'Click the handle name to read his/her data. And to edit the user data. And delete user data, please go from editing screen.') + ); ?> + +
+ UserSearchForm->displaySearchButton(__d('user_manager', 'Search for the members'), [], true); ?> + + Paginator->counter('{:count}') > 0) : ?> +
+ + + -
- UserSearchForm->displaySearchButton(__d('user_manager', 'Search for the members'), [], true); ?> + + +
+ -
- Button->addLink(__d('user_manager', 'Add user'), ['controller' => 'user_add', 'action' => 'basic']); ?> +
+ Button->addLink(__d('user_manager', 'Add user'), ['controller' => 'user_add', 'action' => 'basic']); ?> +
-
- -
- - - - UserSearch->tableHeaders(true); ?> - - - - - $user) : ?> - UserSearch->userActiveClass($user); ?>> - UserSearch->tableRow($user, true, ['controller' => 'user_manager', 'action' => 'edit']); ?> + +
+ + +
+ + + + UserSearch->tableHeaders(true); ?> - - -
+ +
-
+ + + + $user) : ?> + NetCommonsForm->domId('User.id.' . $user['User']['id']); + ?> + UserSearch->userActiveClass($user); ?>> + + '; + echo ''; + echo ''; + $bulkUserIds[] = $user['User']['id']; + } else { + $hasAdminUser = true; + } + ?> + + UserSearch->tableRow($user, true, ['controller' => 'user_manager', 'action' => 'edit']); ?> + + + + + NetCommonsForm->create('UserManagerBulk', [ + 'url' => NetCommonsUrl::actionUrlAsArray(['controller' => 'user_manager', 'action' => 'bulk']) + ]); + echo $this->NetCommonsForm->hidden('UserManagerBulk.displayIds', ['value' => implode(',', $bulkUserIds)]); + echo $this->NetCommonsForm->hidden('UserManagerBulk.hasPrev', ['value' => $this->Paginator->hasPrev()]); + echo $this->NetCommonsForm->hidden('UserManagerBulk.hasNext', ['value' => $this->Paginator->hasNext()]); + echo $this->NetCommonsForm->hidden('UserManagerBulk.hasAdminUser', ['value' => $hasAdminUser]); + + echo $this->NetCommonsForm->unlockField('UserManagerBulk.checkedIds'); + echo $this->NetCommonsForm->hidden('UserManagerBulk.checkedIds', ['value' => '']); + + echo $this->NetCommonsForm->unlockField('UserManagerBulk.submit'); + echo $this->NetCommonsForm->hidden('UserManagerBulk.submit', ['value' => '']); + + echo $this->NetCommonsForm->end(); + ?> +
-element('NetCommons.paginator'); + element('NetCommons.paginator'); ?> +
\ No newline at end of file diff --git a/webroot/js/user_manager.js b/webroot/js/user_manager.js index bdec69f..1cf9eda 100644 --- a/webroot/js/user_manager.js +++ b/webroot/js/user_manager.js @@ -10,6 +10,106 @@ NetCommonsApp.controller('UserManagerController', ['$scope', 'NetCommonsModal', 'NC3_URL', function($scope, NetCommonsModal, NC3_URL) { + /** + * 選択したIDリスト + */ + $scope.checkedIds = []; + + /** + * チェックボックスの全選択・全解除 + */ + $scope.allCheck = function($event) { + var elements = $('input[type="checkbox"]'); + + for (var i = 0; i < elements.length; i++) { + if (elements[i].name) { + $scope._changeCheck(elements[i], $event.currentTarget.checked); + } + } + }; + + /** + * チェックボックスクリック + */ + $scope.check = function($event) { + $scope._changeCheck($event.currentTarget, $event.currentTarget.checked); + }; + + /** + * チェックボックス変更処理 + */ + $scope._changeCheck = function(element, checked) { + var id = element.value; + var domId = element.id; + element.checked = checked; + $scope.checkedIds[id] = checked; + + var trElement = $('#Tr' + domId); + + if (checked) { + if (trElement.hasClass('warning')) { + trElement.removeClass('warning'); + trElement.addClass('_warning'); + } else if (trElement.hasClass('danger')) { + trElement.removeClass('danger'); + trElement.addClass('_danger'); + } + + if (! trElement.hasClass('success')) { + trElement.addClass('success'); + } + } else { + if (trElement.hasClass('_warning')) { + trElement.removeClass('_warning'); + trElement.addClass('warning'); + } else if (trElement.hasClass('_danger')) { + trElement.removeClass('_danger'); + trElement.addClass('danger'); + } + + if (trElement.hasClass('success')) { + trElement.removeClass('success'); + } + } + }; + + /** + * 一括登録処理 + */ + $scope.bulk = function($event, action, firstMessage, secondMessage, notSelectMessage) { + var checkedIds = []; + angular.forEach($scope.checkedIds, function(checked, id) { + if (checked) { + checkedIds.push(id); + } + }, checkedIds); + if (! checkedIds.length) { + alert(notSelectMessage); + $event.preventDefault(); + return; + } + + if (! confirm(firstMessage)) { + $event.preventDefault(); + return; + } + + if (secondMessage && ! confirm(secondMessage)) { + $event.preventDefault(); + return; + } + + var checkedElement = $('#UserManagerBulkCheckedIds'); + checkedElement[0].value = checkedIds.join(','); + + var submitElement = $('#UserManagerBulkSubmit'); + submitElement[0].value = action; + + $scope.sending = true; + var formElement = $('#UserManagerBulkBulkForm'); + formElement.submit(); + }; + /** * 検索ダイアログ表示 *