Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
Nov 13, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
],
];
}

}
Loading