diff --git a/src/AccessControl/AccessControlManager.php b/src/AccessControl/AccessControlManager.php new file mode 100644 index 0000000..c9885a6 --- /dev/null +++ b/src/AccessControl/AccessControlManager.php @@ -0,0 +1,84 @@ + + */ + private static \SplStack $access_controllers; + + /** + * Prevent creating multiple instances of the manager. + * + * @since $ver$ + */ + private function __construct() { + } + + /** + * Lazily initializes the Access Control Manager. + * + * @since $ver$ + * + * @return void + */ + private static function initialize(): void { + if ( ! isset( self::$access_controllers ) ) { + self::$access_controllers = new \SplStack(); + self::set( new ReadOnlyAccessController() ); + } + } + + /** + * Sets the current AccessController. + * + * @since $ver$ + * + * @param AccessController $access_controller The access controller. + */ + public static function set( AccessController $access_controller ): void { + self::initialize(); + + self::$access_controllers->push( $access_controller ); + } + + /** + * Returns the current Access Controller. + * + * @since $ver$ + * + * @return AccessController The current AccessController. + */ + public static function current(): AccessController { + self::initialize(); + + return self::$access_controllers->top(); + } + + /** + * Resets the current Access Controller to the previous. + * + * @since $ver$ + */ + public static function reset(): void { + self::initialize(); + + if ( self::$access_controllers->count() > 1 ) { + self::$access_controllers->pop(); + } + } +} diff --git a/src/AccessControl/AccessController.php b/src/AccessControl/AccessController.php new file mode 100644 index 0000000..d64d017 --- /dev/null +++ b/src/AccessControl/AccessController.php @@ -0,0 +1,25 @@ +dataview = $dataview; + } + + /** + * Returns the DataView connected to the capability. + * + * @since $ver$ + */ + public function dataview(): DataView { + return $this->dataview; + } + + /** + * {@inheritDoc} + * + * @since $ver$ + */ + public function is_mutative(): bool { + return false; + } + + /** + * {@inheritDoc} + * + * @since $ver$ + */ + public function is_destructive(): bool { + return false; + } +} diff --git a/src/AccessControl/Capability/DeleteDataView.php b/src/AccessControl/Capability/DeleteDataView.php new file mode 100644 index 0000000..181d359 --- /dev/null +++ b/src/AccessControl/Capability/DeleteDataView.php @@ -0,0 +1,19 @@ +field = $field; + } + + /** + * Returns the Field to view. + * + * @since $ver$ + * + * @return Field The Field. + */ + public function field(): Field { + return $this->field; + } +} diff --git a/src/AccessControl/ReadOnlyAccessController.php b/src/AccessControl/ReadOnlyAccessController.php new file mode 100644 index 0000000..a3fee34 --- /dev/null +++ b/src/AccessControl/ReadOnlyAccessController.php @@ -0,0 +1,21 @@ +is_mutative() && ! $capability->is_destructive(); + } +} diff --git a/src/DataView/DataView.php b/src/DataView/DataView.php index 5dc4c43..c6dc4e2 100644 --- a/src/DataView/DataView.php +++ b/src/DataView/DataView.php @@ -2,6 +2,8 @@ namespace DataKit\DataViews\DataView; +use DataKit\DataViews\AccessControl\AccessControlManager; +use DataKit\DataViews\AccessControl\Capability\ViewField; use DataKit\DataViews\Data\DataSource; use DataKit\DataViews\Data\Exception\DataSourceException; use DataKit\DataViews\Data\MutableDataSource; @@ -354,7 +356,7 @@ public function get_data( ?DataSource $data_source = null, ?Pagination $paginati */ $data = $data_source->get_data_by_id( $data_id ); - foreach ( $this->directory_fields as $field ) { + foreach ( $this->allowed_fields( $this->directory_fields ) as $field ) { $data[ $field->uuid() ] = $field->get_value( $data ); } @@ -377,31 +379,32 @@ public function get_data( ?DataSource $data_source = null, ?Pagination $paginati * @throws DataSourceException When the data source encounters an issue. */ public function get_view_data_item( string $data_id ): DataItem { - $data = $this->data_source()->get_data_by_id( $data_id ); + $data = $this->data_source()->get_data_by_id( $data_id ); + $fields = $this->allowed_fields( $this->view_fields ); - foreach ( $this->view_fields as $field ) { + foreach ( $fields as $field ) { $data[ $field->uuid() ] = $field->get_value( $data ); } return DataItem::from_array( [ - 'fields' => $this->view_fields, + 'fields' => $fields, 'data' => $data, ], ); } /** - * Returns all the fields for the dictionary view. + * Returns all the fields for the directory view. * * @since $ver$ * * @return array[] The fields as arrays. */ - private function dictionary_fields(): array { + private function directory_fields_for_json(): array { $fields = []; - foreach ( $this->directory_fields as $field ) { + foreach ( $this->allowed_fields( $this->directory_fields ) as $field ) { $fields[] = array_filter( $field->to_array(), static fn( $value ) => ! is_null( $value ), @@ -448,7 +451,7 @@ private function default_layouts(): array { private function get_field_ids( ?callable $filter = null ): array { $field_ids = []; - foreach ( $this->directory_fields as $field ) { + foreach ( $this->allowed_fields( $this->directory_fields ) as $field ) { if ( $filter && ! $filter( $field ) ) { continue; } @@ -542,7 +545,7 @@ public function to_array(): array { 'defaultLayouts' => $this->default_layouts(), 'paginationInfo' => $this->pagination->info( $this->data_source() ), 'view' => $this->view(), - 'fields' => $this->dictionary_fields(), + 'fields' => $this->directory_fields_for_json(), 'data' => $this->get_data(), 'actions' => $this->actions ? $this->actions->to_array() : [], ]; @@ -734,4 +737,20 @@ private function get_media_field_id(): string { return $image_fields ? reset( $image_fields ) : ''; } + + /** + * Filters out the fields the current user cannot view. + * + * @since $ver$ + * + * @return Field[] The fields. + */ + private function allowed_fields( array $fields ): array { + return array_filter( + $fields, + fn( Field $field ) => AccessControlManager::current()->can( + new ViewField( $this, $field ) + ) + ); + } } diff --git a/src/DataView/Operator.php b/src/DataView/Operator.php index e033dd2..9cea8e7 100644 --- a/src/DataView/Operator.php +++ b/src/DataView/Operator.php @@ -2,6 +2,8 @@ namespace DataKit\DataViews\DataView; +use DataKit\DataViews\EnumObject; + /** * Represents a valid filter operator. * diff --git a/src/DataView/View.php b/src/DataView/View.php index 5c93d2f..c2f109f 100644 --- a/src/DataView/View.php +++ b/src/DataView/View.php @@ -2,6 +2,8 @@ namespace DataKit\DataViews\DataView; +use DataKit\DataViews\EnumObject; + /** * Represents the valid view types. * diff --git a/src/DataView/EnumObject.php b/src/EnumObject.php similarity index 98% rename from src/DataView/EnumObject.php rename to src/EnumObject.php index d5e5dc7..37ea9fa 100644 --- a/src/DataView/EnumObject.php +++ b/src/EnumObject.php @@ -1,6 +1,6 @@ can( new ViewDataView( $dataview ) ) ); + self::assertFalse( $controller->can( new EditDataView( $dataview ) ) ); + self::assertFalse( $controller->can( new DeleteDataView( $dataview ) ) ); + self::assertTrue( $controller->can( new ViewField( $dataview, $field ) ) ); + } +} diff --git a/tests/DataView/EnumObjectTest.php b/tests/EnumObjectTest.php similarity index 94% rename from tests/DataView/EnumObjectTest.php rename to tests/EnumObjectTest.php index 18b7f29..800ebb8 100644 --- a/tests/DataView/EnumObjectTest.php +++ b/tests/EnumObjectTest.php @@ -1,8 +1,8 @@