diff --git a/classes/components/listPanels/PKPSelectReviewerListPanel.php b/classes/components/listPanels/PKPSelectReviewerListPanel.php index 8a3ff2054db..906dfbbbfcc 100644 --- a/classes/components/listPanels/PKPSelectReviewerListPanel.php +++ b/classes/components/listPanels/PKPSelectReviewerListPanel.php @@ -121,11 +121,17 @@ public function getConfig() ]; if (!empty($this->lastRoundReviewers)) { - $config['lastRoundReviewers'] = Repo::user() + $reviewers = Repo::user() ->getSchemaMap() ->summarizeManyReviewers($this->lastRoundReviewers) ->values() ->toArray(); + $contextId = $this->getParams['contextId']; + foreach ($reviewers as $key => $reviewer) { + $userPrivateNote = Repo::userPrivateNote()->getUserPrivateNote($reviewer->getId(), $contextId); + $reviewers[$key]['userPrivateNote'] = $userPrivateNote?->getNote(); + } + $config['lastRoundReviewers'] = $reviewers; } if (!empty($this->getParams)) { @@ -147,6 +153,7 @@ public function getConfig() $config['declinedReviewsLabel'] = __('reviewer.list.declinedReviews'); $config['emptyLabel'] = __('reviewer.list.empty'); $config['gossipLabel'] = __('user.gossip'); + $config['userPrivateNotesLabel'] = __('user.private.notes'); $config['neverAssignedLabel'] = __('reviewer.list.neverAssigned'); $config['reassignLabel'] = __('reviewer.list.reassign'); $config['reassignWithNameLabel'] = __('reviewer.list.reassign.withName'); @@ -172,8 +179,12 @@ public function getItems($request) $reviewers = $this->_getCollector()->getMany(); $items = []; $map = Repo::user()->getSchemaMap(); + $contextId = $request->getContext()->getId(); foreach ($reviewers as $reviewer) { - $items[] = $map->summarizeReviewer($reviewer); + $item = $map->summarizeReviewer($reviewer); + $userPrivateNote = Repo::userPrivateNote()->getUserPrivateNote($reviewer->getId(), $contextId); + $item['userPrivateNote'] = $userPrivateNote?->getNote(); + $items[] = $item; } return $items; diff --git a/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php b/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php index 31cb644e837..2beb2bb1189 100644 --- a/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php +++ b/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.php @@ -1011,8 +1011,17 @@ public function gossip($args, $request) return new JSONMessage(false, __('user.authorization.roleBasedAccessDenied')); } + $userPrivateNote = Repo::userPrivateNote()->getUserPrivateNote($user->getId(), $request->getContext()->getId()); + if (!$userPrivateNote) { + $userPrivateNote = Repo::userPrivateNote()->newDataObject([ + 'userId' => $user->getId(), + 'contextId' => $request->getContext()->getId(), + 'note' => '' + ]); + } + $requestArgs = array_merge($this->getRequestArgs(), ['reviewAssignmentId' => $reviewAssignment->getId()]); - $reviewerGossipForm = new ReviewerGossipForm($user, $requestArgs); + $reviewerGossipForm = new ReviewerGossipForm($user, $userPrivateNote, $requestArgs); // View form if (!$request->isPost()) { diff --git a/classes/facades/Repo.php b/classes/facades/Repo.php index 6142f56b567..fcbaac2bd52 100644 --- a/classes/facades/Repo.php +++ b/classes/facades/Repo.php @@ -37,6 +37,7 @@ use PKP\log\event\Repository as EventLogRepository; use PKP\submissionFile\Repository as SubmissionFileRepository; use PKP\userGroup\Repository as UserGroupRepository; +use PKP\userPrivateNote\Repository as UserPrivateNoteRepository; class Repo { @@ -90,6 +91,11 @@ public static function userGroup(): UserGroupRepository return app(UserGroupRepository::class); } + public static function userPrivateNote(): UserPrivateNoteRepository + { + return app(UserPrivateNoteRepository::class); + } + public static function eventLog(): EventLogRepository { return app(EventLogRepository::class); diff --git a/classes/migration/install/UserPrivateNotesMigration.php b/classes/migration/install/UserPrivateNotesMigration.php new file mode 100644 index 00000000000..10f4b0b2a74 --- /dev/null +++ b/classes/migration/install/UserPrivateNotesMigration.php @@ -0,0 +1,56 @@ +comment('User private notes are an addition to the gossip, but this one is private to each context.'); + $table->bigInteger('user_private_note_id')->autoIncrement(); + + $table->bigInteger('context_id'); + $contextDao = Application::getContextDAO(); + $table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade'); + + $table->bigInteger('user_id'); + $userDao = Repo::user()->dao; + $table->foreign('user_id')->references($userDao->primaryKeyColumn)->on($userDao->table)->onDelete('cascade'); + + $table->unique(['context_id', 'user_id'], 'user_private_notes_unique'); + $table->index(['context_id'], 'user_private_notes_context_id_foreign'); + + $table->string('note')->default(''); + }); + } + + /** + * Reverse the migration. + */ + public function down(): void + { + Schema::drop('user_private_notes'); + } +} diff --git a/classes/migration/upgrade/v3_5_0/I9456_UserPrivateNotes.php b/classes/migration/upgrade/v3_5_0/I9456_UserPrivateNotes.php new file mode 100644 index 00000000000..09b350617a1 --- /dev/null +++ b/classes/migration/upgrade/v3_5_0/I9456_UserPrivateNotes.php @@ -0,0 +1,54 @@ +comment('User private notes are an addition to the gossip, but this one is private to each context.'); + $table->bigInteger('user_private_note_id')->autoIncrement(); + + $table->bigInteger('context_id'); + $contextDao = Application::getContextDAO(); + $table->foreign('context_id')->references($contextDao->primaryKeyColumn)->on($contextDao->tableName)->onDelete('cascade'); + + $table->bigInteger('user_id'); + $userDao = Repo::user()->dao; + $table->foreign('user_id')->references($userDao->primaryKeyColumn)->on($userDao->table)->onDelete('cascade'); + + $table->unique(['context_id', 'user_id'], 'user_private_notes_unique'); + $table->index(['context_id'], 'user_private_notes_context_id_foreign'); + + $table->string('note')->default(''); + }); + } + + /** + * Reverse the migration. + */ + public function down(): void + { + Schema::drop('user_private_notes'); + } +} diff --git a/classes/services/PKPSchemaService.php b/classes/services/PKPSchemaService.php index ac4d57a9401..8029a73fae2 100644 --- a/classes/services/PKPSchemaService.php +++ b/classes/services/PKPSchemaService.php @@ -47,6 +47,7 @@ class PKPSchemaService public const SCHEMA_SUBMISSION_FILE = 'submissionFile'; public const SCHEMA_USER = 'user'; public const SCHEMA_USER_GROUP = 'userGroup'; + public const SCHEMA_USER_PRIVATE_NOTE = 'userPrivateNote'; public const SCHEMA_EVENT_LOG = 'eventLog'; /** @var array cache of schemas that have been loaded */ @@ -631,6 +632,7 @@ class_alias('\PKP\services\PKPSchemaService', '\PKPSchemaService'); 'SCHEMA_SUBMISSION_FILE', 'SCHEMA_USER', 'SCHEMA_USER_GROUP', + 'SCHEMA_USER_PRIVATE_NOTE', ] as $constantName) { if (!defined($constantName)) { define($constantName, constant('PKPSchemaService::' . $constantName)); diff --git a/classes/userPrivateNote/Collector.php b/classes/userPrivateNote/Collector.php new file mode 100644 index 00000000000..dacdac8c8bc --- /dev/null +++ b/classes/userPrivateNote/Collector.php @@ -0,0 +1,167 @@ +dao = $dao; + } + + public function getCount(): int + { + return $this->dao->getCount($this); + } + + /** + * @return Collection + */ + public function getIds(): Collection + { + return $this->dao->getIds($this); + } + + /** + * @copydoc DAO::getMany() + * + * @return LazyCollection + */ + public function getMany(): LazyCollection + { + return $this->dao->getMany($this); + } + + /** + * Filter by multiple ids + */ + public function filterByUserPrivateNoteIds(?array $ids): self + { + $this->userPrivateNoteIds = $ids; + return $this; + } + + /** + * Filter by context IDs + */ + public function filterByContextIds(?array $contextIds): self + { + $this->contextIds = $contextIds; + return $this; + } + + /** + * Filter by user ids + */ + public function filterByUserIds(?array $userIds): self + { + $this->userIds = $userIds; + return $this; + } + + /** + * Include orderBy columns to the collector query + */ + public function orderBy(?string $orderBy): self + { + $this->orderBy = $orderBy; + return $this; + } + + /** + * Limit the number of objects retrieved + */ + public function limit(?int $count): self + { + $this->count = $count; + return $this; + } + + /** + * Offset the number of objects retrieved, for example to + * retrieve the second page of contents + */ + public function offset(?int $offset): self + { + $this->offset = $offset; + return $this; + } + + /** + * @copydoc CollectorInterface::getQueryBuilder() + * + * @hook UserGroup::Collector [[&$q, $this]] + */ + public function getQueryBuilder(): Builder + { + $q = DB::table('user_private_notes as upn') + ->select('upn.*'); + + if (isset($this->userPrivateNoteIds)) { + $q->whereIn('upn.user_private_note_id', $this->userPrivateNoteIds); + } + + if (isset($this->contextIds)) { + $q->whereIn('upn.context_id', $this->contextIds); + } + + if (isset($this->userIds)) { + $q->whereIn('upn.user_id', $this->userIds); + } + + if (isset($this->count)) { + $q->limit($this->count); + } + + if (isset($this->offset)) { + $q->offset($this->offset); + } + + if ($this->orderBy == self::ORDERBY_ID) { + $q->orderBy('upn.user_private_note_id'); + } + + // Add app-specific query statements + Hook::call('UserPrivateNote::Collector', [&$q, $this]); + + return $q; + } +} diff --git a/classes/userPrivateNote/DAO.php b/classes/userPrivateNote/DAO.php new file mode 100644 index 00000000000..1e9258bbbba --- /dev/null +++ b/classes/userPrivateNote/DAO.php @@ -0,0 +1,141 @@ + + */ +class DAO extends EntityDAO +{ + use EntityWithParent; + + /** @copydoc EntityDAO::$schema */ + public $schema = PKPSchemaService::SCHEMA_USER_PRIVATE_NOTE; + + /** @copydoc EntityDAO::$table */ + public $table = 'user_private_notes'; + + /** @copydoc EntityDAO::$primaryKeyColumn */ + public $primaryKeyColumn = 'user_private_note_id'; + + /** @copydoc EntityDAO::$primaryTableColumns */ + public $primaryTableColumns = [ + 'id' => 'user_private_note_id', + 'contextId' => 'context_id', + 'userId' => 'user_id', + 'note' => 'note', + ]; + + /** + * Get the parent object ID column name + */ + public function getParentColumn(): string + { + return 'context_id'; + } + + /** + * Instantiate a new DataObject + */ + public function newDataObject(): UserPrivateNote + { + return app(UserPrivateNote::class); + } + + /** + * Get the total count of rows matching the configured query + */ + public function getCount(Collector $query): int + { + return $query + ->getQueryBuilder() + ->count(); + } + + /** + * Get a list of ids matching the configured query + * + * @return Collection + */ + public function getIds(Collector $query): Collection + { + return $query + ->getQueryBuilder() + ->select('upn.' . $this->primaryKeyColumn) + ->pluck('upn.' . $this->primaryKeyColumn); + } + + /** + * Get a collection of publications matching the configured query + * + * @return LazyCollection + */ + public function getMany(Collector $query): LazyCollection + { + $rows = $query + ->getQueryBuilder() + ->get(); + + return LazyCollection::make(function () use ($rows) { + foreach ($rows as $row) { + yield $row->user_private_note_id => $this->fromRow($row); + } + }); + } + + /** + * @copydoc EntityDAO::fromRow() + */ + public function fromRow(object $row): UserPrivateNote + { + return parent::fromRow($row); + } + + /** + * @copydoc EntityDAO::insert() + * @throws Exception + */ + public function insert(UserPrivateNote $privateNote): int + { + return parent::_insert($privateNote); + } + + /** + * @copydoc EntityDAO::update() + */ + public function update(UserPrivateNote $privateNote): void + { + parent::_update($privateNote); + } + + /** + * @copydoc EntityDAO::delete() + */ + public function delete(UserPrivateNote $privateNote): void + { + parent::_delete($privateNote); + } +} diff --git a/classes/userPrivateNote/Repository.php b/classes/userPrivateNote/Repository.php new file mode 100644 index 00000000000..b96038d969d --- /dev/null +++ b/classes/userPrivateNote/Repository.php @@ -0,0 +1,128 @@ + */ + protected PKPSchemaService $schemaService; + + public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService) + { + $this->dao = $dao; + $this->request = $request; + $this->schemaService = $schemaService; + } + + /** @copydoc DAO::newDataObject() */ + public function newDataObject(array $params = []): UserPrivateNote + { + $object = $this->dao->newDataObject(); + if (!empty($params)) { + $object->setAllData($params); + } + return $object; + } + + /** @copydoc DAO::get() */ + public function get(int $id, int $contextId = null): ?UserPrivateNote + { + return $this->dao->get($id, $contextId); + } + + /** @copydoc DAO::exists() */ + public function exists(int $id, int $contextId = null): bool + { + return $this->dao->exists($id, $contextId); + } + + /** @copydoc DAO::getCollector() */ + public function getCollector(): Collector + { + return app(Collector::class); + } + + /** + * Get an instance of the map class for mapping + * user private notes to their schema + */ + public function getSchemaMap(): maps\Schema + { + return app('maps')->withExtensions($this->schemaMap); + } + + /** + * @throws Exception + */ + public function add(UserPrivateNote $userPrivateNote): int + { + $userPrivateNoteId = $this->dao->insert($userPrivateNote); + $userPrivateNote = Repo::userPrivateNote()->get($userPrivateNoteId); + + Hook::call('UserPrivateNote::add', [$userPrivateNote]); + + return $userPrivateNote->getId(); + } + + public function edit(UserPrivateNote $userPrivateNote, array $params): void + { + $newUserPrivateNote = Repo::userPrivateNote()->newDataObject(array_merge($userPrivateNote->_data, $params)); + + Hook::call('UserPrivateNote::edit', [$newUserPrivateNote, $userPrivateNote, $params]); + + $this->dao->update($newUserPrivateNote); + + Repo::userPrivateNote()->get($newUserPrivateNote->getId()); + } + + public function delete(UserPrivateNote $userPrivateNote): void + { + Hook::call('UserPrivateNote::delete::before', [$userPrivateNote]); + + $this->dao->delete($userPrivateNote); + + Hook::call('UserPrivateNote::delete', [$userPrivateNote]); + } + + /** + * Get the user private note for the specified context. + * + * This returns the user private note, as the "user ID/context ID" key should be unique. + */ + public function getUserPrivateNote(int $userId, int $contextId): ?UserPrivateNote + { + return Repo::userPrivateNote() + ->getCollector() + ->filterByUserIds([$userId]) + ->filterByContextIds([$contextId]) + ->limit(1) + ->getMany() + ->first(); + } +} diff --git a/classes/userPrivateNote/UserPrivateNote.php b/classes/userPrivateNote/UserPrivateNote.php new file mode 100644 index 00000000000..ec1bc8f795b --- /dev/null +++ b/classes/userPrivateNote/UserPrivateNote.php @@ -0,0 +1,85 @@ +getData('contextId'); + } + + /** + * Set private note context ID. + * + * @param $contextId int + */ + public function setContextId(int $contextId): void + { + $this->setData('contextId', $contextId); + } + + /** + * Get private note user ID. + * + * @return int + */ + public function getUserId(): int + { + return $this->getData('userId'); + } + + /** + * Set private note user ID. + * + * @param $userId int + */ + public function setUserId(int $userId): void + { + $this->setData('userId', $userId); + } + + + /** + * Get private note value. + * + * @return string + */ + public function getNote(): string + { + return $this->getData('note'); + } + + /** + * Set private note value. + * + * @param $note string + */ + public function setNote(string $note): void + { + $this->setData('note', $note); + } +} diff --git a/classes/userPrivateNote/maps/Schema.php b/classes/userPrivateNote/maps/Schema.php new file mode 100644 index 00000000000..041e9be8067 --- /dev/null +++ b/classes/userPrivateNote/maps/Schema.php @@ -0,0 +1,95 @@ +mapByProperties($this->getProps(), $item); + } + + /** + * Summarize a user private note + * + * Includes properties with the apiSummary flag in the user private note schema. + */ + public function summarize(UserPrivateNote $item): array + { + return $this->mapByProperties($this->getSummaryProps(), $item); + } + + /** + * Map a collection of user private notes + * + * @see self::map + */ + public function mapMany(Enumerable $collection): Enumerable + { + $this->collection = $collection; + return $collection->map(function ($item) { + return $this->map($item); + }); + } + + /** + * Summarize a collection of user private notes + * + * @see self::summarize + */ + public function summarizeMany(Enumerable $collection): Enumerable + { + $this->collection = $collection; + return $collection->map(function ($item) { + return $this->summarize($item); + }); + } + + /** + * Map schema properties of a user private note to an assoc array + */ + protected function mapByProperties(array $props, UserPrivateNote $item): array + { + $output = []; + foreach ($props as $prop) { + $output[$prop] = $item->getData($prop); + } + + $output = $this->schemaService->addMissingMultilingualValues($this->schema, $output, $this->context->getSupportedSubmissionLocales()); + + ksort($output); + + return $this->withExtensions($output, $item); + } +} diff --git a/controllers/grid/settings/user/form/UserDetailsForm.php b/controllers/grid/settings/user/form/UserDetailsForm.php index 9756ccc2313..71558354e15 100644 --- a/controllers/grid/settings/user/form/UserDetailsForm.php +++ b/controllers/grid/settings/user/form/UserDetailsForm.php @@ -174,9 +174,13 @@ public function initData() 'interests' => $interestManager->getInterestsForUser($user), 'locales' => $user->getLocales(), ]; - $data['canCurrentUserGossip'] = Repo::user()->canCurrentUserGossip($user->getId()); + $userId = $user->getId(); + $data['canCurrentUserGossip'] = Repo::user()->canCurrentUserGossip($userId); if ($data['canCurrentUserGossip']) { $data['gossip'] = $user->getGossip(); + $contextId = $request->getContext()->getId(); + $userPrivateNote = Repo::userPrivateNote()->getUserPrivateNote($userId, $contextId); + $data['userPrivateNote'] = $userPrivateNote ? $userPrivateNote->getNote() : ''; } } elseif (isset($this->author)) { $author = $this->author; @@ -268,6 +272,7 @@ public function readInputData() 'country', 'biography', 'gossip', + 'userPrivateNote', 'interests', 'locales', 'generatePassword', @@ -359,6 +364,7 @@ public function execute(...$functionParams) } Repo::user()->edit($this->user); + $userId = $this->user->getId(); } else { $this->user->setUsername($this->getData('username')); if ($this->getData('generatePassword')) { @@ -372,7 +378,7 @@ public function execute(...$functionParams) $this->user->setPassword(Validation::encryptCredentials($this->getData('username'), $password)); $this->user->setDateRegistered(Core::getCurrentDate()); - Repo::user()->add($this->user); + $userId = Repo::user()->add($this->user); if ($sendNotify) { // Send welcome email to user @@ -398,6 +404,12 @@ public function execute(...$functionParams) } } + // Users can never view/edit their own private notes fields + if (Repo::user()->canCurrentUserGossip($userId)) { + $userPrivateNote = Repo::userPrivateNote()->getUserPrivateNote($userId, $context->getId()); + Repo::userPrivateNote()->edit($userPrivateNote, ['note', $this->getData('userPrivateNote')]); + } + $interestManager = new InterestManager(); $interestManager->setInterestsForUser($this->user, $this->getData('interests')); diff --git a/controllers/grid/users/reviewer/ReviewerGridRow.php b/controllers/grid/users/reviewer/ReviewerGridRow.php index 8873cc3d4bf..e8b4e5191e6 100644 --- a/controllers/grid/users/reviewer/ReviewerGridRow.php +++ b/controllers/grid/users/reviewer/ReviewerGridRow.php @@ -207,10 +207,10 @@ public function initialize($request, $template = null) 'gossip', new AjaxModal( $router->url($request, null, null, 'gossip', null, $actionArgs), - __('user.gossip'), + __('user.notes'), 'modal_information' ), - __('user.gossip'), + __('user.notes'), 'more_info' ) ); diff --git a/controllers/grid/users/reviewer/form/ReviewerGossipForm.php b/controllers/grid/users/reviewer/form/ReviewerGossipForm.php index 71750ea0e80..7729f349d93 100644 --- a/controllers/grid/users/reviewer/form/ReviewerGossipForm.php +++ b/controllers/grid/users/reviewer/form/ReviewerGossipForm.php @@ -20,12 +20,16 @@ use APP\template\TemplateManager; use PKP\form\Form; use PKP\user\User; +use PKP\userPrivateNote\UserPrivateNote; class ReviewerGossipForm extends Form { /** @var User The user to gossip about */ public $_user; + /** @var UserPrivateNote The user's private note */ + public UserPrivateNote $_userPrivateNote; + /** @var array Arguments used to route the form op */ public $_requestArgs; @@ -33,13 +37,15 @@ class ReviewerGossipForm extends Form * Constructor. * * @param User $user The user to gossip about + * @param UserPrivateNote $userPrivateNote The user's private note * @param array $requestArgs Arguments used to route the form op to the * correct submission, stage and review round */ - public function __construct($user, $requestArgs) + public function __construct($user, $userPrivateNote, $requestArgs) { parent::__construct('controllers/grid/users/reviewer/form/reviewerGossipForm.tpl'); $this->_user = $user; + $this->_userPrivateNote = $userPrivateNote; $this->_requestArgs = $requestArgs; $this->addCheck(new \PKP\form\validation\FormValidatorPost($this)); $this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this)); @@ -52,6 +58,7 @@ public function readInputData() { $this->readUserVars([ 'gossip', + 'userPrivateNote', ]); } @@ -66,6 +73,7 @@ public function fetch($request, $template = null, $display = false) $templateMgr->assign([ 'requestArgs' => $this->_requestArgs, 'gossip' => $this->_user->getGossip(), + 'userPrivateNote' => $this->_userPrivateNote->getNote(), ]); return parent::fetch($request, $template, $display); @@ -78,6 +86,7 @@ public function execute(...$functionArgs) { $this->_user->setGossip($this->getData('gossip')); Repo::user()->edit($this->_user); + Repo::userPrivateNote()->edit($this->_userPrivateNote, ['note' => $this->getData('userPrivateNote')]); parent::execute(...$functionArgs); } } diff --git a/locale/en/user.po b/locale/en/user.po index 3e86305a91d..78173d9a963 100644 --- a/locale/en/user.po +++ b/locale/en/user.po @@ -102,13 +102,25 @@ msgstr "Given Name" msgid "user.interests" msgstr "Reviewing interests" -msgid "user.gossip" +msgid "user.notes" msgstr "Editorial Notes" +msgid "user.private.notes" +msgstr "Private Editorial Notes" + +msgid "user.private.notes.description" +msgstr "" +"Private record notes about this reviewer that you would like to make visible " +"to other administrators, managers and all editors. Notes will be visible for " +"future review assignments." + +msgid "user.gossip" +msgstr "Public Editorial Notes" + msgid "user.gossip.description" msgstr "" -"Record notes about this reviewer that you would like to make visible to " -"other administrators, managers and all editors. Notes will be visible for " +"Public record notes about this reviewer that you would like to make visible " +"to other administrators, managers and all editors. Notes will be visible for " "future review assignments." msgid "user.group" diff --git a/locale/fr_CA/user.po b/locale/fr_CA/user.po index 96088b2a6fc..4e9a6ef604e 100644 --- a/locale/fr_CA/user.po +++ b/locale/fr_CA/user.po @@ -113,15 +113,28 @@ msgstr "Prénom" msgid "user.interests" msgstr "Champs d'intérêts pour les évaluations" -msgid "user.gossip" +msgid "user.notes" +msgstr "Notes" + +msgid "user.private.notes" msgstr "Notes privées" +msgid "user.private.notes.description" +msgstr "" +"Enregistrer des renseignements relatifs privés à cet ou cette " +"évaluateur-trice que vous souhaitez partager aux autres " +"administrateurs-trices, directeurs-trices et rédacteurs-trices. " +"Ces informations seront visibles lors de futures évaluations." + +msgid "user.gossip" +msgstr "Notes publiques" + msgid "user.gossip.description" msgstr "" -"Enregistrer des renseignements relatifs à cet ou cette évaluateur-trice que " -"vous souhaitez partager aux autres administrateurs-trices, directeurs-trices " -"et rédacteurs-trices. Ces informations seront visibles lors de futures " -"évaluations." +"Enregistrer des renseignements relatifs publiques à cet ou cette " +"évaluateur-trice que vous souhaitez partager aux autres " +"administrateurs-trices, directeurs-trices et rédacteurs-trices. " +"Ces informations seront visibles lors de futures évaluations." msgid "user.group" msgstr "Groupe d'utilisateurs-trices" diff --git a/schemas/userPrivateNote.json b/schemas/userPrivateNote.json new file mode 100644 index 00000000000..77a62fbd5a1 --- /dev/null +++ b/schemas/userPrivateNote.json @@ -0,0 +1,19 @@ +{ + "title": "UserPrivateNote", + "description": "A private note associated to the user.", + "properties": { + "id": { + "type": "integer", + "readOnly": true + }, + "contextId": { + "type": "integer" + }, + "userId": { + "type": "integer" + }, + "note": { + "type": "string" + } + } +} diff --git a/templates/controllers/grid/settings/user/form/userDetailsForm.tpl b/templates/controllers/grid/settings/user/form/userDetailsForm.tpl index 512cc2d6f1c..a4b122585ed 100644 --- a/templates/controllers/grid/settings/user/form/userDetailsForm.tpl +++ b/templates/controllers/grid/settings/user/form/userDetailsForm.tpl @@ -56,6 +56,9 @@ {fbvFormSection label="user.gossip" description="user.gossip.description"} {fbvElement type="textarea" name="gossip" id="gossip" rich=true value=$gossip} {/fbvFormSection} + {fbvFormSection label="user.private.notes" description="user.private.notes.description"} + {fbvElement type="textarea" name="userPrivateNote" id="userPrivateNote" rich=true value=$userPrivateNote} + {/fbvFormSection} {/if} {/if} diff --git a/templates/controllers/grid/users/reviewer/form/reviewerGossipForm.tpl b/templates/controllers/grid/users/reviewer/form/reviewerGossipForm.tpl index 48a5a0a20e8..711310ce31f 100644 --- a/templates/controllers/grid/users/reviewer/form/reviewerGossipForm.tpl +++ b/templates/controllers/grid/users/reviewer/form/reviewerGossipForm.tpl @@ -16,9 +16,15 @@
{csrf} +

{translate key="user.gossip"}

{fbvFormSection} {fbvElement type="textarea" name="gossip" id="gossip" label="user.gossip.description" rich=true value=$gossip} {/fbvFormSection} +

{translate key="user.private.notes"}

+ {fbvFormSection} + {fbvElement type="textarea" name="userPrivateNote" id="userPrivateNote" label="user.private.notes.description" rich=true value=$userPrivateNote} + {/fbvFormSection} + {fbvFormButtons}