Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
919032b
update dependencies
v1r0x Mar 25, 2025
7827b33
update changelog with proposed new name
v1r0x Mar 25, 2025
13f6459
add dismiss functionality to alert
v1r0x Mar 25, 2025
81722b1
make map component compatible with ol 10+ again
v1r0x Jun 4, 2025
5411302
add access points to allow plugins to define restricted views
v1r0x Jun 27, 2025
7e03401
improve logic in app.vue and appview.vue
v1r0x Jul 1, 2025
00fc201
rework logic and plugin exports so plugins can set system-wide data
v1r0x Jul 1, 2025
ab9bb52
Merge remote-tracking branch 'origin/release-0.12-rebased' into 0.12-…
v1r0x Sep 15, 2025
e1dc253
remove merge artifacts
v1r0x Sep 15, 2025
17de921
fix last editor not shown; fix login
v1r0x Sep 16, 2025
cce934a
fix start and end of transaction
v1r0x Sep 17, 2025
620445a
fix attribute list padding
v1r0x Sep 17, 2025
1d9bbfa
fix serialize of geodata
v1r0x Sep 17, 2025
5d11258
restructure accesspoints
v1r0x Sep 17, 2025
92b650b
fix tests
v1r0x Sep 17, 2025
c4e7d72
fix wrong col in bibliography seeder
v1r0x Sep 17, 2025
de8e84b
fix indent
v1r0x Sep 17, 2025
7467bd9
fix tests
v1r0x Sep 17, 2025
0fa6ed0
more test fixes
v1r0x Sep 17, 2025
63d2a9a
fix adding accesspoints on user create; fix redirect with no
v1r0x Sep 19, 2025
7a36b4d
fix login errors
v1r0x Oct 9, 2025
6924d51
add missing import
v1r0x Oct 10, 2025
9856326
fix style
v1r0x Oct 10, 2025
a613bc5
move entity dependency logic store
v1r0x Oct 10, 2025
a255680
fix getting attribute datatype
v1r0x Oct 17, 2025
8d13129
small fixes
v1r0x Oct 17, 2025
a2d2754
set appInitialized in extra step to prevent wrong components from
v1r0x Oct 20, 2025
f8a8262
add transparent border utility class
v1r0x Oct 20, 2025
f985076
allow patching attributes outside of controller
v1r0x Oct 24, 2025
fcd9978
allow plugins to define global scopes
v1r0x Oct 24, 2025
1741442
introduce some more icons
v1r0x Oct 24, 2025
a0269ad
make sure refresh session api endpoint gets not deleted
v1r0x Oct 27, 2025
9aca0d8
move refresh session to usercontroller
v1r0x Oct 27, 2025
21f2a81
move getscopes method to plugin
v1r0x Oct 28, 2025
a269627
Merge branch 'release-0.12-rebased' into 0.12-feat-accesspoints
v1r0x Oct 28, 2025
40998c7
Remove unnecessary getScopes.
Severino Oct 29, 2025
a393b78
[0.12] Moved Plugin Scopes To Trait, Added Caching And Added TestCase…
Severino Oct 30, 2025
7ea0dca
[0.12] Moved the AccesPointUtils into a Service. (#590)
Severino Dec 4, 2025
0a86190
fix temporary patch code
v1r0x Dec 4, 2025
c450d20
Fixed multicall for fetchUser and moved it to userstore
Severino Dec 10, 2025
bcc6edb
fix coding style
v1r0x Dec 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ All notable changes to this project will be documented in this file.

## 0.12 - (Luxor)
### Added
- Access Points (Plugins can define additional access points to restrict user access to certain parts of the plugin)
- Plugin-System now supports custom components, e.g. attribute types
- Plugin-System now supports PluginScopes
- .env variable `ALLOW_FILESYSTEM_MIGRATIONS` to explcitly enable filesystem migrations
- API endpoint for getting all entity details data in one request: GET::v1/entity/{id}/entity_detail
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion app/AttributeTypes/GeographyAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static function unserialize(mixed $data): mixed {

public static function serialize(mixed $data): mixed {
if($data instanceof Geometry) {
return $data;
return Geodata::toWKT($data);
} else {
return Geodata::wkb2wkt($data);
}
Expand Down
100 changes: 98 additions & 2 deletions app/AttributeValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

use App\Geodata;
use App\AttributeTypes\AttributeBase;
use App\Exceptions\InvalidDataException;
use Illuminate\Database\Eloquent\Model;
use Clickbar\Magellan\Data\Geometries\Geometry;
use App\Traits\CommentTrait;
use App\Traits\ModerationTrait;
use Clickbar\Magellan\Data\Geometries\Geometry;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
use Spatie\Searchable\Searchable;
Expand Down Expand Up @@ -87,6 +88,101 @@ public function getValue() {
return AttributeBase::serializeValue($this);
}

// [VR] Temporary solution to allow patching entities/attribute values outside of controller
// But this is also already fixed (in a different way) on pull/585 (0.11.2-fix-attribute-value-list-error)
public static function handlePatch(int $entity_id, int $attribute_id, mixed $value, string $operation, User $user, array &$added = [], array &$deleted = []) {
$error = null;
$code = 400;
switch($operation) {
case 'remove':
$attrval = AttributeValue::where([
['entity_id', '=', $entity_id],
['attribute_id', '=', $attribute_id],
])->first();
if(!isset($attrval)) {
$error = __('This attribute value does either not exist or is in moderation state.');
break;
}
if($user->isModerated()) {
$attrval->moderate('pending-delete', true);
} else {
$deleted[$attribute_id] = $attrval;
$attrval->delete();
}
break;

/**
* In the case when a user created the attribute, while another was visiting the
* page and sends an 'add' operation, and the other user also sends their changes,
* the application would have thrown an error, that the attribute was already created.
*
* That's why we combined the add and replace operations into one case.
* [SO] 29.01.2025
*/
case 'add':
case 'replace':
$alreadyModerated = AttributeValue::where('entity_id', $entity_id)
->where('attribute_id', $attribute_id)
->onlyModerated()
->exists();

// Currently the logic is that a moderated state cannot be changed
// by a moderated user.
if($alreadyModerated && $user->isModerated()) {
$error = __('This attribute value is in moderation state. A user with appropriate permissions has to accept or deny it first.');
break;
}
$attrval = AttributeValue::firstOrNew([
'entity_id' => $entity_id,
'attribute_id' => $attribute_id,
], [
'certainty' => null
]);
if($user->isModerated()) {
$attrval = $attrval->moderate('pending', false, true);
unset($attrval->comments_count);
}
break;
default:
$error = __('Unknown operation');
}

if($error !== null) {
return [
'message' => $error,
'code' => $code,
];
}

// no further action required for deleted attribute values, continue with next patch
if($operation == 'remove') {
return false;
}

try {
$attr = Attribute::findOrFail($attribute_id);
$formKeyValue = AttributeValue::getFormattedKeyValue($attr->datatype, $value);
} catch(InvalidDataException $ide) {
return [
'message' => $ide->getMessage(),
'code' => 422,
];
}

$attrval->{$formKeyValue->key} = $formKeyValue->val;
$attrval->user_id = $user->id;
$attrval->save();

// As we cannot ensure that the 'add' is correct,
// we use this laravel option to ensure the attribute
// was created and not replaced.
if($attrval->wasRecentlyCreated) {
$added[$attribute_id] = $attrval;
}

return false;
}

public static function getValueFromKey($arr) {
if(!isset($arr)) return null;

Expand Down
7 changes: 5 additions & 2 deletions app/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Exceptions\AmbiguousValueException;
use App\Import\EntityImporter;
use App\Traits\CommentTrait;
use App\Traits\HasPluginScopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -25,6 +26,7 @@ class Entity extends Model implements Searchable {
use CommentTrait;
use SearchableTrait;
use LogsActivity;
use HasPluginScopes;

/**
* The attributes that are assignable.
Expand Down Expand Up @@ -98,6 +100,7 @@ public static function getSearchCols(): array {
return array_keys(self::searchCols);
}


public static function getFromPath($path, $delimiter = "\\\\"): ?int {
if(!isset($path)) {
return null;
Expand Down Expand Up @@ -215,7 +218,7 @@ public static function create($fields, $entityTypeId, $user, $rootEntityId = nul
$entity->save();

// TODO workaround to get all (optional, not part of request) attributes
$entity = self::find($entity->id);
$entity = self::withoutGlobalScopes()->find($entity->id);
AttributeBase::onCreateHandler($entity, $user);
$entity->children_count = 0;

Expand Down Expand Up @@ -269,7 +272,7 @@ private function moveOrFail(int | null $parentId) {
}
return $query;
}

public function move($parentId, $rank, $user) {
if($rank == null){
if(isset($parentId)) {
Expand Down
103 changes: 13 additions & 90 deletions app/Http/Controllers/EntityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -831,15 +831,14 @@ public function patchAttributes($id, Request $request) {
], 403);
}

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

DB::beginTransaction();
$addedAttributes = [];
$removedAttributes = [];

Expand All @@ -851,95 +850,19 @@ public function patchAttributes($id, Request $request) {
], 204);
}

DB::beginTransaction();

foreach($request->request as $patch) {
$op = $patch['op'];
$aid = $patch['params']['aid'];
$error = null;
switch($op) {
case 'remove':
$attrval = AttributeValue::where([
['entity_id', '=', $id],
['attribute_id', '=', $aid],
])->first();
if(!isset($attrval)) {
$error = __('This attribute value does either not exist or is in moderation state.');
break;
}
if($user->isModerated()) {
$attrval->moderate('pending-delete', true);
} else {
$removedAttributes[$aid] = $attrval;
$attrval->delete();
}
break;

/**
* In the case when a user created the attribute, while another was visiting the
* page and sends an 'add' operation, and the other user also sends his changes,
* the application would have thrown an error, that the attribute was already created.
*
* That's why we combined the add and replace operations into one case.
* [SO] 29.01.2025
*/
case 'add':
case 'replace':
$alreadyModerated = AttributeValue::where('entity_id', $id)
->where('attribute_id', $aid)
->onlyModerated()
->exists();

// Currently the logic is that a moderated state cannot be changed
// by a moderated user.
if($alreadyModerated && $user->isModerated()) {
$error = __('This attribute value is in moderation state. A user with appropriate permissions has to accept or deny it first.');
break;
}
$value = $patch['value'];
$attrval = AttributeValue::firstOrNew([
'entity_id' => $id,
'attribute_id' => $aid,
], [
'certainty' => null
]);
if($user->isModerated()) {
$attrval = $attrval->moderate('pending', false, true);
unset($attrval->comments_count);
}
break;
default:
$error = __('Unknown operation');
}

if($error !== null) {
DB::rollBack();
// FIXME [VR]: `?? null` is only necessary, because of temporary AttributeValue::handlePatch() implementation
$value = $patch['value'] ?? null;
$error = AttributeValue::handlePatch($id, $aid, $value, $op, $user, $addedAttributes, $removedAttributes);
if($error !== false) {
DB::rollback();
return response()->json([
'error' => $error,
], 400);
}

// no further action required for deleted attribute values, continue with next patch
if($op == 'remove') {
continue;
}

try {
$attr = Attribute::findOrFail($aid);
$formKeyValue = AttributeValue::getFormattedKeyValue($attr->datatype, $value);
} catch(InvalidDataException $ide) {
return response()->json([
'error' => $ide->getMessage(),
], 422);
}

$attrval->{$formKeyValue->key} = $formKeyValue->val;
$attrval->user_id = $user->id;
$attrval->save();

// As we cannot ensure that the 'add' is correct,
// we use this laravel option to ensure the attribute
// was created and not replaced.
if($attrval->wasRecentlyCreated) {
$addedAttributes[$aid] = $attrval;
'error' => $error['message'],
], $error['code']);
}
}

Expand All @@ -948,7 +871,7 @@ public function patchAttributes($id, Request $request) {
$entity->user_id = $user->id;
if($entity->isDirty()) {
$entity->save();
}else{
} else {
$entity->touch();
}

Expand All @@ -971,14 +894,14 @@ public function patchAttribute($id, $aid, Request $request) {
}
$this->validate($request, AttributeValue::patchRules);

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