Skip to content

Commit

Permalink
Merge pull request #908 from UN-OCHA/feature/RW-1058
Browse files Browse the repository at this point in the history
RW 1058: User posting rights for reports
  • Loading branch information
attiks authored Dec 5, 2024
2 parents 6e04c13 + 2cc3119 commit e19ea14
Show file tree
Hide file tree
Showing 44 changed files with 2,031 additions and 192 deletions.
2 changes: 2 additions & 0 deletions .docksal/docksal-local.env
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
MYSQL_PORT_MAPPING='3380:3306'
XDEBUG_ENABLED=1
XDEBUG_MODE=coverage
2 changes: 1 addition & 1 deletion .docksal/docksal.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DOCKSAL_STACK=default
DOCROOT=html
DB_IMAGE="docksal/mariadb:10.6"
CLI_IMAGE='docksal/cli:php8.2-build'
CLI_IMAGE='docksal/cli:php8.3-build'
2 changes: 2 additions & 0 deletions .docksal/etc/mysql/my.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mysqld]
innodb_force_recovery=0
3 changes: 1 addition & 2 deletions .docksal/etc/php/php.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
; Mail settings
sendmail_path = '/usr/bin/msmtp -t --host=mail --port=1025 --read-envelope-from'
xdebug.mode=coverage
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ jobs:
docker compose -f tests/docker-compose.yml exec -T drupal chmod -R 777 /srv/www/html/sites/default/files /srv/www/html/sites/default/private
docker compose -f tests/docker-compose.yml exec -T drupal mkdir -p /srv/www/html/build/logs
docker compose -f tests/docker-compose.yml exec -T drupal chmod -R 777 /srv/www/html/build/logs
docker compose -f tests/docker-compose.yml exec -T -w /srv/www -e XDEBUG_MODE=coverage -e BROWSERTEST_OUTPUT_DIRECTORY=/srv/www/html/sites/default/files/browser_output -e DTT_BASE_URL=http://127.0.0.1 drupal ./vendor/bin/phpunit --coverage-clover /srv/www/html/build/logs/clover.xml --debug
docker compose -f tests/docker-compose.yml exec -T -w /srv/www -e XDEBUG_MODE=coverage -e BROWSERTEST_OUTPUT_DIRECTORY=/srv/www/html/sites/default/files/browser_output -e SIMPLETEST_BASE_URL=http://127.0.0.1 -e DTT_BASE_URL=http://127.0.0.1 drupal ./vendor/bin/phpunit --coverage-clover /srv/www/html/build/logs/clover.xml --debug
docker cp "$(docker compose -f tests/docker-compose.yml ps -q drupal)":/srv/www/html/build/logs/clover.xml .
env:
fail-fast: true
Expand Down
2 changes: 1 addition & 1 deletion config/filter.format.markdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ filters:
status: false
weight: 0
settings:
replace_empty: '0'
replace_empty: 0
14 changes: 14 additions & 0 deletions config/system.action.user_add_role_action.contributor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
uuid: 8a1c6d2a-cf03-48d7-9dc8-ba12b7239159
langcode: en
status: true
dependencies:
config:
- user.role.contributor
module:
- user
id: user_add_role_action.contributor
label: 'Add the Contributor role role to the selected user(s)'
type: user
plugin: user_add_role_action
configuration:
rid: contributor
14 changes: 14 additions & 0 deletions config/system.action.user_remove_role_action.contributor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
uuid: e0c6f28b-74a1-41a4-a75f-b9c12fe98676
langcode: en
status: true
dependencies:
config:
- user.role.contributor
module:
- user
id: user_remove_role_action.contributor
label: 'Remove the Contributor role role from the selected user(s)'
type: user
plugin: user_remove_role_action
configuration:
rid: contributor
2 changes: 0 additions & 2 deletions config/user.role.authenticated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ langcode: en
status: true
dependencies:
config:
- filter.format.markdown
- filter.format.markdown_editor
- filter.format.token_markdown
- node.type.job
Expand Down Expand Up @@ -49,7 +48,6 @@ permissions:
- 'subscribe to jobs'
- 'subscribe to training'
- 'use enhanced input forms'
- 'use text format markdown'
- 'use text format markdown_editor'
- 'use text format token_markdown'
- 'view media'
Expand Down
27 changes: 27 additions & 0 deletions config/user.role.contributor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
uuid: 42dacb5f-f71a-4e37-858e-d4cf46729e39
langcode: en
status: true
dependencies:
config:
- media.type.image_report
- node.type.report
module:
- media
- node
- reliefweb_files
- reliefweb_form
id: contributor
label: Contributor
weight: 9
is_admin: null
permissions:
- 'access reliefweb private files'
- 'create image_report media'
- 'create report content'
- 'delete media'
- 'delete own image_report media'
- 'display source attention messages'
- 'edit own image_report media'
- 'edit own report content'
- 'update media'
- 'view own unpublished media'
1 change: 1 addition & 0 deletions config/user.role.user_manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ permissions:
- 'administer subscriptions'
- 'administer users'
- 'assign beta_tester role'
- 'assign contributor role'
- 'assign editor role'
- 'assign external_disaster_manager role'
- 'assign job_importer role'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function reliefweb_entities_entity_bundle_field_info_alter(array &$fields, Entit
* Add bundle classes to nodes and terms to handle business logic.
*/
function reliefweb_entities_entity_bundle_info_alter(&$bundles) {
$namespace = '\Drupal\reliefweb_entities\Entity';
$namespace = 'Drupal\reliefweb_entities\Entity';

foreach ($bundles as $entity_type_id => $items) {
foreach ($items as $bundle => $info) {
Expand All @@ -96,6 +96,8 @@ function reliefweb_entities_entity_bundle_info_alter(&$bundles) {
}
if ($class !== FALSE && is_subclass_of($class, BundleEntityInterface::class)) {
$label = ucwords(str_replace(['_', '-'], ' ', $bundle));
// No leading \ otherwise EntityTypeRepository::getEntityTypeFromClass
// fails.
$bundles[$entity_type_id][$bundle]['class'] = $class;
$bundles[$entity_type_id][$bundle]['label'] = $label;
}
Expand Down
39 changes: 39 additions & 0 deletions html/modules/custom/reliefweb_entities/src/DocumentTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
Expand Down Expand Up @@ -394,4 +395,42 @@ protected function updateSourceModerationStatus() {
}
}

/**
* Update the status to refused if any of the sources is blocked.
*/
protected function updateModerationStatusFromSourceStatus() {
if (!$this->hasField('field_source') || $this->field_source->isEmpty()) {
return;
}

$blocked = [];
foreach ($this->field_source as $item) {
$source = $item->entity;
if (empty($source) || !($source instanceof Source)) {
continue;
}

if ($source->getModerationStatus() === 'blocked') {
$blocked[] = $source->label();
}
}

if (!empty($blocked)) {
$this->setModerationStatus('refused');

// Add a message to the revision log.
if ($this instanceof RevisionLogInterface) {
$message = 'Submissions from "' . implode('", "', $blocked) . '" are no longer allowed.';

$log = $this->getRevisionLogMessage();
if (empty($log)) {
$this->setRevisionLogMessage($message);
}
else {
$this->setRevisionLogMessage($message . ' ' . $log);
}
}
}
}

}
111 changes: 108 additions & 3 deletions html/modules/custom/reliefweb_entities/src/Entity/Report.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
use Drupal\reliefweb_entities\DocumentTrait;
use Drupal\reliefweb_moderation\EntityModeratedInterface;
use Drupal\reliefweb_moderation\EntityModeratedTrait;
use Drupal\reliefweb_moderation\Helpers\UserPostingRightsHelper;
use Drupal\reliefweb_revisions\EntityRevisionedInterface;
use Drupal\reliefweb_revisions\EntityRevisionedTrait;
use Drupal\reliefweb_utility\Helpers\DateHelper;
use Drupal\reliefweb_utility\Helpers\ReliefWebStateHelper;
use Drupal\reliefweb_utility\Helpers\TaxonomyHelper;
use Drupal\reliefweb_utility\Helpers\UrlHelper;
use Drupal\reliefweb_utility\Helpers\UserHelper;

/**
* Bundle class for report nodes.
Expand Down Expand Up @@ -210,8 +213,6 @@ public function getAttachments(?array $build = NULL) {
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);

// Change the publication date if bury is selected, to the original
// publication date.
if (!empty($this->field_bury->value) && !$this->field_original_publication_date->isEmpty()) {
Expand Down Expand Up @@ -249,13 +250,21 @@ public function preSave(EntityStorageInterface $storage) {
'@date' => DateHelper::format($this->field_embargo_date->value, 'custom', 'd M Y H:i e'),
]);

$log = trim($this->getRevisionLogMessage());
$log = !empty($this->getRevisionLogMessage()) ? trim($this->getRevisionLogMessage()) : '';
$log = $message . (!empty($log) ? "\n" . $log : '');
$this->setRevisionLogMessage($log);
}

// Prepare notifications.
$this->preparePublicationNotification();

// Update the entity status based on the user posting rights.
$this->updateModerationStatusFromPostingRights();

// Update the entity status based on the source(s) moderation status.
$this->updateModerationStatusFromSourceStatus();

parent::preSave($storage);
}

/**
Expand Down Expand Up @@ -353,6 +362,102 @@ protected function sendPublicationNotification() {
->mail('reliefweb_entities', 'report_publication_notification', $to, $langcode, $parameters, $from, TRUE);
}

/**
* Update the status for the entity based on the user posting rights.
*/
protected function updateModerationStatusFromPostingRights() {
// In theory the revision user here, is the current user saving the entity.
/** @var \Drupal\user\UserInterface|null $user */
$user = $this->getRevisionUser();
$status = $this->getModerationStatus();

// Skip if there is no revision user. That should normally not happen with
// new content but some old revisions may reference users that don't exist
// anymore (which should not happen either but...).
if (empty($user)) {
return;
}

// For non editors, we determine the real status based on the user
// posting rights for the selected sources.
if (!UserHelper::userHasRoles(['editor'], $user) && in_array($status, ['to-review'])) {
// Retrieve the list of sources and check the user rights.
if (!$this->field_source->isEmpty()) {
// Extract source ids.
$sources = [];
foreach ($this->field_source as $item) {
if (!empty($item->target_id)) {
$sources[] = $item->target_id;
}
}

// Get the user's posting right for the document.
$rights = [];
foreach (UserPostingRightsHelper::getUserPostingRights($user, $sources) as $tid => $data) {
$rights[$data[$this->bundle()] ?? 0][] = $tid;
}

// Blocked for some sources.
if (!empty($rights[1])) {
$status = 'refused';
}
// Trusted for all the sources.
elseif (isset($rights[3]) && count($rights[3]) === count($sources)) {
$status = 'published';
}
// Trusted for at least 1.
elseif (isset($rights[3]) && count($rights[3]) > 0) {
$status = 'to-review';
}
// Allowed for all the sources.
elseif (isset($rights[2]) && count($rights[2]) === count($sources)) {
$status = 'to-review';
}
// Unverified for some sources.
else {
$status = 'pending';
}

$this->setModerationStatus($status);

// Add messages indicating the posting rights for easier review.
$message = '';
if (!empty($rights[1])) {
$message = trim($message . strtr(' Blocked user for @sources.', [
'@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[1])),
]));
}
if (!empty($rights[0])) {
$message = trim($message . strtr(' Unverified user for @sources.', [
'@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[0])),
]));
}
if (!empty($rights[2])) {
$message = trim($message . strtr(' Allowed user for @sources.', [
'@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[2])),
]));
}
if (!empty($rights[3])) {
$message = trim($message . strtr(' Trusted user for @sources.', [
'@sources' => implode(', ', TaxonomyHelper::getSourceShortnames($rights[3])),
]));
}

// Update the log message.
if (!empty($message)) {
$revision_log_field = $this->getEntityType()
->getRevisionMetadataKey('revision_log_message');

if (!empty($revision_log_field)) {
$log = trim($this->{$revision_log_field}->value ?? '');
$log = $message . (!empty($log) ? ' ' . $log : '');
$this->{$revision_log_field}->value = $log;
}
}
}
}
}

/**
* Temporarily store the email address to notify after publication.
*
Expand Down
26 changes: 14 additions & 12 deletions html/modules/custom/reliefweb_entities/src/Entity/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,18 +146,20 @@ protected function getOrganizationDetails() {
$meta = [];

// Organization type.
$type = $this->field_organization_type->entity->label();
$meta['type'] = [
'type' => 'link',
'label' => $this->t('Organization type'),
'value' => [
'url' => RiverServiceBase::getRiverUrl('source', [
'search' => 'type.exact:"' . $type . '"',
]),
'title' => $type,
'external' => FALSE,
],
];
if ($this->hasField('field_organization_type') && !$this->get('field_organization_type')->isEmpty()) {
$type = $this->field_organization_type->entity->label();
$meta['type'] = [
'type' => 'link',
'label' => $this->t('Organization type'),
'value' => [
'url' => RiverServiceBase::getRiverUrl('source', [
'search' => 'type.exact:"' . $type . '"',
]),
'title' => $type,
'external' => FALSE,
],
];
}

// Headquarters.
$countries = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ public static function retrievePotentialNewSourceInformation(EntityModeratedInte
}

/**
* Add the user information to a job/training node form.
* Add the user information to a job/training/report node form.
*
* @param array $form
* The entity form.
Expand All @@ -594,7 +594,7 @@ protected function addUserInformation(array &$form, FormStateInterface $form_sta
$entity_id = $entity->id();
$bundle = $entity->bundle();

// It's only for jobs and training.
// It's only for jobs and trainings.
if (!in_array($bundle, ['job', 'training'])) {
return;
}
Expand Down
Loading

0 comments on commit e19ea14

Please sign in to comment.