diff --git a/demo/app/Sharp/Entities/PostBlockEntity.php b/demo/app/Sharp/Entities/PostBlockEntity.php
index 2d5c3891a..65887937e 100644
--- a/demo/app/Sharp/Entities/PostBlockEntity.php
+++ b/demo/app/Sharp/Entities/PostBlockEntity.php
@@ -4,9 +4,6 @@
use App\Sharp\Posts\Blocks\PostBlockList;
use App\Sharp\Posts\Blocks\PostBlockPolicy;
-use App\Sharp\Posts\Blocks\PostBlockTextForm;
-use App\Sharp\Posts\Blocks\PostBlockVideoForm;
-use App\Sharp\Posts\Blocks\PostBlockVisualsForm;
use Code16\Sharp\Utils\Entities\SharpEntity;
class PostBlockEntity extends SharpEntity
@@ -14,13 +11,4 @@ class PostBlockEntity extends SharpEntity
protected ?string $list = PostBlockList::class;
protected ?string $policy = PostBlockPolicy::class;
protected string $label = 'Block';
-
- public function getMultiforms(): array
- {
- return [
- 'text' => [PostBlockTextForm::class, 'Text block'],
- 'visuals' => [PostBlockVisualsForm::class, 'Visuals block'],
- 'video' => [PostBlockVideoForm::class, 'Video block'],
- ];
- }
}
diff --git a/demo/app/Sharp/Entities/PostBlockTextEntity.php b/demo/app/Sharp/Entities/PostBlockTextEntity.php
new file mode 100644
index 000000000..2dbde053e
--- /dev/null
+++ b/demo/app/Sharp/Entities/PostBlockTextEntity.php
@@ -0,0 +1,11 @@
+configureMultiformAttribute('type')
+ $this
+ ->configureEntityMap(
+ attribute: 'type',
+ entities: EntityListEntities::make()
+ ->addEntity('text', PostBlockTextEntity::class)
+ ->addEntity('video', PostBlockVideoEntity::class)
+ ->addEntity('visuals', PostBlockVisualsEntity::class),
+ )
->configureReorderable(new SimpleEloquentReorderHandler(PostBlock::class))
->configureQuickCreationForm();
}
diff --git a/demo/app/Sharp/Posts/Blocks/PostBlockVisualsShow.php b/demo/app/Sharp/Posts/Blocks/PostBlockVisualsShow.php
new file mode 100644
index 000000000..18e06f07c
--- /dev/null
+++ b/demo/app/Sharp/Posts/Blocks/PostBlockVisualsShow.php
@@ -0,0 +1,62 @@
+addField(
+ SharpShowListField::make('files')
+ ->setLabel('Visuals')
+ ->addItemField(
+ SharpShowFileField::make('file')
+ )
+ ->addItemField(
+ SharpShowTextField::make('legend')
+ ->setLabel('Legend')
+ )
+ );
+ }
+
+ protected function buildShowLayout(ShowLayout $showLayout): void
+ {
+ $showLayout
+ ->addSection(function (ShowLayoutSection $section) {
+ $section
+ ->addColumn(12, function (ShowLayoutColumn $column) {
+ $column
+ ->withListField('files', function (ShowLayoutColumn $item) {
+ $item->withFields('file')
+ ->withField('legend');
+ });
+ });
+ });
+ }
+
+ public function find(mixed $id): array
+ {
+ $postBlock = PostBlock::findOrFail($id);
+
+ return $this
+ ->setCustomTransformer('files', new SharpUploadModelFormAttributeTransformer())
+ ->transform($postBlock);
+ }
+
+ public function delete(mixed $id): void
+ {
+ PostBlock::findOrFail($id)->delete();
+ }
+}
diff --git a/docs/.vitepress/sidebar.ts b/docs/.vitepress/sidebar.ts
index b5e87562e..a8dae5976 100644
--- a/docs/.vitepress/sidebar.ts
+++ b/docs/.vitepress/sidebar.ts
@@ -1,4 +1,4 @@
-import type {DefaultTheme} from "vitepress";
+import type { DefaultTheme } from "vitepress";
export function sidebar(): DefaultTheme.SidebarItem[] {
return [
@@ -26,7 +26,6 @@ export function sidebar(): DefaultTheme.SidebarItem[] {
collapsed: true,
items: [
{ text: 'Create a Form', link: '/guide/building-form.md' },
- { text: 'Multi-Forms', link: '/guide/multiforms.md' },
{ text: 'Using Single Form for unique resources', link: '/guide/single-form.md' },
{ text: 'Quick creation form', link: '/guide/quick-creation-form.md' },
{ text: 'Write an Embed for the Editor field', link: '/guide/form-editor-embeds.md' },
@@ -44,6 +43,7 @@ export function sidebar(): DefaultTheme.SidebarItem[] {
{ text: 'List', link: '/guide/form-fields/list.md' },
{ text: 'AutocompleteList', link: '/guide/form-fields/autocomplete-list.md' },
{ text: 'Geolocation', link: '/guide/form-fields/geolocation.md' },
+ { text: 'Multi-Forms (deprecated)', link: '/guide/multiforms.md' },
// { text: 'Custom form field', link: '/guide/custom-form-fields.md' }
]
},
diff --git a/docs/guide/building-entity-list.md b/docs/guide/building-entity-list.md
index d54f6831c..bc606fe60 100644
--- a/docs/guide/building-entity-list.md
+++ b/docs/guide/building-entity-list.md
@@ -225,7 +225,9 @@ Here is the full list of available methods:
- `configureDefaultSort(string $sortBy, string $sortDir = "asc")`: `EntityListQueryParams $queryParams` will be filled with this default value (see above)
-- `configureMultiformAttribute(string $attribute)`: handle various types of entities; see [detailed doc](multiforms.md)
+- `configureMultiformAttribute(string $attribute)`: :warning: This feature has been deprecated in version 9.6.0 and was replaced by the [Entity Map](entity-map.md) feature. You can still access to the [documentation](multiforms.md) for legacy usage.
+
+- `configureEntityMap(string $attribute, EntityListEntities $entities)`: configure an Entity Map to display multiple entities in a single Entity List; [see detailed section](#entity-map) above.
- `configurePageAlert(string $template, string $alertLevel = null, string $fieldKey = null, bool $declareTemplateAsPath = false)`: display a dynamic message above the list; [see detailed doc](page-alerts.md)
@@ -239,10 +241,65 @@ Here is the full list of available methods:
- `configureCreateButtonLabel(string $label)` to set a custom "New..." button label.
-## Configure the Entity List
+## Declare the Entity List
The Entity List must be declared in the correct entity class, as documented here: [Write an entity](entity-class.md)).
After this we can access the Entity List at the following URL: **/sharp/s-list/products** (replace "products" by our entity key).
To go ahead and learn how to add a link in the Sharp side menu, [look here](building-menu.md).
+
+## Entity Map
+
+::: info
+This feature replaces the deprecated Multiforms functionality, which remains available for legacy use in version 9.x but will be removed in 10.x.
+:::
+
+The Entity Map lets you display multiple entities within a single Entity List. This makes it possible to link different Show Pages or Forms based on a discriminating attribute.
+
+To set it up, declare the Entity Map in the `buildListConfig()` method by using `configureEntityMap()`:
+
+```php
+class CarList extends SharpEntityList
+{
+ // ...
+
+ public function buildListConfig(): void
+ {
+ $this
+ ->configureEntityMap(
+ attribute: 'engine',
+ entities: EntityListEntities::make()
+ ->addEntity('ice', InternalCombustionEngineCarEntity::class)
+ ->addEntity('ev', ElectricEngineCarEntity::class, 'lucide-plug', 'EV')
+ );
+ }
+}
+```
+
+The `attribute` parameter defines which attribute will be used to distinguish between entities. If needed, you can compute this value using a [custom transformer](./how-to-transform-data.md).
+
+The `entities` parameter expects an instance of `EntityListEntities`, which maps each discriminant value to a specific Entity. You can also specify the icon and label that will be displayed in the "Create" dropdown.
+
+Each mapped entity should be declared like any regular Entity, and can include a Show Page, a Form, a Policy, etc.
+
+::: tip
+It’s not mandatory, but a good practice is to make your “sub-entities” extend the main Entity class to share policies or common logic. For example:
+:::
+
+```php
+class CarEntity extends SharpEntity
+{
+ protected ?string $list = CarList::class;
+ protected ?string $policy = CarPolicy::class;
+ protected string $label = 'Car';
+}
+```
+
+```php
+class ElectricEngineCarEntity extends CarEntity
+{
+ protected ?string $form = ElectricEngineCarForm::class;
+ protected string $label = 'EV';
+}
+```
diff --git a/docs/guide/entity-class.md b/docs/guide/entity-class.md
index eddc90b38..ef5fa3f6a 100644
--- a/docs/guide/entity-class.md
+++ b/docs/guide/entity-class.md
@@ -84,11 +84,13 @@ class MyEntity extends SharpEntity
### Single shows and forms
-When you need to configure a "unique" resource that does not fit into a List / Show schema, like for instance an account or a configuration item, you can use a Single Show or Form. This is a dedicated topic, [documented here](single-show.md).
+When you need to configure a "unique" resource that does not fit into a List / Show schema, like an account or a configuration item, you can use a Single Show or Form. This is a dedicated topic, [documented here](single-show.md).
### Handle Multiforms
-Multiforms allows to declare different forms for the same entity, to hanle variants. This is a dedicated topic, [documented here](multiforms.md).
+::: info
+This feature has been deprecated and was replaced in version 9.6.0 by the [Entity Map](./building-entity-list.md#entity-map) feature.
+:::
## Declare the Entity in Sharp configuration
diff --git a/docs/guide/multiforms.md b/docs/guide/multiforms.md
index 739454fc0..12698f049 100644
--- a/docs/guide/multiforms.md
+++ b/docs/guide/multiforms.md
@@ -1,8 +1,12 @@
-# Multi-Forms
+# Multi-Forms (deprecated)
+
+::: warning
+Multi-Forms is a feature that has been deprecated in version 9.6.0 and was replaced by the [Entity Map](./building-entity-list.md#entity-map) feature.
+:::
Let's say you want to handle different variants for an entity in one Entity List.
-For instance, maybe you want to display sold cars on an Entity List: easy enough, you create a `Car` entity, list and form. But you want the to handle different form fields for cars with an internal combustion engine and those with an electric engine; you can of course use a form and [conditional display](building-form.md#conditional-display) to achieve this, but in a case where there are many differences, the best option may be to split the Entity in two (or more) Forms. That's Multi-Form.
+For instance, maybe you want to display sold cars on an Entity List: easy enough, you create a `Car` entity, list and form. But you want to handle different form fields for cars with an internal combustion engine and those with an electric engine; you can of course use a form and [conditional display](building-form.md#conditional-display) to achieve this, but in a case where there are many differences, the best option may be to split the Entity in two (or more) Forms. That's Multi-Form.
## Write the Form classes
diff --git a/resources/js/Layouts/Layout.vue b/resources/js/Layouts/Layout.vue
index 8b48edf9c..abb9c0dd4 100644
--- a/resources/js/Layouts/Layout.vue
+++ b/resources/js/Layouts/Layout.vue
@@ -219,8 +219,8 @@
-
-
+
+
{{ __('sharp::action_bar.color-mode-dropdown.label') }}
@@ -233,7 +233,7 @@
diff --git a/resources/js/Pages/Show/Show.vue b/resources/js/Pages/Show/Show.vue
index 88a06ee60..703498790 100644
--- a/resources/js/Pages/Show/Show.vue
+++ b/resources/js/Pages/Show/Show.vue
@@ -335,7 +335,7 @@
:collapsable="section.collapsable"
:entity-key="entityKey"
:instance-id="instanceId"
- :is-right-col="columnIndex === section.columns.length - 1"
+ :is-right-col="columnIndex > 0 && columnIndex === section.columns.length - 1"
:row="row"
@reordering="onEntityListReordering(fieldLayout.key, $event)"
/>
diff --git a/resources/js/entity-list/EntityList.ts b/resources/js/entity-list/EntityList.ts
index 14d9ecfbb..25f55f146 100644
--- a/resources/js/entity-list/EntityList.ts
+++ b/resources/js/entity-list/EntityList.ts
@@ -6,7 +6,6 @@ import {
EntityStateValueData,
FilterData,
} from "@/types";
-import { getAppendableParentUri, route } from "@/utils/url";
import { EntityListInstance, InstanceId } from "./types";
export class EntityList implements EntityListData {
@@ -14,7 +13,7 @@ export class EntityList implements EntityListData {
config: EntityListData['config'];
data: EntityListData['data'];
fields: EntityListData['fields'];
- forms: EntityListData['forms'];
+ entities: EntityListData['entities'];
meta: EntityListData['meta'];
pageAlert: EntityListData['pageAlert'];
query: EntityListData['query'];
@@ -110,31 +109,6 @@ export class EntityList implements EntityListData {
return instance[this.config.instanceIdAttribute];
}
- instanceUrl(instance: EntityListInstance): string | null {
- const entityKey = this.entityKey;
- const instanceId = this.instanceId(instance);
-
- if(!this.authorizations.view.includes(instanceId)) {
- return null;
- }
-
- const multiform = this.forms && Object.values(this.forms).find(form => form.instances.includes(instanceId));
-
- if(this.config.hasShowPage) {
- return route('code16.sharp.show.show', {
- parentUri: getAppendableParentUri(),
- entityKey: multiform ? `${entityKey}:${multiform.key}` : entityKey,
- instanceId,
- });
- }
-
- return route('code16.sharp.form.edit', {
- parentUri: getAppendableParentUri(),
- entityKey: multiform ? `${entityKey}:${multiform.key}` : entityKey,
- instanceId,
- });
- }
-
instanceState(instance: EntityListInstance): string | number | null {
return this.config.state
? instance[this.config.state.attribute]
@@ -153,10 +127,7 @@ export class EntityList implements EntityListData {
}
instanceCanDelete(instance: EntityListInstance): boolean {
- if(Array.isArray(this.authorizations.delete)) {
- return this.authorizations.delete?.includes(this.instanceId(instance));
- }
- return !!this.authorizations.delete;
+ return instance._meta.authorizations.delete;
}
instanceCommands(instance: EntityListInstance): Array> | undefined {
diff --git a/resources/js/entity-list/components/EntityList.vue b/resources/js/entity-list/components/EntityList.vue
index 2c858ff8c..805f85d31 100644
--- a/resources/js/entity-list/components/EntityList.vue
+++ b/resources/js/entity-list/components/EntityList.vue
@@ -3,9 +3,9 @@
import { FilterManager } from "@/filters/FilterManager";
import { EntityList } from "../EntityList";
import {
- CommandData, EntityListFieldData, EntityListMultiformData, EntityListQueryParamsData,
+ CommandData, EntityListFieldData, EntityListQueryParamsData,
EntityStateValueData,
- FilterData
+ FilterData, EntityListEntityData
} from "@/types";
import WithCommands from "@/commands/components/WithCommands.vue";
import { CommandManager } from "@/commands/CommandManager";
@@ -68,6 +68,7 @@
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useElementVisibility } from "@vueuse/core";
import StateBadge from "@/components/ui/StateBadge.vue";
+ import Icon from "@/components/ui/Icon.vue";
// import StateBadgeTest from "@/components/dev/StateBadgeTest.vue";
const props = withDefaults(defineProps<{
@@ -175,7 +176,7 @@
});
}
- async function onCreate(event: MouseEvent, form?: EntityListMultiformData) {
+ async function onCreate(event: MouseEvent, listEntity?: EntityListEntityData) {
if(event.metaKey || event.ctrlKey || event.shiftKey) {
return;
}
@@ -186,19 +187,18 @@
if(props.entityList.config.quickCreationForm) {
await props.commands.send({ hasForm: true } as CommandData, {
postCommand: route('code16.sharp.api.list.command.quick-creation-form.store', {
- entityKey: form ? `${entityKey}:${form.key}` : entityKey,
+ entityKey,
+ formEntityKey: listEntity ? listEntity.entityKey : entityKey,
}),
getForm: route('code16.sharp.api.list.command.quick-creation-form.create', {
- entityKey: form ? `${entityKey}:${form.key}` : entityKey,
+ entityKey,
+ formEntityKey: listEntity ? listEntity.entityKey : entityKey,
}),
query: props.entityList.query,
- entityKey: form ? `${entityKey}:${form.key}` : entityKey,
+ entityKey: listEntity ? listEntity.entityKey : entityKey,
});
} else {
- router.visit(route('code16.sharp.form.create', {
- parentUri: getAppendableParentUri(),
- entityKey: form ? `${entityKey}:${form.key}` : entityKey,
- }));
+ router.visit(listEntity ? listEntity.formCreateUrl : props.entityList.config.formCreateUrl);
}
}
@@ -469,7 +469,7 @@
-
+
-
+
- {{ form.label }}
+
+
+
+ {{ entity.label }}
@@ -786,10 +789,10 @@
{{ item[field.key] }}
-
+
diff --git a/resources/js/types/generated.d.ts b/resources/js/types/generated.d.ts
index e33b0dd8c..2f69f578c 100644
--- a/resources/js/types/generated.d.ts
+++ b/resources/js/types/generated.d.ts
@@ -133,8 +133,6 @@ export type EmbedFormData = {
layout: FormLayoutData | null;
};
export type EntityListAuthorizationsData = {
- view: Array;
- delete: Array;
create: boolean;
reorder: boolean;
};
@@ -147,7 +145,8 @@ export type EntityListConfigData = {
hasShowPage: boolean;
deleteConfirmationText: string;
deleteHidden: boolean;
- multiformAttribute: string | null;
+ formCreateUrl: string;
+ entityAttribute: string | null;
createButtonLabel: string | null;
quickCreationForm: boolean;
filters: ConfigFiltersData | null;
@@ -159,13 +158,20 @@ export type EntityListData = {
authorizations: EntityListAuthorizationsData;
config: EntityListConfigData;
fields: Array;
- data: Array<{ [key: string]: any }>;
+ data: Array<{ [key: string]: any; _meta: EntityListItemMeta }>;
filterValues: FilterValuesData;
query: EntityListQueryParamsData | null;
- forms: { [key: string]: EntityListMultiformData } | null;
+ entities: Array | null;
meta: PaginatorMetaData | null;
pageAlert: PageAlertData | null;
};
+export type EntityListEntityData = {
+ key: string;
+ entityKey: string;
+ label: string;
+ icon: IconData | null;
+ formCreateUrl: string | null;
+};
export type EntityListFieldData = {
type: "text" | "state" | "badge";
key: string;
@@ -176,10 +182,9 @@ export type EntityListFieldData = {
html: boolean | null;
tooltip: string | null;
};
-export type EntityListMultiformData = {
- key: string;
- label: string;
- instances: Array;
+export type EntityListItemMeta = {
+ url: string | null;
+ authorizations: InstanceAuthorizationsData;
};
export type EntityListQueryParamsData = {
search?: string;
diff --git a/resources/js/types/routes.d.ts b/resources/js/types/routes.d.ts
index 31664ff36..ed41fe615 100644
--- a/resources/js/types/routes.d.ts
+++ b/resources/js/types/routes.d.ts
@@ -127,12 +127,20 @@ declare module 'ziggy-js' {
{
"name": "entityKey",
"binding": "key"
+ },
+ {
+ "name": "formEntityKey",
+ "binding": "key"
}
],
"code16.sharp.api.list.command.quick-creation-form.store": [
{
"name": "entityKey",
"binding": "key"
+ },
+ {
+ "name": "formEntityKey",
+ "binding": "key"
}
],
"code16.sharp.api.list": [
diff --git a/src/Config/SharpConfigBuilder.php b/src/Config/SharpConfigBuilder.php
index 6ec709234..ce8fcf3df 100644
--- a/src/Config/SharpConfigBuilder.php
+++ b/src/Config/SharpConfigBuilder.php
@@ -154,6 +154,17 @@ public function declareEntity(string $entityClass): self
}
$entityKey = $entityClass::$entityKey ?? null;
+
+ if (isset($entityClass::$entityKey)
+ && (get_parent_class($entityClass)::$entityKey ?? null) === $entityClass::$entityKey) {
+ throw new SharpInvalidEntityKeyException(
+ sprintf(
+ '%s has same entity key than its parent class: %s. Specify a new one or remove entity keys completely.',
+ $entityClass, get_parent_class($entityClass)
+ )
+ );
+ }
+
if (! $entityKey) {
$entityKey = str(class_basename($entityClass))
->beforeLast('Entity')
diff --git a/src/Data/EntityList/EntityListConfigData.php b/src/Data/EntityList/EntityListConfigData.php
index 361dbb579..d6f91e051 100644
--- a/src/Data/EntityList/EntityListConfigData.php
+++ b/src/Data/EntityList/EntityListConfigData.php
@@ -21,7 +21,8 @@ public function __construct(
public bool $hasShowPage,
public string $deleteConfirmationText,
public bool $deleteHidden,
- public ?string $multiformAttribute,
+ public string $formCreateUrl,
+ public ?string $entityAttribute,
public ?string $createButtonLabel = null,
public bool $quickCreationForm = false,
public ?ConfigFiltersData $filters = null,
diff --git a/src/Data/EntityList/EntityListData.php b/src/Data/EntityList/EntityListData.php
index cccd40c27..eecbe40d6 100644
--- a/src/Data/EntityList/EntityListData.php
+++ b/src/Data/EntityList/EntityListData.php
@@ -20,12 +20,12 @@ public function __construct(
public EntityListConfigData $config,
/** @var EntityListFieldData[] */
public array $fields,
- #[LiteralTypeScriptType('Array<{ [key: string]: any }>')]
+ #[LiteralTypeScriptType('Array<{ [key: string]: any, _meta: EntityListItemMeta }>')]
public array $data,
public FilterValuesData $filterValues,
public ?EntityListQueryParamsData $query,
- /** @var array|null */
- public ?array $forms = null,
+ /** @var EntityListEntityData[]|null */
+ public ?array $entities = null,
public ?PaginatorMetaData $meta = null,
public ?PageAlertData $pageAlert = null,
) {}
@@ -40,7 +40,9 @@ public static function from(array $entityList): self
data: $entityList['data'],
filterValues: FilterValuesData::from($entityList['filterValues']),
query: EntityListQueryParamsData::optional($entityList['query']),
- forms: $entityList['forms'] ? EntityListMultiformData::collection($entityList['forms']) : null,
+ entities: $entityList['entities']
+ ? EntityListEntityData::collection($entityList['entities'])
+ : null,
meta: PaginatorMetaData::optional($entityList['meta'] ?? null),
pageAlert: PageAlertData::optional($entityList['pageAlert'] ?? null),
);
diff --git a/src/Data/EntityList/EntityListEntityData.php b/src/Data/EntityList/EntityListEntityData.php
new file mode 100644
index 000000000..dcb678702
--- /dev/null
+++ b/src/Data/EntityList/EntityListEntityData.php
@@ -0,0 +1,27 @@
+ */
- public array $instances,
- ) {}
-}
diff --git a/src/Data/EntityListAuthorizationsData.php b/src/Data/EntityListAuthorizationsData.php
index 856a2f5f3..0f59ed69b 100644
--- a/src/Data/EntityListAuthorizationsData.php
+++ b/src/Data/EntityListAuthorizationsData.php
@@ -8,11 +8,12 @@
final class EntityListAuthorizationsData extends Data
{
public function __construct(
- /** @var array */
- public array $view,
- /** @var array */
- public array $delete,
public bool $create,
- public bool $reorder,
+ public bool $reorder = false,
) {}
+
+ public static function from(array $authorizations): self
+ {
+ return new self(...$authorizations);
+ }
}
diff --git a/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php b/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php
index f9e1e356b..0de23d327 100644
--- a/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php
+++ b/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php
@@ -7,7 +7,6 @@
use Code16\Sharp\Form\Fields\SharpFormField;
use Code16\Sharp\Form\SharpForm;
use Code16\Sharp\Utils\Fields\FieldsContainer;
-use Illuminate\Support\Uri;
class QuickCreationCommand extends EntityCommand
{
@@ -74,13 +73,12 @@ public function getInstanceId(): mixed
public function execute(array $data = []): array
{
- $currentUrl = sharp()->context()->breadcrumb()->getCurrentSegmentUrl();
-
- sharp()->context()->breadcrumb()->forceRequestSegments(
- str(Uri::of($currentUrl)->path())
+ $breadcrumb = sharp()->context()->breadcrumb();
+ $currentPageUrl = $breadcrumb->getCurrentSegmentUrl();
+ $breadcrumb->forceRequestSegments(
+ str($breadcrumb->getCurrentPath())
->explode('/')
->filter()
- ->skip(1)
->concat(['s-form', $this->entityKey])
);
$this->instanceId = $this->sharpForm->update(null, $data);
@@ -90,7 +88,7 @@ public function execute(array $data = []): array
}
return $this->sharpForm->isDisplayShowPageAfterCreation()
- ? $this->link(sprintf('%s/s-show/%s/%s', $currentUrl, $this->entityKey, $this->instanceId))
+ ? $this->link(sprintf('%s/s-show/%s/%s', $currentPageUrl, $this->entityKey, $this->instanceId))
: $this->reload();
}
diff --git a/src/EntityList/EntityListEntities.php b/src/EntityList/EntityListEntities.php
new file mode 100644
index 000000000..4564c0dbb
--- /dev/null
+++ b/src/EntityList/EntityListEntities.php
@@ -0,0 +1,39 @@
+entities[$key] = new EntityListEntity($entityKeyOrClassName, $icon, $label);
+
+ return $this;
+ }
+
+ public function find(string $key): ?EntityListEntity
+ {
+ return $this->entities[$key] ?? null;
+ }
+
+ public function all(): array
+ {
+ return $this->entities;
+ }
+}
diff --git a/src/EntityList/EntityListEntity.php b/src/EntityList/EntityListEntity.php
new file mode 100644
index 000000000..e24919da4
--- /dev/null
+++ b/src/EntityList/EntityListEntity.php
@@ -0,0 +1,38 @@
+entityFor($this->getEntityKey());
+ }
+
+ public function getEntityKey(): string
+ {
+ return app(SharpEntityManager::class)->entityKeyFor($this->entityKeyOrClassName);
+ }
+
+ public function getIcon(): ?string
+ {
+ return $this->icon;
+ }
+
+ public function getLabel(): ?string
+ {
+ return $this->label;
+ }
+}
diff --git a/src/EntityList/SharpEntityList.php b/src/EntityList/SharpEntityList.php
index 74f4f2f0d..bac259936 100644
--- a/src/EntityList/SharpEntityList.php
+++ b/src/EntityList/SharpEntityList.php
@@ -27,7 +27,8 @@ abstract class SharpEntityList
private ?EntityListFieldsContainer $fieldsContainer = null;
protected ?EntityListQueryParams $queryParams;
protected string $instanceIdAttribute = 'id';
- protected ?string $multiformAttribute = null;
+ protected ?string $entityAttribute = null;
+ protected ?EntityListEntities $entities = null;
protected bool $searchable = false;
protected ?ReorderHandler $reorderHandler = null;
private bool $disabledReorder = false;
@@ -84,13 +85,13 @@ final public function data(?array $query = null): array
// Filter model attributes on actual form fields
->map(fn ($row) => collect($row)
->only(
- array_merge(
- array_keys($this->transformers),
- $this->entityStateAttribute ? [$this->entityStateAttribute] : [],
- $this->multiformAttribute ? [$this->multiformAttribute] : [],
- [$this->instanceIdAttribute],
- $this->getDataKeys(),
- ),
+ [
+ ...array_keys($this->transformers),
+ ...$this->entityStateAttribute ? [$this->entityStateAttribute] : [],
+ ...$this->getEntityAttribute() ? [$this->getEntityAttribute()] : [],
+ $this->instanceIdAttribute,
+ ...$this->getDataKeys(),
+ ]
)
->toArray()
)
@@ -108,7 +109,7 @@ final public function listConfig(bool $hasShowPage = false): array
{
$config = [
'instanceIdAttribute' => $this->instanceIdAttribute,
- 'multiformAttribute' => $this->multiformAttribute,
+ 'entityAttribute' => $this->getEntityAttribute(),
'searchable' => $this->searchable,
'reorderable' => ! is_null($this->reorderHandler) && ! $this->disabledReorder,
'defaultSort' => $this->defaultSort,
@@ -135,6 +136,14 @@ final public function configureInstanceIdAttribute(string $instanceIdAttribute):
return $this;
}
+ /**
+ * @internal
+ */
+ final public function getInstanceIdAttribute(): string
+ {
+ return $this->instanceIdAttribute;
+ }
+
final public function configureReorderable(ReorderHandler|string $reorderHandler): self
{
$this->reorderHandler = instanciate($reorderHandler);
@@ -180,13 +189,41 @@ final public function configureDefaultSort(string $sortBy, string $sortDir = 'as
return $this;
}
+ /**
+ * @deprecated
+ * @see self::configureEntityMap()
+ */
final protected function configureMultiformAttribute(?string $attribute): self
{
- $this->multiformAttribute = $attribute;
+ $this->entityAttribute = $attribute;
+
+ return $this;
+ }
+
+ final protected function configureEntityMap(string $attribute, EntityListEntities $entities): self
+ {
+ $this->entityAttribute = $attribute;
+ $this->entities = $entities;
return $this;
}
+ /**
+ * @internal
+ */
+ final public function getEntityAttribute(): ?string
+ {
+ return $this->entityAttribute;
+ }
+
+ /**
+ * @internal
+ */
+ final public function getEntities(): ?EntityListEntities
+ {
+ return $this->entities;
+ }
+
final public function reorderHandler(): ?ReorderHandler
{
return $this->reorderHandler;
diff --git a/src/Http/Context/SharpBreadcrumb.php b/src/Http/Context/SharpBreadcrumb.php
index 80c674fb7..a3f88c956 100644
--- a/src/Http/Context/SharpBreadcrumb.php
+++ b/src/Http/Context/SharpBreadcrumb.php
@@ -62,9 +62,9 @@ public function previousSegment(): ?BreadcrumbItem
return $this->breadcrumbItems()->reverse()->skip(1)->first();
}
- public function previousShowSegment(?string $entityKeyOrClassName = null, ?string $subEntity = null): ?BreadcrumbItem
+ public function previousShowSegment(?string $entityKeyOrClassName = null, ?string $multiformKey = null): ?BreadcrumbItem
{
- return $this->findPreviousSegment('s-show', $entityKeyOrClassName, $subEntity);
+ return $this->findPreviousSegment('s-show', $entityKeyOrClassName, $multiformKey);
}
public function previousListSegment(?string $entityKeyOrClassName = null): ?BreadcrumbItem
@@ -78,13 +78,18 @@ public function getCurrentSegmentUrl(): string
sprintf(
'%s/%s',
sharp()->config()->get('custom_url_segment'),
- $this->breadcrumbItems()
- ->map(fn (BreadcrumbItem $item) => $item->toUri())
- ->implode('/')
+ $this->getCurrentPath()
)
);
}
+ public function getCurrentPath(): ?string
+ {
+ return $this->breadcrumbItems()
+ ->map(fn (BreadcrumbItem $item) => $item->toUri())
+ ->implode('/');
+ }
+
public function getPreviousSegmentUrl(): string
{
return url(
@@ -111,7 +116,7 @@ public function breadcrumbItems(): Collection
return $this->breadcrumbItems;
}
- private function findPreviousSegment(string $type, ?string $entityKeyOrClassName = null, ?string $subEntity = null): ?BreadcrumbItem
+ private function findPreviousSegment(string $type, ?string $entityKeyOrClassName = null, ?string $multiformKey = null): ?BreadcrumbItem
{
$modeNotEquals = false;
if ($entityKeyOrClassName && Str::startsWith($entityKeyOrClassName, '!')) {
@@ -124,8 +129,8 @@ private function findPreviousSegment(string $type, ?string $entityKeyOrClassName
->filter(fn (BreadcrumbItem $item) => $item->type === $type)
->when($entityKeyOrClassName !== null, fn ($items) => $items
->filter(fn (BreadcrumbItem $breadcrumbItem) => $modeNotEquals
- ? ! $breadcrumbItem->entityIs($entityKeyOrClassName, $subEntity)
- : $breadcrumbItem->entityIs($entityKeyOrClassName, $subEntity)
+ ? ! $breadcrumbItem->entityIs($entityKeyOrClassName, $multiformKey)
+ : $breadcrumbItem->entityIs($entityKeyOrClassName, $multiformKey)
)
)
->first();
@@ -243,14 +248,14 @@ private function getEntityLabelForInstance(BreadcrumbItem $item, bool $isLeaf):
return app(SharpEntityManager::class)
->entityFor($item->key)
- ->getLabelOrFail((new EntityKey($item->key))->subEntity());
+ ->getLabelOrFail((new EntityKey($item->key))->multiformKey());
}
private function isSameEntityKeys(string $key1, string $key2, bool $compareBaseEntities): bool
{
if ($compareBaseEntities) {
- $key1 = explode(':', $key1)[0];
- $key2 = explode(':', $key2)[0];
+ $key1 = (new EntityKey($key1))->baseKey();
+ $key2 = (new EntityKey($key2))->baseKey();
}
return $key1 === $key2;
diff --git a/src/Http/Context/Util/BreadcrumbItem.php b/src/Http/Context/Util/BreadcrumbItem.php
index 7d6163085..d3bc74c73 100644
--- a/src/Http/Context/Util/BreadcrumbItem.php
+++ b/src/Http/Context/Util/BreadcrumbItem.php
@@ -68,13 +68,13 @@ public function is(BreadcrumbItem $item): bool
&& $this->instance === $item->instance;
}
- public function entityIs(string $entityKeyOrClassName, ?string $subEntity = null): bool
+ public function entityIs(string $entityKeyOrClassName, ?string $multiformKey = null): bool
{
$selfKey = new EntityKey($this->key);
$resolvedEntityKey = app(SharpEntityManager::class)->entityKeyFor($entityKeyOrClassName);
return $selfKey->baseKey() === $resolvedEntityKey
- && (! $subEntity || $selfKey->subEntity() === $subEntity);
+ && (! $multiformKey || $selfKey->multiformKey() === $multiformKey);
}
public function entityKey(): string
diff --git a/src/Http/Controllers/Api/ApiController.php b/src/Http/Controllers/Api/ApiController.php
index bbca6a693..f605b8b4b 100644
--- a/src/Http/Controllers/Api/ApiController.php
+++ b/src/Http/Controllers/Api/ApiController.php
@@ -32,9 +32,4 @@ protected function getDashboardInstance(string $dashboardKey): ?SharpDashboard
{
return $this->entityManager->entityFor($dashboardKey)->getViewOrFail();
}
-
- protected function isSubEntity(string $entityKey): bool
- {
- return str_contains($entityKey, ':');
- }
}
diff --git a/src/Http/Controllers/Api/ApiEntityListController.php b/src/Http/Controllers/Api/ApiEntityListController.php
index cfa16fc76..231064446 100644
--- a/src/Http/Controllers/Api/ApiEntityListController.php
+++ b/src/Http/Controllers/Api/ApiEntityListController.php
@@ -13,7 +13,7 @@ class ApiEntityListController extends ApiController
*/
public function update(string $entityKey)
{
- sharp_check_ability('entity', $entityKey);
+ $this->authorizationManager->check('entity', $entityKey);
$list = $this->getListInstance($entityKey);
$list->buildListConfig();
@@ -30,7 +30,7 @@ public function update(string $entityKey)
*/
public function delete(string $entityKey, string $instanceId)
{
- sharp_check_ability('delete', $entityKey, $instanceId);
+ $this->authorizationManager->check('delete', $entityKey, $instanceId);
$impl = $this->getListInstance($entityKey);
if (! self::isDeleteMethodImplementedInConcreteClass($impl)) {
diff --git a/src/Http/Controllers/Api/ApiEntityListFiltersController.php b/src/Http/Controllers/Api/ApiEntityListFiltersController.php
index e538e81a0..7c9828a06 100644
--- a/src/Http/Controllers/Api/ApiEntityListFiltersController.php
+++ b/src/Http/Controllers/Api/ApiEntityListFiltersController.php
@@ -6,7 +6,7 @@ class ApiEntityListFiltersController extends ApiController
{
public function store(string $entityKey)
{
- sharp_check_ability('entity', $entityKey);
+ $this->authorizationManager->check('entity', $entityKey);
$list = $this->getListInstance($entityKey);
$list->buildListConfig();
diff --git a/src/Http/Controllers/Api/ApiFormAutocompleteController.php b/src/Http/Controllers/Api/ApiFormAutocompleteController.php
index 8ea8ee7dc..125a27f02 100644
--- a/src/Http/Controllers/Api/ApiFormAutocompleteController.php
+++ b/src/Http/Controllers/Api/ApiFormAutocompleteController.php
@@ -120,7 +120,7 @@ private function getFieldContainer(EntityKey $entityKey): SharpFormEditorEmbed|C
);
}
- return $entity->getFormOrFail($entityKey->subEntity());
+ return $entity->getFormOrFail($entityKey->multiformKey());
}
private function getFormattedData() {}
diff --git a/src/Http/Controllers/Api/ApiFormUploadThumbnailController.php b/src/Http/Controllers/Api/ApiFormUploadThumbnailController.php
index 9eb0b6d0b..9e40b6da1 100644
--- a/src/Http/Controllers/Api/ApiFormUploadThumbnailController.php
+++ b/src/Http/Controllers/Api/ApiFormUploadThumbnailController.php
@@ -2,6 +2,7 @@
namespace Code16\Sharp\Http\Controllers\Api;
+use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Form\Eloquent\Uploads\Traits\UsesSharpUploadModel;
use Illuminate\Routing\Controller;
@@ -9,10 +10,12 @@ class ApiFormUploadThumbnailController extends Controller
{
use UsesSharpUploadModel;
+ public function __construct(private readonly SharpAuthorizationManager $authorizationManager) {}
+
// Used to generate large thumbnail for upload crop modal
public function show(string $entityKey, ?string $instanceId = null)
{
- sharp_check_ability('view', $entityKey, $instanceId);
+ $this->authorizationManager->check('view', $entityKey, $instanceId);
return response()->json([
'thumbnail' => static::getUploadModelClass()::make([
diff --git a/src/Http/Controllers/Api/Commands/ApiDashboardCommandController.php b/src/Http/Controllers/Api/Commands/ApiDashboardCommandController.php
index 88ae383e9..360cf5d38 100644
--- a/src/Http/Controllers/Api/Commands/ApiDashboardCommandController.php
+++ b/src/Http/Controllers/Api/Commands/ApiDashboardCommandController.php
@@ -44,7 +44,7 @@ public function update(string $entityKey, string $commandKey)
$commandHandler = $this->getDashboardCommandHandler($dashboard, $commandKey);
$formattedData = $commandHandler->formatAndValidateRequestData((array) request('data'));
- $result = $this->returnCommandResult($dashboard, $commandHandler->execute($formattedData));
+ $result = $this->returnCommandResult($dashboard, $entityKey, $commandHandler->execute($formattedData));
$this->uploadManager->dispatchJobs();
return $result;
diff --git a/src/Http/Controllers/Api/Commands/ApiEntityListEntityCommandController.php b/src/Http/Controllers/Api/Commands/ApiEntityListEntityCommandController.php
index ce91e2cc9..c1e416bfb 100644
--- a/src/Http/Controllers/Api/Commands/ApiEntityListEntityCommandController.php
+++ b/src/Http/Controllers/Api/Commands/ApiEntityListEntityCommandController.php
@@ -42,7 +42,7 @@ public function update(string $entityKey, string $commandKey)
$commandHandler = $this->getEntityCommandHandler($list, $commandKey);
$formattedData = $commandHandler->formatAndValidateRequestData((array) request('data'));
- $result = $this->returnCommandResult($list, $commandHandler->execute($formattedData));
+ $result = $this->returnCommandResult($list, $entityKey, $commandHandler->execute($formattedData));
$this->uploadManager->dispatchJobs();
return $result;
diff --git a/src/Http/Controllers/Api/Commands/ApiEntityListEntityStateController.php b/src/Http/Controllers/Api/Commands/ApiEntityListEntityStateController.php
index 9c05664f4..e15728a00 100644
--- a/src/Http/Controllers/Api/Commands/ApiEntityListEntityStateController.php
+++ b/src/Http/Controllers/Api/Commands/ApiEntityListEntityStateController.php
@@ -23,6 +23,7 @@ public function update(string $entityKey, mixed $instanceId)
return $this->returnCommandResult(
$list,
+ $entityKey,
array_merge(
$list->entityStateHandler()->execute($instanceId, request()->only('value')),
['value' => request('value')],
diff --git a/src/Http/Controllers/Api/Commands/ApiEntityListInstanceCommandController.php b/src/Http/Controllers/Api/Commands/ApiEntityListInstanceCommandController.php
index cbbb8a241..15bd9b603 100644
--- a/src/Http/Controllers/Api/Commands/ApiEntityListInstanceCommandController.php
+++ b/src/Http/Controllers/Api/Commands/ApiEntityListInstanceCommandController.php
@@ -45,7 +45,7 @@ public function update(string $entityKey, string $commandKey, mixed $instanceId)
$commandHandler = $this->getInstanceCommandHandler($list, $commandKey, $instanceId);
$formattedData = $commandHandler->formatAndValidateRequestData((array) request('data'), $instanceId);
- $result = $this->returnCommandResult($list, $commandHandler->execute($instanceId, $formattedData));
+ $result = $this->returnCommandResult($list, $entityKey, $commandHandler->execute($instanceId, $formattedData));
$this->uploadManager->dispatchJobs($instanceId);
return $result;
diff --git a/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php b/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php
index ed31d995b..76150765e 100644
--- a/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php
+++ b/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php
@@ -17,7 +17,7 @@ public function __construct(private readonly SharpUploadManager $uploadManager)
parent::__construct();
}
- public function create(EntityKey $entityKey)
+ public function create(EntityKey $entityKey, EntityKey $formEntityKey)
{
$entity = $this->entityManager->entityFor($entityKey);
@@ -29,14 +29,14 @@ public function create(EntityKey $entityKey)
403
);
- $form = $entity->getFormOrFail($entityKey->subEntity());
+ $form = $this->entityManager->entityFor($formEntityKey)->getFormOrFail($formEntityKey->multiformKey());
$form->buildFormConfig();
$quickCreationHandler
->setEntityKey($entityKey)
->setFormInstance($form)
->setTitle(__('sharp::breadcrumb.form.create_entity', [
- 'entity' => $entity->getLabelOrFail($entityKey->subEntity()),
+ 'entity' => $entity->getLabelOrFail($entityKey->multiformKey()),
]));
$quickCreationHandler->buildCommandConfig();
@@ -48,9 +48,9 @@ public function create(EntityKey $entityKey)
);
}
- public function store(EntityKey $entityKey)
+ public function store(EntityKey $entityKey, EntityKey $formEntityKey)
{
- $list = $this->getListInstance($entityKey);
+ $list = $this->entityManager->entityFor($entityKey)->getListOrFail();
$list->buildListConfig();
abort_if(
@@ -58,7 +58,7 @@ public function store(EntityKey $entityKey)
403
);
- $form = $this->entityManager->entityFor($entityKey)->getFormOrFail($entityKey->subEntity());
+ $form = $this->entityManager->entityFor($formEntityKey)->getFormOrFail($formEntityKey->multiformKey());
$form->buildFormConfig();
$quickCreationHandler
@@ -68,6 +68,7 @@ public function store(EntityKey $entityKey)
$formattedData = $quickCreationHandler->formatAndValidateRequestData((array) request('data'));
$result = $this->returnCommandResult(
$list,
+ $entityKey,
$quickCreationHandler->execute($formattedData)
);
$this->uploadManager->dispatchJobs($quickCreationHandler->getInstanceId());
diff --git a/src/Http/Controllers/Api/Commands/ApiShowEntityStateController.php b/src/Http/Controllers/Api/Commands/ApiShowEntityStateController.php
index 5d13e94d8..343816331 100644
--- a/src/Http/Controllers/Api/Commands/ApiShowEntityStateController.php
+++ b/src/Http/Controllers/Api/Commands/ApiShowEntityStateController.php
@@ -23,6 +23,7 @@ public function update(string $entityKey, mixed $instanceId = null)
return $this->returnCommandResult(
$showPage,
+ $entityKey,
array_merge(
$stateHandler->execute($instanceId, request()->only('value')),
['value' => request('value')],
diff --git a/src/Http/Controllers/Api/Commands/ApiShowInstanceCommandController.php b/src/Http/Controllers/Api/Commands/ApiShowInstanceCommandController.php
index 961522aa2..0cfbc6edf 100644
--- a/src/Http/Controllers/Api/Commands/ApiShowInstanceCommandController.php
+++ b/src/Http/Controllers/Api/Commands/ApiShowInstanceCommandController.php
@@ -37,7 +37,7 @@ public function update(string $entityKey, string $commandKey, mixed $instanceId
$commandHandler = $this->getInstanceCommandHandler($showPage, $commandKey, $instanceId);
$formattedData = $commandHandler->formatAndValidateRequestData((array) request('data'), $instanceId);
- $result = $this->returnCommandResult($showPage, $commandHandler->execute($instanceId, $formattedData));
+ $result = $this->returnCommandResult($showPage, $entityKey, $commandHandler->execute($instanceId, $formattedData));
$this->uploadManager->dispatchJobs($instanceId);
return $result;
diff --git a/src/Http/Controllers/Api/Commands/HandlesCommandResult.php b/src/Http/Controllers/Api/Commands/HandlesCommandResult.php
index 73797334b..aa46dc27f 100644
--- a/src/Http/Controllers/Api/Commands/HandlesCommandResult.php
+++ b/src/Http/Controllers/Api/Commands/HandlesCommandResult.php
@@ -5,6 +5,7 @@
use Code16\Sharp\Dashboard\SharpDashboard;
use Code16\Sharp\EntityList\SharpEntityList;
use Code16\Sharp\Enums\CommandAction;
+use Code16\Sharp\Http\Controllers\HandlesEntityListItems;
use Code16\Sharp\Show\SharpShow;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Storage;
@@ -12,8 +13,11 @@
trait HandlesCommandResult
{
+ use HandlesEntityListItems;
+
protected function returnCommandResult(
SharpEntityList|SharpShow|SharpDashboard $commandContainer,
+ string $entityKey,
array $returnedValue
): StreamedResponse|JsonResponse {
if ($returnedValue['action'] == CommandAction::Download->value) {
@@ -35,9 +39,13 @@ function () use ($returnedValue) {
if ($returnedValue['action'] == CommandAction::Refresh->value && $commandContainer instanceof SharpEntityList) {
// We have to load and build items from ids
- $returnedValue['items'] = $commandContainer
- ->updateQueryParamsWithSpecificIds($returnedValue['items'])
- ->data()['items'];
+ $returnedValue['items'] = $this->addMetaToItems(
+ $commandContainer
+ ->updateQueryParamsWithSpecificIds($returnedValue['items'])
+ ->data()['items'],
+ $entityKey,
+ $commandContainer,
+ );
}
return response()->json($returnedValue);
diff --git a/src/Http/Controllers/Api/DownloadController.php b/src/Http/Controllers/Api/DownloadController.php
index e7a64dfe8..1167c8a77 100644
--- a/src/Http/Controllers/Api/DownloadController.php
+++ b/src/Http/Controllers/Api/DownloadController.php
@@ -8,7 +8,7 @@ class DownloadController extends ApiController
{
public function show(string $entityKey, ?string $instanceId = null)
{
- sharp_check_ability('view', $entityKey, $instanceId);
+ $this->authorizationManager->check('view', $entityKey, $instanceId);
abort_if(
! ($path = request()->get('path'))
diff --git a/src/Http/Controllers/Api/Embeds/ApiEmbedsFormController.php b/src/Http/Controllers/Api/Embeds/ApiEmbedsFormController.php
index ee3e32bdb..42c916a0f 100644
--- a/src/Http/Controllers/Api/Embeds/ApiEmbedsFormController.php
+++ b/src/Http/Controllers/Api/Embeds/ApiEmbedsFormController.php
@@ -2,6 +2,7 @@
namespace Code16\Sharp\Http\Controllers\Api\Embeds;
+use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Data\Embeds\EmbedFormData;
use Illuminate\Routing\Controller;
@@ -9,12 +10,14 @@ class ApiEmbedsFormController extends Controller
{
use HandlesEmbed;
+ public function __construct(private readonly SharpAuthorizationManager $authorizationManager) {}
+
public function show(string $embedKey, string $entityKey, ?string $instanceId = null)
{
if ($instanceId) {
- sharp_check_ability('view', $entityKey, $instanceId);
+ $this->authorizationManager->check('view', $entityKey, $instanceId);
} else {
- sharp_check_ability('entity', $entityKey);
+ $this->authorizationManager->check('entity', $entityKey);
}
$embed = $this->getEmbedFromKey($embedKey);
@@ -31,9 +34,9 @@ public function show(string $embedKey, string $entityKey, ?string $instanceId =
public function update(string $embedKey, string $entityKey, ?string $instanceId = null)
{
if ($instanceId) {
- sharp_check_ability('update', $entityKey, $instanceId);
+ $this->authorizationManager->check('update', $entityKey, $instanceId);
} else {
- sharp_check_ability('create', $entityKey);
+ $this->authorizationManager->check('create', $entityKey);
}
$embed = $this->getEmbedFromKey($embedKey);
diff --git a/src/Http/Controllers/DashboardController.php b/src/Http/Controllers/DashboardController.php
index a33fb8b39..51c88b6ee 100644
--- a/src/Http/Controllers/DashboardController.php
+++ b/src/Http/Controllers/DashboardController.php
@@ -4,19 +4,13 @@
use Code16\Sharp\Data\BreadcrumbData;
use Code16\Sharp\Data\Dashboard\DashboardData;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Inertia\Inertia;
class DashboardController extends SharpProtectedController
{
- public function __construct(private SharpEntityManager $entityManager)
- {
- parent::__construct();
- }
-
public function show(string $dashboardKey)
{
- sharp_check_ability('entity', $dashboardKey);
+ $this->authorizationManager->check('entity', $dashboardKey);
$dashboard = $this->entityManager->entityFor($dashboardKey)->getViewOrFail();
$dashboard->buildDashboardConfig();
diff --git a/src/Http/Controllers/DashboardFiltersController.php b/src/Http/Controllers/DashboardFiltersController.php
index b2589f3ce..563c96824 100644
--- a/src/Http/Controllers/DashboardFiltersController.php
+++ b/src/Http/Controllers/DashboardFiltersController.php
@@ -2,19 +2,11 @@
namespace Code16\Sharp\Http\Controllers;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
-
class DashboardFiltersController extends SharpProtectedController
{
- public function __construct(
- readonly private SharpEntityManager $entityManager
- ) {
- parent::__construct();
- }
-
public function store(string $dashboardKey)
{
- sharp_check_ability('entity', $dashboardKey);
+ $this->authorizationManager->check('entity', $dashboardKey);
$dashboard = $this->entityManager->entityFor($dashboardKey)->getViewOrFail();
$dashboard->buildDashboardConfig();
diff --git a/src/Http/Controllers/EntityListController.php b/src/Http/Controllers/EntityListController.php
index 56fc64dfe..7476a7bbb 100644
--- a/src/Http/Controllers/EntityListController.php
+++ b/src/Http/Controllers/EntityListController.php
@@ -2,29 +2,25 @@
namespace Code16\Sharp\Http\Controllers;
-use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Data\BreadcrumbData;
use Code16\Sharp\Data\EntityList\EntityListData;
use Code16\Sharp\Data\NotificationData;
+use Code16\Sharp\EntityList\EntityListEntity;
+use Code16\Sharp\EntityList\SharpEntityList;
use Code16\Sharp\Exceptions\SharpInvalidConfigException;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
+use Code16\Sharp\Utils\Entities\ValueObjects\EntityKey;
+use Code16\Sharp\Utils\Icons\IconManager;
use Code16\Sharp\Utils\Menu\SharpMenuManager;
use Inertia\Inertia;
class EntityListController extends SharpProtectedController
{
+ use HandlesEntityListItems;
use HandlesSharpNotificationsInRequest;
- public function __construct(
- private readonly SharpAuthorizationManager $sharpAuthorizationManager,
- private readonly SharpEntityManager $entityManager,
- ) {
- parent::__construct();
- }
-
public function show(string $entityKey)
{
- sharp_check_ability('entity', $entityKey);
+ $this->authorizationManager->check('entity', $entityKey);
$list = $this->entityManager->entityFor($entityKey)->getListOrFail();
$list->buildListConfig();
@@ -35,19 +31,23 @@ public function show(string $entityKey)
$data = [
'fields' => $list->fields(),
- 'data' => $listData['items'],
+ 'data' => $this->addMetaToItems($listData['items'], $entityKey, $list),
'meta' => $listData['meta'],
'pageAlert' => $list->pageAlert($listData['items']),
- 'config' => $listConfig,
- 'authorizations' => $this->getAuthorizationsForEntityList(
+ 'config' => [
+ ...$listConfig,
+ 'formCreateUrl' => route('code16.sharp.form.create', [
+ 'parentUri' => sharp()->context()->breadcrumb()->getCurrentPath(),
+ 'entityKey' => $entityKey,
+ ]),
+ ],
+ 'authorizations' => [
+ 'reorder' => $this->authorizationManager->isAllowed('reorder', $entityKey),
+ 'create' => $this->authorizationManager->isAllowed('create', $entityKey),
+ ],
+ 'entities' => $this->getEntitiesDataForEntityList(
$entityKey,
- $listData['items'],
- $listConfig,
- ),
- 'forms' => $this->getMultiformDataForEntityList(
- $entityKey,
- $listData['items'],
- $listConfig,
+ $list
),
'filterValues' => $list->filterContainer()->getCurrentFilterValuesForFront(request()->all()),
'query' => count(request()->query()) ? request()->query() : null,
@@ -72,54 +72,62 @@ public function show(string $entityKey)
]);
}
- private function getAuthorizationsForEntityList(string $entityKey, array $listItems, array $listConfig): array
- {
- $authorizations = [
- 'view' => [],
- 'reorder' => $this->sharpAuthorizationManager->isAllowed('reorder', $entityKey),
- 'delete' => [],
- 'create' => $this->sharpAuthorizationManager->isAllowed('create', $entityKey),
- ];
-
- // Collect instanceIds from response
- collect($listItems)
- ->pluck($listConfig['instanceIdAttribute'])
- ->each(function ($instanceId) use (&$authorizations, $entityKey) {
- if ($this->sharpAuthorizationManager->isAllowed('view', $entityKey, $instanceId)) {
- $authorizations['view'][] = $instanceId;
- }
- if ($this->sharpAuthorizationManager->isAllowed('delete', $entityKey, $instanceId)) {
- $authorizations['delete'][] = $instanceId;
- }
- });
-
- return $authorizations;
- }
-
- private function getMultiformDataForEntityList(string $entityKey, array $listItems, array $listConfig): ?array
+ private function getEntitiesDataForEntityList(string $entityKey, SharpEntityList $list): ?array
{
- if ($listConfig['multiformAttribute'] === null) {
+ if ($list->getEntityAttribute() === null) {
return null;
}
- if (! $forms = $this->entityManager->entityFor($entityKey)->getMultiforms()) {
+ $forms = $this->entityManager->entityFor($entityKey)->getMultiforms();
+ $entities = $list->getEntities()?->all();
+
+ if (! $forms && ! $entities) {
throw new SharpInvalidConfigException(
- 'The list for the entity ['.$entityKey.'] defines a multiform attribute ['
- .$listConfig['multiformAttribute']
- .'] but the entity is not configured as multiform.'
+ 'The list for the entity ['.$entityKey.'] defines a sub-entity attribute ['
+ .$list->getEntityAttribute()
+ .'] but the entity is has no sub-entities.'
);
}
- return collect($forms)
- ->map(fn ($value, $key) => [
- 'key' => $key,
- 'label' => is_array($value) && count($value) > 1 ? $value[1] : $key,
- 'instances' => collect($listItems)
- ->where($listConfig['multiformAttribute'], $key)
- ->pluck($listConfig['instanceIdAttribute'])
- ->toArray(),
- ])
- ->keyBy('key')
+ if ($forms) {
+ return collect($forms)
+ ->map(fn ($value, $key) => [
+ 'key' => $key,
+ 'entityKey' => EntityKey::multiform(baseKey: $entityKey, multiformKey: $key),
+ 'label' => is_array($value) && count($value) > 1 ? $value[1] : $key,
+ 'icon' => null,
+ 'formCreateUrl' => route('code16.sharp.form.create', [
+ 'parentUri' => sharp()->context()->breadcrumb()->getCurrentPath(),
+ 'entityKey' => EntityKey::multiform(baseKey: $entityKey, multiformKey: $key),
+ ]),
+ ])
+ ->whereNotNull('label')
+ ->values()
+ ->all();
+ }
+
+ return collect($entities)
+ ->map(function (EntityListEntity $listEntity, $key) {
+ $entity = $listEntity->getEntity();
+ $entityKey = $this->entityManager->entityKeyFor($entity);
+
+ if (! $this->authorizationManager->isAllowed('create', $entityKey)) {
+ return null;
+ }
+
+ return [
+ 'key' => $key,
+ 'entityKey' => $entityKey,
+ 'label' => $listEntity->getLabel() ?: $entity->getLabelOrFail(),
+ 'icon' => app(IconManager::class)->iconToArray($listEntity->getIcon()),
+ 'formCreateUrl' => route('code16.sharp.form.create', [
+ 'parentUri' => sharp()->context()->breadcrumb()->getCurrentPath(),
+ 'entityKey' => $entityKey,
+ ]),
+ ];
+ })
+ ->filter()
+ ->values()
->all();
}
}
diff --git a/src/Http/Controllers/EntityListFiltersController.php b/src/Http/Controllers/EntityListFiltersController.php
index dcd5b7553..622ad8daa 100644
--- a/src/Http/Controllers/EntityListFiltersController.php
+++ b/src/Http/Controllers/EntityListFiltersController.php
@@ -2,19 +2,11 @@
namespace Code16\Sharp\Http\Controllers;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
-
class EntityListFiltersController extends SharpProtectedController
{
- public function __construct(
- private readonly SharpEntityManager $entityManager,
- ) {
- parent::__construct();
- }
-
public function store(string $entityKey)
{
- sharp_check_ability('entity', $entityKey);
+ $this->authorizationManager->check('entity', $entityKey);
// We have to get rid of the final /filters in the current URL
// to prevent SharpBreadcrumb from considering it as a segment
diff --git a/src/Http/Controllers/FormController.php b/src/Http/Controllers/FormController.php
index bf9ef41f8..f65300c55 100644
--- a/src/Http/Controllers/FormController.php
+++ b/src/Http/Controllers/FormController.php
@@ -2,13 +2,11 @@
namespace Code16\Sharp\Http\Controllers;
-use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Data\BreadcrumbData;
use Code16\Sharp\Data\Form\FormData;
use Code16\Sharp\Exceptions\Form\SharpFormUpdateException;
use Code16\Sharp\Form\SharpForm;
use Code16\Sharp\Form\SharpSingleForm;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Code16\Sharp\Utils\Entities\ValueObjects\EntityKey;
use Code16\Sharp\Utils\Uploads\SharpUploadManager;
use Illuminate\Support\Uri;
@@ -19,8 +17,6 @@ class FormController extends SharpProtectedController
use HandlesSharpNotificationsInRequest;
public function __construct(
- private readonly SharpAuthorizationManager $sharpAuthorizationManager,
- private readonly SharpEntityManager $entityManager,
private readonly SharpUploadManager $uploadManager,
) {
parent::__construct();
@@ -30,24 +26,22 @@ public function create(string $parentUri, EntityKey $entityKey)
{
$entity = $this->entityManager->entityFor($entityKey);
- $form = $entity->getFormOrFail($entityKey->subEntity());
+ $form = $entity->getFormOrFail($entityKey->multiformKey());
if ($form instanceof SharpSingleForm) {
// There is no creation in SingleForms
return $this->edit($parentUri, $entityKey);
}
- sharp_check_ability('create', $entityKey);
-
+ $this->authorizationManager->check('create', $entityKey);
$form->buildFormConfig();
-
$formData = $form->newInstance();
return Inertia::render('Form/Form', [
'form' => FormData::from([
...$this->buildFormData($form, $formData, $entityKey),
'title' => $form->getCreateTitle() ?: trans('sharp::breadcrumb.form.create_entity', [
- 'entity' => $entity->getLabelOrFail($entityKey->subEntity()),
+ 'entity' => $entity->getLabelOrFail($entityKey->multiformKey()),
]),
]),
'breadcrumb' => BreadcrumbData::from([
@@ -66,13 +60,13 @@ public function edit(string $parentUri, EntityKey $entityKey, ?string $instanceI
{
$entity = $this->entityManager->entityFor($entityKey);
- sharp_check_ability(
+ $this->authorizationManager->check(
$entity->hasShow() ? 'update' : 'view',
$entityKey,
$instanceId
);
- $form = $entity->getFormOrFail($entityKey->subEntity());
+ $form = $entity->getFormOrFail($entityKey->multiformKey());
abort_if(
(! $instanceId && ! $form instanceof SharpSingleForm)
@@ -94,7 +88,7 @@ public function edit(string $parentUri, EntityKey $entityKey, ?string $instanceI
$titleEntityLabel ??= sharp()
->context()
->breadcrumb()
- ->getParentShowCachedBreadcrumbLabel() ?: $entity->getLabelOrFail($entityKey->subEntity());
+ ->getParentShowCachedBreadcrumbLabel() ?: $entity->getLabelOrFail($entityKey->multiformKey());
return Inertia::render('Form/Form', [
'form' => FormData::from([
@@ -122,11 +116,11 @@ public function edit(string $parentUri, EntityKey $entityKey, ?string $instanceI
public function update(string $parentUri, EntityKey $entityKey, ?string $instanceId = null)
{
- sharp_check_ability('update', $entityKey, $instanceId);
+ $this->authorizationManager->check('update', $entityKey, $instanceId);
$form = $this->entityManager
->entityFor($entityKey)
- ->getFormOrFail($entityKey->subEntity());
+ ->getFormOrFail($entityKey->multiformKey());
abort_if(
(! $instanceId && ! $form instanceof SharpSingleForm)
@@ -152,16 +146,15 @@ public function store(string $parentUri, EntityKey $entityKey)
{
$form = $this->entityManager
->entityFor($entityKey)
- ->getFormOrFail($entityKey->subEntity());
+ ->getFormOrFail($entityKey->multiformKey());
if ($form instanceof SharpSingleForm) {
// There is no creation in SingleForms
return $this->update($parentUri, $entityKey);
}
- sharp_check_ability('create', $entityKey);
+ $this->authorizationManager->check('create', $entityKey);
$form->buildFormConfig();
-
$formattedData = $form->formatAndValidateRequestData(request()->all());
$instanceId = $form->update(null, $formattedData);
@@ -223,10 +216,10 @@ private function buildFormData(SharpForm $form, array $formData, string $entityK
? $form->getDataLocalizations()
: [],
'authorizations' => [
- 'create' => $this->sharpAuthorizationManager->isAllowed('create', $entityKey),
- 'view' => $this->sharpAuthorizationManager->isAllowed('view', $entityKey, $instanceId),
- 'update' => $this->sharpAuthorizationManager->isAllowed('update', $entityKey, $instanceId),
- 'delete' => $this->sharpAuthorizationManager->isAllowed('delete', $entityKey, $instanceId),
+ 'create' => $this->authorizationManager->isAllowed('create', $entityKey),
+ 'view' => $this->authorizationManager->isAllowed('view', $entityKey, $instanceId),
+ 'update' => $this->authorizationManager->isAllowed('update', $entityKey, $instanceId),
+ 'delete' => $this->authorizationManager->isAllowed('delete', $entityKey, $instanceId),
],
];
}
diff --git a/src/Http/Controllers/GlobalFilterController.php b/src/Http/Controllers/GlobalFilterController.php
index b807cdea3..7c2697ed4 100644
--- a/src/Http/Controllers/GlobalFilterController.php
+++ b/src/Http/Controllers/GlobalFilterController.php
@@ -18,5 +18,4 @@ public function update(string $filterKey, GlobalFilters $globalFilters): Redirec
return redirect()->route('code16.sharp.home');
}
-
}
diff --git a/src/Http/Controllers/HandlesEntityListItems.php b/src/Http/Controllers/HandlesEntityListItems.php
new file mode 100644
index 000000000..3da2094f2
--- /dev/null
+++ b/src/Http/Controllers/HandlesEntityListItems.php
@@ -0,0 +1,91 @@
+entityFor($entityKey);
+
+ return collect($listItems)
+ ->map(function ($item) use ($entity, $entityKey, $list) {
+ $itemEntityKey = $this->getItemEntityKey($item, $entityKey, $entity, $list);
+ $instanceId = $item[$list->getInstanceIdAttribute()] ?? null;
+
+ return [
+ ...$item,
+ '_meta' => [
+ 'url' => $this->getItemUrl($item, $entityKey, $entity, $list),
+ 'authorizations' => [
+ 'view' => app(SharpAuthorizationManager::class)->isAllowed('view', $itemEntityKey, $instanceId),
+ 'delete' => app(SharpAuthorizationManager::class)->isAllowed('delete', $itemEntityKey, $instanceId),
+ ],
+ ],
+ ];
+ })
+ ->all();
+ }
+
+ private function getItemEntityKey(array $item, string $entityKey, SharpEntity $entity, SharpEntityList $list): string
+ {
+ $itemEntityAttributeValue = $list->getEntityAttribute()
+ ? ($item[$list->getEntityAttribute()] ?? null)
+ : null;
+
+ if ($itemEntityAttributeValue) {
+ if (count($entity->getMultiforms()) > 0) {
+ return EntityKey::multiform(baseKey: $entityKey, multiformKey: $itemEntityAttributeValue);
+ }
+
+ if (! $listEntity = ($list->getEntities()->find($itemEntityAttributeValue))) {
+ throw new SharpInvalidEntityKeyException(
+ sprintf('The sub-entity [%s] for the entity-list [%s] was not found.', $itemEntityAttributeValue, get_class($list))
+ );
+ }
+
+ return $listEntity->getEntityKey();
+ }
+
+ return $entityKey;
+ }
+
+ private function getItemUrl(array $item, string $entityKey, SharpEntity $entity, SharpEntityList $list): ?string
+ {
+ $breadcrumb = sharp()->context()->breadcrumb();
+
+ $itemEntityKey = $this->getItemEntityKey($item, $entityKey, $entity, $list);
+ $instanceId = $item[$list->getInstanceIdAttribute()] ?? null;
+
+ if (! app(SharpAuthorizationManager::class)->isAllowed('view', $itemEntityKey, $instanceId)) {
+ return null;
+ }
+
+ $itemEntity = app(SharpEntityManager::class)->entityFor($itemEntityKey);
+
+ if ($breadcrumb->getCurrentPath()) {
+ if ($itemEntity->hasShow()) {
+ return route('code16.sharp.show.show', [
+ 'parentUri' => $breadcrumb->getCurrentPath(),
+ 'entityKey' => $itemEntityKey,
+ 'instanceId' => $item[$list->getInstanceIdAttribute()],
+ ]);
+ } elseif ($itemEntity->hasForm()) {
+ return route('code16.sharp.form.edit', [
+ 'parentUri' => $breadcrumb->getCurrentPath(),
+ 'entityKey' => $itemEntityKey,
+ 'instanceId' => $item[$list->getInstanceIdAttribute()],
+ ]);
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Http/Controllers/SharpProtectedController.php b/src/Http/Controllers/SharpProtectedController.php
index 13e64f8a7..234240925 100644
--- a/src/Http/Controllers/SharpProtectedController.php
+++ b/src/Http/Controllers/SharpProtectedController.php
@@ -2,12 +2,20 @@
namespace Code16\Sharp\Http\Controllers;
+use Code16\Sharp\Auth\SharpAuthorizationManager;
+use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Illuminate\Routing\Controller;
class SharpProtectedController extends Controller
{
+ protected SharpEntityManager $entityManager;
+ protected SharpAuthorizationManager $authorizationManager;
+
public function __construct()
{
+ $this->entityManager = app(SharpEntityManager::class);
+ $this->authorizationManager = app(SharpAuthorizationManager::class);
+
$guardSuffix = sharp()->config()->get('auth.guard') ? ':'.sharp()->config()->get('auth.guard') : '';
$this->middleware('sharp_auth'.$guardSuffix);
}
diff --git a/src/Http/Controllers/ShowController.php b/src/Http/Controllers/ShowController.php
index 631305f4a..5b237e825 100644
--- a/src/Http/Controllers/ShowController.php
+++ b/src/Http/Controllers/ShowController.php
@@ -2,12 +2,10 @@
namespace Code16\Sharp\Http\Controllers;
-use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Data\BreadcrumbData;
use Code16\Sharp\Data\NotificationData;
use Code16\Sharp\Data\Show\ShowData;
use Code16\Sharp\Show\SharpSingleShow;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Code16\Sharp\Utils\Entities\ValueObjects\EntityKey;
use Inertia\Inertia;
@@ -16,16 +14,9 @@ class ShowController extends SharpProtectedController
use HandlesSharpNotificationsInRequest;
use PreloadsShowEntityLists;
- public function __construct(
- private readonly SharpAuthorizationManager $sharpAuthorizationManager,
- private readonly SharpEntityManager $entityManager,
- ) {
- parent::__construct();
- }
-
public function show(string $parentUri, EntityKey $entityKey, string $instanceId)
{
- sharp_check_ability('view', $entityKey, $instanceId);
+ $this->authorizationManager->check('view', $entityKey, $instanceId);
$entity = $this->entityManager->entityFor($entityKey);
$show = $entity->getShowOrFail();
@@ -36,7 +27,7 @@ public function show(string $parentUri, EntityKey $entityKey, string $instanceId
$showData = $show->instance($instanceId);
$payload = ShowData::from([
- 'title' => $showData[$show->titleAttribute()] ?? $entity->getLabelOrFail($entityKey->subEntity()),
+ 'title' => $showData[$show->titleAttribute()] ?? $entity->getLabelOrFail($entityKey->multiformKey()),
'config' => $show->showConfig($instanceId),
'fields' => $show->fields(),
'layout' => $show->showLayout(),
@@ -46,10 +37,10 @@ public function show(string $parentUri, EntityKey $entityKey, string $instanceId
? $show->getDataLocalizations()
: null,
'authorizations' => [
- 'create' => $this->sharpAuthorizationManager->isAllowed('create', $entityKey),
- 'view' => $this->sharpAuthorizationManager->isAllowed('view', $entityKey, $instanceId),
- 'update' => $this->sharpAuthorizationManager->isAllowed('update', $entityKey, $instanceId),
- 'delete' => $this->sharpAuthorizationManager->isAllowed('delete', $entityKey, $instanceId),
+ 'create' => $this->authorizationManager->isAllowed('create', $entityKey),
+ 'view' => $this->authorizationManager->isAllowed('view', $entityKey, $instanceId),
+ 'update' => $this->authorizationManager->isAllowed('update', $entityKey, $instanceId),
+ 'delete' => $this->authorizationManager->isAllowed('delete', $entityKey, $instanceId),
],
]);
@@ -70,10 +61,9 @@ public function show(string $parentUri, EntityKey $entityKey, string $instanceId
public function delete(string $parentUri, string $entityKey, string $instanceId)
{
- sharp_check_ability('delete', $entityKey, $instanceId);
+ $this->authorizationManager->check('delete', $entityKey, $instanceId);
$show = $this->entityManager->entityFor($entityKey)->getShowOrFail();
-
$show->delete($instanceId);
return redirect()->to(sharp()->context()->breadcrumb()->getPreviousSegmentUrl());
diff --git a/src/Http/Controllers/SingleShowController.php b/src/Http/Controllers/SingleShowController.php
index d05d16ff0..35666f6f4 100644
--- a/src/Http/Controllers/SingleShowController.php
+++ b/src/Http/Controllers/SingleShowController.php
@@ -2,11 +2,9 @@
namespace Code16\Sharp\Http\Controllers;
-use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Data\BreadcrumbData;
use Code16\Sharp\Data\NotificationData;
use Code16\Sharp\Data\Show\ShowData;
-use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Code16\Sharp\Utils\Entities\ValueObjects\EntityKey;
use Inertia\Inertia;
@@ -15,16 +13,9 @@ class SingleShowController extends SharpProtectedController
use HandlesSharpNotificationsInRequest;
use PreloadsShowEntityLists;
- public function __construct(
- private SharpAuthorizationManager $sharpAuthorizationManager,
- private SharpEntityManager $entityManager,
- ) {
- parent::__construct();
- }
-
public function show(EntityKey $entityKey)
{
- sharp_check_ability('view', $entityKey);
+ $this->authorizationManager->check('view', $entityKey);
$entity = $this->entityManager->entityFor($entityKey);
$show = $entity->getShowOrFail();
@@ -33,7 +24,7 @@ public function show(EntityKey $entityKey)
$showData = $show->instance(null);
$payload = ShowData::from([
- 'title' => $showData[$show->titleAttribute()] ?? $entity->getLabelOrFail($entityKey->subEntity()),
+ 'title' => $showData[$show->titleAttribute()] ?? $entity->getLabelOrFail($entityKey->multiformKey()),
'config' => $show->showConfig(null),
'fields' => $show->fields(),
'layout' => $show->showLayout(),
@@ -44,8 +35,8 @@ public function show(EntityKey $entityKey)
: null,
'authorizations' => [
'create' => false,
- 'view' => $this->sharpAuthorizationManager->isAllowed('view', $entityKey),
- 'update' => $this->sharpAuthorizationManager->isAllowed('update', $entityKey),
+ 'view' => $this->authorizationManager->isAllowed('view', $entityKey),
+ 'update' => $this->authorizationManager->isAllowed('update', $entityKey),
'delete' => false,
],
]);
diff --git a/src/Utils/Entities/BaseSharpEntity.php b/src/Utils/Entities/BaseSharpEntity.php
index c01a72476..5dd3efa4d 100644
--- a/src/Utils/Entities/BaseSharpEntity.php
+++ b/src/Utils/Entities/BaseSharpEntity.php
@@ -11,6 +11,7 @@ abstract class BaseSharpEntity
public static string $entityKey;
protected ?string $policy = null;
protected string $label = 'entity';
+ protected ?string $icon = null;
final public function getPolicyOrDefault(): SharpEntityPolicy
{
diff --git a/src/Utils/Entities/SharpEntity.php b/src/Utils/Entities/SharpEntity.php
index 2cf13aca1..3213cb963 100644
--- a/src/Utils/Entities/SharpEntity.php
+++ b/src/Utils/Entities/SharpEntity.php
@@ -42,15 +42,26 @@ final public function hasShow(): bool
return $this->getShow() !== null;
}
- final public function getFormOrFail(?string $subEntity = null): SharpForm
+ final public function hasForm(): bool
{
- if ($subEntity) {
- if (! $form = ($this->getMultiforms()[$subEntity][0] ?? null)) {
- throw new SharpInvalidEntityKeyException(
- sprintf('The subform for the entity [%s:%s] was not found.', get_class($this), $subEntity)
- );
+ return $this->getForm() !== null;
+ }
+
+ final public function getFormOrFail(?string $multiformKey = null): SharpForm
+ {
+ if ($multiformKey) {
+ if (count($this->getMultiforms())) {
+ if (! $form = ($this->getMultiforms()[$multiformKey][0] ?? null)) {
+ throw new SharpInvalidEntityKeyException(
+ sprintf('The subform for the entity [%s:%s] was not found.', get_class($this), $multiformKey)
+ );
+ }
+
+ return instanciate($form);
}
- } elseif (! $form = $this->getForm()) {
+ }
+
+ if (! $form = $this->getForm()) {
throw new SharpInvalidEntityKeyException(
sprintf('The form for the entity [%s] was not found.', get_class($this))
);
@@ -59,15 +70,15 @@ final public function getFormOrFail(?string $subEntity = null): SharpForm
return instanciate($form);
}
- final public function getLabelOrFail(?string $subEntity = null): string
+ final public function getLabelOrFail(?string $multiformKey = null): string
{
- $label = $subEntity
- ? $this->getMultiforms()[$subEntity][1] ?? null
+ $label = $multiformKey
+ ? $this->getMultiforms()[$multiformKey][1] ?? null
: $this->getLabel();
if ($label === null) {
throw new SharpInvalidEntityKeyException(
- sprintf('The label of the subform for the entity [%s:%s] was not found.', get_class($this), $subEntity)
+ sprintf('The label of the subform for the entity [%s:%s] was not found.', get_class($this), $multiformKey)
);
}
@@ -110,6 +121,10 @@ protected function getForm(): ?SharpForm
return $this->form ? app($this->form) : null;
}
+ /**
+ * @deprecated
+ * @see SharpEntityList::configureEntityMap()
+ */
public function getMultiforms(): array
{
return [];
diff --git a/src/Utils/Entities/SharpEntityManager.php b/src/Utils/Entities/SharpEntityManager.php
index 78de98f2d..286b2e128 100644
--- a/src/Utils/Entities/SharpEntityManager.php
+++ b/src/Utils/Entities/SharpEntityManager.php
@@ -4,13 +4,13 @@
use Code16\Sharp\Exceptions\SharpInvalidConfigException;
use Code16\Sharp\Exceptions\SharpInvalidEntityKeyException;
-use Illuminate\Support\Str;
+use Code16\Sharp\Utils\Entities\ValueObjects\EntityKey;
class SharpEntityManager
{
public function entityFor(string $entityKey): SharpEntity|SharpDashboardEntity
{
- $entityKey = Str::before($entityKey, ':');
+ $entityKey = (new EntityKey($entityKey))->baseKey();
if (count(sharp()->config()->get('entities')) > 0) {
$entityClass = sharp()->config()->get('entities.'.$entityKey);
diff --git a/src/Utils/Entities/ValueObjects/EntityKey.php b/src/Utils/Entities/ValueObjects/EntityKey.php
index bd9978b1c..035e232a8 100644
--- a/src/Utils/Entities/ValueObjects/EntityKey.php
+++ b/src/Utils/Entities/ValueObjects/EntityKey.php
@@ -15,6 +15,11 @@ public function __construct(
protected ?string $key = null
) {}
+ public static function multiform(string $baseKey, ?string $multiformKey): static
+ {
+ return new static($multiformKey ? "$baseKey:$multiformKey" : $baseKey);
+ }
+
public function baseKey(): string
{
return str_contains($this->key, ':')
@@ -22,7 +27,7 @@ public function baseKey(): string
: $this->key;
}
- public function subEntity(): ?string
+ public function multiformKey(): ?string
{
return str_contains($this->key, ':')
? Str::after($this->key, ':')
diff --git a/src/Utils/Menu/SharpMenuItemLink.php b/src/Utils/Menu/SharpMenuItemLink.php
index 6c093439b..d720be255 100644
--- a/src/Utils/Menu/SharpMenuItemLink.php
+++ b/src/Utils/Menu/SharpMenuItemLink.php
@@ -3,6 +3,7 @@
namespace Code16\Sharp\Utils\Menu;
use Closure;
+use Code16\Sharp\Auth\SharpAuthorizationManager;
use Code16\Sharp\Utils\Entities\SharpDashboardEntity;
use Code16\Sharp\Utils\Entities\SharpEntity;
use Code16\Sharp\Utils\Entities\SharpEntityManager;
@@ -110,7 +111,8 @@ public function isExternalLink(): bool
public function isAllowed(): bool
{
return $this->isExternalLink()
- || sharp_has_ability('entity', $this->getEntityKey());
+ || app(SharpAuthorizationManager::class)
+ ->isAllowed('entity', $this->getEntityKey());
}
public function isCurrent(): bool
diff --git a/src/routes/api.php b/src/routes/api.php
index 3010b6f34..9d025f701 100644
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -29,10 +29,10 @@
Route::post('/dashboard/{dashboardKey}/command/{commandKey}', [ApiDashboardCommandController::class, 'update'])
->name('code16.sharp.api.dashboard.command');
- Route::get('/list/{entityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'create'])
+ Route::get('/list/{entityKey}/form/{formEntityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'create'])
->name('code16.sharp.api.list.command.quick-creation-form.create');
- Route::post('/list/{entityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'store'])
+ Route::post('/list/{entityKey}/form/{formEntityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'store'])
->name('code16.sharp.api.list.command.quick-creation-form.store');
// EEL
diff --git a/src/sharp_helper.php b/src/sharp_helper.php
index 47e30108a..77ebd577e 100644
--- a/src/sharp_helper.php
+++ b/src/sharp_helper.php
@@ -1,7 +1,5 @@
isAllowed($ability, Str::before($entityKey, ':'), $instanceId);
-}
-
-function sharp_check_ability(string $ability, string $entityKey, ?string $instanceId = null)
-{
- app(Code16\Sharp\Auth\SharpAuthorizationManager::class)
- ->check($ability, Str::before($entityKey, ':'), $instanceId);
-}
diff --git a/tests/Fixtures/Entities/PersonChemistEntity.php b/tests/Fixtures/Entities/PersonChemistEntity.php
new file mode 100644
index 000000000..ac22a61f4
--- /dev/null
+++ b/tests/Fixtures/Entities/PersonChemistEntity.php
@@ -0,0 +1,10 @@
+config()->declareEntity(PersonEntity::class);
@@ -133,14 +139,154 @@ public function getListData(): array|Arrayable
}
});
- $this->getJson('/sharp/api/list/person')
+ $this->getJson('/sharp/api/list/person', headers: [
+ SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'),
+ ])
->assertOk()
- ->assertJsonFragment([
- 'data' => [
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->has('data.0', fn (AssertableJson $json) => $json->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('data.1', fn (AssertableJson $json) => $json->where('id', 2)
+ ->where('name', 'Niels Bohr')
+ ->etc()
+ )
+ ->etc()
+ );
+});
+
+it('sets appropriate `_meta` for each item linking to a show in an EEL case', function () {
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return $this->transform([
['id' => 1, 'name' => 'Marie Curie'],
['id' => 2, 'name' => 'Niels Bohr'],
- ],
- ]);
+ ]);
+ }
+ });
+
+ $this->getJson('/sharp/api/list/person', headers: [
+ SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'),
+ ])
+ ->assertOk()
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->has('data.0', fn (AssertableJson $json) => $json
+ ->where('_meta.url', route('code16.sharp.show.show', [
+ 'parentUri' => 's-list/person/s-show/person/1',
+ 'entityKey' => 'person',
+ 'instanceId' => 1,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->has('data.1', fn (AssertableJson $json) => $json->where('id', 2)
+ ->where('_meta.url', route('code16.sharp.show.show', [
+ 'parentUri' => 's-list/person/s-show/person/1',
+ 'entityKey' => 'person',
+ 'instanceId' => 2,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->etc()
+ );
+});
+
+it('sets appropriate `_meta` for each item linking to a form in an EEL case', function () {
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return $this->transform([
+ ['id' => 1, 'name' => 'Marie Curie'],
+ ['id' => 2, 'name' => 'Niels Bohr'],
+ ]);
+ }
+ });
+
+ fakeShowFor('person', null);
+
+ $this->getJson('/sharp/api/list/person', headers: [
+ SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'),
+ ])
+ ->assertOk()
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->has('data.0', fn (AssertableJson $json) => $json
+ ->where('_meta.url', route('code16.sharp.form.edit', [
+ 'parentUri' => 's-list/person/s-show/person/1',
+ 'entityKey' => 'person',
+ 'instanceId' => 1,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->has('data.1', fn (AssertableJson $json) => $json->where('id', 2)
+ ->where('_meta.url', route('code16.sharp.form.edit', [
+ 'parentUri' => 's-list/person/s-show/person/1',
+ 'entityKey' => 'person',
+ 'instanceId' => 2,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->etc()
+ );
+});
+
+it('sets appropriate `_meta` for with entities map', function () {
+ sharp()->config()->declareEntity(PersonChemistEntity::class);
+ sharp()->config()->declareEntity(PersonPhysicistEntity::class);
+ sharp()->config()->declareEntity(PersonUnknownEntity::class);
+
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return [
+ ['id' => 1, 'name' => 'Marie Curie', 'job' => 'chemist'],
+ ['id' => 2, 'name' => 'Rosalind Franklin', 'job' => 'physicist'],
+ ['id' => 3, 'name' => 'James Bond', 'job' => 'unknown'],
+ ];
+ }
+
+ public function buildListConfig(): void
+ {
+ $this->configureEntityMap(
+ attribute: 'job',
+ entities: EntityListEntities::make()
+ ->addEntity('chemist', PersonChemistEntity::class, icon: 'testicon-car')
+ ->addEntity('physicist', PersonPhysicistEntity::class)
+ ->addEntity('unknown', PersonUnknownEntity::class),
+ );
+ }
+ });
+
+ $this->getJson('/sharp/api/list/person', headers: [
+ SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'),
+ ])
+ ->assertOk()
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->count('data', 3)
+ ->where('data.0._meta.url', route('code16.sharp.form.edit', [
+ 'parentUri' => 's-list/person/s-show/person/1',
+ 'entityKey' => 'person-chemist',
+ 'instanceId' => 1,
+ ]))
+ ->where('data.1._meta.url', route('code16.sharp.show.show', [
+ 'parentUri' => 's-list/person/s-show/person/1',
+ 'entityKey' => 'person-physicist',
+ 'instanceId' => 2,
+ ]))
+ ->whereNull('data.2._meta.url')
+ ->etc()
+ );
});
it('gets paginated data if wanted as JSON in an EEL case', function () {
@@ -157,25 +303,91 @@ public function getListData(): array|Arrayable
}
});
- $metaJson = $this->getJson('/sharp/api/list/person')
+ $this->getJson('/sharp/api/list/person', headers: [
+ SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'),
+ ])
->assertOk()
- ->assertJsonFragment([
- 'data' => [
- ['id' => 1, 'name' => 'Marie Curie'],
- ['id' => 2, 'name' => 'Niels Bohr'],
- ],
- ])
- ->json('meta');
-
- expect($metaJson)
- ->toMatchArray([
- 'current_page' => 1,
- 'first_page_url' => '/?page=1',
- 'last_page_url' => '/?page=10',
- 'from' => 1,
- 'to' => 2,
- 'last_page' => 10,
- 'per_page' => 2,
- 'total' => 20,
- ]);
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->has('data.0', fn (AssertableJson $json) => $json->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('data.1', fn (AssertableJson $json) => $json->where('id', 2)
+ ->where('name', 'Niels Bohr')
+ ->etc()
+ )
+ ->has('meta', fn (AssertableJson $json) => $json->where('current_page', 1)
+ ->where('per_page', 2)
+ ->where('total', 20)
+ ->where('from', 1)
+ ->where('to', 2)
+ ->where('last_page', 10)
+ ->where('first_page_url', '/?page=1')
+ ->where('last_page_url', '/?page=10')
+ ->etc()
+ )
+ ->etc()
+ );
+});
+
+it('get entities if configured', function () {
+ $this->withoutExceptionHandling();
+
+ sharp()->config()->declareEntity(PersonChemistEntity::class);
+ sharp()->config()->declareEntity(PersonPhysicistEntity::class);
+ sharp()->config()->declareEntity(PersonUnknownEntity::class);
+
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return [
+ ['id' => 1, 'name' => 'Marie Curie', 'job' => 'chemist'],
+ ['id' => 2, 'name' => 'Rosalind Franklin', 'job' => 'physicist'],
+ ['id' => 3, 'name' => 'James Bond', 'job' => 'unknown'],
+ ];
+ }
+
+ public function buildListConfig(): void
+ {
+ $this->configureEntityMap(
+ attribute: 'job',
+ entities: EntityListEntities::make()
+ ->addEntity('chemist', PersonChemistEntity::class, icon: 'testicon-car')
+ ->addEntity('physicist', PersonPhysicistEntity::class)
+ ->addEntity('unknown', PersonUnknownEntity::class),
+ );
+ }
+ });
+
+ $this->getJson('/sharp/api/list/person', headers: [
+ SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'),
+ ])
+ ->assertOk()
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->count('data', 3)
+ ->has('entities.0', fn (AssertableJson $json) => $json
+ ->where('key', 'chemist')
+ ->where('entityKey', 'person-chemist')
+ ->where('label', 'Chemist')
+ ->where('icon.name', 'testicon-car')
+ ->where('formCreateUrl', route('code16.sharp.form.create', ['parentUri' => 's-list/person/s-show/person/1', 'entityKey' => 'person-chemist']))
+ ->etc()
+ )
+ ->has('entities.1', fn (AssertableJson $json) => $json
+ ->where('key', 'physicist')
+ ->where('entityKey', 'person-physicist')
+ ->where('label', 'Physicist')
+ ->where('formCreateUrl', route('code16.sharp.form.create', ['parentUri' => 's-list/person/s-show/person/1', 'entityKey' => 'person-physicist']))
+ ->etc()
+ )
+ ->has('entities.2', fn (AssertableJson $json) => $json
+ ->where('key', 'unknown')
+ ->where('entityKey', 'person-unknown')
+ ->where('label', 'Unknown')
+ ->where('formCreateUrl', route('code16.sharp.form.create', ['parentUri' => 's-list/person/s-show/person/1', 'entityKey' => 'person-unknown']))
+ ->etc()
+ )
+ ->etc()
+ );
});
diff --git a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php
index 1e6825f3b..8359cf5d9 100644
--- a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php
+++ b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php
@@ -34,7 +34,7 @@ public function buildFormFields(FieldsContainer $formFields): void
$this
->getJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
)
->assertOk()
->assertJson([
@@ -69,7 +69,7 @@ public function buildFormFields(FieldsContainer $formFields): void
$this
->getJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
)
->assertOk()
->assertJsonCount(1, 'fields');
@@ -83,7 +83,7 @@ public function buildListConfig(): void {}
$this
->getJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
)
->assertStatus(403);
});
@@ -118,7 +118,7 @@ public function update($id, array $data)
$this
->postJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
['data' => ['name' => 'Marie Curie', 'job' => 'Scientist']],
)
->assertOk()
@@ -143,7 +143,7 @@ public function update($id, array $data) {}
$this
->postJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
['data' => []],
)
->assertOk()
@@ -178,7 +178,7 @@ public function update($id, array $data)
$this
->postJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
['data' => []],
[
SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person'),
@@ -217,7 +217,7 @@ public function update($id, array $data)
$this
->postJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
['data' => ['name' => '']],
)
->assertStatus(422)
@@ -250,7 +250,7 @@ public function update($id, array $data)
$this
->postJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['person']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['person', 'person']),
['data' => ['name' => 'Marie Curie']],
)
->assertOk()
@@ -298,7 +298,7 @@ public function update($id, array $data)
// Simulate a post of the colleague quick creation command from an EEL
$this
->postJson(
- route('code16.sharp.api.list.command.quick-creation-form.create', ['colleague']),
+ route('code16.sharp.api.list.command.quick-creation-form.create', ['colleague', 'colleague']),
['data' => ['name' => 'Marie Curie']],
)
->assertOk()
diff --git a/tests/Http/Auth/AuthorizationsTest.php b/tests/Http/Auth/AuthorizationsTest.php
index babcf7288..f156f02e1 100644
--- a/tests/Http/Auth/AuthorizationsTest.php
+++ b/tests/Http/Auth/AuthorizationsTest.php
@@ -11,7 +11,7 @@
use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Gate;
-use Inertia\Testing\AssertableInertia as Assert;
+use Illuminate\Testing\Fluent\AssertableJson;
beforeEach(function () {
login();
@@ -38,7 +38,17 @@
$this->delete('/sharp/s-list/person/s-show/person/1')->assertForbidden();
// We can still view the list
- $this->get('/sharp/s-list/person')->assertOk();
+ $this->get('/sharp/s-list/person')
+ ->assertOk()
+ ->assertInertia(fn (AssertableJson $json) => $json
+ ->where('entityList.authorizations.create', false)
+ ->where('entityList.data.0._meta.authorizations.view', false)
+ ->where('entityList.data.0._meta.authorizations.delete', false)
+ ->whereNull('entityList.data.0._meta.url')
+ ->where('entityList.data.1._meta.authorizations.view', false)
+ ->where('entityList.data.1._meta.authorizations.delete', false)
+ ->whereNull('entityList.data.1._meta.url')
+ );
});
it('allows to access to the form in readonly mode if there is no show', function () {
@@ -73,7 +83,7 @@
$this
->get('/sharp/s-list/person/s-form/person')
- ->assertInertia(fn (Assert $page) => $page
+ ->assertInertia(fn (AssertableJson $json) => $json
->where('form.authorizations', [
'delete' => false,
'update' => false,
@@ -83,7 +93,7 @@
);
$this->get('/sharp/s-list/person/s-show/person/1')
- ->assertInertia(fn (Assert $page) => $page
+ ->assertInertia(fn (AssertableJson $json) => $json
->where('show.authorizations', [
'delete' => false,
'update' => false,
@@ -110,7 +120,7 @@ public function findSingle(): array
$this
->get('/sharp/s-show/single_person')
->assertOk()
- ->assertInertia(fn (Assert $page) => $page
+ ->assertInertia(fn (AssertableJson $json) => $json
->where('show.authorizations', [
'delete' => false,
'update' => true,
@@ -138,13 +148,13 @@ public function getListData(): array|Arrayable
$this
->get('/sharp/s-list/person')
- ->assertInertia(fn (Assert $page) => $page
- ->where('entityList.authorizations', [
- 'delete' => [],
- 'reorder' => true,
- 'create' => true,
- 'view' => [1, 2],
- ])
+ ->assertInertia(fn (AssertableJson $json) => $json
+ ->where('entityList.authorizations.reorder', true)
+ ->where('entityList.authorizations.create', true)
+ ->where('entityList.data.0._meta.authorizations.view', true)
+ ->where('entityList.data.0._meta.authorizations.delete', false)
+ ->where('entityList.data.1._meta.authorizations.view', true)
+ ->where('entityList.data.1._meta.authorizations.delete', false)
);
});
@@ -163,7 +173,7 @@ public function getListData(): array|Arrayable
// Create (no instanceId, only create is allowed)
$this
->get('/sharp/s-list/person/s-form/person')
- ->assertInertia(fn (Assert $page) => $page
+ ->assertInertia(fn (AssertableJson $json) => $json
->where('form.authorizations', [
'delete' => false,
'update' => false,
@@ -175,7 +185,7 @@ public function getListData(): array|Arrayable
// Edit
$this
->get('/sharp/s-list/person/s-form/person/1')
- ->assertInertia(fn (Assert $page) => $page
+ ->assertInertia(fn (AssertableJson $json) => $json
->where('form.authorizations', [
'delete' => true,
'update' => true,
@@ -187,26 +197,27 @@ public function getListData(): array|Arrayable
// EL (inertia)
$this
->get('/sharp/s-list/person')
- ->assertInertia(fn (Assert $page) => $page
- ->where('entityList.authorizations', [
- 'delete' => [1, 2],
- 'reorder' => true,
- 'create' => true,
- 'view' => [1, 2],
- ])
+ ->assertInertia(fn (AssertableJson $json) => $json
+ ->where('entityList.authorizations.reorder', true)
+ ->where('entityList.authorizations.create', true)
+ ->where('entityList.data.0._meta.authorizations.view', true)
+ ->where('entityList.data.0._meta.authorizations.delete', true)
+ ->where('entityList.data.1._meta.authorizations.view', true)
+ ->where('entityList.data.1._meta.authorizations.delete', true)
);
// EEL (json)
$this
->getJson('/sharp/api/list/person')
- ->assertJsonFragment([
- 'authorizations' => [
- 'delete' => [1, 2],
- 'reorder' => true,
- 'create' => true,
- 'view' => [1, 2],
- ],
- ]);
+ ->assertJson(fn (AssertableJson $json) => $json
+ ->where('authorizations.reorder', true)
+ ->where('authorizations.create', true)
+ ->where('data.0._meta.authorizations.view', true)
+ ->where('data.0._meta.authorizations.delete', true)
+ ->where('data.1._meta.authorizations.view', true)
+ ->where('data.1._meta.authorizations.delete', true)
+ ->etc()
+ );
});
it('checks the main entity prohibited actions in case of a sub entity', function () {
diff --git a/tests/Http/Auth/PolicyAuthorizationsTest.php b/tests/Http/Auth/PolicyAuthorizationsTest.php
index 987f11cb3..a085bcd89 100644
--- a/tests/Http/Auth/PolicyAuthorizationsTest.php
+++ b/tests/Http/Auth/PolicyAuthorizationsTest.php
@@ -128,10 +128,16 @@ public function delete($user, $instanceId): bool
->get('/sharp/s-list/person')
->assertInertia(fn (Assert $page) => $page
->where('entityList.authorizations', [
- 'delete' => [1],
'reorder' => true,
'create' => true,
- 'view' => [1, 2],
+ ])
+ ->where('entityList.data.0._meta.authorizations', [
+ 'view' => true,
+ 'delete' => true,
+ ])
+ ->where('entityList.data.1._meta.authorizations', [
+ 'view' => true,
+ 'delete' => false,
])
);
});
diff --git a/tests/Http/EntityListControllerTest.php b/tests/Http/EntityListControllerTest.php
index 522ccb6ea..2ff0b1a10 100644
--- a/tests/Http/EntityListControllerTest.php
+++ b/tests/Http/EntityListControllerTest.php
@@ -1,15 +1,20 @@
get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Niels Bohr')
+ ->etc()
+ )
+ ->count('entityList.data', 2)
+ );
+});
+
+it('sets appropriate `_meta` for each items linking to a show', function () {
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return $this->transform([
['id' => 1, 'name' => 'Marie Curie'],
['id' => 2, 'name' => 'Niels Bohr'],
- ])
+ ]);
+ }
+ });
+
+ $this->get('/sharp/s-list/person')
+ ->assertOk()
+ ->assertInertia(fn (Assert $page) => $page
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('_meta.url', route('code16.sharp.show.show', [
+ 'parentUri' => 's-list/person',
+ 'entityKey' => 'person',
+ 'instanceId' => 1,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('_meta.url', route('code16.sharp.show.show', [
+ 'parentUri' => 's-list/person',
+ 'entityKey' => 'person',
+ 'instanceId' => 2,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->count('entityList.data', 2)
+ );
+});
+
+it('sets appropriate `_meta` for each items linking to a form', function () {
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return $this->transform([
+ ['id' => 1, 'name' => 'Marie Curie'],
+ ['id' => 2, 'name' => 'Niels Bohr'],
+ ]);
+ }
+ });
+
+ fakeShowFor('person', null);
+
+ $this->get('/sharp/s-list/person')
+ ->assertOk()
+ ->assertInertia(fn (Assert $page) => $page
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('_meta.url', route('code16.sharp.form.edit', [
+ 'parentUri' => 's-list/person',
+ 'entityKey' => 'person',
+ 'instanceId' => 1,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('_meta.url', route('code16.sharp.form.edit', [
+ 'parentUri' => 's-list/person',
+ 'entityKey' => 'person',
+ 'instanceId' => 2,
+ ]))
+ ->where('_meta.authorizations.view', true)
+ ->where('_meta.authorizations.delete', true)
+ ->etc()
+ )
+ ->count('entityList.data', 2)
+ );
+});
+
+it('sets appropriate `_meta` for with entities map', function () {
+ sharp()->config()->declareEntity(PersonChemistEntity::class);
+ sharp()->config()->declareEntity(PersonPhysicistEntity::class);
+ sharp()->config()->declareEntity(PersonUnknownEntity::class);
+
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return [
+ ['id' => 1, 'name' => 'Marie Curie', 'job' => 'chemist'],
+ ['id' => 2, 'name' => 'Rosalind Franklin', 'job' => 'physicist'],
+ ['id' => 3, 'name' => 'James Bond', 'job' => 'unknown'],
+ ];
+ }
+
+ public function buildListConfig(): void
+ {
+ $this->configureEntityMap(
+ attribute: 'job',
+ entities: EntityListEntities::make()
+ ->addEntity('chemist', PersonChemistEntity::class, icon: 'testicon-car')
+ ->addEntity('physicist', PersonPhysicistEntity::class)
+ ->addEntity('unknown', PersonUnknownEntity::class),
+ );
+ }
+ });
+
+ $this->get('/sharp/s-list/person')
+ ->assertOk()
+ ->assertInertia(fn (Assert $page) => $page
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('_meta.url', route('code16.sharp.form.edit', [
+ 'parentUri' => 's-list/person',
+ 'entityKey' => 'person-chemist',
+ 'instanceId' => 1,
+ ]))
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('_meta.url', route('code16.sharp.show.show', [
+ 'parentUri' => 's-list/person',
+ 'entityKey' => 'person-physicist',
+ 'instanceId' => 2,
+ ]))
+ ->etc()
+ )
+ ->has('entityList.data.2', fn (Assert $json) => $json
+ ->whereNull('_meta.url')
+ ->etc()
+ )
);
});
@@ -60,10 +206,17 @@ public function getListData(): array|Arrayable
$this->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ['id' => 2, 'name' => 'Niels Bohr'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Niels Bohr')
+ ->etc()
+ )
+ ->count('entityList.data', 2)
->has('entityList.meta', fn (Assert $name) => $name
->where('current_page', 1)
->where('from', 1)
@@ -108,10 +261,17 @@ public function buildListConfig(): void
$this->get('/sharp/s-list/person?search=Curie')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ['id' => 3, 'name' => 'Pierre Curie'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('id', 3)
+ ->where('name', 'Pierre Curie')
+ ->etc()
+ )
+ ->count('entityList.data', 2)
);
});
@@ -241,12 +401,18 @@ public function delete($user, $instanceId): bool
$this->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->has('entityList.authorizations', fn (Assert $config) => $config
- ->where('create', true)
- ->where('view', [1, 2])
- ->where('reorder', true)
- ->where('delete', [2])
- )
+ ->where('entityList.authorizations', [
+ 'create' => true,
+ 'reorder' => true,
+ ])
+ ->where('entityList.data.0._meta.authorizations', [
+ 'view' => true,
+ 'delete' => false,
+ ])
+ ->where('entityList.data.1._meta.authorizations', [
+ 'view' => true,
+ 'delete' => true,
+ ])
);
});
@@ -268,7 +434,7 @@ public function buildListConfig(): void
}
});
- app(\Code16\Sharp\Utils\Entities\SharpEntityManager::class)
+ app(SharpEntityManager::class)
->entityFor('person')
->setMultiforms([
'yes' => [PersonForm::class, 'With Nobel prize'],
@@ -278,15 +444,76 @@ public function buildListConfig(): void
$this->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->has('entityList.forms', 2)
- ->has('entityList.forms.yes', fn (Assert $config) => $config
+ ->has('entityList.entities', 2)
+ ->has('entityList.entities.0', fn (Assert $config) => $config
+ ->where('key', 'yes')
+ ->where('entityKey', 'person:yes')
->where('label', 'With Nobel prize')
- ->where('instances', [1])
->etc()
)
- ->has('entityList.forms.nope', fn (Assert $config) => $config
+ ->has('entityList.entities.1', fn (Assert $config) => $config
+ ->where('key', 'nope')
+ ->where('entityKey', 'person:nope')
->where('label', 'No Nobel prize')
- ->where('instances', [2])
+ ->etc()
+ )
+ );
+});
+
+it('get entities if configured', function () {
+ $this->withoutExceptionHandling();
+
+ sharp()->config()->declareEntity(PersonChemistEntity::class);
+ sharp()->config()->declareEntity(PersonPhysicistEntity::class);
+ sharp()->config()->declareEntity(PersonUnknownEntity::class);
+
+ fakeListFor('person', new class() extends PersonList
+ {
+ public function getListData(): array|Arrayable
+ {
+ return [
+ ['id' => 1, 'name' => 'Marie Curie', 'job' => 'chemist'],
+ ['id' => 2, 'name' => 'Rosalind Franklin', 'job' => 'physicist'],
+ ['id' => 3, 'name' => 'James Bond', 'job' => 'unknown'],
+ ];
+ }
+
+ public function buildListConfig(): void
+ {
+ $this->configureEntityMap(
+ attribute: 'job',
+ entities: EntityListEntities::make()
+ ->addEntity('chemist', PersonChemistEntity::class, icon: 'testicon-car')
+ ->addEntity('physicist', PersonPhysicistEntity::class)
+ ->addEntity('unknown', PersonUnknownEntity::class),
+ );
+ }
+ });
+
+ $this->get('/sharp/s-list/person')
+ ->assertOk()
+ ->assertInertia(fn (Assert $page) => $page
+ ->has('entityList.entities', 3)
+ ->has('entityList.entities.0', fn (Assert $config) => $config
+ ->where('key', 'chemist')
+ ->where('entityKey', 'person-chemist')
+ ->where('label', 'Chemist')
+ ->where('icon.name', 'testicon-car')
+ ->where('formCreateUrl', route('code16.sharp.form.create', ['parentUri' => 's-list/person', 'entityKey' => 'person-chemist']))
+ ->etc()
+ )
+ ->has('entityList.entities.1', fn (Assert $config) => $config
+ ->where('key', 'physicist')
+ ->where('entityKey', 'person-physicist')
+ ->where('label', 'Physicist')
+ ->where('formCreateUrl', route('code16.sharp.form.create', ['parentUri' => 's-list/person', 'entityKey' => 'person-physicist']))
+ ->etc()
+ )
+ ->has('entityList.entities.2', fn (Assert $config) => $config
+ ->where('key', 'unknown')
+ ->where('entityKey', 'person-unknown')
+ ->where('label', 'Unknown')
+ ->where('formCreateUrl', route('code16.sharp.form.create', ['parentUri' => 's-list/person', 'entityKey' => 'person-unknown']))
->etc()
)
);
diff --git a/tests/Http/FiltersInRequestTest.php b/tests/Http/FiltersInRequestTest.php
index c172c5bd5..3bb05cada 100644
--- a/tests/Http/FiltersInRequestTest.php
+++ b/tests/Http/FiltersInRequestTest.php
@@ -53,9 +53,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person?filter_job=physicist')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
});
@@ -103,9 +106,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
});
@@ -149,10 +155,17 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person?filter_job=physicist,physician')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ['id' => 2, 'name' => 'Louis Pasteur'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Louis Pasteur')
+ ->etc()
+ )
+ ->count('entityList.data', 2)
);
});
@@ -213,9 +226,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
// Third call: should use QS instead of session
@@ -223,9 +239,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person?filter_job=physician')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 2, 'name' => 'Louis Pasteur'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Louis Pasteur')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
// reset retained filter value
@@ -242,10 +261,17 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ['id' => 2, 'name' => 'Louis Pasteur'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->has('entityList.data.1', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Louis Pasteur')
+ ->etc()
+ )
+ ->count('entityList.data', 2)
);
});
@@ -342,9 +368,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
$this
@@ -361,9 +390,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person?filter_job=physician')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 2, 'name' => 'Louis Pasteur'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Louis Pasteur')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
// Third call: no filter, use retained value
@@ -371,9 +403,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 2, 'name' => 'Louis Pasteur'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 2)
+ ->where('name', 'Louis Pasteur')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
$this
@@ -390,9 +425,12 @@ public function getListData(): array|Arrayable
->get('/sharp/s-list/person')
->assertOk()
->assertInertia(fn (Assert $page) => $page
- ->where('entityList.data', [
- ['id' => 1, 'name' => 'Marie Curie'],
- ])
+ ->has('entityList.data.0', fn (Assert $json) => $json
+ ->where('id', 1)
+ ->where('name', 'Marie Curie')
+ ->etc()
+ )
+ ->count('entityList.data', 1)
);
});
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 0443cf7a7..bafa29a4f 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -5,7 +5,9 @@
use BladeUI\Icons\BladeIconsServiceProvider;
use Code16\ContentRenderer\ContentRendererServiceProvider;
use Code16\Sharp\SharpInternalServiceProvider;
+use Illuminate\Testing\Fluent\AssertableJson;
use Orchestra\Testbench\TestCase as Orchestra;
+use PHPUnit\Framework\Assert as PHPUnit;
class TestCase extends Orchestra
{
@@ -18,6 +20,23 @@ protected function setUp(): void
config()->set('app.key', 'base64:'.base64_encode(random_bytes(32)));
config()->set('view.cache', false);
config()->set('inertia.testing.page_paths', [__DIR__.'/../resources/js/Pages']);
+
+ // laravel 11 polyfill, TODO to remove when laravel 12+ only
+ AssertableJson::macro('whereNull', function ($key) {
+ $this->has($key);
+
+ $actual = $this->prop($key);
+
+ PHPUnit::assertNull(
+ $actual,
+ sprintf(
+ 'Property [%s] should be null.',
+ $this->dotPath($key),
+ )
+ );
+
+ return $this;
+ });
}
protected function getPackageProviders($app)
diff --git a/tests/Unit/EntityList/SharpEntityListTest.php b/tests/Unit/EntityList/SharpEntityListTest.php
index 4a3e8776b..ffb18eda5 100644
--- a/tests/Unit/EntityList/SharpEntityListTest.php
+++ b/tests/Unit/EntityList/SharpEntityListTest.php
@@ -248,7 +248,7 @@ public function buildListConfig(): void
'reorderable' => false,
'hasShowPage' => false,
'instanceIdAttribute' => 'id',
- 'multiformAttribute' => null,
+ 'entityAttribute' => null,
'defaultSort' => 'name',
'defaultSortDir' => 'asc',
'deleteHidden' => false,