diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php
index c745efd..80f4468 100644
--- a/src/Entity/Channel.php
+++ b/src/Entity/Channel.php
@@ -96,12 +96,12 @@ class Channel extends HasValidator implements SRSSElement
* @validate hour
* @format hour
*/
- protected ?string $skipHours = null;
+ protected ?array $skipHours = null;
/**
* @validate day
* @format day
*/
- protected ?string $skipDays = null;
+ protected ?array $skipDays = null;
/**
* @return bool
diff --git a/src/Entity/Item.php b/src/Entity/Item.php
index 8e43f6a..a50fe93 100644
--- a/src/Entity/Item.php
+++ b/src/Entity/Item.php
@@ -12,6 +12,17 @@ use Shikiryu\SRSS\Validator\Validator;
/**
* https://cyber.harvard.edu/rss/rss.html#hrelementsOfLtitemgt
+ * @property null|string title
+ * @property null|string link
+ * @property null|string description
+ * @property null|string author
+ * @property null|array category
+ * @property null|string comments
+ * @property null|Enclosure enclosure
+ * @property null|string guid
+ * @property null|string pubDate
+ * @property null|Source source
+ * @property array medias
*/
class Item extends HasValidator implements SRSSElement
{
diff --git a/src/Exception/InvalidPropertyException.php b/src/Exception/InvalidPropertyException.php
new file mode 100644
index 0000000..818f855
--- /dev/null
+++ b/src/Exception/InvalidPropertyException.php
@@ -0,0 +1,12 @@
+value = $child->nodeValue;
$this->doc->channel->category = $category;
+ } elseif ($child->nodeName === 'skipHours') {
+ foreach ($child->childNodes as $hour) {
+ if ($hour->nodeType === XML_ELEMENT_NODE) {
+ $this->doc->channel->skipHours = $hour->nodeValue;
+ }
+ }
+ } elseif ($child->nodeName === 'skipDays') {
+ foreach ($child->childNodes as $day) {
+ if ($day->nodeType === XML_ELEMENT_NODE) {
+ $this->doc->channel->skipDays = $day->nodeValue;
+ }
+ }
} else {
$this->doc->channel->{$child->nodeName} = $child->nodeValue;
}
diff --git a/src/SRSS.php b/src/SRSS.php
index 4296286..1d86f89 100644
--- a/src/SRSS.php
+++ b/src/SRSS.php
@@ -11,6 +11,7 @@ use Shikiryu\SRSS\Entity\Channel\Cloud;
use Shikiryu\SRSS\Entity\Channel\Image;
use Shikiryu\SRSS\Entity\Item;
use Shikiryu\SRSS\Exception\ChannelNotFoundInRSSException;
+use Shikiryu\SRSS\Exception\InvalidPropertyException;
use Shikiryu\SRSS\Exception\PropertyNotFoundException;
use Shikiryu\SRSS\Exception\SRSSException;
use Shikiryu\SRSS\Exception\UnreadableRSSException;
@@ -27,17 +28,18 @@ use Shikiryu\SRSS\Validator\Validator;
* @property null|string $managingEditor
* @property null|string $webMaster
* @property null|string $pubDate
- * @property null|string $lastBuildDate
+ * @property null|string $lastBuildDate
* @property null|Category[] $category
- * @property null|string $generator
- * @property null|string $docs
- * @property null|Cloud $cloud
- * @property null|string $ttl
- * @property null|Image $image
- * @property null|string $rating
- * @property null|string $textInput
- * @property null|string $skipHours
- * @property null|string $skipDays
+ * @property null|string $generator
+ * @property null|string $docs
+ * @property null|Cloud $cloud
+ * @property null|string $ttl
+ * @property null|Image $image
+ * @property null|string $rating
+ * @property null|string $textInput
+ * @property null|string $skipHours
+ * @property null|string $skipDays
+ * @property string|null $validated
*/
class SRSS implements Iterator
{
@@ -126,15 +128,16 @@ class SRSS implements Iterator
if (!property_exists(Channel::class, $name)) {
throw new PropertyNotFoundException(Channel::class, $name);
}
- if ((new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) {
- if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') {
- $this->channel->{$name} = $val;
- } else {
- $val = is_string($val) ? (new Formator())->formatValue($this->channel, $name, $val) : $val;
- $this->channel->{$name} = $val;
- }
+ if (!(new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) {
+ throw new InvalidPropertyException(get_class($this), $name, $val);
+ }
+ if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') {
+ $this->channel->{$name} = $val;
+ } else {
+ $val = is_string($val) ? (new Formator())->formatValue($this->channel, $name, $val) : $val;
+ $this->channel->{$name} = $val;
}
}
diff --git a/src/Validator/Formator.php b/src/Validator/Formator.php
index b81ced9..924ecd1 100644
--- a/src/Validator/Formator.php
+++ b/src/Validator/Formator.php
@@ -143,7 +143,7 @@ class Formator
return $check;
}
- return sprintf('', htmlspecialchars($check));
+ return sprintf('', $check);
}
/**
diff --git a/src/Validator/HasValidator.php b/src/Validator/HasValidator.php
index 5017501..8896080 100644
--- a/src/Validator/HasValidator.php
+++ b/src/Validator/HasValidator.php
@@ -2,6 +2,7 @@
namespace Shikiryu\SRSS\Validator;
+use Shikiryu\SRSS\Exception\InvalidPropertyException;
use Shikiryu\SRSS\Exception\PropertyNotFoundException;
use Shikiryu\SRSS\Exception\SRSSException;
use Shikiryu\SRSS\SRSSTools;
@@ -27,17 +28,19 @@ abstract class HasValidator
if (!property_exists(static::class, $name)) {
throw new PropertyNotFoundException(static::class, $name);
}
- if ((new Validator())->isValidValueForObjectProperty($this, $name, $val)) {
-
- if (SRSSTools::getPropertyType(static::class, $name) === 'array') {
- /** @var array $this->{$name} */
- $this->{$name}[] = $val;
- } else {
- $val = is_string($val) ? (new Formator())->formatValue($this, $name, $val) : $val;
- $this->{$name} = $val;
- }
+ if (!(new Validator())->isValidValueForObjectProperty($this, $name, $val)) {
+ throw new InvalidPropertyException(get_class($this), $name, $val);
}
+
+ if (SRSSTools::getPropertyType(static::class, $name) === 'array') {
+ /** @var array $this->{$name} */
+ $this->{$name}[] = $val;
+ } else {
+ $val = is_string($val) ? (new Formator())->formatValue($this, $name, $val) : $val;
+ $this->{$name} = $val;
+ }
+
}
/**
diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php
index 7bdd03e..f69700a 100644
--- a/src/Validator/Validator.php
+++ b/src/Validator/Validator.php
@@ -24,6 +24,7 @@ class Validator
*/
public function isValidValueForObjectProperty($object, $property, $value): bool
{
+ $this->object = $object;
try {
$property = $this->getReflectedProperty($object, $property);
} catch (ReflectionException) {
@@ -31,17 +32,19 @@ class Validator
}
$propertyAnnotations = $this->_getPropertyAnnotations($property);
- if (empty($value) && !in_array('required', $propertyAnnotations, true)) {
+ if (empty($value) && count(array_filter($propertyAnnotations, static fn($rule) => str_starts_with($rule, 'required'))) === 0) {
return true;
}
foreach ($propertyAnnotations as $propertyAnnotation) {
$annotation = explode(' ', $propertyAnnotation);
- $object->validated[$property->name] = $this->_validateProperty($annotation, $value);
+ if ($this->_validateProperty($annotation, $value) === false) {
+ return false;
+ }
}
- return count(array_filter($object->validated, static fn($v) => ($v !== null && $v === false))) === 0;
+ return true;
}
/**
@@ -69,9 +72,10 @@ class Validator
*/
public function isObjectValid($object): bool
{
- if (!$object->validated) {
+ $object->validated = [];
+// if (!$object->validated) {
$object = $this->validateObject($object);
- }
+// }
return !in_array(false, $object->validated, true);
}
@@ -91,7 +95,7 @@ class Validator
foreach ($properties as $property) {
$propertyValue = $object->{$property['name']};
- if (empty($propertyValue) && !in_array('required', $property['rules'], true)) {
+ if (empty($propertyValue) && count(array_filter($property['rules'], static fn($rule) => str_starts_with($rule, 'required'))) === 0) {
continue;
}
@@ -117,7 +121,7 @@ class Validator
$args_annotation = array_splice($annotation, 1);
- return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property, ...$args_annotation);
+ return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property, $args_annotation);
}
@@ -197,18 +201,38 @@ class Validator
private function _validateHour($value): bool
{
+ if (is_array($value)) {
+ foreach ($value as $val) {
+ if ($this->_validateHour($val) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
$options = [
'options' => [
- 'default' => 0,
'min_range' => 0,
'max_range' => 23
]
];
+
return filter_var($value, FILTER_VALIDATE_INT, $options) !== false;
}
private function _validateDay($value): bool
{
+ if (is_array($value)) {
+ foreach ($value as $val) {
+ if ($this->_validateDay($val) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
return in_array(
strtolower($value),
['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
@@ -251,4 +275,13 @@ class Validator
{
return filter_var($value, FILTER_VALIDATE_EMAIL);
}
+
+ private function _validateMax($value, array $max): bool
+ {
+ return $value <= current($max);
+ }
+ private function _validateMin($value, array $max): bool
+ {
+ return $value >= current($max);
+ }
}
\ No newline at end of file
diff --git a/tests/CompleteBuilderTest.php b/tests/CompleteBuilderTest.php
index f8bcb80..d210203 100644
--- a/tests/CompleteBuilderTest.php
+++ b/tests/CompleteBuilderTest.php
@@ -37,8 +37,8 @@ class CompleteBuilderTest extends TestCase
$image->title = 'title of image';
$rating = 'yes';
$textInput = 'ignore';
- $skipDays = 'monday';
- $skipHours = '8';
+ $skipDays = ['monday'];
+ $skipHours = ['8', '9'];
$srss = SRSS::create();
$srss->title = $title;
diff --git a/tests/FailingTest.php b/tests/FailingTest.php
new file mode 100644
index 0000000..7dacf65
--- /dev/null
+++ b/tests/FailingTest.php
@@ -0,0 +1,230 @@
+isValid(), var_export($rss->validated, true));
+ $rss->title = 'title'; // mandatory
+ self::assertFalse($rss->isValid(), var_export($rss->validated, true));
+ $rss->description = 'desc'; // mandatory
+ self::assertFalse($rss->isValid(), var_export($rss->validated, true));
+ $rss->link = 'https://example.org';
+ self::assertTrue($rss->isValid(), var_export($rss->validated, true));
+ }
+
+ public function testInvalidChannelLink(): void
+ {
+ $this->expectException(InvalidPropertyException::class);
+ $rss = SRSS::create();
+ $rss->title = 'title'; // mandatory
+ $rss->description = 'desc'; // mandatory
+ $rss->link = 'desc'; // mandatory but should be an url
+ }
+
+ public function testInvalidChannelLanguage(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->language = 'en-en'; // should be a valid language
+ }
+
+ public function testInvalidChannelCopyright(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->copyright = 'test'; // should not have html element
+ }
+
+ public function testInvalidChannelManagingEditor(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->managingEditor = 'test'; // should not have html element
+ }
+
+ public function testInvalidChannelWebmaster(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->webMaster = 'test'; // should not have html element
+ }
+
+ public function testInvalidChannelPubDate(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->pubDate = 'test'; // should be a valid date
+ }
+
+ public function testInvalidChannelLastBuildDate(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->lastBuildDate = 'test'; // should be a valid date
+ }
+
+ public function testInvalidChannelGenerator(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->generator = 'test'; // should not have html element
+ }
+
+ public function testInvalidChannelDocs(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->docs = 'desc'; //should be a url
+ }
+
+ public function testInvalidChannelTTL(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->ttl = 'desc'; // should be an int
+ }
+
+ public function testInvalidChannelSkipHours(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->skipHours = 'desc'; // should be an hour
+ }
+
+ public function testInvalidChannelSkipDays(): void
+ {
+ $rss = SRSS::create();
+ $this->expectException(InvalidPropertyException::class);
+ $rss->skipDays = 'desc'; // should be a day
+ }
+
+ public function testInvalidItemAuthor(): void
+ {
+ $item = new Shikiryu\SRSS\Entity\Item();
+ $this->expectException(InvalidPropertyException::class);
+ $item->author = 'test'; // should be an email
+ }
+
+ public function testInvalidItemComments(): void
+ {
+ $item = new Shikiryu\SRSS\Entity\Item();
+ $this->expectException(InvalidPropertyException::class);
+ $item->comments = 'test'; // should be an url
+ }
+
+ public function testInvalidItemMandatory(): void
+ {
+ $item = new Shikiryu\SRSS\Entity\Item();
+ $item->title = 'title';
+ self::assertTrue($item->isValid(), var_export($item->validated, true));
+ $item->description = 'desc';
+ $item->title = null;
+ self::assertTrue($item->isValid(), var_export($item->validated, true));
+
+ $this->expectException(InvalidPropertyException::class);
+ $item->title = null;
+ $item->description = null;
+ }
+
+ public function testChannelCategoryDomain(): void
+ {
+ $category = new Category();
+ $this->expectException(InvalidPropertyException::class);
+ $category->domain = 'test';
+ }
+
+ public function testChannelCloudPort(): void
+ {
+ $cloud = new Cloud();
+ $this->expectException(InvalidPropertyException::class);
+ $cloud->port = 'test';
+ }
+
+ public function testChannelImageUrl(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->url = 'test';
+ }
+
+ public function testChannelImageTitle(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->title = 'test';
+ }
+
+ public function testChannelImageLink(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->link = 'test';
+ }
+
+ public function testChannelImageWidthType(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->width = 'test';
+ }
+
+ public function testChannelImageWidthMax(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->width = '150';
+ }
+
+ public function testChannelImageHeightType(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->height = 'test';
+ }
+
+ public function testChannelImageHeightMax(): void
+ {
+ $image = new Image();
+ $this->expectException(InvalidPropertyException::class);
+ $image->height = '500';
+ }
+
+ public function testItemEnclosureUrl(): void
+ {
+ $enclosure = new Enclosure();
+ $this->expectException(InvalidPropertyException::class);
+ $enclosure->url = 'test';
+ }
+
+ public function testItemEnclosureLength(): void
+ {
+ $enclosure = new Enclosure();
+ $this->expectException(InvalidPropertyException::class);
+ $enclosure->length = 'test';
+ }
+
+ public function testItemSourceUrl()
+ {
+ $source = new Source();
+ $this->expectException(InvalidPropertyException::class);
+ $source->url = 'test';
+ }
+
+ public function testItemSourceValue()
+ {
+ $source = new Source();
+ $this->expectException(InvalidPropertyException::class);
+ $source->value = 'test';
+ }
+}