3 namespace Drupal\crop\Entity;
5 use Drupal\Core\Entity\ContentEntityBase;
6 use Drupal\Core\Entity\EntityStorageInterface;
7 use Drupal\Core\Entity\EntityTypeInterface;
8 use Drupal\Core\Field\BaseFieldDefinition;
9 use Drupal\crop\CropInterface;
10 use Drupal\crop\EntityProviderNotFoundException;
11 use Drupal\image\ImageStyleInterface;
14 * Defines the crop entity class.
18 * label = @Translation("Crop"),
19 * bundle_label = @Translation("Crop type"),
21 * "storage" = "Drupal\crop\CropStorage",
22 * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
23 * "access" = "Drupal\Core\Entity\EntityAccessControlHandler",
25 * "default" = "Drupal\Core\Entity\ContentEntityForm",
26 * "delete" = "Drupal\Core\Entity\ContentEntityConfirmFormBase",
27 * "edit" = "Drupal\Core\Entity\ContentEntityForm"
29 * "translation" = "Drupal\content_translation\ContentTranslationHandler"
31 * base_table = "crop",
32 * data_table = "crop_field_data",
33 * revision_table = "crop_revision",
34 * revision_data_table = "crop_field_revision",
36 * translatable = TRUE,
37 * render_cache = FALSE,
42 * "langcode" = "langcode",
45 * revision_metadata_keys = {
46 * "revision_timestamp" = "revision_timestamp",
47 * "revision_uid" = "revision_uid",
48 * "revision_log" = "revision_log"
50 * bundle_entity_type = "crop_type",
51 * permission_granularity = "entity_type",
52 * admin_permission = "administer crop",
57 class Crop extends ContentEntityBase implements CropInterface {
62 public function position() {
64 'x' => (int) $this->x->value,
65 'y' => (int) $this->y->value,
72 public function setPosition($x, $y) {
82 public function anchor() {
84 'x' => (int) ($this->x->value - ($this->width->value / 2)),
85 'y' => (int) ($this->y->value - ($this->height->value / 2)),
92 public function size() {
94 'width' => (int) $this->width->value,
95 'height' => (int) $this->height->value,
102 public function setSize($width, $height) {
103 $this->width = $width;
104 $this->height = $height;
111 public function provider() {
112 /** @var \Drupal\crop\EntityProviderManager $plugin_manager */
113 $plugin_manager = \Drupal::service('plugin.manager.crop.entity_provider');
115 if (!$plugin_manager->hasDefinition($this->entity_type->value)) {
116 throw new EntityProviderNotFoundException(t('Entity provider @id not found.', ['@id' => $this->entity_type->value]));
119 return $plugin_manager->createInstance($this->entity_type->value);
125 public static function cropExists($uri, $type = NULL) {
126 $query = \Drupal::entityQuery('crop')
127 ->condition('uri', $uri);
129 $query->condition('type', $type);
131 return (bool) $query->execute();
137 public static function findCrop($uri, $type) {
138 return \Drupal::entityTypeManager()->getStorage('crop')->getCrop($uri, $type);
144 public static function getCropFromImageStyle($uri, ImageStyleInterface $image_style) {
148 foreach ($image_style->getEffects() as $uuid => $effect) {
149 // Store the effects parameters for later use.
150 $effects[$effect->getPluginId()] = [
152 'provider' => $effect->getPluginDefinition()['provider'],
156 if (isset($effects['crop_crop']) && $image_style->getEffects()
157 ->has($effects['crop_crop']['uuid'])) {
158 $type = $image_style->getEffect($effects['crop_crop']['uuid'])
159 ->getConfiguration()['data']['crop_type'];
160 $crop = self::findCrop($uri, $type);
163 // Fallback to use the provider as a fallback to check if provider name,
164 // match with crop types for modules non-based on "manual crop" effects.
166 foreach ($effects as $effect) {
167 $provider = $effect['provider'];
168 $crop = self::findCrop($uri, $provider);
178 public function preSave(EntityStorageInterface $storage) {
179 parent::preSave($storage);
181 // If no revision author has been set explicitly, make the current user
183 if (!$this->get('revision_uid')->entity) {
184 $this->set('revision_uid', \Drupal::currentUser()->id());
187 // Try to set URI if not yet defined.
188 if (empty($this->uri->value) && !empty($this->entity_type->value) && !empty($this->entity_id->value)) {
189 $entity = \Drupal::entityTypeManager()->getStorage($this->entity_type->value)->load($this->entity_id->value);
190 if ($uri = $this->provider()->uri($entity)) {
191 $this->set('uri', $uri);
199 public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
200 parent::preSaveRevision($storage, $record);
202 if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
203 // If we are updating an existing crop without adding a new revision, we
204 // need to make sure $entity->revision_log is reset whenever it is empty.
205 // Therefore, this code allows us to avoid clobbering an existing log
206 // entry with an empty one.
207 $record->revision_log = $this->original->revision_log->value;
214 public function postSave(EntityStorageInterface $storage, $update = TRUE) {
215 parent::postSave($storage, $update);
217 // If you are manually generating your image derivatives instead of waiting
218 // for them to be generated on the fly, because you are using a cloud
219 // storage service (like S3), then you may not want your image derivatives
220 // to be flushed. If they are you could end up serving 404s during the time
221 // between the crop entity being saved and the image derivative being
222 // manually generated and pushed to your cloud storage service. In that
223 // case, set this configuration variable to false.
224 $flush_derivative_images = \Drupal::config('crop.settings')->get('flush_derivative_images');
225 if ($flush_derivative_images) {
226 image_path_flush($this->uri->value);
233 public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
236 $fields['cid'] = BaseFieldDefinition::create('integer')
237 ->setLabel(t('Crop ID'))
238 ->setDescription(t('The crop ID.'))
240 ->setSetting('unsigned', TRUE);
242 $fields['uuid'] = BaseFieldDefinition::create('uuid')
243 ->setLabel(t('UUID'))
244 ->setDescription(t('The crop UUID.'))
247 $fields['vid'] = BaseFieldDefinition::create('integer')
248 ->setLabel(t('Revision ID'))
249 ->setDescription(t('The crop revision ID.'))
251 ->setSetting('unsigned', TRUE);
253 $fields['type'] = BaseFieldDefinition::create('entity_reference')
254 ->setLabel(t('Type'))
255 ->setDescription(t('The crop type.'))
256 ->setSetting('target_type', 'crop_type')
259 $fields['langcode'] = BaseFieldDefinition::create('language')
260 ->setLabel(t('Language code'))
261 ->setDescription(t('The node language code.'))
262 ->setRevisionable(TRUE);
264 $fields['entity_id'] = BaseFieldDefinition::create('integer')
265 ->setLabel(t('Entity ID'))
266 ->setDescription(t('ID of entity crop belongs to.'))
267 ->setSetting('unsigned', TRUE)
268 ->setRevisionable(TRUE)
271 $fields['entity_type'] = BaseFieldDefinition::create('string')
272 ->setLabel(t('Entity type'))
273 ->setDescription(t('The type of entity crop belongs to.'))
274 ->setRevisionable(TRUE)
277 // Denormalized information, which is calculated in storage plugin for a
278 // given entity type. Saved here for performance reasons in image effects.
280 // TODO - we are not enforcing uniqueness on this as we want to support more
281 // crops per same image/image_style combination. However, image effect
282 // operates with image URI only, which means we have no mechanism to
283 // distinguish between multiple crops in there. If we really want to
284 // support multiple crops we'll need to override core at least,
285 // in \Drupal\Core\Image\ImageFactory and \Drupal\Core\Image\Image.
286 // Let's leave this for now and simply load based on URI only.
287 // We can use some semi-smart approach in case there are multiple crops
288 // with same URI for now (first created, last created, ...).
289 $fields['uri'] = BaseFieldDefinition::create('uri')
291 ->setDescription(t('The URI of the image crop belongs to.'))
292 ->setRevisionable(TRUE)
293 ->setTranslatable(TRUE)
294 ->setSetting('max_length', 255);
296 $fields['height'] = BaseFieldDefinition::create('integer')
297 ->setLabel(t('Height'))
298 ->setDescription(t('The crop height.'))
299 ->setRevisionable(TRUE)
300 ->setTranslatable(TRUE)
302 ->setSetting('unsigned', TRUE);
304 $fields['width'] = BaseFieldDefinition::create('integer')
305 ->setLabel(t('Width'))
306 ->setDescription(t('The crop width.'))
307 ->setRevisionable(TRUE)
308 ->setTranslatable(TRUE)
310 ->setSetting('unsigned', TRUE);
312 $fields['x'] = BaseFieldDefinition::create('integer')
313 ->setLabel(t('X coordinate'))
314 ->setDescription(t("The crop's X coordinate."))
315 ->setRevisionable(TRUE)
316 ->setTranslatable(TRUE)
318 ->setSetting('unsigned', TRUE);
320 $fields['y'] = BaseFieldDefinition::create('integer')
321 ->setLabel(t('Y coordinate'))
322 ->setDescription(t("The crop's Y coordinate."))
323 ->setRevisionable(TRUE)
324 ->setTranslatable(TRUE)
326 ->setSetting('unsigned', TRUE);
328 $fields['revision_timestamp'] = BaseFieldDefinition::create('created')
329 ->setLabel(t('Revision timestamp'))
330 ->setDescription(t('The time that the current revision was created.'))
331 ->setRevisionable(TRUE);
333 $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
334 ->setLabel(t('Revision author ID'))
335 ->setDescription(t('The user ID of the author of the current revision.'))
336 ->setSetting('target_type', 'user')
337 ->setRevisionable(TRUE);
339 $fields['revision_log'] = BaseFieldDefinition::create('string_long')
340 ->setLabel(t('Revision Log'))
341 ->setDescription(t('The log entry explaining the changes in this revision.'))
342 ->setRevisionable(TRUE)
343 ->setTranslatable(TRUE);