diff --git a/config/schema/google_vision.schema.yml b/config/schema/google_vision.schema.yml index 76f0c1a..07850ab 100644 --- a/config/schema/google_vision.schema.yml +++ b/config/schema/google_vision.schema.yml @@ -24,3 +24,6 @@ field.field.*.*.*.third_party.google_vision: emotion_detect: type: boolean label: 'Emotion Detection' + google_vision: + type: boolean + label: 'Google Vision' diff --git a/google_vision.links.task.yml b/google_vision.links.task.yml new file mode 100644 index 0000000..98d41df --- /dev/null +++ b/google_vision.links.task.yml @@ -0,0 +1,5 @@ +google_vision.related_content: + route_name: google_vision.related_content + base_route: entity.file.canonical + title: 'Similar Contents' + weight: 10 diff --git a/google_vision.module b/google_vision.module index 3d722fa..551e03b 100644 --- a/google_vision.module +++ b/google_vision.module @@ -8,7 +8,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\field\FieldConfigInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; - +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\taxonomy\Entity\Vocabulary; /** * Implements hook_entity_presave(). */ @@ -29,6 +31,23 @@ function google_vision_entity_presave(EntityInterface $entity) { // Take first referenced vocabulary. $vid = reset($field->getSettings()['handler_settings']['target_bundles']); google_vision_file_entity_add_labels($entity, $field, $vid); + + //Use a new taxonomy vocabulary to group the objects by their dominant color. + $new_vid = "dominant_color"; + $name = "Dominant Color"; + $vocabularies = Vocabulary::loadMultiple(); + // Create a vocabulary if not already present. + if (!isset($vocabularies[$new_vid])) { + $vocabulary = \Drupal\taxonomy\Entity\Vocabulary::create(array( + 'vid' => $new_vid, + 'machine_name' => $new_vid, + 'name' => $name, + )); + $vocabulary->save(); + } + if (!empty($field->getSettings()['handler_settings']['target_bundles']['dominant_color'])) { + google_vision_file_entity_dominant_color($entity, $field, $new_vid); + } } } } @@ -102,6 +121,53 @@ function google_vision_form_field_config_form_image_builder($entity_type, FieldC } /** + * Add the terms to the corresponding taxonomy vocabulary and save the + * values in files. + */ +function google_vision_add_terms_to_vocabulary($file, $field, $vid, $labels) { + // Get existing values from field. + $values = $file->get($field->getName())->getValue(); + $taxonomy_term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + + // Try to find an existing terms by these labels. + $query = \Drupal::database()->select('taxonomy_term_field_data', 'tfd'); + $query->fields('tfd', ['tid', 'name']); + $query->condition('tfd.vid', $vid); + $query->condition('tfd.name', $labels, 'IN'); + $existing_terms = $query->execute()->fetchAllKeyed(); + + // Handle retrieved labels. + foreach ($labels as $label) { + // Use existing term. + if ($existing_tid = array_search($label, $existing_terms)) { + $already_have = FALSE; + // If we already have this label in this field. Just skip. + foreach ($values as $value) { + if ($value['target_id'] == $existing_tid) { + $already_have = TRUE; + } + } + // Add existing label into field if we haven't it yet. + if (!$already_have) { + $values[] = ['target_id' => $existing_tid]; + } + } + // Create new term and add into field. + else { + $label_term = $taxonomy_term_storage->create([ + 'name' => $label, + 'vid' => $vid + ]); + $label_term->enforceIsNew(); + $label_term->save(); + $values[] = ['target_id' => $label_term->id()]; + } + } + // Save collected values. + $file->set($field->getName(), $values); +} + +/** * Try to get and add labels for the file entity. */ function google_vision_file_entity_add_labels($file, $field, $vid) { @@ -116,46 +182,42 @@ function google_vision_file_entity_add_labels($file, $field, $vid) { $labels[] = $item['description']; } - // Get existing values from field. - $values = $file->get($field->getName())->getValue(); - $taxonomy_term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); - - // Try to find an existing terms by these labels. - $query = \Drupal::database()->select('taxonomy_term_field_data', 'tfd'); - $query->fields('tfd', ['tid', 'name']); - $query->condition('tfd.vid', $vid); - $query->condition('tfd.name', $labels, 'IN'); - $existing_terms = $query->execute()->fetchAllKeyed(); - - // Handle retrieved labels. - foreach ($labels as $label) { - // Use existing term. - if ($existing_tid = array_search($label, $existing_terms)) { - $already_have = FALSE; - // If we already have this label in this field. Just skip. - foreach ($values as $value) { - if ($value['target_id'] == $existing_tid) { - $already_have = TRUE; - } - } - // Add existing label into field if we haven't it yet. - if (!$already_have) { - $values[] = ['target_id' => $existing_tid]; - } - } - // Create new term and add into field. - else { - $label_term = $taxonomy_term_storage->create([ - 'name' => $label, - 'vid' => $vid - ]); - $label_term->enforceIsNew(); - $label_term->save(); - $values[] = ['target_id' => $label_term->id()]; - } + //Add the terms to vocabulary, and set the values to fields in files. + google_vision_add_terms_to_vocabulary($file, $field, $vid, $labels); + } + } +} + +/** + * Try to get the dominant color. + */ +function google_vision_file_entity_dominant_color($file, $field, $vid) { + $file_uri = $file->getFileUri(); + if ($filepath = \Drupal::service('file_system')->realpath($file_uri)) { + $data = \Drupal::service('google_vision.api')->imageAttributesDetection($filepath); + // If we have retrieved labels. + if (!empty($data['responses'][0]['imagePropertiesAnnotation'])) { + foreach ($data['responses'][0]['imagePropertiesAnnotation']['dominantColors']['colors'] as $item) { + $red = $item['color']['red']; + $green = $item['color']['green']; + $blue = $item['color']['blue']; + } + //Create an array of the colors along with their values. + $color = array( + 'red' => $red, + 'green' => $green, + 'blue' => $blue + ); + if($red == $green && $green == $blue) { + $dominant_color = ['red', 'green', 'blue']; + } + else { + $max_value = max($red, $green, $blue); + //Get the dominant color based on the maximum value. + $dominant_color = array_keys($color, $max_value); } - // Save collected values. - $file->set($field->getName(), $values); + //Add the terms to vocabulary, and set the values to fields in files. + google_vision_add_terms_to_vocabulary($file, $field, $vid, $dominant_color); } } } diff --git a/google_vision.routing.yml b/google_vision.routing.yml index ef3434c..cbe4000 100644 --- a/google_vision.routing.yml +++ b/google_vision.routing.yml @@ -5,3 +5,12 @@ google_vision.settings: _title: 'Google vision settings' requirements: _permission: 'administer google vision' + +google_vision.related_content: + path: '/file/{file}/similarcontent' + defaults: + _controller: '\Drupal\google_vision\Controller\SimilarContentController::fetchContent' + _title: 'Similar Contents' + requirements: + _permission: 'administer google vision' + file: \d+ diff --git a/google_vision.services.yml b/google_vision.services.yml index be39f19..91ddb31 100644 --- a/google_vision.services.yml +++ b/google_vision.services.yml @@ -1,4 +1,4 @@ services: google_vision.api: class: Drupal\google_vision\GoogleVisionAPI - arguments: ['@config.factory', '@http_client'] \ No newline at end of file + arguments: ['@config.factory', '@http_client'] diff --git a/src/Controller/SimilarContentController.php b/src/Controller/SimilarContentController.php new file mode 100644 index 0000000..e16df87 --- /dev/null +++ b/src/Controller/SimilarContentController.php @@ -0,0 +1,105 @@ +connection = $connection; + } + + /** + * Get the file title. + */ + public function getFileTitle($fid) { + $query = $this->connection->select('file_managed', 'fm'); + $query->fields('fm', ['filename']); + $query->condition('fm.fid', $fid); + $title = $query->execute()->fetchField(); + return $title; + } + + /** + * Returns the list of image links which share the same dominant color. + */ + public function fetchContent(FileInterface $file) { + // Get the file id. + $file_id = $file->id(); + + //Get an array of just term ids. + $query = \Drupal::entityQuery('taxonomy_term'); + $query->condition('vid', 'dominant_color'); + $tids = $query->execute(); + $terms = Term::loadMultiple($tids); + $term_id = array_keys($terms); + + // Get the list of dominant colors per file. + $dominant_color = []; + foreach ($term_id as $key => $value) { + $query = $this->connection->select('file__field_labels', 'ffl'); + $query->fields('ffl', ['field_labels_target_id']); + $query->condition('ffl.entity_id', $file_id); + $query->condition('ffl.field_labels_target_id', $value); + $dominant_color[] = $query->execute()->fetchField(); + } + + $build = array(); + + if (!empty($dominant_color)) { + //Get all the file ids which have one or more dominant colors in common with $dominant_color. + $query = $this->connection->select('file__field_labels', 'ffl'); + $query->fields('ffl', ['entity_id', 'field_labels_target_id']); + $query->condition('field_labels_target_id', $dominant_color, 'IN'); + $files = $query->execute()->fetchAllKeyed(); + + $build['#prefix'] = ''; + + foreach ($files as $key => $value) { + $build[$key] = [ + '#prefix' => '
  • ', + '#type' => 'link', + '#title' => $this->getFileTitle($key), + '#url' => Url::fromRoute('entity.file.canonical', ['file' => $key]), + '#suffix' => '
  • ', + ]; + } + } + else { + $build = [ + '#markup' => t('No items found.'), + ]; + } + + return $build; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('database')); + } +} diff --git a/src/GoogleVisionAPI.php b/src/GoogleVisionAPI.php index 4232e6c..7118e6c 100644 --- a/src/GoogleVisionAPI.php +++ b/src/GoogleVisionAPI.php @@ -338,7 +338,7 @@ class GoogleVisionAPI { 'requests' => [ [ 'image' => [ - 'content' => $encoded_imageьфшьть + 'content' => $encoded_image ], 'features' => [ [ @@ -357,4 +357,4 @@ class GoogleVisionAPI { return FALSE; } -} \ No newline at end of file +} diff --git a/src/Tests/SimilarContentsTest.php b/src/Tests/SimilarContentsTest.php new file mode 100644 index 0000000..1e6070d --- /dev/null +++ b/src/Tests/SimilarContentsTest.php @@ -0,0 +1,205 @@ +adminUser = $this->drupalCreateUser([ + 'administer google vision', + 'administer files', + 'edit any image files', + 'create files', + 'administer file types', + 'administer file fields', + 'administer taxonomy', + ]); + $this->drupalLogin($this->adminUser); + //Check whether the API key is set. + $this->drupalGet(Url::fromRoute('google_vision.settings')); + $this->assertNotNull('api_key', 'The api key is set'); + } + + /** + * Uploads an image file and saves it. + * + * @param integer $count + * The index for the image file. + * + * @return integer + * The file id of the newly created image file. + */ + public function uploadImageFile($count) { + $images = $this->drupalGetTestFiles('image'); + $edit = [ + 'files[upload]' => \Drupal::service('file_system')->realpath($images[$count]->uri), + ]; + $this->drupalPostForm('file/add', $edit, t('Next')); + $this->drupalPostForm(NULL, array(), t('Next')); + $this->drupalPostForm(NULL, array(), t('Save')); + return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField(); + } + + /** + * Creates a new field for referencing taxonomy vocabulary. + * + * @param \Drupal\taxonomy\Entity\Vocabulary $vocabulary. + * The vocabulary. + */ + public function createEntityReferenceField($vocabulary) { + $entity_type = 'taxonomy_term'; + $field_name = 'field_labels'; + $field_storage = FieldStorageConfig::create(array( + 'field_name' => $field_name, + 'entity_type' => 'file', + 'translatable' => FALSE, + 'settings' => array( + 'target_type' => $entity_type, + ), + 'type' => 'entity_reference', + 'cardinality' => 1, + )); + $field_storage->save(); + $field = FieldConfig::create(array( + 'field_storage' => $field_storage, + 'entity_type' => 'file', + 'bundle' => 'image', + 'settings' => array( + 'handler' => 'default', + 'handler_settings' => array( + // Restrict selection of terms to a single vocabulary. + 'target_bundles' => array( + $vocabulary->id() => $vocabulary->id(), + ), + ), + ), + )); + $field->save(); + } + + /** + * Creates and returns a new vocabulary. + * + * @param string $name. + * The name for the created vocabulary. + * + * @param string $vid. + * The vocabulary id. + * + * @return \Drupal\taxonomy\Entity\Vocabulary $vocabulary. + * The vocabulary. + */ + function createTaxonomyVocabulary($name, $vid) { + $vocabulary = Vocabulary::create([ + 'name' => $name, + 'description' => t('Stores the dominant color of the images.'), + 'vid' => $vid, + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $vocabulary->save(); + return $vocabulary; + } + + /** + * Test to ensure that the Similar Contents contains the related images. + */ + public function testSimilarContents() { + $name = 'Dominant Color'; + $vid = 'dominant_color'; + $count = 0; + $vocabulary = $this->createTaxonomyVocabulary($name, $vid); + //Check whether the vocabulary is created. + $this->drupalGet(Url::fromRoute('entity.taxonomy_vocabulary.collection')); + $this->assertResponse(200); + //Create a taxonomy reference field. + $this->createEntityReferenceField($vocabulary); + $edit = [ + 'google_vision' => 1, + 'settings[handler_settings][auto_create]' => 1, + ]; + $this->drupalPostForm('admin/structure/file-types/manage/image/edit/fields/file.image.field_labels', $edit, t('Save settings')); + //Ensure that the Dominant Color option is selected. + $this->drupalGet('admin/structure/file-types/manage/image/edit/fields/file.image.field_labels'); + // Upload an image file. + $file_id = $this->uploadImageFile($count); + + //Create multiple images to be displayed in the similar contents link. + for ($count=1; $count < 3; $count++) { + $id[$count] = $this->uploadImageFile($count); + } + $this->drupalGet('file/' . $file_id . '/similarcontent'); + $this->assertResponse(200); + $this->assertNoText('No items found.', 'Similar Contents are displayed.'); + } + + /** + * Test to ensure that no similar images are present. + */ + public function testNoSimilarContents() { + $name = 'Labels'; + $vid = 'other_labels'; + $count = 0; + $vocabulary = $this->createTaxonomyVocabulary($name, $vid); + //Check whether the vocabulary is created. + $this->drupalGet(Url::fromRoute('entity.taxonomy_vocabulary.collection')); + $this->assertResponse(200); + //Create a taxonomy reference field. + $this->createEntityReferenceField($vocabulary); + $edit = [ + 'google_vision' => 1, + 'settings[handler_settings][auto_create]' => 1, + ]; + $this->drupalPostForm('admin/structure/file-types/manage/image/edit/fields/file.image.field_labels', $edit, t('Save settings')); + //Ensure that the Dominant Color option is not selected. + $this->drupalGet('admin/structure/file-types/manage/image/edit/fields/file.image.field_labels'); + // Upload an image file. + $file_id = $this->uploadImageFile($count); + + //Create multiple images to be displayed in the similar contents link. + for ($count=1; $count < 3; $count++) { + $id[$count] = $this->uploadImageFile($count); + } + $this->drupalGet('file/' . $file_id . '/similarcontent'); + $this->assertResponse(200); + $this->assertText('No items found.', 'No items found message is displayed.'); + } +} diff --git a/tests/modules/google_vision_test/src/GoogleVisionAPIFake.php b/tests/modules/google_vision_test/src/GoogleVisionAPIFake.php index c35f123..e426967 100644 --- a/tests/modules/google_vision_test/src/GoogleVisionAPIFake.php +++ b/tests/modules/google_vision_test/src/GoogleVisionAPIFake.php @@ -49,6 +49,31 @@ class GoogleVisionAPIFake extends GoogleVisionAPI { } /** + * Function to retrieve labels for given image. + * + * @param string $filepath . + * + * @return Array|bool. + */ + public function labelDetection($filepath) { + if (!$this->apiKey) { + return FALSE; + } + $response = [ + 'responses' => [ + '0' => [ + 'labelAnnotations' => [ + '0' => [ + 'description' => 'Sample Image', + ], + ], + ], + ], + ]; + return $response; + } + + /** * Function to return the response showing the image contains explicit content. * * @param string $filepath . @@ -73,4 +98,37 @@ class GoogleVisionAPIFake extends GoogleVisionAPI { ); return $response; } + + /** + * Function to retrieve image attributes for given image. + * + * @param string $filepath . + * + * @return Array|bool. + */ + public function imageAttributesDetection($filepath) { + if (!$this->apiKey) { + return FALSE; + } + $response = [ + 'responses' => [ + '0' => [ + 'imagePropertiesAnnotation' => [ + 'dominantColors' => [ + 'colors' => [ + '0' => [ + 'color' => [ + 'red' => 124, + 'blue' => 159, + 'green' => 20, + ], + ], + ], + ], + ], + ], + ], + ]; + return $response; + } }