Skip to content

Commit

Permalink
Moderate attributes (#402)
Browse files Browse the repository at this point in the history
* add multiedit support for entity attributes

* re-implement simple migration handling

* update i18n

* fix keys

---------

Signed-off-by: Vinzenz Rosenkranz <[email protected]>
  • Loading branch information
v1r0x authored Oct 9, 2023
1 parent 24a806d commit 895d13f
Show file tree
Hide file tree
Showing 41 changed files with 961 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## 0.10 - Jelling
### Added
- Support Multiediting attributes across multiple entities (through ...-menu in entity tree)
- Support moderated roles/users that require a privileged user to accept/deny their changes in entity forms
### Changed
- Entity tree sorting is now accessible through ...-menu

Expand Down
7 changes: 7 additions & 0 deletions app/AttributeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Model;
use MStaack\LaravelPostgis\Eloquent\PostgisTrait;
use App\Traits\CommentTrait;
use App\Traits\ModerationTrait;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
use Spatie\Searchable\Searchable;
Expand All @@ -17,6 +18,7 @@ class AttributeValue extends Model implements Searchable
{
use PostgisTrait;
use CommentTrait;
use ModerationTrait;
use LogsActivity;

protected $table = 'attribute_values';
Expand Down Expand Up @@ -57,6 +59,11 @@ class AttributeValue extends Model implements Searchable
'geography_val',
];

protected $copyOn = [
'entity_id',
'attribute_id',
];

const patchRules = [
'certainty' => 'integer|between:0,100',
];
Expand Down
125 changes: 119 additions & 6 deletions app/Http/Controllers/EntityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,14 @@ public function getData($id, $aid = null) {
})
->where('entity_id', $id)
->where('attribute_id', $aid)
->withModerated()
->get();
} else {
$attributes = AttributeValue::whereHas('attribute', function(Builder $q) {
$q->where('datatype', '!=', 'sql');
})
->where('entity_id', $id)
->withModerated()
->get();
}

Expand All @@ -215,16 +217,34 @@ public function getData($id, $aid = null) {
$a->name = Entity::find($a->entity_val)->name;
break;
case 'entity-mc':
$value = [];
$names = [];
foreach(json_decode($a->json_val) as $dec) {
$value[] = Entity::find($dec)->name;
$names[] = Entity::find($dec)->name;
}
$a->name = $value;
$a->name = $names;
break;
default:
break;
}
$a->value = $a->getValue();
$value = $a->getValue();
if($a->moderation_state == 'pending-delete') {
$a->value = [];
$a->original_value = $value;
} else {
$a->value = $value;
}
if(isset($data[$a->attribute_id])) {
$oldAttr = $data[$a->attribute_id];
// check if stored entry is moderated one
// if so, add current value as original value
// otherwise, set stored entry as original value
if(isset($oldAttr->moderation_state)) {
$oldAttr->original_value = $value;
$a = $oldAttr;
} else {
$a->original_value = $oldAttr->value;
}
}
$data[$a->attribute_id] = $a;
}

Expand Down Expand Up @@ -540,20 +560,54 @@ public function patchAttributes($id, Request $request) {
['entity_id', '=', $id],
['attribute_id', '=', $aid]
])->first();
$attrval->delete();
if(!isset($attrval)) {
return response()->json([
'error' => __('This attribute value does either not exist or is in moderation state.')
], 400);
}
if($user->isModerated()) {
$attrval->moderate('pending-delete', true);
} else {
$attrval->delete();
}
break;
case 'add':
$alreadyAdded = AttributeValue::where('entity_id', $id)
->where('attribute_id', $aid)
->withModerated()
->exists();
if($alreadyAdded) {
return response()->json([
'error' => __('There is already a value set for this attribute or it is in moderation state.')
], 400);
}
$value = $patch['value'];
$attrval = new AttributeValue();
$attrval->entity_id = $id;
$attrval->attribute_id = $aid;
if($user->isModerated()) {
$attrval->moderate('pending', true, true);
}
break;
case 'replace':
$alreadyModerated = AttributeValue::where('entity_id', $id)
->where('attribute_id', $aid)
->onlyModerated()
->exists();
if($alreadyModerated) {
return response()->json([
'error' => __('This attribute value is in moderation state. A user with appropriate permissions has to accept or deny it first.')
], 400);
}
$value = $patch['value'];
$attrval = AttributeValue::where([
['entity_id', '=', $id],
['attribute_id', '=', $aid]
])->first();
if($user->isModerated()) {
$attrval = $attrval->moderate('pending', false, true);
unset($attrval->comments_count);
}
break;
default:
return response()->json([
Expand All @@ -572,7 +626,7 @@ public function patchAttributes($id, Request $request) {
'error' => $ide->getMessage(),
], 422);
}
$attrval->{$formKeyValue->col} = $formKeyValue->val;
$attrval->{$formKeyValue->key} = $formKeyValue->val;
$attrval->user_id = $user->id;
$attrval->save();
}
Expand Down Expand Up @@ -688,6 +742,65 @@ public function multieditAttributes(Request $request) {
return response()->json(null, 204);
}

