Passed
Push — master ( e9f6a4...7ddfc7 )
by Jakub
01:51
created

Generator::writeCategories()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 4
nc 3
nop 2
dl 0
loc 5
ccs 4
cts 4
cp 1
c 0
b 0
f 0
cc 3
crap 3
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Nexendrie\Rss;
5
6
use Symfony\Component\OptionsResolver\OptionsResolver;
7
use Symfony\Component\OptionsResolver\Options;
8
use Nette\Utils\Arrays;
9
use Nexendrie\Utils\Numbers;
10
11
/**
12
 * RSS Channel Generator
13
 *
14
 * @author Jakub Konečný
15
 * @property callable $dataSource
16
 * @property int $shortenDescription
17
 * @property string $dateTimeFormat
18
 * @property string $generator
19
 * @property string $docs
20
 * @property string $template
21
 * @method void onBeforeGenerate(Generator $generator, array $info)
22
 * @method void onAddItem(Generator $generator, \SimpleXMLElement $channel, RssChannelItem $itemDefinition, \SimpleXMLElement $item)
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 132 characters
Loading history...
23
 * @method void onAfterGenerate(Generator $generator, array $info)
24
 */
25 1
final class Generator {
26 1
  use \Nette\SmartObject;
27
28
  /** @var string */
29
  protected $dateTimeFormat = "r";
30
  /** @var callable|null */
31
  protected $dataSource = null;
32
  /** @var int */
33
  protected $shortenDescription = 150;
34
  /** @var string */
35
  protected $generator = "Nexendrie RSS";
36
  /** @var string */
37
  protected $docs = "http://www.rssboard.org/rss-specification";
38
  /** @var string */
39
  protected $template = __DIR__ . "/template.xml";
40
  /** @var callable[] */
41
  public $onBeforeGenerate = [];
42
  /** @var callable[] */
43
  public $onAddItem = [];
44
  /** @var callable[] */
45
  public $onAfterGenerate = [];
46
47
  public function setDataSource(callable $dataSource): void {
48 1
    $this->dataSource = $dataSource;
49 1
  }
50
  
51
  public function getShortenDescription(): int {
52 1
    return $this->shortenDescription;
53
  }
54
  
55
  public function setShortenDescription(int $value): void {
56 1
    $this->shortenDescription = $value;
57 1
  }
58
  
59
  public function getDateTimeFormat(): string {
60 1
    return $this->dateTimeFormat;
61
  }
62
  
63
  public function setDateTimeFormat(string $format): void {
64 1
    $this->dateTimeFormat = $format;
65 1
  }
66
67
  public function getGenerator(): string {
68 1
    return $this->generator;
69
  }
70
71
  public function setGenerator(string $generator): void {
72 1
    $this->generator = $generator;
73 1
  }
74
75
  public function getDocs(): string {
76 1
    return $this->docs;
77
  }
78
79
  public function setDocs(string $docs): void {
80 1
    $this->docs = $docs;
81 1
  }
82
  
83
  public function getTemplate(): string {
84 1
    return $this->template;
85
  }
86
  
87
  /**
88
   * @throws \RuntimeException
89
   */
90
  public function setTemplate(string $template): void {
91 1
    if(!is_file($template) OR !is_readable($template)) {
92 1
      throw new \RuntimeException("File $template does not exist or is not readable.");
93
    }
94 1
    $this->template = $template;
95 1
  }
96
  
97
  /**
98
   * @throws InvalidStateException
99
   * @throws \InvalidArgumentException
100
   */
101
  protected function getData(): Collection {
102 1
    if(is_null($this->dataSource)) {
0 ignored issues
show
introduced by
The condition is_null($this->dataSource) is always false.
Loading history...
103 1
      throw new InvalidStateException("Data source for RSS generator is not set.");
104
    }
105 1
    $items = call_user_func($this->dataSource);
106 1
    if(!$items instanceof Collection) {
107 1
      throw new \InvalidArgumentException("Callback for data source for RSS generator has to return an instance of  " . Collection::class . ".");
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 145 characters
Loading history...
108
    }
109 1
    return $items;
110
  }
111
  
112
  protected function writeProperty(\SimpleXMLElement &$channel, array $info, string $property): void {
113 1
    $value = Arrays::get($info, $property, "");
114 1
    if($value === "") {
115 1
      return;
116
    }
117 1
    switch($property) {
118 1
      case "skipDays":
119 1
        $element = $channel->channel->addChild("skipDays");
120 1
        array_walk($value, function(string $value) use($element) {
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $array of array_walk() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
        array_walk(/** @scrutinizer ignore-type */ $value, function(string $value) use($element) {
Loading history...
121 1
          $element->addChild("day", $value);
122 1
        });
123 1
        break;
124 1
      case "skipHours":
125 1
        $element = $channel->channel->addChild("skipHours");
126 1
        array_walk($value, function(string $value) use($element) {
127 1
          $element->addChild("hour", $value);
128 1
        });
129 1
        break;
130
      default:
131 1
        $channel->channel->{$property} = $value;
132 1
        break;
133
    }
134 1
  }
135
136
  protected function configureOptions(OptionsResolver $resolver): void {
137 1
    $resolver->setRequired(["title", "description", "link", "lastBuildDate", ]);
138 1
    $resolver->setAllowedTypes("title", "string");
139 1
    $resolver->setAllowedTypes("description", "string");
140 1
    $resolver->setAllowedTypes("link", "string");
141 1
    $resolver->setAllowedTypes("lastBuildDate", "callable");
142 1
    $resolver->setDefault("lastBuildDate", "time");
143 1
    $resolver->setDefined([
144 1
      "language", "copyright", "managingEditor", "webMaster", "ttl",  "pubDate", "rating", "categories", "skipDays",
145
      "skipHours",
146
    ]);
147 1
    $resolver->setAllowedTypes("language", "string");
148 1
    $resolver->setAllowedTypes("copyright", "string");
149 1
    $resolver->setAllowedTypes("managingEditor", "string");
150 1
    $resolver->setAllowedTypes("webMaster", "string");
151 1
    $resolver->setAllowedTypes("ttl", "int");
152 1
    $resolver->setAllowedValues("ttl", function(int $value) {
153 1
      return ($value >= 0);
154 1
    });
155 1
    $resolver->setAllowedTypes("pubDate", "callable");
156 1
    $resolver->setAllowedTypes("rating", "string");
157 1
    $resolver->setAllowedTypes("categories", Category::class . "[]");
158 1
    $resolver->setAllowedTypes("skipDays", "string[]");
159 1
    $resolver->setAllowedValues("skipDays", function(array $value) {
160 1
      $allowedValues = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", ];
161 1
      return Arrays::every($value, function(string $value) use($allowedValues) {
162 1
        return in_array($value, $allowedValues, true);
163 1
      });
164 1
    });
165 1
    $resolver->setNormalizer("skipDays", function(Options $options, array $value) {
0 ignored issues
show
introduced by
The method parameter $options is never used
Loading history...
166 1
      return array_unique($value);
167 1
    });
168 1
    $resolver->setAllowedTypes("skipHours", "int[]");
169 1
    $resolver->setAllowedValues("skipHours", function(array $value) {
170 1
      return Arrays::every($value, function(int $value) {
171 1
        return Numbers::isInRange($value, 0, 23);
172 1
      });
173 1
    });
174 1
    $resolver->setNormalizer("skipHours", function(Options $options, array $value) {
0 ignored issues
show
introduced by
The method parameter $options is never used
Loading history...
175 1
      array_walk($value, function(int &$value) {
176 1
        if($value < 10) {
177 1
          $value = "0" . (string) $value;
178
        }
179 1
        $value = (string) $value;
180 1
      });
181 1
      return array_unique($value);
182 1
    });
183 1
  }
184
  
185
  /**
186
   * @throws InvalidStateException
187
   * @throws \InvalidArgumentException
188
   */
189
  public function generate(array $info): string {
190 1
    $this->onBeforeGenerate($this, $info);
191 1
    $items = $this->getData();
192 1
    $resolver = new OptionsResolver();
193 1
    $this->configureOptions($resolver);
194 1
    $info = $resolver->resolve($info);
195 1
    $lastBuildDate = call_user_func($info["lastBuildDate"]);
196 1
    if(!is_int($lastBuildDate)) {
197 1
      throw new \InvalidArgumentException("Callback for last build date for RSS generator has to return integer.");
198
    }
199
    /** @var \SimpleXMLElement $channel */
200 1
    $channel = simplexml_load_file($this->template);
201 1
    $channel->channel->lastBuildDate = date($this->dateTimeFormat, $lastBuildDate);
202 1
    if(isset($info["pubDate"])) {
203 1
      $pubDate = call_user_func($info["pubDate"]);
204 1
      if(!is_int($pubDate)) {
205 1
        throw new \InvalidArgumentException("Callback for pub date for RSS generator has to return integer.");
206
      }
207 1
      $channel->channel->addChild("pubDate", date($this->dateTimeFormat, $pubDate));
208
    }
209 1
    $this->writeProperty($channel, $info, "link");
210 1
    $this->writeProperty($channel, $info, "title");
211 1
    $this->writeProperty($channel, $info, "description");
212 1
    $this->writeProperty($channel, $info, "language");
213 1
    $this->writeProperty($channel, $info, "copyright");
214 1
    $this->writeProperty($channel, $info, "managingEditor");
215 1
    $this->writeProperty($channel, $info, "webMaster");
216 1
    $this->writeProperty($channel, $info, "ttl");
217 1
    $this->writeProperty($channel, $info, "skipDays");
218 1
    $this->writeProperty($channel, $info, "skipHours");
219 1
    if($this->generator !== "") {
220 1
      $channel->channel->generator = $this->generator;
221
    }
222 1
    if($this->docs !== "") {
223 1
      $channel->channel->docs = $this->docs;
224
    }
225 1
    $this->writeProperty($channel, $info, "rating");
226 1
    $categories =  Arrays::get($info, "categories", []);
227 1
    array_walk($categories, function(Category $value) use($channel) {
228 1
      $value->appendToXml($channel->channel);
229 1
    });
230
    /** @var RssChannelItem $item */
231 1
    foreach($items as $item) {
232
      /** @var \SimpleXMLElement $i */
233 1
      $i = $channel->channel->addChild("item");
234 1
      $item->toXml($i, $this);
235 1
      $this->onAddItem($this, $channel, $item, $i);
236
    }
237 1
    $this->onAfterGenerate($this, $info);
238 1
    $dom = new \DOMDocument();
239 1
    $dom->preserveWhiteSpace = false;
240 1
    $dom->formatOutput = true;
241 1
    $dom->loadXML($channel->asXML());
242 1
    return $dom->saveXML();
243
  }
244
  
245
  /**
246
   * @throws InvalidStateException
247
   * @throws \InvalidArgumentException
248
   */
249
  public function response(array $info): RssResponse {
250 1
    return new RssResponse($this->generate($info));
251
  }
252
}
253
?>