diff --git a/fid/config/vufind/fid.ini b/fid/config/vufind/fid.ini index 849eafe4edef9e5f6245714de74eb9e0ef4b4894..8ad0334e9c8acaec84a14e0737613ae485b37ba0 100644 --- a/fid/config/vufind/fid.ini +++ b/fid/config/vufind/fid.ini @@ -3,4 +3,23 @@ baseUrl = http://172.18.113.133/bbi-alpha.3 [Security] access_levels[] = limited_access -access_levels[] = full_access \ No newline at end of file +access_levels[] = full_access + +[Admin] +; Whitelist of all fields that admins shall be able +; to change for other users +;editable_user_fields[] = 'HomeLibrary' +editable_user_fields[] = 'Permissions' +editable_user_fields[] = 'Salutation' + +;list of user fields to be shown in overview table +overview_fields[] = 'UserName' +overview_fields[] = 'Firstname' +overview_fields[] = 'Lastname' +overview_fields[] = 'Permissions' + +; List of all available user permissions +permission_options[] = 'edit_user' +permission_options[] = 'read_user_list' +permission_options[] = 'limited_access' +permission_options[] = 'full_access' \ No newline at end of file diff --git a/fid/config/vufind/permissionBehavior.ini b/fid/config/vufind/permissionBehavior.ini new file mode 100644 index 0000000000000000000000000000000000000000..c5d8cf1661a251631d5801a512296cecfdd77821 --- /dev/null +++ b/fid/config/vufind/permissionBehavior.ini @@ -0,0 +1,22 @@ +;#################################################################### +;##################### DO NOT DELETE THIS HEADER #################### +;################### Leipzig University Library © 2019 ############## +; +; This is the FID-master default INI-file and inherits +; all the settings from the INI-file defined in [Parent_Config] which +; points to the default INI-file located in the folder vufind2/local +; + +[Parent_Config] +relative_path = ../../../local/config/vufind/permissionBehavior.ini + +; A comma-separated list of config sections from the parent which should be +; completely overwritten by the equivalent sections in this configuration; +; any sections not listed here will be merged on a section-by-section basis. +;override_full_sections = "" + +; +; Add instance-specific customization after this header. +; +;##################### DO NOT DELETE THIS HEADER #################### +;#################################################################### \ No newline at end of file diff --git a/fid/config/vufind/permissions.ini b/fid/config/vufind/permissions.ini index 09473231d4d630422b1329ddfdffb83ce195e536..2269abcc3024634b7da35f0a085d1e47e75f8d88 100644 --- a/fid/config/vufind/permissions.ini +++ b/fid/config/vufind/permissions.ini @@ -19,4 +19,20 @@ relative_path = ../../../local/config/vufind/permissions.ini ; Add instance-specific customization after this header. ; ;##################### DO NOT DELETE THIS HEADER #################### -;#################################################################### \ No newline at end of file +;#################################################################### +; Show staff view for all users by default +[fid.ReadList] +role[] = loggedin +FidApiPermission = read_user_list + +[fid.EditUser] +role[] = loggedin +FidApiPermission = edit_user + +[fid.LimitedAccess] +role[] = loggedin +FidApiPermission = limited_access + +[fid.FullAccess] +role[] = loggedin +FidApiPermission = full_access \ No newline at end of file diff --git a/local/config/vufind/permissionBehavior.ini b/local/config/vufind/permissionBehavior.ini new file mode 100644 index 0000000000000000000000000000000000000000..7297e4a66f87957bd24de31e21e4cab604d83c4f --- /dev/null +++ b/local/config/vufind/permissionBehavior.ini @@ -0,0 +1,25 @@ +;#################################################################### +;##################### DO NOT DELETE THIS HEADER #################### +;################### Leipzig University Library © 2019 ############## +; +; This is the ISIL-instance-specific default INI-file and inherits +; all the settings from the INI-file defined in [Parent_Config] which +; points to the default INI-file located in the folder vufind2/local +; + +[Parent_Config] +relative_path = ../../../config/vufind/permissionBehavior.ini + +; A comma-separated list of config sections from the parent which should be +; completely overwritten by the equivalent sections in this configuration; +; any sections not listed here will be merged on a section-by-section basis. +;override_full_sections = "" + +; +; Add instance-specific customization after this header. +; +;##################### DO NOT DELETE THIS HEADER #################### +;#################################################################### + +[global] +defaultDeniedTemplateBehavior = true \ No newline at end of file diff --git a/module/fid/config/module.config.php b/module/fid/config/module.config.php index 075a68cf034e5af117a91b2e0bd67a270097368d..dfbbf46edc8e328761350308ab3df558c12a7209 100644 --- a/module/fid/config/module.config.php +++ b/module/fid/config/module.config.php @@ -21,6 +21,7 @@ use fid\Controller\UserController; use fid\Controller\UserControllerFactory; +use fid\FormModel\UsernameChangeModel; use fid\FormModel\PasswordChangeModel; use fid\FormModel\PasswordResetModel; use fid\FormModel\UserCreateModel; @@ -55,6 +56,7 @@ return [ UserUpdateModel::class => require_once 'user-update-form.php', PasswordResetModel::class => require_once 'password-reset-form.php', PasswordChangeModel::class => require_once 'password-change-form.php', + UsernameChangeModel::class => require_once 'username-change-form.php', ], 'controllers' => [ 'factories' => [ @@ -129,6 +131,18 @@ return [ ], ], ], + // Authorization configuration: + 'zfc_rbac' => [ + 'vufind_permission_provider_manager' => [ + 'factories' => [ + 'fid\Role\PermissionProvider\FidApiPermission' => + 'fid\Role\PermissionProvider\Factory::getFidApiPermission' + ], + 'aliases' => [ + 'FidApiPermission' => 'fid\Role\PermissionProvider\FidApiPermission' + ] + ] + ], 'router' => [ 'routes' => [ 'fid' => [ @@ -215,9 +229,59 @@ return [ 'action' => 'changePassword', ], ], + ], + 'change-username' => [ + 'type' => 'literal', + 'options' => [ + 'route' => '/change-username', + 'defaults' => [ + 'controller' => UserController::class, + 'action' => 'changeUsername', + ], + ], + ], + 'update-username' => [ + 'type' => 'literal', + 'options' => [ + 'route' => '/update-username', + 'defaults' => [ + 'controller' => UserController::class, + 'action' => 'updateUsername', + ], + ], ] ], ], + 'admin' => [ + 'may_terminate' => false, + 'type' => 'literal', + 'options' => [ + 'route' => '/admin' + ], + 'child_routes' => [ + 'list' => [ + 'type' => 'literal', + 'options' => [ + 'route' => '/list', + 'defaults' => [ + 'controller' => UserController::class, + 'action' => 'list', + ], + ], + ], + 'edit' => [ + 'type' => 'Zend\Router\Http\Segment', + 'options' => [ + 'route' => '/edit/[:userid]', + 'defaults' => [ + 'controller' => UserController::class, + 'action' => 'edit', + 'userid' => null + ], + ], + ], + ], + ], ], ], 'myresearch-account' => [ @@ -238,4 +302,4 @@ return [ ] ], ], -]; \ No newline at end of file +]; diff --git a/module/fid/config/user-update-form.php b/module/fid/config/user-update-form.php index 6ce6b77daffa9c7d07559526d87f66f45a2cb675..546c62246b35da8b6ac74bc6fdf18352b7561165 100644 --- a/module/fid/config/user-update-form.php +++ b/module/fid/config/user-update-form.php @@ -22,17 +22,28 @@ use Zend\Filter\StringTrim; use Zend\Filter\ToInt; use Zend\Filter\ToNull; +use Zend\Filter\Callback; use Zend\Form\Element\Number; use Zend\Form\Element\Select; use Zend\Form\Element\Submit; use Zend\Form\Element\Text; +use Zend\Form\Element\Hidden; +use Zend\Form\Element\MultiCheckbox; use Zend\Hydrator\ClassMethods; use Zend\Validator\Regex; use Zend\Validator\StringLength; +use Zend\Validator\InArray; +use Zend\Validator\Explode; return [ 'hydrator' => ClassMethods::class, 'elements' => [ + 'userId' => [ + 'spec' => [ + 'name' => 'userId', + 'type' => Hidden::class, + ], + ], 'salutation' => [ 'spec' => [ 'name' => 'salutation', @@ -130,6 +141,17 @@ return [ ] ], ], + 'permissions' => [ + 'spec' => [ + 'name' => 'permissions', + 'type' => MultiCheckbox::class, + 'options' => [ + 'label' => 'label_permissions', + 'checked_value' => 'granted', + 'unchecked_value' => 'denied', + ], + ], + ], 'submit' => [ 'spec' => [ 'name' => 'submit', @@ -244,6 +266,35 @@ return [ ], ], ], + 'permissions' => [ + 'name' => 'permissions', + 'required' => false, + 'validators' => [ + Explode::class => [ + 'name' => Explode::class, + 'options' => [ + 'validator' => [ + 'name' => InArray::class, + 'options' => [ + 'haystack' => [ + 'granted', + 'denied', + 'requested' + ], + ] + ], + ], + ], + ], + 'filters' => [ + Callback::class => [ + 'name' => Callback::class, + 'options' => [ + 'callback' => '\fid\Controller\UserController::combinePermissionsArray' + ] + ], + ], + ], 'submit' => [ 'name' => 'submit', 'required' => true, diff --git a/module/fid/config/username-change-form.php b/module/fid/config/username-change-form.php new file mode 100644 index 0000000000000000000000000000000000000000..75ac717b6a862512ecfb7193f0c4ddb59fafc102 --- /dev/null +++ b/module/fid/config/username-change-form.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright (C) 2019 Leipzig University Library + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @author Sebastian Kehr <kehr@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +use Zend\Filter\StringTrim; +use Zend\Form\Element\Email; +use Zend\Form\Element\Submit; +use Zend\Form\Element\Text; +use Zend\Hydrator\ClassMethods; +use Zend\Validator\EmailAddress; +use Zend\Validator\Identical; +use Zend\Validator\StringLength; + +return [ + 'hydrator' => ClassMethods::class, + 'elements' => [ + 'username' => [ + 'spec' => [ + 'name' => 'username', + 'type' => Email::class, + 'options' => [ + 'label' => 'label_newusername', + ], + 'attributes' => [ + 'required' => 'required', + ], + ], + ], + 'usernameConfirmation' => [ + 'spec' => [ + 'name' => 'usernameConfirmation', + 'type' => Text::class, + 'options' => [ + 'label' => 'label_newusername_confirmation', + ], + 'attributes' => [ + 'required' => 'required', + ], + ], + ], + 'submit' => [ + 'spec' => [ + 'name' => 'submit', + 'type' => Submit::class, + 'attributes' => [ + 'value' => 'label_submit', + ], + ], + ], + ], + 'input_filter' => [ + 'username' => [ + 'name' => 'username', + 'required' => true, + 'filters' => [ + StringTrim::class => [ + 'name' => StringTrim::class, + ], + ], + 'validators' => [ + StringLength::class => [ + 'name' => StringLength::class, + 'options' => [ + 'max' => 255 + ], + ], + EmailAddress::class => [ + 'name' => EmailAddress::class, + ], + ], + ], + 'usernameConfirmation' => [ + 'name' => 'usernameConfirmation', + 'required' => true, + 'validators' => [ + Identical::class => [ + 'name' => Identical::class, + 'options' => [ + 'strict' => false, + 'token' => 'username', + 'messages' => [ + Identical::NOT_SAME => 'error_username_confirmation', + ], + ], + ], + ], + ], + 'submit' => [ + 'name' => 'submit', + 'required' => true, + ], + ], +]; diff --git a/module/fid/src/Controller/UserController.php b/module/fid/src/Controller/UserController.php index 10deb2e9c83e9068af4458f9494b7f2c95e9879d..d101d2e4e1c066866b0125db422b46592e8b6506 100644 --- a/module/fid/src/Controller/UserController.php +++ b/module/fid/src/Controller/UserController.php @@ -27,14 +27,17 @@ use fid\FormModel\PasswordResetModel; use fid\FormModel\UserCreateModel; use fid\FormModel\UserInitModel; use fid\FormModel\UserUpdateModel; +use fid\FormModel\UsernameChangeModel; use fid\Service\Client; use fid\Service\ClientException; use fid\Service\DataTransferObject\Library; use fid\Service\DataTransferObject\User; +use fid\Service\UserNotAuthorizedException; use Symfony\Component\Serializer\SerializerAwareTrait; use VuFind\Auth\Manager as AuthManager; use VuFind\Controller\AbstractBase; use VuFind\Exception\Auth as AuthException; +use Zend\Form\Element\MultiCheckbox; use Zend\Form\Element\Radio; use Zend\Form\Element\Select; use Zend\Form\Form; @@ -84,6 +87,7 @@ class UserController extends AbstractBase } /** + * @noinspection PhpUnused * @return ViewModel */ public function initAction() @@ -108,6 +112,7 @@ class UserController extends AbstractBase } /** + * @noinspection PhpUnused * @return Response|ViewModel */ public function createAction() @@ -175,6 +180,11 @@ class UserController extends AbstractBase return $view; } + + /** + * @noinspection PhpUnused + * @return Response|ViewModel + */ public function updateAction() { /** @var Request $request */ @@ -215,6 +225,73 @@ class UserController extends AbstractBase return $viewModel; } + /** + * @noinspection PhpUnused + * @return ViewModel + */ + public function changeUsernameAction() + { + /** @var Request $request */ + $request = $this->getRequest(); + $form = $this->serviceLocator->get(UsernameChangeModel::class); + $forwarded = $this->params()->fromRoute('forwarded', false); + + if ($submitted = $this->formWasSubmitted()) { + $form->setData($request->getPost()); + if (!$forwarded && $form->isValid()) { + return $this->changeUsername($form); + } + } + + $view = $this->createViewModel(); + $view->setVariables(compact('form')); + $view->setTemplate('fid/user/username-change'); + + return $view; + } + + /** + * @noinspection PhpUnused + * @return Response + */ + public function updateUsernameAction() + { + /** @var Request $request */ + $request = $this->getRequest(); + $query = $request->getQuery(); + $messenger = $this->getMessenger(); + + if ($credentials = $query->get('logon')) { + try { + $this->client->logon($credentials); + } catch (ClientException $exception) { + $message = $exception->getCode() === 401 + ? 'fid::username_update_error_expired' + : 'fid::username_update_error'; + $messenger->addErrorMessage($this->translate($message)); + return $this->redirect()->toRoute('fid/user/change-username'); + } + + $query->offsetUnset('logon'); + return $this->redirect()->toRoute('fid/user/update-username', [], [ + 'query' => $query->toArray() + ]); + } + + try { + $user = $this->client->requestUserDetails(); + $user->setUsername($query->get('username')); + $this->client->requestUserUsernameUpdate($user); + $message = $this->translate('fid::username_update_success'); + $this->getMessenger()->addSuccessMessage($message); + } catch (ClientException $exception) { + $message = $this->translate('fid::username_update_error'); + $this->getMessenger()->addErrorMessage($message); + } + + return $this->redirect()->toRoute('myresearch-home'); + } + public function policyAction() { $viewModel = $this->createViewModel(); @@ -396,7 +473,7 @@ class UserController extends AbstractBase * * @return Response */ - protected function update(Form $form) + protected function update(Form $form,string $redirect = 'myresearch-home') { $messenger = $this->getMessenger(); /** @var UserUpdateModel $model */ @@ -404,7 +481,8 @@ class UserController extends AbstractBase new UserUpdateModel()); try { - $user = $this->client->requestUserDetails(); + $userId = $model->getUserId(); + $user = $this->client->requestUserDetails($userId); $user->setHomeLibrary($model->getHomeLibrary()); $user->setSalutation($model->getSalutation()); $user->setAcademicTitle($model->getAcademicTitle()); @@ -413,6 +491,7 @@ class UserController extends AbstractBase $user->setYearOfBirth($model->getYearOfBirth()); $user->setCollege($model->getCollege()); $user->setJobTitle($model->getJobTitle()); + $user->setPermissions($model->getPermissions()); $this->client->requestUserUpdate($user); $message = $this->translate('fid::user_update_success'); $messenger->addSuccessMessage($message); @@ -427,7 +506,8 @@ class UserController extends AbstractBase $messenger->addErrorMessage($message); } - return $this->redirect()->toRoute('myresearch-home', [], [ + $this->client->flushUserList(); + return $this->redirect()->toRoute($redirect, [], [ 'query' => ['redirect' => false] ]); } @@ -493,6 +573,35 @@ class UserController extends AbstractBase ]); } + protected function changeUsername(Form $form) + { + $messenger = $this->getMessenger(); + $model = $form->getHydrator()->hydrate( + $form->getData(), new UsernameChangeModel()); + + /** @noinspection PhpUndefinedFieldInspection */ + $query['lng'] = $this->layout()->userLang; + $username = $query['username'] = $model->getUsername(); + $baseUrl = $this->url()->fromRoute('fid/user/update-username', + [], ['query' => $query, 'force_canonical' => true]); + + try { + $this->client->requestUsernameLink($baseUrl, $username); + } catch (ClientException $exception) { + $message = $exception->getCode() === 400 + ? 'fid::username_change_error_username' + : 'fid::username_change_error'; + $messenger->addErrorMessage($this->translate($message)); + return $this->forward()->dispatch(self::class, [ + 'action' => 'changeUsername', + 'forwarded' => true + ]); + } + + $message = $this->translate('fid::username_change_success'); + $messenger->addSuccessMessage(sprintf($message, $username)); + return $this->redirect()->toRoute('myresearch-home'); + } protected function getMessenger(): FlashMessenger { @@ -500,4 +609,161 @@ class UserController extends AbstractBase /** @var FlashMessenger $messenger */ return $this->flashMessenger(); } -} \ No newline at end of file + + public function editAction() + { + // Not logged in? Force user to log in: + if (!$this->getAuthManager()->isLoggedIn()) { + // Allow bypassing of post-login redirect + if ($this->params()->fromQuery('redirect', true)) { + $this->setFollowupUrlToReferer(); + } + return $this->forwardTo('MyResearch', 'Login'); + } + + /** @var Request $request */ + $request = $this->getRequest(); + $userId = $this->params()->fromRoute('userid'); + if (empty($userId)) { + // if no user ID is set the call is not valid + // unless we are in submission state + return $this->redirect()->toRoute('fid/admin/list'); + } + + $editableFields = $this->config['Admin']['editable_user_fields'] ?? []; + + try { + try { + $user = $this->client->requestUserDetails($userId); + } catch (UserNotAuthorizedException $ex) { + // either user does not exist, or we are not allowed to edit it + $this->getMessenger()->addErrorMessage($this->translate('fid::user_edit_not_allowed', ['%%userid%%' => $userId])); + return $this->redirect()->toRoute('fid/admin/list'); + } catch (\Exception $ex) { + $this->getMessenger()->addErrorMessage($this->translate('fid::user_read_error',['%%userid%%' => $userId])); + return $this->redirect()->toRoute('fid/admin/list'); + } + $libraries = $this->client->requestLibraryList(); + } catch (ClientException $exception) { + $this->setFollowupUrlToReferer(); + $message = $exception->getCode() === 401 + ? 'fid::user_update_error_expired' + : 'fid::user_update_error'; + $this->getMessenger()->addErrorMessage($this->translate($message)); + return $this->redirect()->toRoute('fid/admin/list'); + } + + /** @var Form $form */ + $form = $this->serviceLocator->get(UserUpdateModel::class); + + if (!empty($libraries)) { + $userLibrary = $user->getHomeLibrary(); + $libraries = array_map(function (Library $libary) use ($editableFields,$userLibrary) { + $option = [ + 'label' => $libary->getLabel(), + 'value' => $id = $libary->getId(), + 'selected' => $id == $userLibrary + ]; + if ($id != $userLibrary && !in_array('HomeLibrary', $editableFields)) { + $option['attributes']['disabled'] = 'disabled'; + } + return $option; + }, $libraries); + + /** @var Select $homeLibraryElement */ + $homeLibraryElement = $form->get('homeLibrary'); + $homeLibraryElement->setValueOptions($libraries); + } + + if ($permissions = $this->config['Admin']['permission_options'] ?? []) { + + $userPermissions = $user->getPermissions(); + if (!isset(self::$currentPermissions)) { + $valid = []; + foreach ($permissions as $permission) { + $valid[$permission] = $userPermissions[$permission] ?? 'denied'; + } + self::$currentPermissions = $valid; + } + /** @var MultiCheckbox $permissionsElement */ + $permissionsElement = $form->get('permissions'); + $permissions = array_map(function ($permission) use ($editableFields,$userPermissions) { + $option = [ + 'label' => $this->translate('fid::permission_' . $permission), + 'name' => $permission, + 'label_attributes' => ['class' => 'permission-label col-sm-11'], + 'attributes' => ['class' => 'permission-option col-sm-1'], + 'selected' => isset($userPermissions[$permission]) && $userPermissions[$permission] == 'granted', + 'value' => $permission . '::' . ($userPermissions[$permission] ?? 'denied'), + ]; + if (!in_array('Permissions', $editableFields)) { + $option['disabled'] = '1'; + } + return $option; + }, $permissions); + $permissionsElement->setDisableInArrayValidator(true); + $permissionsElement->setValueOptions($permissions); + } + + if ($this->formWasSubmitted()) { + $form->setData($request->getPost()); + if ($form->isValid()) { + return $this->update($form,'fid/admin/list'); + } + } + + $viewModel = $this->createViewModel(); + $viewModel->setVariables(compact('form', 'user','editableFields')); + $viewModel->setTemplate('fid/admin/edit'); + + return $viewModel; + } + + public static $currentPermissions; + + public static function combinePermissionsArray($input) { + $output = []; + foreach ($input as $line) { + list($key,) = explode('::',$line); + if (isset(self::$currentPermissions[$key])) { + $output[$key] = 'granted'; + } + } + foreach (self::$currentPermissions as $key => $state) { + if ($state === 'granted' && !isset($output[$key])) { + $output[$key] = 'denied'; + } + } + return array_intersect_key($output,['full_access'=>'0','limited_access'=>1]); + } + + public function listAction() { + + // Not logged in? Force user to log in: + if (!$this->getAuthManager()->isLoggedIn()) { + // Allow bypassing of post-login redirect + if ($this->params()->fromQuery('redirect', true)) { + $this->setFollowupUrlToReferer(); + } + return $this->forwardTo('MyResearch', 'Login'); + } + + $viewModel = $this->createViewModel(); + try { + $list = $this->client->requestUserList(); + $viewModel->setVariable('list',$list); + } catch (UserNotAuthorizedException $exception) { + $this->getMessenger()->addErrorMessage('fid::read_user_list_not_allowed'); + } catch (ClientException $exception) { + $this->getMessenger()->addErrorMessage('fid::read_user_list_error'); + } + + if ($fields = $this->config['Admin']['overview_fields']) { + $viewModel->setVariable('fields',$fields); + } + + $viewModel->setTemplate('fid/admin/list'); + return $viewModel; + } + +} diff --git a/module/fid/src/FormModel/UserUpdateModel.php b/module/fid/src/FormModel/UserUpdateModel.php index e6bda2be41d7a7a684bd70f41a6db0e76dd9f0dd..fcf67c9a8649f4fdac3d3d4f748003c5e7689e42 100644 --- a/module/fid/src/FormModel/UserUpdateModel.php +++ b/module/fid/src/FormModel/UserUpdateModel.php @@ -24,6 +24,11 @@ namespace fid\FormModel; class UserUpdateModel { + /** + * @var string + */ + protected $userId; + /** * @var string */ @@ -64,11 +69,32 @@ class UserUpdateModel */ protected $college; + /** + * @var array + */ + protected $permissions; + /** * @var string|null */ protected $submit; + /** + * @return string|null + */ + public function getUserId(): ?string + { + return $this->userId; + } + + /** + * @param string $userId + */ + public function setUserId(string $userId = null): void + { + $this->userId = $userId; + } + /** * @return string */ @@ -197,6 +223,22 @@ class UserUpdateModel $this->college = $college; } + /** + * @param array $permissions + */ + public function setPermissions(array $permissions): void + { + $this->permissions = $permissions; + } + + /** + * @return array + */ + public function getPermissions(): array + { + return $this->permissions; + } + /** * @return string|null */ diff --git a/module/fid/src/FormModel/UsernameChangeModel.php b/module/fid/src/FormModel/UsernameChangeModel.php new file mode 100644 index 0000000000000000000000000000000000000000..940e46d75d40e784b314e11adfc4b8ec361a383b --- /dev/null +++ b/module/fid/src/FormModel/UsernameChangeModel.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright (C) 2019 Leipzig University Library + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @author Gregor Gawol <gawol@ub.uni-leipzig.de> + * @author Sebastian Kehr <kehr@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +namespace fid\FormModel; + +class UsernameChangeModel +{ + /** + * @var string + */ + protected $username; + + /** + * @var string + */ + protected $usernameConfirmation; + + /** + * @var string + */ + protected $submit; + + /** + * @return string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * @param string $username + */ + public function setUsername(string $username): void + { + $this->username = $username; + } + + /** + * @return string + */ + public function getUsernameConfirmation(): string + { + return $this->usernameConfirmation; + } + + /** + * @param string $usernameConfirmation + */ + public function setUsernameConfirmation(string $usernameConfirmation): void + { + $this->usernameConfirmation = $usernameConfirmation; + } + + /** + * @return string + */ + public function getSubmit(): string + { + return $this->submit; + } + + /** + * @param string $submit + */ + public function setSubmit(string $submit): void + { + $this->submit = $submit; + } +} diff --git a/module/fid/src/Role/PermissionProvider/Factory.php b/module/fid/src/Role/PermissionProvider/Factory.php new file mode 100644 index 0000000000000000000000000000000000000000..761c717d2192efd9c234729a148ba6e7f73308f2 --- /dev/null +++ b/module/fid/src/Role/PermissionProvider/Factory.php @@ -0,0 +1,58 @@ +<?php +/** + * Permission Provider Factory Class + * + * PHP version 5 + * + * Copyright (C) Villanova University 2014. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace fid\Role\PermissionProvider; + +use Zend\ServiceManager\ServiceManager; + +/** + * Permission Provider Factory Class + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + * + * @codeCoverageIgnore + */ +class Factory extends \VuFind\Role\PermissionProvider\Factory +{ + /** + * Factory for IpRangeFoFor + * + * @param ServiceManager $container Service manager. + * + * @return FidApiPermission + */ + public static function getFidApiPermission(ServiceManager $container) + { + return new FidApiPermission( + $container->get('fid\Service\Client') + ); + } +} diff --git a/module/fid/src/Role/PermissionProvider/FidApiPermission.php b/module/fid/src/Role/PermissionProvider/FidApiPermission.php new file mode 100644 index 0000000000000000000000000000000000000000..51fa15ef4574b6b2edcd89d4613c2a668a2538fd --- /dev/null +++ b/module/fid/src/Role/PermissionProvider/FidApiPermission.php @@ -0,0 +1,76 @@ +<?php +/** + * IpRange permission provider for VuFind. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2007. + * Copyright (C) The National Library of Finland 2015. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @author Jochen Lienhard <lienhard@ub.uni-freiburg.de> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @author Gregor Gawol <gawol@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +namespace fid\Role\PermissionProvider; + +use VuFind\Role\PermissionProvider\PermissionProviderInterface; +use fid\Service\Client as FidApiClient; +/** + * Permission provider authorizing FID API roles + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @author Jochen Lienhard <lienhard@ub.uni-freiburg.de> + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @author Dorian Merz <merz@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class FidApiPermission implements PermissionProviderInterface +{ + /** + * @var FidApiClient + */ + private $client; + + public function __construct(FidApiClient $client) + { + $this->client = $client; + } + + /** + * Return an array of roles which may be granted the permission based on + * the options. + * + * @param mixed $options Options provided from configuration. + * + * @return array + */ + public function getPermissions($options) + { + if ($this->client->isAuthorized($options)) { + return ['loggedin']; + } + // No match? No permissions. + return []; + } +} diff --git a/module/fid/src/Service/Client.php b/module/fid/src/Service/Client.php index 32eb30ff41e86f0b9044905a69bce1dcac942c66..bda65b19de033fd438ade5e3250307a7e1224b37 100644 --- a/module/fid/src/Service/Client.php +++ b/module/fid/src/Service/Client.php @@ -241,21 +241,45 @@ class Client } } + /** + * @param string $baseUrl + * @param string $username + * + * @throws ClientException + */ + public function requestUsernameLink(string $baseUrl, string $username): void + { + $body = json_encode(compact('baseUrl', 'username')); + $request = $this->buildRequest('post', 'mail/username', $body); + $response = $this->sendAuthenticatedRequest($request); + + if ($response->getStatusCode() !== 204) { + $this->throwException($response); + } + } /** * @return User|null * @throws ClientException + * @throws UserNotAuthorizedException */ - public function requestUserDetails(): ?User + public function requestUserDetails($userId = null): ?User { $logon = $this->restoreLogon(); /** @var User $user */ - if ($user = $this->session['user'] ?? null) { - return $user; + $user = $this->session['user'] ?? null; + $ownId = $logon->getOwnerId(); + + if (is_null($userId) || $userId === $ownId) { + // user asks for own profile data + if (!empty($user)) return $user; + $userId = $ownId; + } else { + // user asks for another user's profile + // this shall only be possible for authorized admins + $this->authorize('edit_user'); } - - $userId = $logon->getOwnerId(); $request = $this->buildRequest('get', "users/$userId"); $response = $this->sendAuthenticatedRequest($request); @@ -267,7 +291,8 @@ class Client $user = $this->serializer->deserialize((string)$response->getBody(), User::class, 'json', ['groups' => ['user:details:response']]); - return $this->session['user'] = $user; + if ($ownId === $userId) $this->session['user'] = $user; + return $user; } /** @@ -318,6 +343,81 @@ class Client ['user:update-password:request']); } + /** + * @param User $user + * + * @return User + * @throws ClientException + */ + public function requestUserUsernameUpdate(User $user): User + { + return $this->doRequestUserUpdate($user, [ + 'user:update-username:request' + ]); + } + + /** + * @return User[] + * @throws ClientException + * @throws UserNotAuthorizedException + */ + public function requestUserList(): array + { + // user asks for another users' profiles + // this shall only be possible for authorized admins + $this->authorize('read_user_list'); + + if ($list = $this->session['users'] ?? null) { + return $list; + } + + $request = $this->buildRequest('get', 'users'); + $response = $this->sendAuthenticatedRequest($request); + + if ($response->getStatusCode() !== 200) { + $this->throwException($response); + } + /** @var Library[] $list */ + $list = $this->serializer->deserialize( + (string)$response->getBody(), User::class . '[]', 'json'); + + $keys = array_map(function (User $libary) { + return $libary->getId(); + }, $list); + + return $this->session['users'] = array_combine($keys, $list); + } + + public function flushUserList() { + unset($this->session['users']); + } + + /** + * throws an Exception in case the user does not have the requested permission + * or the permission cannot be verified + * @param String $permission Name of the permission + * @param User|null $user user object or null if we want to validate the currently logged in user + * @throws ClientException + * @throws UserNotAuthorizedException + */ + protected function authorize(String $permission,User $user = null) { + + $user = $this->requestUserDetails(); + if (!$user->hasPermission($permission)) { + throw new UserNotAuthorizedException(); + } + } + + public function isAuthorized(String $permission) { + + try { + $this->authorize($permission); + } catch (\Exception $ex) { + return FALSE; + } + return TRUE; + } + /** * @return Library[] * @throws ClientException @@ -365,7 +465,12 @@ class Client $result = $this->serializer->deserialize((string)$response->getBody(), User::class, 'json', ['groups' => ['user:update:response']]); - return $this->session['user'] = $result; + $logon = $this->restoreLogon(); + if ($logon->getOwnerId() === $user->getId()) { + // refresh user data + $this->session['user'] = $result; + } + return $result; } diff --git a/module/fid/src/Service/DataTransferObject/User.php b/module/fid/src/Service/DataTransferObject/User.php index fc5695d8aa542af7ed7874b8b09b146e13eb4f7d..e7cc1bfc025370279a619e67d4bc94217c89b3d8 100644 --- a/module/fid/src/Service/DataTransferObject/User.php +++ b/module/fid/src/Service/DataTransferObject/User.php @@ -41,7 +41,8 @@ class User * "user:details:response", * "user:creation:request", * "user:creation:response", - * "user:update:response" + * "user:update:response", + * "user:update-username:request" * }) */ protected $username; @@ -176,6 +177,21 @@ class User */ protected $permissions = []; + /** + * Magic function as shortcut for all getters + * TODO ensure public visibility of all called getters + * @param $name + * @return |null + */ + public function __get($name) + { + $methodName = 'get'.ucfirst($name); + if (method_exists($this,$methodName)) { + return $this->$methodName(); + } + else return null; + } + /** * @return string|null */ @@ -387,4 +403,16 @@ class User { $this->permissions = $permissions; } + + public function hasPermission($permission): bool + { + if ( + isset($this->permissions[$permission]) + && + $this->permissions[$permission] === 'granted' + ) { + return true; + } + return false; + } } diff --git a/module/fid/src/Service/UserNotAuthorizedException.php b/module/fid/src/Service/UserNotAuthorizedException.php new file mode 100644 index 0000000000000000000000000000000000000000..a802c48c66a9517551223bea7644e50c2fe066f1 --- /dev/null +++ b/module/fid/src/Service/UserNotAuthorizedException.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright (C) 2019 Leipzig University Library + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @author Sebastian Kehr <kehr@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +namespace fid\Service; + +class UserNotAuthorizedException extends \Exception +{ +} diff --git a/themes/fid/languages/fid/de.ini b/themes/fid/languages/fid/de.ini index 44d6622ce9a6478dcdb4a927028788b100685753..9cb1972b05b1c4e947d196b9d0fd024ed5722781 100644 --- a/themes/fid/languages/fid/de.ini +++ b/themes/fid/languages/fid/de.ini @@ -6,9 +6,12 @@ user_create_form_title = "Registrierung abschließen" user_update_form_title = "Profildaten editieren." password_reset_form_title = "Passwort zurücksetzen" password_change_form_title = "Neues Passwort speichern" +username_change_form_title = "E-Mail-Adresse ändern" label_username = "E-Mail-Adresse" +label_newusername = "Neue E-Mail-Adresse" label_username_confirmation = "E-Mail-Adresse wiederholen" +label_newusername_confirmation = "Neue E-Mail-Adresse wiederholen" label_firstname = "Vorname" label_lastname = "Nachname" label_salutation = "Anrede" @@ -25,6 +28,7 @@ label_access_level = "Ich gehöre folgender Nutzergruppe an" label_access_level_full_access = "HochschullehrerInnen, wissenschaftliche MitarbeiterInnen, Mitglied einer Fachgesellschaft, DoktorandInnen, StipendiatInnen oder externe WissenschaftlerInnen (z. B. Lehrbeauftragte)" label_access_level_limited_access = "Studierende, Sonstige" label_access_level_unknown = "Unbekannte oder unbestätigte Gruppe(n)." +label_permissions = "autorisiert für" label_delivery_address = "Privatadresse als Lieferadresse verwenden" Business = "Dienstadresse" @@ -70,6 +74,31 @@ password_change_error_expired = "Der Link ist abgelaufen." password_change_error_autologon = "Ihr Passwort wurde gespeichert. Jedoch ist ein unerwarteteter Fehler bei der automatischen Anmeldung aufgetreten." password_change_success = "Passwort erfolgreich gespeichert." +username_change_link = "E-Mail-Adresse ändern" +username_change_error = "Es ist ein unerwarteter Fehler aufgetreten." +username_change_error_username = "Die von Ihnen gewählte E-Mail-Adresse ist schon vergeben." +username_change_success = "Ein Verifikationslink wurde soeben an Ihre neue E-Mail-Addresse %s gesendet. Die Zustellung kann einige Minuten in Anspruch nehmen. Bitte schauen Sie ggf. auch in Ihren Spamordner." + +username_update_error = "Es ist ein unerwarteter Fehler beim Aktualisieren Ihrer E-Mail-Adresse aufgetreten." +username_update_error_expired = "Ihr Verifikationslink ist bereits abgelaufen. Bitte starten Sie den Vorgang erneut." +username_update_success = "Ihre E-Mail-Addresse wurde erfolgreich aktualisiert." + Edit Account = "Profildaten ändern" -access_level_requested = "Ihre Zugehörigkeit zur unten stehenden Gruppe wird derzeit noch überprüft." \ No newline at end of file + +access_level_requested = "Ihre Zugehörigkeit zur unten stehenden Gruppe wird derzeit noch überprüft." + +; Admin section +user_edit_not_allowed = "Sie haben keine Berechtigung Nutzer %%userid%% zu editieren" +user_read_error = "Fehler beim Lesen der Daten von Nutzer %%userid%%" +read_user_list_not_allowed = "Sie haben keine Berechtigung die Nutzerliste einzusehen" +read_user_list_error = "Fehler beim Lesen der Nutzerliste" + +user_edit_form_title = "Nutzerdaten ändern für <em>%%username%%</em> (ID %%userid%%)" + +user_edit = "Nutzer %%userid%% bearbeiten" + +permission_read_user_list = "Liste aller Nutzer einsehen" +permission_edit_user = "Daten anderer Nutzer bearbeiten" +permission_full_access = "Zugriff auf FID-Lizenzen" +permission_limited_access = "Einfacher Zugriff" \ No newline at end of file diff --git a/themes/fid/languages/fid/en.ini b/themes/fid/languages/fid/en.ini index 5f4b131105dd8331b10d4924b419f345667e9fa5..9ef8f48fe05fb1f5fdf550cb7f404143b23cbecf 100644 --- a/themes/fid/languages/fid/en.ini +++ b/themes/fid/languages/fid/en.ini @@ -6,9 +6,12 @@ user_create_form_title = "Complete registration" user_update_form_title = "Profile data" password_reset_form_title = "Reset password" password_change_form_title = "Save new password" +username_change_form_title = "Update email address" label_username = "Email address" +label_newusername = "New email address" label_username_confirmation = "Retype email address" +label_newusername_confirmation = "Retype new email address" label_password = "Password" label_password_confirmation = "Retype Password" label_salutation = "Salutation" @@ -25,6 +28,7 @@ label_access_level = "Please select your membership level" label_access_level_full_access = "Professor, Research Associate/Assistant, Member of an Academic Association, PhD Student (also Scholarships), (Visiting) Lecturer" label_access_level_limited_access = "Student, Other" label_access_level_unknown = "Unknown or ungranted user group(s)." +label_permissions = "authorized for" label_delivery_address = "Use the following address for deliveries" Business = "Office Address" @@ -70,6 +74,30 @@ password_change_error = "An unexpected error has occurred." password_change_error_expired = "The link has already expired." password_change_error_autologon = "You password has been update. However, an unexpected error has occurred during the process of logging on." +username_change_link = "Update email address" +username_change_error = "An unexpected error has occurred." +username_change_error_username = "The specified email address is already taken." +username_change_success = "A confirmation link has just been sent to your new email address %s. This may take several minutes. Please check also the junk folder of your mailbox." + +username_update_error = "An unexpected error has occurred when updating your email address." +username_update_error_expired = "Your configmration link has already expired." +username_update_success = "Your email address has successfully been updated." + Edit Account = "Edit Account" -access_level_requested = "Your membership status regarding the group below is currently being verified." \ No newline at end of file +access_level_requested = "Your membership status regarding the group below is currently being verified." + +; Admin section +user_edit_not_allowed = "You are not entitled to edit user %%userid%%" +user_read_error = "Error reading data of user %%userid%%" +read_user_list_not_allowed = "You are not entitled to read the user list" +read_user_list_error = "Error reading user list" + +user_edit_form_title = "Edit user data of <em>%%username%%</em> (ID %%userid%%)" + +user_edit = "Edit user %%userid%%" + +permission_read_user_list = "Read list of all users" +permission_edit_user = "Edit other user's data" +permission_full_access = "privileged access" +permission_limited_access = "basic access" \ No newline at end of file diff --git a/themes/fid/scss/compiled.scss b/themes/fid/scss/compiled.scss index 9f6357ce4c11d67119bd3ac177b7eb6bdfcb0a12..d87a310aaeb1fb19a179c705cdbb65b5c7704f78 100644 --- a/themes/fid/scss/compiled.scss +++ b/themes/fid/scss/compiled.scss @@ -66,4 +66,12 @@ margin-right: $margin-right-width; padding-left: $margin-right-width; } +} + +.permission-options { + margin-left: -$grid-gutter-width; +} + +.template-dir-fid #content { + padding-top: 20px; } \ No newline at end of file diff --git a/themes/fid/templates/fid/admin/edit.phtml b/themes/fid/templates/fid/admin/edit.phtml new file mode 100644 index 0000000000000000000000000000000000000000..ef46743db675a7b319d20895342e2c86223a6260 --- /dev/null +++ b/themes/fid/templates/fid/admin/edit.phtml @@ -0,0 +1,249 @@ +<?php +/** + * Copyright (C) 2019 Leipzig University Library + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @author Gregor Gawol <gawol@ub.uni-leipzig.de> + * @author Sebastian Kehr <kehr@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +use fid\Service\DataTransferObject\User; +use Zend\Form\Element as Element; +use Zend\Form\Element\Submit; +use Zend\Form\Form; +use Zend\Form\View\Helper\FormElementErrors; +use Zend\Form\View\Helper\FormLabel; +use Zend\Form\View\Helper\FormRadio; +use Zend\Form\View\Helper\FormSelect; +use Zend\Form\View\Helper\FormSubmit; +use Zend\I18n\Translator\TranslatorInterface; + +/** @var FormLabel $formLabel */ +$formLabel = $this->formLabel(); +/**@var FormRadio $formRadio */ +$formRadio = $this->formRadio(); +/**@var FormSelect $formSelect */ +$formSelect = $this->formSelect(); +/**@var FormSubmit $formSubmit */ +$formSubmit = $this->formSubmit(); +/** @var FormElementErrors $formElementErrors */ +$formElementErrors = $this->formElementErrors(); +$formLabel->setTranslatorTextDomain('fid'); +$formSelect->setTranslatorTextDomain('fid'); +$formRadio->setTranslatorTextDomain('fid'); +$formSubmit->setTranslatorTextDomain('fid'); +$formElementErrors->setTranslatorTextDomain('fid'); +/** @var TranslatorInterface $translator */ +$translator = $this->getHelperPluginManager()->get('translate') + ->getTranslator(); + +$formLabel->setTranslator($translator); +$formElementErrors->setTranslator($translator); + +/** @var Form $form */ +/** @var User $user */ +$user = $this->user; +$userId = $user->getId(); +$form = $this->form; +$form->setAttribute('method', 'post'); +$form->setAttribute('action', $this->url('fid/admin/edit',['userid' => $userId])); +$form->setAttribute('class', 'form-horizontal'); +$form->prepare(); + +$this->headTitle($this->translate('Profile Form')); +$this->headTitle($this->translate("fid::user_update_form_title")); +?> +<!-- fid: admin - edit --> + <h2><?= $this->translate("fid::user_edit_form_title",['%%userid%%' => $userId, '%%username%%' => $user->getUsername()]) ?></h2> +<?= $this->flashmessages() ?> +<?= $this->form()->openTag($form) ?> +<?php +/** @var Element\Hidden $elemUserId */ +$elemUserId = $form->get('userId'); +$elemUserId->setValue($userId); +?> + <div class="form-group"><?=$this->formHidden($elemUserId)?></div> + +<? /* home library */ ?> +<?php +/** @var Element\Select $elemHomeLibrary */ +$elemHomeLibrary = $form->get('homeLibrary'); +$elemHomeLibrary->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemHomeLibrary->setAttributes(['class' => 'form-control inline col-sm-6']); +$elemHomeLibrary->setValue($user->getHomeLibrary()); +if (!in_array('HomeLibrary',$editableFields)) { + $elemHomeLibrary->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemHomeLibrary) ?> + <?= $this->formSelect($elemHomeLibrary) ?> + <?= $this->formElementErrors($elemHomeLibrary) ?> + </div> + +<? /* salutation */ ?> +<?php +/** @var Element\Select $elemSalutation */ +$elemSalutation = $form->get('salutation'); +$elemSalutation->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemSalutation->setAttributes(['class' => 'form-control inline col-sm-2']); +$elemSalutation->setValue($user->getSalutation()); +if (!in_array('Salutation',$editableFields)) { + $elemSalutation->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemSalutation) ?> + <?= $this->formSelect($elemSalutation) ?> + + </div> + +<? /* academic title */ ?> +<?php +/** @var Element\Text $elemAcademicTitle */ +$elemAcademicTitle = $form->get('academicTitle'); +$elemAcademicTitle->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemAcademicTitle->setAttributes(['class' => 'form-control']); +$elemAcademicTitle->setValue($user->getAcademicTitle()); +if (!in_array('AcademicTitle',$editableFields)) { + $elemAcademicTitle->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemAcademicTitle) ?> + <?= $this->formElement($elemAcademicTitle) ?> + </div> + +<? /* firstname */ ?> +<?php +/** @var Element\Text $elemFirstname */ +$elemFirstname = $form->get('firstname'); +$elemFirstname->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemFirstname->setAttributes(['class' => 'form-control']); +$elemFirstname->setValue($user->getFirstname()); +if (!in_array('Firstname',$editableFields)) { + $elemFirstname->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemFirstname) ?> + <?= $this->formElement($elemFirstname) ?> + <?= $this->formElementErrors($elemFirstname) ?> + </div> + +<? /* lastname */ ?> +<?php +/** @var Element\Text $elemLastname */ +$elemLastname = $form->get('lastname'); +$elemLastname->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemLastname->setAttributes(['class' => 'form-control']); +$elemLastname->setValue($user->getLastname()); +if (!in_array('Lastname',$editableFields)) { + $elemLastname->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemLastname) ?> + <?= $this->formElement($elemLastname) ?> + <?= $this->formElementErrors($elemLastname) ?> + </div> + +<? /* year of birth */ ?> +<?php +/** @var Element\Text $elemYearOfBirth */ +$elemYearOfBirth = $form->get('yearOfBirth'); +$elemYearOfBirth->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemYearOfBirth->setAttributes(['class' => 'form-control']); +$elemYearOfBirth->setValue($user->getYearOfBirth()); +if (!in_array('YearOfBirth',$editableFields)) { + $elemYearOfBirth->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemYearOfBirth) ?> + <?= $this->formElement($elemYearOfBirth) ?> + <?= $this->formElementErrors($elemYearOfBirth) ?> + </div> + +<? /* college */ ?> +<?php +/** @var Element\Text $elemCollege */ +$elemCollege = $form->get('college'); +$elemCollege->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemCollege->setAttributes(['class' => 'form-control']); +$elemCollege->setValue($user->getCollege()); +if (!in_array('College',$editableFields)) { + $elemCollege->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemCollege) ?> + <?= $this->formElement($elemCollege) ?> + <?= $this->formElementErrors($elemCollege) ?> + </div> + +<? /* job title */ ?> +<?php +/** @var Element\Text $elemJobTitle */ +$elemJobTitle = $form->get('jobTitle'); +$elemJobTitle->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +$elemJobTitle->setAttributes(['class' => 'form-control']); +$elemJobTitle->setValue($user->getJobTitle()); +if (!in_array('JobTitle',$editableFields)) { + $elemJobTitle->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemJobTitle) ?> + <?= $this->formElement($elemJobTitle) ?> + <?= $this->formElementErrors($elemJobTitle) ?> + </div> + +<? /* Permissions */ ?> +<?php +/** @var Element\MultiCheckbox $elemPermissions */ +$elemPermissions = $form->get('permissions'); +$elemPermissions->setLabelAttributes(['class' => 'inline col-md-4 col-sm-10']); +if (!in_array('Permissions',$editableFields)) { + $elemPermissions->setAttribute('readonly', '1'); +} +?> + <div class="form-group"> + <?= $this->formLabel($elemPermissions) ?> + <div class="permission-options col-sm-7"> + <?= $this->formMultiCheckbox($elemPermissions) ?> + </div> + <?= $this->formElementErrors($elemPermissions) ?> + </div> + +<? /* submit button */ ?> +<?php +/** @var Submit $elemSubmit */ +$elemSubmit = $form->get('submit'); +$elemSubmit->setAttributes(['class' => 'btn btn-primary']); +?> + <div class="form-group"> + <div class="col-lg-11 col-md-9 col-sm-11 col-xs-12"> + <?= $this->formSubmit($elemSubmit) ?> + <a href="<?= $this->url('fid/admin/list') ?>" + class="btn btn-primary"> + <?= $this->transEsc('Cancel') ?> + </a> + </div> + </div> + +<?= $this->form()->closeTag($form) ?> +<!-- fid: admin - edit - END --> \ No newline at end of file diff --git a/themes/fid/templates/fid/admin/list-entry.phtml b/themes/fid/templates/fid/admin/list-entry.phtml new file mode 100644 index 0000000000000000000000000000000000000000..758790b43d39c1df1c9ad2ff3ee9d62db0b45931 --- /dev/null +++ b/themes/fid/templates/fid/admin/list-entry.phtml @@ -0,0 +1,17 @@ +<!-- fid: user - admin - list-entry --> +<?php +$permissions = []; +if (!empty($permissions = $user->getPermissions())) { + foreach ($permissions as $key => &$perm) { + $perm = $this->escapeHtml($key).' ('.$this->escapeHtml($perm).')'; + } +} +foreach ($this->fields as $field): +?> +<?php if ($field === 'Permissions'): ?> + <td><?=implode(', ',$permissions)?></td> +<?php else: ?> + <td><?=$this->escapeHtml($user->$field)?></td> +<?php endif; ?> +<?php endforeach; ?> +<!-- fid: user - admin - list-entry - END --> \ No newline at end of file diff --git a/themes/fid/templates/fid/admin/list.phtml b/themes/fid/templates/fid/admin/list.phtml new file mode 100644 index 0000000000000000000000000000000000000000..a5c81b74f18d84ec99feefed4fec3d6b9dbd1965 --- /dev/null +++ b/themes/fid/templates/fid/admin/list.phtml @@ -0,0 +1,21 @@ +<!-- fid - user - admin - list --> +<?=$this->translate('Users')?> +<?= $this->flashmessages() ?> +<?php if (!empty($this->list)): ?> + <table class="fid fid-admin table user-table table-striped"> + <tr> + <td><!-- empty cell --></td> + <th>#</th> + <?php foreach ($fields as $fieldname): ?> + <th><?=$this->translate('fid::'.$fieldname)?></th> + <?php endforeach; ?> + </tr> + <?php foreach ($list as $id => $user):?> + <?php $tooltip = $this->translate('fid::user_edit',['%%userid%%' => $id]); ?> + <tr><td><a href="<?=$this->url('fid/admin/edit',['userid' => $id])?>" title="<?=$tooltip?>"><i class="fa fa-pencil-square-o"></i><span class="sr-only"><?=$tooltip?></span></a></td><th><?=$id?></th><?= $this->render('fid/admin/list-entry',['user' => $user,'fields'=>$fields]) ?></tr> + <?php endforeach; ?> + </table> +<?php else: ?> + <?= $this->translate('fid::user_list_empty') ?> +<?php endif; ?> +<!-- fid - user - admin - list - END --> \ No newline at end of file diff --git a/themes/fid/templates/fid/user/username-change.phtml b/themes/fid/templates/fid/user/username-change.phtml new file mode 100644 index 0000000000000000000000000000000000000000..4d9f6945dfcb05cf80734001916971165d550969 --- /dev/null +++ b/themes/fid/templates/fid/user/username-change.phtml @@ -0,0 +1,104 @@ +<?php +/** + * Copyright (C) 2019 Leipzig University Library + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * @author Gregor Gawol <gawol@ub.uni-leipzig.de> + * @author Sebastian Kehr <kehr@ub.uni-leipzig.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU GPLv2 + */ + +use Zend\Form\Element; +use Zend\Form\Form; +use Zend\Form\View\Helper\FormElementErrors; +use Zend\Form\View\Helper\FormLabel; +use Zend\Form\View\Helper\FormSubmit; +use Zend\I18n\Translator\TranslatorInterface; + +/** @var FormLabel $formLabel */ +$formLabel = $this->formLabel(); +/**@var FormSubmit $formSubmit */ +$formSubmit = $this->formSubmit(); +/** @var FormElementErrors $formElementErrors */ +$formElementErrors = $this->formElementErrors(); + +$formLabel->setTranslatorTextDomain('fid'); +$formSubmit->setTranslatorTextDomain('fid'); +$formElementErrors->setTranslatorTextDomain('fid'); +/** @var TranslatorInterface $translator */ +$translator = $this->getHelperPluginManager()->get('translate') + ->getTranslator(); +$formLabel->setTranslator($translator); +$formElementErrors->setTranslator($translator); + +/** @var Form $form */ +$form = $this->form; +$form->setAttribute('method', 'post'); +$form->setAttribute('action', $this->url('fid/user/change-username')); +$form->setAttribute('class', 'registration'); +$form->prepare(); + +$this->headTitle($this->translate("fid::username_change_form_title")); +?> + +<h2><?= $this->translate("fid::username_change_form_title") ?></h2> + +<div class="subito-pg"> + * <?=$this->transEsc("This field is required")?> +</div> + +<?= $this->flashmessages() ?> +<?= $this->form()->openTag($form) ?> +<br/> +<? /* username */ ?> +<?php +/** @var Element\Text $elemUsername */ +$elemUsername = $form->get('username'); +$elemUsername->setLabelAttributes(['class' => 'control-label']); +$elemUsername->setAttributes(['class' => 'form-control']); +?> +<div class="form-group"> + <?= $this->formLabel($elemUsername) ?> + <?= $this->formElement($elemUsername) ?> + <?= $this->formElementErrors($elemUsername) ?> +</div> + +<? /* username confirmation */ ?> +<?php +/** @var Element\Text $elemUsernameConfirmation */ +$elemUsernameConfirmation = $form->get('usernameConfirmation'); +$elemUsernameConfirmation->setLabelAttributes(['class' => 'control-label']); +$elemUsernameConfirmation->setAttributes(['class' => 'form-control']); +?> +<div class="form-group"> + <?= $this->formLabel($elemUsernameConfirmation) ?> + <?= $this->formElement($elemUsernameConfirmation) ?> + <?= $this->formElementErrors($elemUsernameConfirmation) ?> +</div> + +<? /* submit button */ ?> +<?php +/** @var Element\Submit $elemSubmit */ +$elemSubmit = $form->get('submit'); +$elemSubmit->setAttributes(['class' => 'btn btn-primary']); +?> +<div class="form-group"> + <a class="back-to-login btn btn-link" href="<?=$this->url('myresearch-userlogin') ?>"> + <i class="fa fa-chevron-left" aria-hidden="true"></i> + <?=$this->transEsc('Back')?> + </a> + <?= $this->formElement($elemSubmit) ?> +</div> +<?= $this->form()->closeTag($form) ?> diff --git a/themes/fid/templates/myresearch/menu.phtml b/themes/fid/templates/myresearch/menu.phtml index c4691604ba8802848fb6fd96aef1ae2bcf946a91..3813038b0a516fb8724acfcd35ca32013bbbedc2 100644 --- a/themes/fid/templates/myresearch/menu.phtml +++ b/themes/fid/templates/myresearch/menu.phtml @@ -69,6 +69,15 @@ </a> <? endif; ?> + <?php if ($this->auth()->isLoggedIn()): ?> + <a href="<?=$this->url('fid/user/change-username')?>" data-lightbox> + <span class="no-padding"> + <i class="fa fa-fw fa-envelope" aria-hidden="true"></i> + </span> + <?=$this->transEsc('fid::username_change_link')?> + </a> + <? endif; ?> + <?php if ($user = $this->auth()->isLoggedIn()): ?> <span class="logout-button"> <a href="<?=$this->url('myresearch-logout')?>">