public function handleModeration($id, $aid, Request $request) {
$user = auth()->user();
if(!$user->can('entity_data_write') || $user->isModerated()) {
return response()->json([
'error' => __('You do not have the permission to modify an entity\'s data')
], 403);
}
$this->validate($request, [
'action' => 'required|string|mod_action',
'value' => 'nullable',
]);

$action = $request->get('action');

try {
Entity::findOrFail($id);
} catch(ModelNotFoundException $e) {
return response()->json([
'error' => __('This entity does not exist')
], 400);
}
try {
$attribute = Attribute::findOrFail($aid);
} catch(ModelNotFoundException $e) {
return response()->json([
'error' => __('This attribute does not exist')
], 400);
}

$attrValue = AttributeValue::where('entity_id', $id)
->where('attribute_id', $aid)
->onlyModerated()
->first();

if(!isset($attrValue)) {
return response()->json([
'error' => __('This attribute value does not exist')
], 400);
}

$attrValue->moderate($action);

$editedValue = $request->get('value');
if(isset($editedValue) && $action == 'accept') {
try {
$formKeyValue = AttributeValue::getFormattedKeyValue($attribute->datatype, $editedValue);
} catch(InvalidDataException $ide) {
return response()->json([
'error' => $ide->getMessage(),
], 422);
}
$attrValue->{$formKeyValue->key} = $formKeyValue->val;
$attrValue->user_id = $user->id;
$attrValue->save();
}

return response()->json(null, 204);
}

public function patchName($id, Request $request) {
$user = auth()->user();
if(!$user->can('entity_write')) {
Expand Down
4 changes: 4 additions & 0 deletions app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ public function patchRole(Request $request, $id) {
}
$this->validate($request, [
'permissions' => 'array',
'is_moderated' => 'boolean',
'display_name' => 'string',
'description' => 'string'
]);
Expand All @@ -408,6 +409,9 @@ public function patchRole(Request $request, $id) {
// Update updated_at column
$role->touch();
}
if($request->has('is_moderated')) {
$role->is_moderated = $request->get('is_moderated');
}
if($request->has('display_name')) {
$role->display_name = $request->get('display_name');
}
Expand Down
9 changes: 7 additions & 2 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\View;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\DB;

class AppServiceProvider extends ServiceProvider
{
Expand All @@ -20,8 +21,8 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
\DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('geography', 'string');
\DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('geometry', 'string');
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('geography', 'string');
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('geometry', 'string');

Paginator::useBootstrap();

Expand Down Expand Up @@ -75,6 +76,10 @@ public function boot()
}
return true;
});
Validator::extend('mod_action', function($attribute, $value, $parameters, $validator) {
$lowVal = strtolower($value);
return $lowVal == 'accept' || $lowVal == 'deny';
});
Validator::extend('bibtex_type', function ($attribute, $value, $parameters, $validator) {
return in_array($value, array_keys(Bibliography::bibtexTypes));
});
Expand Down
4 changes: 4 additions & 0 deletions app/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public function getActivitylogOptions() : LogOptions
->logOnlyDirty();
}

public function isModerated() : bool {
return !empty($this->is_moderated);
}

public function derived() {
return $this->hasOne('App\RolePreset', 'id', 'derived_from');
}
Expand Down
2 changes: 1 addition & 1 deletion app/Traits/CommentTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ public function addComment($data, $user = null, $notify = true, $resourceMetadat
}

public function comments() {
return $this->morphMany('App\Comment', 'commentable')->orderBy('id');
return $this->morphMany(Comment::class, 'commentable')->orderBy('id');
}
}
Loading

0 comments on commit 895d13f

Please sign in to comment.