Skip to content
This repository has been archived by the owner on Jan 5, 2018. It is now read-only.

Covert validation to constraints API #9

Merged
merged 1 commit into from Oct 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 50 additions & 0 deletions .travis.yml
@@ -0,0 +1,50 @@
# @file
# .travis.yml - Drupal for Travis CI Integration
#
# Template provided by https://github.com/LionsAd/drupal_ti.
#
# Based for simpletest upon:
# https://github.com/sonnym/travis-ci-drupal-module-example

language: php
sudo: false

cache:
directories:
- $HOME/.composer/cache

php:
- 5.5
- 5.6
- 7
- hhvm

matrix:
fast_finish: true

branches:
only:
- "8.x-1.x"

env:
global:
# add composer's global bin directory to the path
# see: https://github.com/drush-ops/drush#install---composer
- PATH="$PATH:$HOME/.composer/vendor/bin"

before_install:
- composer self-update
# Codesniffer and Coder
- composer global require "squizlabs/php_codesniffer:2.0.*@dev"
- composer global require drupal/coder:8.2.0-beta1
- ln -s ~/.composer/vendor/drupal/coder/coder_sniffer/Drupal ~/.composer/vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/

script:
- phpcs --report=full --standard=Drupal "$TRAVIS_BUILD_DIR" || true
- cd ~/
- git clone --depth 1 --branch 8.0.x http://git.drupal.org/project/drupal.git
- cd drupal/modules
- git clone --depth 1 --branch 8.x-1.x http://git.drupal.org/project/media_entity.git
- ln -s $TRAVIS_BUILD_DIR
- cd ../
- ./vendor/bin/phpunit -c core modules/media_entity_twitter/tests/src/Unit
46 changes: 13 additions & 33 deletions src/Plugin/MediaEntity/Type/Twitter.php
Expand Up @@ -14,7 +14,6 @@
use Drupal\media_entity\MediaTypeBase;
use Drupal\media_entity\MediaTypeException;
use Drupal\Component\Serialization\Json;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;


