diff --git a/google_vision.module b/google_vision.module index a4c567c..dea6244 100644 --- a/google_vision.module +++ b/google_vision.module @@ -7,6 +7,11 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\field\FieldConfigInterface; use Drupal\google_vision\GoogleVisionAPI; +use Drupal\field\FieldStorageConfigInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\FieldDefinitionInterface; /** * Implements hook_entity_presave(). @@ -49,18 +54,50 @@ function google_vision_form_field_config_edit_form_alter(&$form, \Drupal\Core\Fo '#default_value' => !empty($settings['google_vision']) ? $settings['google_vision'] : FALSE, ]; - $form['#entity_builders'][] = 'google_vision_form_field_config_form_builder'; + $form['#entity_builders'][] = 'google_vision_form_field_config_form_taxonomy_builder'; + } + + if($field_entity->getType() == 'image') { + $settings = $form_state->getFormObject()->getEntity()->getThirdPartySettings('google_vision'); + $form['safe_search'] = [ + '#type' => 'checkbox', + '#title' => t('Enable Safe Search'), + '#description' => t('Detects and avoids explicit contents.'), + '#default_value' => !empty($settings['google_vision']) ? $settings['google_vision'] : FALSE, + ]; + + $form['#entity_builders'][] = 'google_vision_form_field_config_form_image_builder'; } } /** - * Form builder to save the settings. + * Form builder to save the settings for entity reference. */ -function google_vision_form_field_config_form_builder($entity_type, FieldConfigInterface $type, &$form, FormStateInterface $form_state) { +function google_vision_form_field_config_form_taxonomy_builder($entity_type, FieldConfigInterface $type, &$form, FormStateInterface $form_state) { $type->setThirdPartySetting('google_vision', 'google_vision', $form_state->getValue('google_vision')); } /** + * Implements hook_entity_bundle_field_info_alter(). + */ +function google_vision_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) { + if ($entity_type->id() == 'node') { + foreach ($fields as $field) { + if ($field->getType() == 'image') { + $fields[$field->getName()]->addConstraint('SafeSearch'); + } + } + } +} + +/** + * Form builder to save the settings for images. + */ +function google_vision_form_field_config_form_image_builder($entity_type, FieldConfigInterface $type, &$form, FormStateInterface $form_state) { + $type->setThirdPartySetting('google_vision', 'google_vision', $form_state->getValue('safe_search')); +} + +/** * Try to get and add labels for the file entity. */ function google_vision_file_entity_add_labels($file, $field, $vid) { diff --git a/src/Plugin/Validation/Constraint/SafeSearchConstraint.php b/src/Plugin/Validation/Constraint/SafeSearchConstraint.php new file mode 100755 index 0000000..9bf064d --- /dev/null +++ b/src/Plugin/Validation/Constraint/SafeSearchConstraint.php @@ -0,0 +1,18 @@ +googleVisionAPI = $google_vision; + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public function validate($data, Constraint $constraint) { + $field_def = $data->getFieldDefinition(); + $settings = $field_def->getThirdPartySettings('google_vision'); + // if the Safe Search detection is on. + if (!empty($settings['google_vision'])) { + // if the image is uploaded. + if (!empty($data->getValue('target_id'))) { + // Retrieve the file uri. + $file_uri = $data->entity->getFileUri(); + if ($filepath = $this->fileSystem->realpath($file_uri)) { + $result = $this->googleVisionAPI->safeSearchDetection($filepath); + if (!empty($result['responses'][0]['safeSearchAnnotation'])) { + $adult = $result['responses'][0]['safeSearchAnnotation']['adult']; + $likelihood = array('POSSIBLE', 'LIKELY', 'VERY_LIKELY'); + // if the image has explicit content. + if (in_array($adult, $likelihood)) { + $this->context->addViolation($constraint->message); + } + } + } + } + } + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static ( + $container->get('google_vision.api'), + $container->get('file_system') + ); + } +} diff --git a/src/Tests/SafeSearchConstraintValidationTest.php b/src/Tests/SafeSearchConstraintValidationTest.php new file mode 100644 index 0000000..1d68ddc --- /dev/null +++ b/src/Tests/SafeSearchConstraintValidationTest.php @@ -0,0 +1,195 @@ +drupalCreateContentType(array('type' => 'test_images', 'name' => 'Test Images')); + // Creates administrative user. + $this->adminUser = $this->drupalCreateUser(array('administer google vision','create test_images content', 'access content', 'access administration pages', 'administer node fields', 'administer nodes', 'administer node display') + ); + $this->drupalLogin($this->adminUser); + } + + /** + * Create a new image field. + * + * @param string $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param string $type_name + * The node type that this field will be added to. + * @param array $storage_settings + * A list of field storage settings that will be added to the defaults. + * @param array $field_settings + * A list of instance settings that will be added to the instance defaults. + * @param array $widget_settings + * Widget settings to be added to the widget defaults. + * @param array $formatter_settings + * Formatter settings to be added to the formatter defaults. + * @param string $description + * A description for the field. + */ + public function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array(), $description = '') { + FieldStorageConfig::create(array( + 'field_name' => $name, + 'entity_type' => 'node', + 'type' => 'image', + 'settings' => $storage_settings, + 'cardinality' => 1, + ))->save(); + + $field_config = FieldConfig::create([ + 'field_name' => $name, + 'label' => $name, + 'entity_type' => 'node', + 'bundle' => $type_name, + 'settings' => $field_settings, + 'description' => $description, + ])->addConstraint('SafeSearch'); + $field_config->save(); + + /*$field_config = FieldConfigBase::create([ + 'field_name' => $name, + 'label' => $name, + 'entity_type' => 'node', + 'bundle' => $type_name, + 'settings' => $field_settings, + 'description' => $description, + ])->addConstraint('SafeSearch'); + $field_config->save();*/ + + entity_get_form_display('node', $type_name, 'default') + ->setComponent($name, array( + 'type' => 'image_image', + 'settings' => $widget_settings, + )) + ->save(); + + entity_get_display('node', $type_name, 'default') + ->setComponent($name, array( + 'type' => 'image', + 'settings' => $formatter_settings, + )) + ->save(); + + return $field_config; + + } + + /** + * Upload an image to the node of type test_images and create the node. + * + * @param The image file $image + * A file object representing the image to upload. + */ + function uploadImageFile($image) { + $edit = array( + 'title[0][value]' => $this->randomMachineName(), + 'files[images_0]' => drupal_realpath($image->getFileUri()), + ); + + $this->drupalPostForm('node/add/test_images' , $edit, t('Save and publish')); + // Add alt text. + $this->drupalPostForm(NULL, ['images[0][alt]' => $this->randomMachineName()], t('Save and publish')); + + // Retrieve ID of the newly created node from the current URL. + $matches = array(); + preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches); + return isset($matches[1]) ? $matches[1] : FALSE; + } + + /** + * + */ + public function testSafeSearchConstraint() { + $this->drupalGet('node/add/test_images'); + $this->assertResponse(200); + + // Create an image field and add an field to the custom content type. + $storage_settings['default_image'] = array( + 'uuid' => 1, + 'alt' => '', + 'title' => '', + 'width' => 0, + 'height' => 0, + ); + $field_settings['default_image'] = array( + 'uuid' => 1, + 'alt' => '', + 'title' => '', + 'width' => 0, + 'height' => 0, + ); + $widget_settings = array( + 'preview_image_style' => 'medium', + ); + $field = $this->createImageField('images', 'test_images', $storage_settings, $field_settings, $widget_settings); + + $field_id = $field->id(); + + //Enable the Safe Search. + $edit = array( + 'safe_search' => 1, + ); + $this->drupalPostForm("admin/structure/types/manage/test_images/fields/$field_id", $edit, t('Save settings')); + + //Ensure that the safe search is enabled. + $this->drupalGet("admin/structure/types/manage/test_images/fields/$field_id"); + + // Get an image with explicit content from web. + + $image = file_get_contents('http://www.menshealth.com/sites/menshealth.com/files/articles/2015/12/man-snoring.jpg'); // string + $file = file_save_data($image, 'public://explicit.jpg', FILE_EXISTS_REPLACE); + + // Save the node. + $node_id = $this->uploadImageFile($file); + + + //Display the node. + $this->drupalGet('node/' . $node_id); + + } +} diff --git a/tests/src/Functional/SafeSearchTest.php b/tests/src/Functional/SafeSearchTest.php new file mode 100755 index 0000000..e8164ba --- /dev/null +++ b/tests/src/Functional/SafeSearchTest.php @@ -0,0 +1,234 @@ + 'foo'. + * + * @return \Drupal\node\Entity\NodeType + * Created content type. + */ + protected function drupalCreateContentType(array $values = array()) { + // Find a non-existent random type name. + if (!isset($values['type'])) { + do { + $id = strtolower($this->randomMachineName(8)); + } while (NodeType::load($id)); + } + else { + $id = $values['type']; + } + $values += array( + 'type' => $id, + 'name' => $id, + ); + $type = NodeType::create($values); + $status = $type->save(); + node_add_body_field($type); + \Drupal::service('router.builder')->rebuild(); + + if ($this instanceof \PHPUnit_Framework_TestCase) { + $this->assertSame($status, SAVED_NEW, (new FormattableMarkup('Created content type %type.', array('%type' => $type->id())))->__toString()); + } + else { + $this->assertEqual($status, SAVED_NEW, (new FormattableMarkup('Created content type %type.', array('%type' => $type->id())))->__toString()); + } + + return $type; + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + //Create custom content type. + $contentType = $this->drupalCreateContentType(array('type' => 'test_images', 'name' => 'Test Images')); + + // Creates user. + $this->adminUser = $this->drupalCreateUser(array('administer google vision', 'create test_images content', 'access content', 'access administration pages', 'administer node fields', 'administer nodes', 'administer node display') + ); + $this->drupalLogin($this->adminUser); + } + + /** + * Create a new image field. + * + * @param string $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param string $type_name + * The node type that this field will be added to. + * @param array $storage_settings + * A list of field storage settings that will be added to the defaults. + * @param array $field_settings + * A list of instance settings that will be added to the instance defaults. + * @param array $widget_settings + * Widget settings to be added to the widget defaults. + * @param array $formatter_settings + * Formatter settings to be added to the formatter defaults. + * @param string $description + * A description for the field. + */ + public function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array(), $description = '') { + FieldStorageConfig::create(array( + 'field_name' => $name, + 'entity_type' => 'node', + 'type' => 'image', + 'settings' => $storage_settings, + 'cardinality' => 1, + ))->save(); + + $field_config = FieldConfig::create([ + 'field_name' => $name, + 'label' => $name, + 'entity_type' => 'node', + 'bundle' => $type_name, + 'settings' => $field_settings, + 'description' => $description, + ])->addConstraint('SafeSearch'); + $field_config->save(); + + /*$field_config = FieldConfigBase::create([ + 'field_name' => $name, + 'label' => $name, + 'entity_type' => 'node', + 'bundle' => $type_name, + 'settings' => $field_settings, + 'description' => $description, + ])->addConstraint('SafeSearch'); + $field_config->save();*/ + + entity_get_form_display('node', $type_name, 'default') + ->setComponent($name, array( + 'type' => 'image_image', + 'settings' => $widget_settings, + )) + ->save(); + + entity_get_display('node', $type_name, 'default') + ->setComponent($name, array( + 'type' => 'image', + 'settings' => $formatter_settings, + )) + ->save(); + + return $field_config; + + } + + /** + * Upload an image to the node of type test_images and create the node. + * + * @param The image file $image + * A file object representing the image to upload. + */ + function uploadImageFile($image) { + $edit = array( + 'title[0][value]' => $this->randomMachineName(), + 'files[images_0]' => drupal_realpath($image->getFileUri()), + ); + + $this->drupalPostForm('node/add/test_images' , $edit, t('Save and publish')); + // Add alt text. + $this->drupalPostForm(NULL, ['images[0][alt]' => $this->randomMachineName()], t('Save and publish')); + + // Retrieve ID of the newly created node from the current URL. + $matches = array(); + preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches); + return isset($matches[1]) ? $matches[1] : FALSE; + } + + /** + * + */ + public function testSafeSearch() { + $this->drupalGet('node/add/test_images'); + $this->assertSession()->statusCodeEquals(200); + + // Create an image field and add an field to the custom content type. + $storage_settings['default_image'] = array( + 'uuid' => 1, + 'alt' => '', + 'title' => '', + 'width' => 0, + 'height' => 0, + ); + $field_settings['default_image'] = array( + 'uuid' => 1, + 'alt' => '', + 'title' => '', + 'width' => 0, + 'height' => 0, + ); + $widget_settings = array( + 'preview_image_style' => 'medium', + ); + $field = $this->createImageField('images', 'test_images', $storage_settings, $field_settings, $widget_settings); + + $field_id = $field->id(); + + //Enable the Safe Search. + $edit = array( + 'safe_search' => 1, + ); + $this->drupalPostForm("admin/structure/types/manage/test_images/fields/$field_id", $edit, t('Save settings')); + + //Ensure that the safe search is enabled. + $this->drupalGet("admin/structure/types/manage/test_images/fields/$field_id"); + + // Get an image with explicit content from web. + + $image = file_get_contents('http://timesofindia.indiatimes.com/thumb/msid-47037838,width-400,resizemode-4/47037838.jpg'); // string + $file = file_save_data($image, 'public://explicit.jpg', FILE_EXISTS_REPLACE); + + // Save the node. + $node_id = $this->uploadImageFile($file); + + + //Display the node. + $this->drupalGet('node/' . $node_id); + } +}