foldershare-8.x-1.2/src/Entity/FolderShare.php
src/Entity/FolderShare.php
<?php namespace Drupal\foldershare\Entity; use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Session\AccountInterface; use Drupal\foldershare\Settings; use Drupal\foldershare\FolderShareInterface; use Drupal\foldershare\ManageFilenameExtensions; use Drupal\foldershare\ManageSearch; /** * Manages a virtual file system hierarchy of folders, subfolders, and files. * * FolderShare entities represent entries in a virtual file system that * includes folders, subfolders, and files in an arbitrarily deep hierarchy. * * Every folder or file entity has a set of common fields, including: * - Entity ID. * - UUID. * - Name. * - Size. * - Visible. * - Creation and changed dates. * - Owner. * - Description. * - Kind. * - MIME type. * - Language code. * - Parent item, if any. * - Root item, if any. * * The 'kind' field has one of these values: * - folder. * - file. * - image. * - media. * * The parent folder field is an entity reference to the parent folder * containing an item. If this field is NULL, the item has no parent and * it exists at the "root" or top-level of the folder hierarchy. * * The root field is an entity reference to the highest ancestor * folder of an item. If this field is NULL, the item has no ancestor root * and it exists at the "root" itself. * * Root-level items have access control fields: * - Access grants for viewing. * - Access grants for authoring. * - Access grants for users known, but currently without viewing or authoring. * * Non-root level items adopt the access controls of their ancestor root * folder. * * When 'kind' is 'file', the entity has an additional field containing the * entity ID of a File object: * - File entity ID. * * When 'kind' is 'image', the entity has an additional field containing the * entity ID of a File object containing an image, plus attributes of that * image filled in by the Image module: * - Image entity ID and attributes. * * When 'kind' is 'media', the entity has an additional field containing the * entity ID of a Media object describing an arbitrary media item, plus * attributes of that item: * - Media entity ID and attributes. * * File and Image fields refer to local files managed by Drupal core and * the File module. The FolderShare entity handles creating, deleting, * and altering File entities and the local files they refer to. * * Media fields refer to media entities that may point to local files or * remote files, such as images or videos on a social media site. The * FolderShare entity handles creating, deleting, and altering Media * entities, but it has no control over any external media these entities * refer to. * * Operations on FolderShare entities include creating, deleting, moving, * copying, duplicating, and renaming objects. Fields on these objects may * be adjusted individually and, in some cases, in recursive operations * (such as to change ownership of an entire folder tree). * * Root-level items manage access control for their descendants. Access * checks on anything in a descendant tree redirect up to the root. Being able * to do this quickly is why every item includes a root reference up * to the root high above it. * * <B>Access control</B> * This class's methods do not do access control. The caller should check * access as needed by their situation. * * @internal * The FolderShare entity disables the static and render caches, while * retaining the persistent cache. * * Benchmarking found that entity views were so basic that the render cache * had negligible benefits. Additionally, view operations on FolderShare * entities rarely loaded the same entity more than once, so the static * cache had negligible benefits. Disabling the render cache saves on database * storage, and disabling the static cache saves on process memory use. The * latter can be important during long folder tree operations, such as copy, * move, and delete on a big tree. Disabling these caches also reduced the * overhead required to clear cache entries after a FolderShare entity is * saved or deleted. * * Benchmarking found that the persistent cache had benefits because the * same entities are often loaded repeatedly as users move up and down * through a folder tree. * * Benchmarking also tested enabling and disabling the views cache used * for folder lists on an entity view page. The cache saves query results * for lists of entities in the rows of a view and benchmarking found this * had a significant impact. This module's views therefore enable this cache. * * @ingroup foldershare * * @ContentEntityType( * id = "foldershare", * label = @Translation("FolderShare file or folder "), * label_singular = @Translation("FolderShare file or folder"), * label_plural = @Translation("FolderShare files or folders"), * label_count = @PluralTranslation( * singular = "@count FolderShare file or folder", * plural = "@count FolderShare files or folders" * ), * handlers = { * "access" = "Drupal\foldershare\Entity\FolderShareAccessControlHandler", * "view_builder" = "Drupal\foldershare\Entity\Builder\FolderShareViewBuilder", * "views_data" = "Drupal\foldershare\Entity\FolderShareViewsData", * "list_builder" = "Drupal\Core\Entity\EntityListBuilder", * "form" = { * "default" = "Drupal\foldershare\Form\EditFolderShare", * "edit" = "Drupal\foldershare\Form\EditFolderShare", * }, * }, * list_cache_contexts = { "user" }, * static_cache = FALSE, * persistent_cache = TRUE, * render_cache = FALSE, * admin_permission = "administer foldershare", * permission_granularity = "entity_type", * translatable = FALSE, * base_table = "foldershare", * fieldable = TRUE, * common_reference_target = TRUE, * field_ui_base_route = "entity.foldershare.settings", * entity_keys = { * "id" = "id", * "uuid" = "uuid", * "uid" = "uid", * "label" = "name", * "langcode" = "langcode", * }, * links = { * "canonical" = "/foldershare/{foldershare}", * } * ) */ final class FolderShare extends ContentEntityBase implements FolderShareInterface { use EntityChangedTrait; use FolderShareTraits\ManageHookTrait; use FolderShareTraits\ManageLocksTrait; use FolderShareTraits\GetSetAccessGrantsTrait; use FolderShareTraits\GetSetCreatedTimeTrait; use FolderShareTraits\GetSetDescriptionTrait; use FolderShareTraits\GetSetFileTrait; use FolderShareTraits\GetSetKindTrait; use FolderShareTraits\GetSetMimeTrait; use FolderShareTraits\GetSetNameTrait; use FolderShareTraits\GetSetOwnerTrait; use FolderShareTraits\GetSetParentTrait; use FolderShareTraits\GetSetRootTrait; use FolderShareTraits\GetSetSizeTrait; use FolderShareTraits\GetSetSystemDisabledTrait; use FolderShareTraits\GetSetSystemHiddenTrait; use FolderShareTraits\FindTrait; use FolderShareTraits\OperationAddFileTrait; use FolderShareTraits\OperationArchiveTrait; use FolderShareTraits\OperationChangeOwnerTrait; use FolderShareTraits\OperationNewFolderTrait; use FolderShareTraits\OperationCopyTrait; use FolderShareTraits\OperationDeleteTrait; use FolderShareTraits\OperationDuplicateTrait; use FolderShareTraits\OperationFsckTrait; use FolderShareTraits\OperationMoveTrait; use FolderShareTraits\OperationRenameTrait; use FolderShareTraits\OperationShareTrait; use FolderShareTraits\OperationUnarchiveTrait; /*--------------------------------------------------------------------- * * Constants - Entity type id. * *---------------------------------------------------------------------*/ /** * The entity type id for the FolderShare entity. * * This is 'foldershare' and it must match the entity type declaration * in this class's comment block. * * @var string */ const ENTITY_TYPE_ID = 'foldershare' ; /*--------------------------------------------------------------------- * * Constants - Database tables. * *---------------------------------------------------------------------*/ /** * The base table for 'foldershare' entities. * * This is 'foldershare' and it must match the base table declaration in * this class's comment block. * * @var string */ const BASE_TABLE = 'foldershare' ; /*--------------------------------------------------------------------- * * Constants - Lock constants. * *---------------------------------------------------------------------*/ /** * The base name of the process lock for individual content operations. * * The content lock appends the ID of the entity being locked. * * @var string */ const CONTENT_LOCK_NAME = 'foldershare_content_lock_' ; /** * The base name of the process lock for large-scale operations. * * The operation lock appends the ID of the root entity for the folder tree * being locked. * * @var string */ const OPERATION_LOCK_NAME = 'foldershare_operation_lock_' ; /*--------------------------------------------------------------------- * * Constants - Misc. * *---------------------------------------------------------------------*/ /** * The number of operations to complete before checking usage limits. * * Memory and execution time limits need to be checked periodically * during long-running operations, such as copy, delete, and move. * Checking them after every item copied, deleted, or moved may be * expensive. Instead, checks are performed ever USAGE_CHECK_INTERVAL * loads/saves/copies/deletes. * * @var int * * @todo This may not be necessary. Benchmarking has found that checking * memory use and execution time have negligable cost. */ const USAGE_CHECK_INTERVAL = 100; /*--------------------------------------------------------------------- * * Entity definition. * *---------------------------------------------------------------------*/ /** * Defines the fields used by instances of this class. * * The following fields are defined, along with their intended * public or private access: * * | Field | Allow for view | Allow for edit | * | ---------------- | -------------- | -------------- | * | id | any user | no | * | uuid | no | no | * | uid | any user | no | * | langcode | no | no | * | created | any user | no | * | changed | any user | no | * | size | any user | no | * | kind | any user | no | * | mime | any user | no | * | name | any user | any user | * | description | any user | any user | * | parentid | any user | no | * | rootid | any user | no | * | file | any user | no | * | image | any user | no | * | media | any user | no | * | grantauthoruids | any user | no | * | grantviewuids | any user | no | * | systemdisabled | no | no | * | systemhidden | no | no | * * Some fields are supported by parent class methods: * * | Field | Get method | * | ---------------- | ------------------------------------ | * | id | ContentEntityBase::id() | * | uuid | ContentEntityBase::uuid() | * | name (label) | ContentEntityBase::getName() | * | changed | EntityChangedTrait::getChangedTime() | * | langcode | ContentEntityBase::language() | * * Some fields are supported by methods in this class: * * | Field | Get method | * | ---------------- | ------------------------------------ | * | uid | getOwner() | * | description | getDescription() | * | created | getCreatedTime() | * | size | getSize() | * | kind | getKind() | * | mime | getMimeType() | * | file | getFileId() | * | image | getImageId() | * | media | getMediaId() | * * Some fields have no get/set methods or no set methods because handling * them requires special code and context: * * | Field | Get method | * | ---------------- | ------------------------------------ | * | parentid | | * | rootid | | * | file | | * | image | | * | media | | * * @param \Drupal\Core\Entity\EntityTypeInterface $entityType * The entity type for which we are returning base field * definitions. * * @return array * An array of field definitions where keys are field names and * values are BaseFieldDefinition objects. */ public static function baseFieldDefinitions(EntityTypeInterface $entityType ) { // // Base class fields // ----------------- // The parent ContentEntityBase class supports several standard // entity fields: // // - id: the entity ID // - uuid: the entity unique ID // - langcode: the content language // - revision: the revision ID // - bundle: the entity bundle // // The parent class ONLY defines these fields if they exist in // THIS class's comment block declaring class fields. Of the // above fields, we only define these for this class: // // - id // - uuid // - langcode // // By invoking the parent class, we don't have to define these // ourselves below. // // Node adds a few fields we don't need, including promote, sticky, // revision_timestamp, revision_uid, and revision_tralsnation_affected. $fields = parent::baseFieldDefinitions( $entityType ); // But we do need to adjust display options a bit (see below). $weight = 0; // // Entity id. // - Integer. // - Primary database index. // - Read-only (set at creation time) // - 'view' shows ID (though usually converted to entity name) // - 'form' unsupported since read only. // // Field editing blocked by FolderShareAccessControlHandler // for all users. The field is read only. // // Note: This field was already defined by the parent class. // We just need to adjust it a little. // - Already has label ("ID"). // - Already marked read only. // - Already marked unsigned. $fields [ $entityType ->getKey( 'id' )] ->setDescription(t( 'The ID of the item.' )) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'number_integer' , 'weight' => $weight , ]); $weight += 10; // // Unique id (UUID). // - Integer. // - Read-only (set at creation time) // - 'view' unsupported. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler // for all users. The field is read only. // // Note: This field was already defined by the parent class. // We just need to adjust it a little to have a better description // and a way to view it. // - Already has label ("UUID"). // - Already marked read only. $fields [ $entityType ->getKey( 'uuid' )] ->setDescription(t( 'The UUID of the item.' )) ->setDisplayConfigurable( 'view' , FALSE) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // Language code. // - 'view' hidden. // - 'form' selects language. // // Field editing blocked by FolderShareAccessControlHandler // for all users. // // Note: This field is required by the ContentEntityBase base class, // which may use it to guide trnaslations. We do not need to // implement anything further. // // Note: This field was already defined by the parent class. // We just need to adjust it a little. // - Already has label ("Language"). // - Already has view (hidden). // - Already has form (language select). $f = $fields [ $entityType ->getKey( 'langcode' )]; $f ->setDescription(t( 'The original language for the item.' )); $fa = $f ->getDisplayOptions( 'form' ); if ( $fa === NULL) { $fa = [ 'type' => 'language_select' ]; } $fa [ 'weight' ] = $weight ; $f ->setDisplayOptions( 'form' , $fa ); // // Common fields // ------------- // Like Drupal nodes, folders have an owner, creation date, // changed date, and name (node calls it a title). We use // definitions that are very similar to those for node fields. // // Name. // - String. // - Required, no default. // - 'view' shows the name. // - 'form' unsupported. // // Note that the 'name' field is specifically excluded from the // folder's default edit form. Names are handled separately via a // 'Rename' command that does not use the default edit form. $fields [ 'name' ] = BaseFieldDefinition::create( 'string' ) ->setLabel(t( 'Name' )) ->setDescription(t( 'The name of the item.' )) ->setRequired(TRUE) ->setSettings([ 'default_value' => 'Item' , 'max_length' => self::MAX_NAME_LENGTH, 'text_processing' => FALSE, ]) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'string' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // User (owner) id. // - Entity reference to user object. // - 'view' shows the user's name. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler // except for admins. // // Note that even for admins, the 'uid' field is specifically // excluded from the item's default edit form. User IDs are // handled separately via a 'Chown' (Change owner) command that // does not use the default edit form (and must recurse through // a folder tree). $fields [ 'uid' ] = BaseFieldDefinition::create( 'entity_reference' ) ->setLabel(t( 'Owner' )) ->setDescription(t( "The user ID of the item's owner." )) ->setRequired(TRUE) ->setSetting( 'target_type' , 'user' ) ->setSetting( 'handler' , 'default' ) ->setDefaultValueCallback( 'Drupal\foldershare\Entity\FolderShare::getCurrentUserId' ) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'author' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // Creation date. // - Time stamp. // - 'view' shows date/time. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler // except for admins. $fields [ 'created' ] = BaseFieldDefinition::create( 'created' ) ->setLabel(t( 'Created' )) ->setDescription(t( 'The date and time when the item was created.' )) ->setRequired(TRUE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'timestamp' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // Changed (modified) date. // - Time stamp. // - 'view' shows date/time. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler. // // Note: This field is required by the EntityChangedTrait, which // implements the EntityChangedInterface. Because we use the trait, // we do not need to implement anything further. $fields [ 'changed' ] = BaseFieldDefinition::create( 'changed' ) ->setLabel(t( 'Modified' )) ->setDescription(t( 'The date and time when the item was most recently modified.' )) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'timestamp' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // Custom fields // ------------- // FolderShare entities have a variety of custom fields. // // Description. // - Long text. // - Optional, no default. // - 'view' shows the text. // - 'form' edits the text as a text area. $fields [ 'description' ] = BaseFieldDefinition::create( 'text_long' ) ->setLabel(t( 'Description' )) ->setDescription(t( 'The description of the item. The text may be empty, brief, or long.' )) ->setRequired(FALSE) ->setSettings([ 'default_value' => '' ]) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'text_default' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , TRUE) ->setDisplayOptions( 'form' , [ 'type' => 'text_textfield' , 'description' => 'Enter a description of the item. The description may be empty, brief, or long.' , 'rows' => 20, 'weight' => $weight , ]); $weight += 10; // // Kind. // - String. // - Required, no default. // - 'view' shows the text. // - 'form' unsupported. $fields [ 'kind' ] = BaseFieldDefinition::create( 'string' ) ->setLabel(t( 'Kind' )) ->setDescription(t( 'The kind of item, such as a file or folder.' )) ->setRequired(TRUE) ->setSettings([ 'default_value' => '' ]) ->setSetting( 'is_ascii' , TRUE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'text_default' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // MIME. // - String. // - Required, no default. // - 'view' shows the text. // - 'form' unsupported. $fields [ 'mime' ] = BaseFieldDefinition::create( 'string' ) ->setLabel(t( 'MIME type' )) ->setDescription(t( "The MIME type characterizing a file's contents, such as a JPEG image or a PDF document." )) ->setRequired(TRUE) ->setSettings([ 'default_value' => '' ]) ->setSetting( 'is_ascii' , TRUE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'text_default' , 'weight' => $weight , ]) ->setDisplayConfigurable( 'form' , FALSE); $weight += 10; // // Parent ID. // - Entity reference to parent Folder, if any. // - Optional, no default (empty means folder is in root list) // - 'view' shows the parent folder's name. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler // for all users. $fields [ 'parentid' ] = BaseFieldDefinition::create( 'entity_reference' ) ->setLabel(t( 'Parent folder' )) ->setDescription(t( "The item's direct ancestor folder. When an item is at the top-level itself, this field is empty (NULL)." )) ->setRequired(FALSE) ->setSetting( 'target_type' , self::ENTITY_TYPE_ID) ->setSetting( 'handler' , 'default' ) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'entity_reference_label' , 'weight' => $weight , ]); $weight += 10; // // Root ID. // - Entity reference to a root item, if any. // - Optional, no default (empty means item is at the root). // - 'view' shows the root item's name. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler // for all users. $fields [ 'rootid' ] = BaseFieldDefinition::create( 'entity_reference' ) ->setLabel(t( 'Top-level folder' )) ->setDescription(t( "The item's highest folder ancestor. When an item is at the top-level itself, this field points back to the same entity." )) ->setRequired(FALSE) ->setSetting( 'target_type' , self::ENTITY_TYPE_ID) ->setSetting( 'handler' , 'default' ) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'hidden' , 'type' => 'entity_reference_label' , 'weight' => $weight , ]); $weight += 10; // // Size. // - Big integer. // - Optional, no default (filled in as needed by auto size calc) // - 'view' shows the size. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler // for all users. $fields [ 'size' ] = BaseFieldDefinition::create( 'integer' ) ->setLabel(t( 'Size' )) ->setDescription(t( 'The storage space (in bytes) used by the file or folder, and all of its child files and folders.' )) ->setRequired(FALSE) ->setSetting( 'size' , 'big' ) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'foldershare_storage_size' , 'weight' => $weight , ]); $weight += 10; // // System hidden field flag. // - Boolean. // - Default is FALSE. // - 'view' unsupported. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler for all users. $fields [ 'systemhidden' ] = BaseFieldDefinition::create( 'boolean' ) ->setLabel(t( 'System hidden' )) ->setDescription(t( 'A TRUE/FALSE value indicating if the entity has been hidden by the system during special operations.' )) ->setRequired(FALSE) ->setDefaultValue(FALSE) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , FALSE); $weight += 10; // // System disabled field flag. // - Boolean. // - Default is FALSE. // - 'view' unsupported. // - 'form' unsupported. // // Field editing blocked by FolderShareAccessControlHandler for all users. $fields [ 'systemdisabled' ] = BaseFieldDefinition::create( 'boolean' ) ->setLabel(t( 'System disabled' )) ->setDescription(t( 'A TRUE/FALSE value indicating if the entity has been disabled by the system during special operations.' )) ->setRequired(FALSE) ->setDefaultValue(FALSE) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , FALSE); $weight += 10; // // File. // - A single File entity for a file item in a folder (i.e. kind == file). // - 'view' shows item. // - 'form' unsupported (custom code later). // // Field editing is blocked by FolderShareAccessControlHandler // for all users. // // Because folders are intended to contain any type of file, // we would rather not restrict the set of file extensions. // Unfortunately, this is currently not possible: // // Instead, elsewhere we provide our own handling of uploaded // files in order to take control over file extension testing. // // File names are constrained to not be empty, not include :, /, or \, // and not collide with an existing file. We check names explicitly // durring add/rename operations instead of using a field constraint, // which would be less efficient since it would require checking and // rechecking the name every time the entity was modified for any // reason (such as changing its description). // // A 'file' field type extends an entity reference field type and // adds these stored values with every instance: // - display = true/false for whether the file should be shown. // - description = description text for the file. // // The 'file' field also adds these settings applied to this usage // of the field type: // - file_extensions = the allowed extensions list. // - file_directory = a subdirectory in which to store files. // - max_filesize = the maximum uploaded file size allowed. // - description_field = whether to include a description per file. // // FolderShare does not use all of these features of 'file' fields: // - description_field is set to FALSE and the description text is // never set or used. The containing FolderShare entity has its // own description field. // - file_directory is ignored and FolderShare determines the directory // path on its own (see FolderShareStream). $extensions = '' ; if (Settings::getFileRestrictExtensions() === TRUE) { $extensions = Settings::getAllowedNameExtensions(); } $fields [ 'file' ] = BaseFieldDefinition::create( 'file' ) ->setLabel(t( 'File' )) ->setDescription(t( 'A non-image file within a folder.' )) ->setRequired(FALSE) ->setSetting( 'target_type' , 'file' ) ->setSetting( 'handler' , 'default' ) ->setSetting( 'file_extensions' , $extensions ) ->setSetting( 'max_filesize' , '' ) ->setSetting( 'description_field' , FALSE) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'file_default' , 'weight' => $weight , ]); $weight += 10; // // Image. // - A single Image entity for a file item in a folder (i.e. kind == image). // - 'view' shows item. // - 'form' unsupported (custom code later). // // See comments above for the file field. // // An 'image' field type extends the 'file' field type and adds these // stored values for every instance: // - alt = alternate text for the image's 'alt' attribute. // - title = title text for the image's 'title' attribute. // - width = the image width. // - height = the image height. // // The 'image' field also adds these settings applied to this usage // of the field type: // - alt_field = whether to include an alt value per image. // - alt_field_required = whether alt text is required. // - title_field = whether to include a title value per image. // - title_field_required = whether title text is required. // - min_resolution = the minimum accepted image resolution. // - max_resolution = the maximum accepted image resolution. // - default_image = a default image if none is provided. // // FolderShare does not use all of these features of 'image' fields: // - alt_field and title_field are not used and alt_field_required // and title_field_required are set to FALSE. // - default_image is not used because the image field is only set if // we have an image file. $fields [ 'image' ] = BaseFieldDefinition::create( 'image' ) ->setLabel(t( 'Image' )) ->setDescription(t( 'An image file within a folder.' )) ->setRequired(FALSE) ->setSetting( 'target_type' , 'file' ) ->setSetting( 'handler' , 'default' ) ->setSetting( 'file_extensions' , $extensions ) ->setSetting( 'description_field' , FALSE) ->setSetting( 'alt_field' , FALSE) ->setSetting( 'alt_field_required' , FALSE) ->setSetting( 'title_field' , FALSE) ->setSetting( 'title_field_required' , FALSE) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'image' , 'weight' => $weight , ]); $weight += 10; // // Media. // - A single Media entity for a media item in a folder (i.e. kind ==media). // - 'view' shows item. // - 'form' unsupported (custom code later). // // See comments above for the file field. // // Media entities do not have a corresponding media field type. Instead // we refer to a media entity using an entity_reference. $fields [ 'media' ] = BaseFieldDefinition::create( 'entity_reference' ) ->setLabel(t( 'Media' )) ->setDescription(t( 'A media item within a folder.' )) ->setRequired(FALSE) ->setSetting( 'target_type' , 'media' ) ->setSetting( 'handler' , 'default' ) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'media_thumbnail' , 'weight' => $weight , ]); $weight += 10; // // Access control for author access. // Access control for view access. // - List of UIDs. // - 'view' shows author names (except disabled field). // - 'form' unsupported (custom code required). // // Field editing blocked by FolderShareAccessControlHandler // for all users. // $fields [ 'grantauthoruids' ] = BaseFieldDefinition::create( 'entity_reference' ) ->setLabel(t( 'Author grants' )) ->setDescription(t( "A list of IDs for users that have been granted author access to the item and all of its descendants. Only top-level items have values. The item's owner is always on this list." )) ->setRequired(FALSE) ->setSetting( 'target_type' , 'user' ) ->setSetting( 'handler' , 'default' ) ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'author' , 'weight' => $weight , ]); $weight += 10; $fields [ 'grantviewuids' ] = BaseFieldDefinition::create( 'entity_reference' ) ->setLabel(t( 'Viewer grants' )) ->setDescription(t( "A list of IDs for users that have been granted view access to the item and all of its descendants. Only top-level items have values. The item's owner is always on this list." )) ->setRequired(FALSE) ->setSetting( 'target_type' , 'user' ) ->setSetting( 'handler' , 'default' ) ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED) ->setDisplayConfigurable( 'form' , FALSE) ->setDisplayConfigurable( 'view' , TRUE) ->setDisplayOptions( 'view' , [ 'label' => 'inline' , 'type' => 'author' , 'weight' => $weight , ]); $weight += 10; return $fields ; } /*--------------------------------------------------------------------- * * Entity management. * *---------------------------------------------------------------------*/ /** * {@inheritdoc} * * This method is called immediately after an entity has been saved. * If the Drupal core Search module is installed, the entity is marked * for search re-indexing. */ public function postSave(EntityStorageInterface $storage , $update = TRUE) { // Let the parent class do its work. parent::postSave( $storage , $update ); // If search module is enabled, mark entity as in need of re-indexing. if ( $update === TRUE) { ManageSearch::markForReindex([ $this ]); } } /** * {@inheritdoc} * * This method is called immediately before an entity is deleted. * If the core 'search' module is installed, the search index entry * for the entity is cleared. */ public static function preDelete( EntityStorageInterface $storage , array $items ) { // Let the parent class do its work. parent::preDelete( $storage , $items ); // Clear entities from the search index. ManageSearch::deleteFromIndex( $items ); } /*--------------------------------------------------------------------- * * General utilities. * *---------------------------------------------------------------------*/ /** * Returns the current user ID. * * This function provides the deault value callback for the 'uid' * base field definition. * * @return array * An array of default values. In this case, the array only * contains the current user ID. * * @see ::baseFieldDefinitions() */ public static function getCurrentUserId() { return [\Drupal::currentUser()->id()]; } /** * {@inheritdoc} */ public function getExtension() { return ManageFilenameExtensions::getExtensionFromPath( $this ->getName()); } /** * {@inheritdoc} */ public function getPath() { $names = $this ->findAncestorFolderNames(); if ( empty ( $names ) === TRUE) { return '/' . $this ->getName(); } return '/' . implode( '/' , $names ) . '/' . $this ->getName(); } /*--------------------------------------------------------------------- * * Access control. * *---------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function access( $operation , AccountInterface $account = NULL, $returnAsObject = FALSE) { // Drupal's Entity class implements this method and splits 'create' // operations off from all other operations: // - 'create' is sent to the access controller's createAccess() method. // - Everything else is sent to the access controller's access() method. // // This is fine for everything except 'create'. By sending 'create' // to the createAccess() method, the context of the current entity is // lost. That method does not include an entity argument. // // Without the context of the current entity, the access controller // cannot check access grants based on the folder tree. All it can do // is check permissions. // // Without checking access grants, createAccess() is forced to return a // generic and incomplete answer. Drupal automatically caches that answer // and uses instead of making further access checks. This will cause a // mess when that answer needs to vary based on entity context and access // grants, and yet it can't. Inappropriate cached answers will be used // over and over again in every context. // // The fix is to route 'create' operations to the access controller's // access() method, along with all other operations. This enables // the access controller to check access grants on the entity's folder // tree and include a result cache dependency on the entity at hand. return $this ->entityTypeManager() ->getAccessControlHandler( $this ->entityTypeId) ->access( $this , $operation , $account , $returnAsObject ); } } |