Version 1
[yaffs-website] / web / modules / contrib / media_entity_instagram / src / Plugin / MediaEntity / Type / Instagram.php
1 <?php
2
3 namespace Drupal\media_entity_instagram\Plugin\MediaEntity\Type;
4
5 use Drupal\Core\Config\ConfigFactoryInterface;
6 use Drupal\Core\Entity\EntityFieldManagerInterface;
7 use Drupal\Core\Entity\EntityTypeManagerInterface;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\media_entity\MediaInterface;
10 use Drupal\media_entity\MediaTypeBase;
11 use Drupal\media_entity\MediaTypeException;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
13 use Instagram\Instagram as InstagramApi;
14
15 /**
16  * Provides media type plugin for Instagram.
17  *
18  * @MediaType(
19  *   id = "instagram",
20  *   label = @Translation("Instagram"),
21  *   description = @Translation("Provides business logic and metadata for Instagram.")
22  * )
23  */
24 class Instagram extends MediaTypeBase {
25
26   /**
27    * Config factory service.
28    *
29    * @var \Drupal\Core\Config\ConfigFactoryInterface
30    */
31   protected $configFactory;
32
33   /**
34    * Constructs a new class instance.
35    *
36    * @param array $configuration
37    *   A configuration array containing information about the plugin instance.
38    * @param string $plugin_id
39    *   The plugin_id for the plugin instance.
40    * @param mixed $plugin_definition
41    *   The plugin implementation definition.
42    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
43    *   Entity type manager service.
44    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
45    *   Entity field manager service.
46    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
47    *   Config factory service.
48    */
49   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory) {
50     parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $config_factory->get('media_entity.settings'));
51     $this->configFactory = $config_factory;
52   }
53
54   /**
55    * {@inheritdoc}
56    */
57   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
58     return new static(
59       $configuration,
60       $plugin_id,
61       $plugin_definition,
62       $container->get('entity_type.manager'),
63       $container->get('entity_field.manager'),
64       $container->get('config.factory')
65     );
66   }
67
68   /**
69    * {@inheritdoc}
70    */
71   public function defaultConfiguration() {
72     return [
73       'use_instagram_api' => FALSE,
74     ];
75   }
76
77   /**
78    * List of validation regular expressions.
79    *
80    * @var array
81    */
82   public static $validationRegexp = array(
83     '@((http|https):){0,1}//(www\.){0,1}instagram\.com/p/(?<shortcode>[a-z0-9_-]+)@i' => 'shortcode',
84     '@((http|https):){0,1}//(www\.){0,1}instagr\.am/p/(?<shortcode>[a-z0-9_-]+)@i' => 'shortcode',
85   );
86
87   /**
88    * {@inheritdoc}
89    */
90   public function providedFields() {
91     $fields = array(
92       'shortcode' => $this->t('Instagram shortcode'),
93     );
94
95     if ($this->configuration['use_instagram_api']) {
96       $fields += array(
97         'id' => $this->t('Media ID'),
98         'type' => $this->t('Media type: image or video'),
99         'thumbnail' => $this->t('Link to the thumbnail'),
100         'thumbnail_local' => $this->t("Copies thumbnail locally and return it's URI"),
101         'thumbnail_local_uri' => $this->t('Returns local URI of the thumbnail'),
102         'username' => $this->t('Author of the post'),
103         'caption' => $this->t('Caption'),
104         'tags' => $this->t('Tags'),
105       );
106     }
107
108     return $fields;
109   }
110
111   /**
112    * {@inheritdoc}
113    */
114   public function getField(MediaInterface $media, $name) {
115     $matches = $this->matchRegexp($media);
116
117     if (!$matches['shortcode']) {
118       return FALSE;
119     }
120
121     if ($name == 'shortcode') {
122       return $matches['shortcode'];
123     }
124
125     // If we have auth settings return the other fields.
126     if ($this->configuration['use_instagram_api'] && $instagram = $this->fetchInstagram($matches['shortcode'])) {
127       switch ($name) {
128         case 'id':
129           if (isset($instagram->id)) {
130             return $instagram->id;
131           }
132           return FALSE;
133
134         case 'type':
135           if (isset($instagram->type)) {
136             return $instagram->type;
137           }
138           return FALSE;
139
140         case 'thumbnail':
141           if (isset($instagram->images->thumbnail->url)) {
142             return $instagram->images->thumbnail->url;
143           }
144           return FALSE;
145
146         case 'thumbnail_local':
147           if (isset($instagram->images->thumbnail->url)) {
148             $local_uri = $this->configFactory->get('media_entity_instagram.settings')->get('local_images') . '/' . $matches['shortcode'] . '.' . pathinfo($instagram->images->thumbnail->url, PATHINFO_EXTENSION);
149
150             if (!file_exists($local_uri)) {
151               file_prepare_directory($local_uri, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
152
153               $image = file_get_contents($local_uri);
154               file_unmanaged_save_data($image, $local_uri, FILE_EXISTS_REPLACE);
155
156               return $local_uri;
157             }
158           }
159           return FALSE;
160
161         case 'thumbnail_local_uri':
162           if (isset($instagram->images->thumbnail->url)) {
163             return $this->configFactory->get('media_entity_instagram.settings')->get('local_images') . '/' . $matches['shortcode'] . '.' . pathinfo($instagram->images->thumbnail->url, PATHINFO_EXTENSION);
164           }
165           return FALSE;
166
167         case 'username':
168           if (isset($instagram->user->username)) {
169             return $instagram->user->username;
170           }
171           return FALSE;
172
173         case 'caption':
174           if (isset($instagram->caption->text)) {
175             return $instagram->caption->text;
176           }
177           return FALSE;
178
179         case 'tags':
180           if (isset($instagram->tags)) {
181             return implode(" ", $instagram->tags);
182           }
183           return FALSE;
184       }
185     }
186
187     return FALSE;
188   }
189
190   /**
191    * {@inheritdoc}
192    */
193   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
194     $options = [];
195     $bundle = $form_state->getFormObject()->getEntity();
196     $allowed_field_types = ['string', 'string_long', 'link'];
197     foreach ($this->entityFieldManager->getFieldDefinitions('media', $bundle->id()) as $field_name => $field) {
198       if (in_array($field->getType(), $allowed_field_types) && !$field->getFieldStorageDefinition()->isBaseField()) {
199         $options[$field_name] = $field->getLabel();
200       }
201     }
202
203     $form['source_field'] = [
204       '#type' => 'select',
205       '#title' => $this->t('Field with source information'),
206       '#description' => $this->t('Field on media entity that stores Instagram embed code or URL. You can create a bundle without selecting a value for this dropdown initially. This dropdown can be populated after adding fields to the bundle.'),
207       '#default_value' => empty($this->configuration['source_field']) ? NULL : $this->configuration['source_field'],
208       '#options' => $options,
209     ];
210
211     $form['use_instagram_api'] = [
212       '#type' => 'select',
213       '#title' => $this->t('Whether to use Instagram api to fetch instagrams or not.'),
214       '#description' => $this->t("In order to use Instagram's api you have to create a developer account and an application. For more information consult the readme file."),
215       '#default_value' => empty($this->configuration['use_instagram_api']) ? 0 : $this->configuration['use_instagram_api'],
216       '#options' => [
217         0 => $this->t('No'),
218         1 => $this->t('Yes'),
219       ],
220     ];
221
222     // @todo Evaluate if this should be a site-wide configuration.
223     $form['client_id'] = [
224       '#type' => 'textfield',
225       '#title' => $this->t('Client ID'),
226       '#default_value' => empty($this->configuration['client_id']) ? NULL : $this->configuration['client_id'],
227       '#states' => [
228         'visible' => [
229           ':input[name="type_configuration[instagram][use_instagram_api]"]' => ['value' => '1'],
230         ],
231       ],
232     ];
233
234     return $form;
235   }
236
237   /**
238    * {@inheritdoc}
239    */
240   public function attachConstraints(MediaInterface $media) {
241     parent::attachConstraints($media);
242
243     if (isset($this->configuration['source_field'])) {
244       $source_field_name = $this->configuration['source_field'];
245       if ($media->hasField($source_field_name)) {
246         foreach ($media->get($source_field_name) as &$embed_code) {
247           /** @var \Drupal\Core\TypedData\DataDefinitionInterface $typed_data */
248           $typed_data = $embed_code->getDataDefinition();
249           $typed_data->addConstraint('InstagramEmbedCode');
250         }
251       }
252     }
253   }
254
255   /**
256    * Runs preg_match on embed code/URL.
257    *
258    * @param MediaInterface $media
259    *   Media object.
260    *
261    * @return array|bool
262    *   Array of preg matches or FALSE if no match.
263    *
264    * @see preg_match()
265    */
266   protected function matchRegexp(MediaInterface $media) {
267     $matches = array();
268
269     if (isset($this->configuration['source_field'])) {
270       $source_field = $this->configuration['source_field'];
271       if ($media->hasField($source_field)) {
272         $property_name = $media->{$source_field}->first()->mainPropertyName();
273         foreach (static::$validationRegexp as $pattern => $key) {
274           if (preg_match($pattern, $media->{$source_field}->{$property_name}, $matches)) {
275             return $matches;
276           }
277         }
278       }
279     }
280     return FALSE;
281   }
282
283   /**
284    * Get a single instagram.
285    *
286    * @param string $shortcode
287    *   The instagram shortcode.
288    */
289   protected function fetchInstagram($shortcode) {
290     $instagram = &drupal_static(__FUNCTION__);
291
292     if (!isset($instagram)) {
293       // Check for dependencies.
294       // @todo There is perhaps a better way to do that.
295       if (!class_exists('\Instagram\Instagram')) {
296         drupal_set_message($this->t('Instagram library is not available. Consult the README.md for installation instructions.'), 'error');
297         return;
298       }
299
300       if (!isset($this->configuration['client_id'])) {
301         drupal_set_message($this->t('The client ID is not available. Consult the README.md for installation instructions.'), 'error');
302         return;
303       }
304       if (empty($this->configuration['client_id'])) {
305         drupal_set_message($this->t('The client ID is missing. Please add it in your Instagram settings.'), 'error');
306         return;
307       }
308       $instagram_object = new InstagramApi();
309       $instagram_object->setClientID($this->configuration['client_id']);
310       $result = $instagram_object->getMediaByShortcode($shortcode)->getData();
311
312       if ($result) {
313         return $result;
314       }
315       else {
316         throw new MediaTypeException(NULL, 'The media could not be retrieved.');
317       }
318     }
319   }
320
321   /**
322    * {@inheritdoc}
323    */
324   public function getDefaultThumbnail() {
325     return $this->config->get('icon_base') . '/instagram.png';
326   }
327
328   /**
329    * {@inheritdoc}
330    */
331   public function thumbnail(MediaInterface $media) {
332     if ($local_image = $this->getField($media, 'thumbnail_local')) {
333       return $local_image;
334     }
335
336     return $this->getDefaultThumbnail();
337   }
338
339   /**
340    * {@inheritdoc}
341    */
342   public function getDefaultName(MediaInterface $media) {
343     // Try to get some fields that need the API, if not available, just use the
344     // shortcode as default name.
345
346     $username = $this->getField($media, 'username');
347     $id = $this->getField($media, 'id');
348     if ($username && $id) {
349       return $username . ' - ' . $id;
350     }
351     else {
352       $code = $this->getField($media, 'shortcode');
353       if (!empty($code)) {
354         return $code;
355       }
356     }
357
358     return parent::getDefaultName($media);
359   }
360
361 }