diff --git a/libraries/src/Form/Field/UrlField.php b/libraries/src/Form/Field/UrlField.php index a7b8471bcdce5..f9cd997f90f8e 100644 --- a/libraries/src/Form/Field/UrlField.php +++ b/libraries/src/Form/Field/UrlField.php @@ -39,6 +39,80 @@ class UrlField extends TextField */ protected $layout = 'joomla.form.field.url'; + /** + * Whether relative URLs are allowed. + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + protected $relative = false; + + /** + * Method to get certain otherwise inaccessible properties from the form field object. + * + * @param string $name The property name for which to get the value. + * + * @return mixed The property value or null. + * + * @since __DEPLOY_VERSION__ + */ + public function __get($name) + { + if ($name === 'relative') { + return $this->relative; + } + + return parent::__get($name); + } + + /** + * Method to set certain otherwise inaccessible properties of the form field object. + * + * @param string $name The property name for which to set the value. + * @param mixed $value The value of the property. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function __set($name, $value) + { + if ($name === 'relative') { + $value = (string) $value; + $this->relative = ($value === 'true' || $value === 'relative' || $value === '1'); + + return; + } + + parent::__set($name, $value); + } + + /** + * Method to attach a Form object to the field. + * + * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object. + * @param mixed $value The form field value to validate. + * @param string $group The field name group control value. This acts as an array container for the field. + * For example if the field has name="foo" and the group value is set to "bar" then the + * full field name would end up being "bar[foo]". + * + * @return boolean True on success. + * + * @see FormField::setup() + * @since __DEPLOY_VERSION__ + */ + public function setup(\SimpleXMLElement $element, $value, $group = null) + { + $return = parent::setup($element, $value, $group); + + if ($return) { + $relative = (string) $this->element['relative']; + $this->relative = ($relative === 'true' || $relative === 'relative' || $relative === '1'); + } + + return $return; + } + /** * Method to get the field input markup. * @@ -68,7 +142,7 @@ protected function getLayoutData() // Note that the input type "url" is suitable only for external URLs, so if internal URLs are allowed // we have to use the input type "text" instead. - $inputType = $this->element['relative'] ? 'type="text"' : 'type="url"'; + $inputType = $this->relative ? 'type="text"' : 'type="url"'; $extraData = [ 'maxLength' => $maxLength, diff --git a/libraries/src/Form/Filter/UrlFilter.php b/libraries/src/Form/Filter/UrlFilter.php index a6d478a6b35ce..6de0a64cd23ce 100644 --- a/libraries/src/Form/Filter/UrlFilter.php +++ b/libraries/src/Form/Filter/UrlFilter.php @@ -57,11 +57,12 @@ public function filter(\SimpleXMLElement $element, $value, $group = null, ?Regis // Check for a protocol $protocol = parse_url($value, PHP_URL_SCHEME); + $relative = ((string) $element['relative'] === 'true' || (string) $element['relative'] === 'relative' || (string) $element['relative'] === '1'); // If there is no protocol and the relative option is not specified, // we assume that it is an external URL and prepend http:// if ( - ((string) $element['type'] === 'url' && !$protocol && !$element['relative']) + ((string) $element['type'] === 'url' && !$protocol && !$relative) || ((string) $element['type'] !== 'url' && !$protocol) ) { $protocol = 'http'; @@ -74,7 +75,7 @@ public function filter(\SimpleXMLElement $element, $value, $group = null, ?Regis // Put the url back together. $value = $protocol . '://' . $value; } - } elseif (!$protocol && $element['relative']) { + } elseif (!$protocol && $relative) { // If relative URLS are allowed we assume that URLs without protocols are internal. $host = Uri::getInstance('SERVER')->getHost(); diff --git a/libraries/src/Form/Rule/UrlRule.php b/libraries/src/Form/Rule/UrlRule.php index c129932c72634..657573002ed62 100644 --- a/libraries/src/Form/Rule/UrlRule.php +++ b/libraries/src/Form/Rule/UrlRule.php @@ -78,11 +78,13 @@ public function test(\SimpleXMLElement $element, $value, $group = null, ?Registr * @link https://www.php.net/manual/en/function.parse-url.php */ if ($urlParts === false || !\array_key_exists('scheme', $urlParts)) { + $relative = ((string) $element['relative'] === 'true' || (string) $element['relative'] === 'relative' || (string) $element['relative'] === '1'); + /* * The function parse_url() returned false (seriously malformed URL) or no scheme * was found and the relative option is not set: in both cases the field is not valid. */ - if ($urlParts === false || !$element['relative']) { + if ($urlParts === false || !$relative) { $element->addAttribute('message', Text::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value, implode(', ', $scheme))); return false; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6634534734bd2..f3aa0fecfb4cd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3963,6 +3963,17 @@ parameters: count: 2 path: administrator/components/com_languages/src/Controller/InstalledController.php + - + message: ''' + #^Call to deprecated method getError\(\) of class Joomla\\CMS\\MVC\\Model\\BaseModel\: + 3\.1\.4 will be removed in 7\.0 + Will be removed without replacement + Catch thrown Exceptions instead of getError$# + ''' + identifier: method.deprecated + count: 2 + path: administrator/components/com_languages/src/Controller/InstalledController.php + - message: ''' #^Call to deprecated method getError\(\) of class Joomla\\CMS\\MVC\\Model\\BaseModel\: @@ -3974,6 +3985,17 @@ parameters: count: 2 path: administrator/components/com_languages/src/Controller/OverrideController.php + - + message: ''' + #^Call to deprecated method getError\(\) of class Joomla\\CMS\\MVC\\Model\\BaseModel\: + 3\.1\.4 will be removed in 7\.0 + Will be removed without replacement + Catch thrown Exceptions instead of getError$# + ''' + identifier: method.deprecated + count: 1 + path: administrator/components/com_languages/src/Controller/OverridesController.php + - message: ''' #^Call to deprecated method getErrors\(\) of class Joomla\\CMS\\MVC\\Model\\BaseModel\: diff --git a/tests/Unit/Libraries/Cms/Form/Field/UrlFieldTest.php b/tests/Unit/Libraries/Cms/Form/Field/UrlFieldTest.php new file mode 100644 index 0000000000000..f5744b3ba64a6 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Field/UrlFieldTest.php @@ -0,0 +1,117 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Field; + +use Joomla\CMS\Form\Field\UrlField; +use Joomla\Tests\Unit\UnitTestCase; + +/** + * Test class for UrlField. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +class UrlFieldTest extends UnitTestCase +{ + /** + * Tests the constructor + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testIsConstructable(): void + { + $this->assertInstanceOf(UrlField::class, new UrlField()); + } + + /** + * Tests getting default property values. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetWithDefaultValues(): void + { + $field = new UrlField(); + + $this->assertEquals('Url', $field->__get('type')); + $this->assertFalse($field->__get('relative')); + } + + /** + * Tests setting and getting properties. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testSetAndGetShouldBeEquals(): void + { + $field = new UrlField(); + + $field->__set('relative', true); + $this->assertTrue($field->__get('relative')); + + $field->__set('relative', 'false'); + $this->assertFalse($field->__get('relative')); + + $field->__set('relative', '1'); + $this->assertTrue($field->__get('relative')); + + $field->__set('relative', '0'); + $this->assertFalse($field->__get('relative')); + + $field->__set('relative', 'relative'); + $this->assertTrue($field->__get('relative')); + } + + /** + * Tests setup method parsing the relative attribute. + * + * @param bool $expected The expected relative boolean value + * @param string $xml The XML string + * + * @return void + * + * @since __DEPLOY_VERSION__ + * @dataProvider dataSetupRelative + */ + public function testSetupRelative(bool $expected, string $xml): void + { + $field = new UrlField(); + $element = new \SimpleXMLElement($xml); + + $this->assertTrue($field->setup($element, '')); + $this->assertEquals($expected, $field->__get('relative')); + } + + /** + * Data provider for testSetupRelative + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataSetupRelative(): array + { + return [ + 'default' => [false, ''], + 'relative_true' => [true, ''], + 'relative_false' => [false, ''], + 'relative_1' => [true, ''], + 'relative_0' => [false, ''], + 'relative_keyword' => [true, ''], + ]; + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/UrlFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/UrlFilterTest.php new file mode 100644 index 0000000000000..4f63313ea8086 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/UrlFilterTest.php @@ -0,0 +1,105 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Form\Filter\UrlFilter; +use Joomla\CMS\Uri\Uri; +use Joomla\Tests\Unit\UnitTestCase; + +/** + * Test class for UrlFilter. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +class UrlFilterTest extends UnitTestCase +{ + /** + * Prepares the environment for each test. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function setUp(): void + { + parent::setUp(); + Uri::reset(); + + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; + $_SERVER['PHP_SELF'] = '/index.php'; + $_SERVER['REQUEST_URI'] = '/index.php'; + } + + /** + * Cleans up the environment after each test. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function tearDown(): void + { + Uri::reset(); + unset($_SERVER['HTTP_HOST'], $_SERVER['SCRIPT_NAME'], $_SERVER['PHP_SELF'], $_SERVER['REQUEST_URI']); + parent::tearDown(); + } + + /** + * Test data for the testFilter method + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function dataFilter(): array + { + $xmlUrl = new \SimpleXMLElement(''); + $xmlUrlRelative = new \SimpleXMLElement(''); + $xmlUrlRelativeNumeric = new \SimpleXMLElement(''); + $xmlUrlRelativeKeyword = new \SimpleXMLElement(''); + $xmlUrlNotRelative = new \SimpleXMLElement(''); + $xmlUrlNotRelativeNumeric = new \SimpleXMLElement(''); + + return [ + 'empty value' => [false, $xmlUrl, ''], + 'external url with scheme' => ['https://example.com', $xmlUrl, 'https://example.com'], + 'external url relative=false' => ['https://example.com', $xmlUrlNotRelative, 'https://example.com'], + 'no scheme default' => ['http://example.com', $xmlUrl, 'example.com'], + 'no scheme relative=false' => ['http://example.com', $xmlUrlNotRelative, 'example.com'], + 'no scheme relative=0' => ['http://example.com', $xmlUrlNotRelativeNumeric, 'example.com'], + 'relative path relative=true' => ['/relative/path', $xmlUrlRelative, '/relative/path'], + 'relative path relative=1' => ['/relative/path', $xmlUrlRelativeNumeric, '/relative/path'], + 'relative path relative=name' => ['/relative/path', $xmlUrlRelativeKeyword, '/relative/path'], + ]; + } + + /** + * Tests UrlFilter::filter + * + * @param mixed $expected The expected filtered result + * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag + * @param string $value The value to filter + * + * @return void + * + * @since __DEPLOY_VERSION__ + * @dataProvider dataFilter + */ + public function testFilter($expected, \SimpleXMLElement $element, string $value): void + { + $filter = new UrlFilter(); + + $this->assertEquals($expected, $filter->filter(clone $element, $value)); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Rule/UrlRuleTest.php b/tests/Unit/Libraries/Cms/Form/Rule/UrlRuleTest.php index e5ae584e3c929..f7aef4765daef 100644 --- a/tests/Unit/Libraries/Cms/Form/Rule/UrlRuleTest.php +++ b/tests/Unit/Libraries/Cms/Form/Rule/UrlRuleTest.php @@ -49,6 +49,34 @@ public function dataTest(): array required="true" relative="true" />'); + $xmlrelativenumeric = new \SimpleXMLElement(''); + $xmlrelativename = new \SimpleXMLElement(''); + $xmlnotrelative = new \SimpleXMLElement(''); + $xmlnotrelativenumeric = new \SimpleXMLElement(''); return [ [true, $xml, 'https://example.com'], @@ -62,6 +90,11 @@ public function dataTest(): array [true, $xmlschemes, 'https://example.com'], [true, $xmlrelative, 'https://example.com'], [true, $xmlrelative, '/relative/path'], + [true, $xmlrelativenumeric, '/relative/path'], + [true, $xmlrelativename, '/relative/path'], + [false, $xmlnotrelative, '/relative/path'], + [true, $xmlnotrelative, 'https://example.com'], + [false, $xmlnotrelativenumeric, '/relative/path'], [false, $xml, ''], [false, $xml, 'invalid://example.com'], [false, $xml, 'example.com'],