Expand All @@ -30,18 +29,11 @@
class Twitter extends MediaTypeBase {

/**
* List of validation regular expressions.
* Embed code validation regexp.
*
* @var array
*/
protected $validationRegexp = '@((http|https):){0,1}//(www\.){0,1}twitter\.com/(?<user>[a-z0-9_-]+)/(status(es){0,1})/(?<id>[a-z0-9_-]+)@i';

/**
* The HTTP client to fetch the feed data with.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;
const VALIDATION_REGEXP = '@((http|https):){0,1}//(www\.){0,1}twitter\.com/(?<user>[a-z0-9_-]+)/(status(es){0,1})/(?<id>[\d]+)@i';

/**
* Config factory service.
Expand All @@ -58,7 +50,6 @@ public static function create(ContainerInterface $container, array $configuratio
$configuration,
$plugin_id,
$plugin_definition,
$container->get('http_client'),
$container->get('entity.manager'),
$container->get('config.factory')
);
Expand All @@ -73,16 +64,13 @@ public static function create(ContainerInterface $container, array $configuratio
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \GuzzleHttp\Client $http_client
* HTTP client.
* @param \Drupal\Core\Entity\EntityManager $entity_manager
* Entity manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Config factory service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, Client $http_client, EntityManager $entity_manager, ConfigFactoryInterface $config_factory) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManager $entity_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $config_factory->get('media_entity.settings'));
$this->httpClient = $http_client;
$this->configFactory = $config_factory;
}

Expand Down Expand Up @@ -253,29 +241,21 @@ public function settingsForm(MediaBundleInterface $bundle) {
),
);


return $form;
}

/**
* {@inheritdoc}
*/
public function validate(MediaInterface $media) {
$matches = $this->matchRegexp($media);

// Validate regex.
if (!$matches) {
throw new MediaTypeException($this->configuration['source_field'], 'Not valid URL/embed code.');
}

// Check that the tweet is publicly visible.
$response = $this->httpClient->get($matches[0], ['allow_redirects' => FALSE]);

if ($response->getStatusCode() == 302 && ($location = $response->getHeader('location'))) {
$effective_url_parts = parse_url($location[0]);
if (!empty($effective_url_parts) && isset($effective_url_parts['query']) && $effective_url_parts['query'] == 'protected_redirect=true') {
throw new MediaTypeException($this->configuration['source_field'], 'The tweet is not reachable.');
}
public function attachConstraints(MediaInterface $media) {
parent::attachConstraints($media);

$source_field_name = $this->configuration['source_field'];
foreach ($media->get($source_field_name) as &$embed_code) {
/** @var \Drupal\Core\TypedData\DataDefinitionInterface $typed_data */
$typed_data = $embed_code->getDataDefinition();
$typed_data->addConstraint('TweetEmbedCode');
$typed_data->addConstraint('TweetVisible');
}
}

Expand Down Expand Up @@ -306,7 +286,7 @@ protected function matchRegexp(MediaInterface $media) {
$source_field = $this->configuration['source_field'];

$property_name = $media->{$source_field}->first()->mainPropertyName();
if (preg_match($this->validationRegexp, $media->{$source_field}->{$property_name}, $matches)) {
if (preg_match(static::VALIDATION_REGEXP, $media->{$source_field}->{$property_name}, $matches)) {
return $matches;
}

Expand Down
29 changes: 29 additions & 0 deletions src/Plugin/Validation/Constraint/TweetEmbedCodeConstraint.php
@@ -0,0 +1,29 @@
<?php

/**
* @file
* Contains \Drupal\media_entity_twitter\Plugin\Validation\Constraint\TweetEmbedCodeConstraint.
*/

namespace Drupal\media_entity_twitter\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
* Checks if a value is a valid Tweet embed code/URL.
*
* @Constraint(
* id = "TweetEmbedCode",
* label = @Translation("Tweet embed code", context = "Validation"),
* type = { "entity", "entity_reference" }
* )
*/
class TweetEmbedCodeConstraint extends Constraint {

/**
* The default violation message.
*
* @var string
*/
public $message = 'Not valid Tweet URL/embed code.';
}
@@ -0,0 +1,34 @@
<?php

/**
* @file
* Contains \Drupal\media_entity_twitter\Plugin\Validation\Constraint\TweetEmbedCodeConstraintValidator.
*/

namespace Drupal\media_entity_twitter\Plugin\Validation\Constraint;

use Drupal\media_entity_twitter\Plugin\MediaEntity\Type\Twitter;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
* Validates the TweetEmbedCode constraint.
*/
class TweetEmbedCodeConstraintValidator extends ConstraintValidator {

/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
if (!isset($entity)) {
return;
}

if (preg_match(Twitter::VALIDATION_REGEXP, $entity->value)) {
return;
}

$this->context->addViolation($constraint->message);
}

}
29 changes: 29 additions & 0 deletions src/Plugin/Validation/Constraint/TweetVisibleConstraint.php
@@ -0,0 +1,29 @@
<?php

/**
* @file
* Contains \Drupal\media_entity_twitter\Plugin\Validation\Constraint\TweetVisibleConstraint.
*/

namespace Drupal\media_entity_twitter\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
* Checks if a Tweet is publicly visible.
*
* @Constraint(
* id = "TweetVisible",
* label = @Translation("Tweet publicly visible", context = "Validation"),
* type = { "entity", "entity_reference" }
* )
*/
class TweetVisibleConstraint extends Constraint {

/**
* The default violation message.
*
* @var string
*/
public $message = 'Referenced tweet is not publicly visible.';
}
@@ -0,0 +1,67 @@
<?php

/**
* @file
* Contains \Drupal\media_entity_twitter\Plugin\Validation\Constraint\TweetVisibleConstraintValidator.
*/

namespace Drupal\media_entity_twitter\Plugin\Validation\Constraint;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\media_entity_twitter\Plugin\MediaEntity\Type\Twitter;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
* Validates the TweetVisible constraint.
*/
class TweetVisibleConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

/**
* The HTTP client to fetch the feed data with.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;

/**
* Constructs a new TweetVisibleConstraintValidator.
*
* @param \GuzzleHttp\Client $http_client
* The http client service.
*/
public function __construct(Client $http_client) {
$this->httpClient = $http_client;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('http_client'));
}


/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
if (!isset($entity)) {
return;
}

$matches = [];
preg_match(Twitter::VALIDATION_REGEXP, $entity->value, $matches);
$response = $this->httpClient->get($matches[0], ['allow_redirects' => FALSE]);

if ($response->getStatusCode() == 302 && ($location = $response->getHeader('location'))) {
$effective_url_parts = parse_url($location[0]);
if (!empty($effective_url_parts) && isset($effective_url_parts['query']) && $effective_url_parts['query'] == 'protected_redirect=true') {
$this->context->addViolation($constraint->message);
}
}
}

}