Skip to content

Commit

Permalink
VACMS-10356 Add content validator for Preview URL links (#10356) (#16086
Browse files Browse the repository at this point in the history
)

* VACMS-10356 Add content validator for Preview URL links (#10356)

* Fix validator logic

* Reclassify tests as unit tests

* Update error message

* Rename PreventPreviewUrlsAsPathsInLinks => PreventPreviewUrlLinks

* Update constraint description
  • Loading branch information
JunTaoLuo authored Nov 13, 2023
1 parent 9fcfaf1 commit 21325fc
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Drupal\va_gov_backend\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
* Checks that the text does not use any preview URL links.
*
* In other words, we want to avoid things like this:
*
* `<a href="https://preview-staging.vfs.va.gov/path/to/content/">VA.gov</a>`
*
* @Constraint(
* id = "PreventPreviewUrlLinks",
* label = @Translation("Prevent Preview URL Links", context = "Validation"),
* type = { "string_long", "text_long" }
* )
*/
class PreventPreviewUrlLinks extends Constraint {

/**
* The error message for plain text fields.
*
* @var string
* @see \Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventPreviewUrlLinksValidator
*/
public $plainTextMessage = 'Replace the preview URL ":url" with a public URL.';

/**
* The error message for rich text fields.
*
* @var string
* @see \Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventPreviewUrlLinksValidator
*/
public $richTextMessage = 'Review the linked text ":link" (:url) and replace the preview URL with a public URL.';

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Drupal\va_gov_backend\Plugin\Validation\Constraint;

use Drupal\Component\Utility\Html;
use Drupal\va_gov_backend\Plugin\Validation\Constraint\Traits\TextValidatorTrait;
use Drupal\va_gov_backend\Plugin\Validation\Constraint\Traits\ValidatorContextAccessTrait;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
* Validates the PreventPreviewUrlLinks constraint.
*/
class PreventPreviewUrlLinksValidator extends ConstraintValidator {

use TextValidatorTrait;
use ValidatorContextAccessTrait;

/**
* {@inheritdoc}
*/
public function validateText(string $text, Constraint $constraint, int $delta) {
/*
* Regular expression explanation:
*
* # Begin delimiter (replace '/', since we're mucking about
* with URLs, which contain slashes.)
* ( Open capture group. We capture the entire URL so that
* we can refer to it in the error message.
* https? Match either 'http' or 'https' ...
* :// ... followed by a colon and two slashes ...
* preview-(staging|prod)
* .vfs.va.gov ... followed by a preview servers for prod|staging ...
* [^\s]+ ... and any non-whitespace characters that follow.
* ) Close capture group.
* # End delimiter (replace '/')
*
* In other words, we look for a string that looks like a preview URL.
*/
/** @var \Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventPreviewUrlLinks $constraint */
if (preg_match('#(https?://preview-(staging|prod).vfs.va.gov[^\s]+)#', $text, $matches)) {
$this->addViolation($delta, $constraint->plainTextMessage, [
':url' => $matches[1],
]);
}
}

/**
* {@inheritdoc}
*/
public function validateHtml(string $html, Constraint $constraint, int $delta) {
/** @var \Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventPreviewUrlLinks $constraint */
$dom = Html::load($html);
$xpath = new \DOMXPath($dom);
foreach ($xpath->query('//a[contains(@href, "preview-staging.vfs.va.gov") or contains(@href, "preview-prod.vfs.va.gov")]') as $element) {
// Ignore non-element nodes.
if (!($element instanceof \DOMElement)) {
continue;
}
$url = $element->getAttribute('href');
$firstChild = $element->hasChildNodes() ? $element->childNodes[0] : NULL;
$link = $element->ownerDocument->saveHTML($firstChild ?? $element);
$this->getContext()
->buildViolation($constraint->richTextMessage, [
':link' => $link,
':url' => $url,
])
->atPath((string) $delta . '.value')
->addViolation();
return;
}
}

}
2 changes: 2 additions & 0 deletions docroot/modules/custom/va_gov_backend/va_gov_backend.module
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,7 @@ function va_gov_backend_entity_bundle_field_info_alter(&$fields, EntityTypeInter
$field->addConstraint('PreventAbsoluteUrlsAsPathsInLinks');
$field->addConstraint('PreventVaGovDomainsAsPathsInLinks');
$field->addConstraint('PreventDomainsAsPathsInLinks');
$field->addConstraint('PreventPreviewUrlLinks');
}
elseif ($field->getType() === 'text_long') {
$field->addConstraint('PreventAbsoluteCmsLinks');
Expand All @@ -1332,6 +1333,7 @@ function va_gov_backend_entity_bundle_field_info_alter(&$fields, EntityTypeInter
$field->addConstraint('PreventAbsoluteUrlsAsPathsInLinks');
$field->addConstraint('PreventVaGovDomainsAsPathsInLinks');
$field->addConstraint('PreventDomainsAsPathsInLinks');
$field->addConstraint('PreventPreviewUrlLinks');
}
}
// Add paragraph checks on clp panels.
Expand Down
131 changes: 131 additions & 0 deletions tests/phpunit/Content/PreventPreviewUrlLinksValidatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace tests\phpunit\Content;

use Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventPreviewUrlLinks;
use Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventPreviewUrlLinksValidator;
use Tests\Support\Classes\VaGovUnitTestBase;
use Tests\Support\Traits\ValidatorTestTrait;

/**
* A test to confirm the proper functioning of this validator.
*
* @group unit
* @group all
* @group validation
*
* @coversDefaultClass \Drupal\va_gov_backend\Plugin\Validation\Constraint\PreventProtocolRelativeLinksValidator
*/
class PreventPreviewUrlLinksValidatorTest extends VaGovUnitTestBase {

use ValidatorTestTrait;

/**
* Test ::addViolation().
*
* @covers ::addViolation
*/
public function testAddViolation() {
$validator = new PreventPreviewUrlLinksValidator();
$this->prepareValidator($validator, FALSE);
$validator->addViolation(3, 'Test violation');
}

/**
* Test ::validate().
*
* @param bool $willValidate
* TRUE if the test string should validate, otherwise FALSE.
* @param string $testString
* Some test string to attempt to validate.
* @param string $fieldType
* The type of the text field, e.g. 'text_long' or 'string_long'.
* @param string $format
* An optional format, like 'plain_text' or 'rich_text'.
*
* @covers validate
* @covers validateText
* @covers validateHtml
* @dataProvider validateDataProvider
*/
public function testValidate(bool $willValidate, string $testString, string $fieldType = 'string_long', string $format = NULL) {
$value = [
'value' => $testString,
];
if ($fieldType !== 'string_long') {
$value['format'] = $format;
}
$items = $this->getFieldItemList([
$this->getFieldItem($value, $fieldType),
]);
$validator = new PreventPreviewUrlLinksValidator();
$this->prepareValidator($validator, $willValidate);
$validator->validate($items, new PreventPreviewUrlLinks());
}

/**
* Data provider.
*/
public function validateDataProvider() {
return [
[
TRUE,
'Normal string_long text should not fail validation.',
],
[
TRUE,
'Normal string_long text with the occasional // scattered throughout should not // fail validation.',
],
[
FALSE,
'However, string_long text with http://preview-staging.vfs.va.gov/path/to/content that consists of an Preview URL should fail validation.',
],
[
FALSE,
'However, string_long text with http://preview-prod.vfs.va.gov/path/to/content that consists of an Preview URL should fail validation.',
],
[
TRUE,
'Normal text_long text should not fail validation.',
'text_long',
],
[
FALSE,
'Something with a http://preview-staging.vfs.va.gov/path/to/content looks like it contains an Preview URL and should fail.',
'text_long',
'plain_text',
],
[
FALSE,
'Something with a https://preview-prod.vfs.va.gov/path/to/content looks like it contains an Preview URL and should fail.',
'text_long',
'plain_text',
],
[
FALSE,
'This contains a <a href="https://preview-staging.vfs.va.gov/path/to/content">path consisting of an Preview URL</a> inside a tag and should fail.',
'text_long',
'plain_text',
],
[
FALSE,
'This contains a <a href="https://preview-prod.vfs.va.gov/path/to/content">path consisting of an Preview URL</a> inside a tag and should fail.',
'text_long',
'plain_text',
],
[
TRUE,
'http://preview-staging.vfs.va.gov/path/to/content outside of <a href="https://www.example.org/">anchor tags</a> should not trigger the validator.',
'text_long',
'rich_text',
],
[
TRUE,
'http://preview-prod.vfs.va.gov/path/to/content outside of <a href="https://www.example.org/">anchor tags</a> should not trigger the validator.',
'text_long',
'rich_text',
],
];
}

}

0 comments on commit 21325fc

Please sign in to comment.