Completed
Push — master ( b09ecd...ff3168 )
by Jakub
02:47
created

Generator::generate()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 5

Importance

Changes 16
Bugs 2 Features 3
Metric Value
cc 5
eloc 23
c 16
b 2
f 3
nc 16
nop 1
dl 0
loc 31
ccs 23
cts 23
cp 1
crap 5
rs 9.2408
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-write 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($this->dataSource === null) {
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
    if(!$value instanceof IXmlConvertible) {
118 1
      $value = new GenericElement($property, $value);
119
    }
120 1
    $value->appendToXml($channel->channel);
121 1
  }
122
123
  protected function configureOptions(OptionsResolver $resolver): void {
124 1
    $resolver->setRequired(["title", "description", "link", "lastBuildDate", ]);
125 1
    $resolver->setAllowedTypes("title", "string");
126 1
    $resolver->setAllowedTypes("description", "string");
127 1
    $resolver->setAllowedTypes("link", "string");
128 1
    $resolver->setAllowedTypes("lastBuildDate", "callable");
129 1
    $resolver->setDefault("lastBuildDate", "time");
130 1
    $resolver->setNormalizer("lastBuildDate", function(Options $options, callable $value) {
131 1
      $value = call_user_func($value);
132 1
      if(!is_int($value)) {
133 1
        throw new \InvalidArgumentException("Callback for last build date for RSS generator has to return integer.");
134
      }
135 1
      $value = date($this->dateTimeFormat, $value);
136 1
      return new GenericElement("lastBuildDate", $value);
137 1
    });
138 1
    $resolver->setDefined([
139 1
      "language", "copyright", "managingEditor", "webMaster", "ttl",  "pubDate", "rating", "categories", "skipDays",
140
      "skipHours", "image", "cloud", "textInput",
141
    ]);
142 1
    $resolver->setAllowedTypes("language", "string");
143 1
    $resolver->setAllowedTypes("copyright", "string");
144 1
    $resolver->setAllowedTypes("managingEditor", "string");
145 1
    $resolver->setAllowedTypes("webMaster", "string");
146 1
    $resolver->setAllowedTypes("ttl", "int");
147 1
    $resolver->setAllowedValues("ttl", function(int $value) {
148 1
      return ($value >= 0);
149 1
    });
150 1
    $resolver->setAllowedTypes("pubDate", "callable");
151 1
    $resolver->setNormalizer("pubDate", function(Options $options, callable $value) {
152 1
      $value = call_user_func($value);
153 1
      if(!is_int($value)) {
154 1
        throw new \InvalidArgumentException("Callback for pub date for RSS generator has to return integer.");
155
      }
156 1
      $value = date($this->dateTimeFormat, $value);
157 1
      return new GenericElement("pubDate", $value);
158 1
    });
159 1
    $resolver->setAllowedTypes("rating", "string");
160 1
    $resolver->setAllowedTypes("categories", Category::class . "[]");
161 1
    $resolver->setNormalizer("categories", function(Options $options, array $value) {
162 1
      return CategoriesCollection::fromArray($value);
163 1
    });
164 1
    $resolver->setAllowedTypes("skipDays", "string[]");
165 1
    $resolver->setAllowedValues("skipDays", function(array $value) {
166 1
      $allowedValues = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", ];
167 1
      return Arrays::every($value, function(string $value) use($allowedValues) {
168 1
        return in_array($value, $allowedValues, true);
169 1
      });
170 1
    });
171 1
    $resolver->setNormalizer("skipDays", function(Options $options, array $value) {
172 1
      return new SkipDaysCollection($value);
173 1
    });
174 1
    $resolver->setAllowedTypes("skipHours", "int[]");
175 1
    $resolver->setAllowedValues("skipHours", function(array $value) {
176 1
      return Arrays::every($value, function(int $value) {
177 1
        return Numbers::isInRange($value, 0, 23);
178 1
      });
179 1
    });
180 1
    $resolver->setNormalizer("skipHours", function(Options $options, array $value) {
181 1
      return new SkipHoursCollection($value);
182 1
    });
183 1
    $resolver->setAllowedTypes("image", Image::class);
184 1
    $resolver->setAllowedTypes("cloud", Cloud::class);
185 1
    $resolver->setAllowedTypes("textInput", TextInput::class);
186 1
  }
187
  
188
  /**
189
   * @throws InvalidStateException
190
   * @throws \InvalidArgumentException
191
   */
192
  public function generate(array $info): string {
193 1
    $this->onBeforeGenerate($this, $info);
194 1
    $items = $this->getData();
195 1
    $resolver = new OptionsResolver();
196 1
    $this->configureOptions($resolver);
197 1
    $info = $resolver->resolve($info);
198
    /** @var \SimpleXMLElement $channel */
199 1
    $channel = simplexml_load_file($this->template);
200 1
    $properties = $resolver->getDefinedOptions();
201 1
    foreach($properties as $property) {
202 1
      $this->writeProperty($channel, $info, $property);
203
    }
204 1
    if($this->generator !== "") {
205 1
      $channel->channel->generator = $this->generator;
206
    }
207 1
    if($this->docs !== "") {
208 1
      $channel->channel->docs = $this->docs;
209
    }
210
    /** @var RssChannelItem $item */
211 1
    foreach($items as $item) {
212
      /** @var \SimpleXMLElement $i */
213 1
      $i = $channel->channel->addChild("item");
214 1
      $item->toXml($i, $this);
215 1
      $this->onAddItem($this, $channel, $item, $i);
216
    }
217 1
    $this->onAfterGenerate($this, $info);
218 1
    $dom = new \DOMDocument();
219 1
    $dom->preserveWhiteSpace = false;
220 1
    $dom->formatOutput = true;
221 1
    $dom->loadXML($channel->asXML());
222 1
    return $dom->saveXML();
223
  }
224
  
225
  /**
226
   * @throws InvalidStateException
227
   * @throws \InvalidArgumentException
228
   */
229
  public function response(array $info): RssResponse {
230 1
    return new RssResponse($this->generate($info));
231
  }
232
}
233
?>