Passed
Push — master ( 99d4dc...7294aa )
by Jakub
01:47
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 Nette\Utils\Arrays;
8
9
/**
10
 * RSS Channel Generator
11
 *
12
 * @author Jakub Konečný
13
 * @property callable $dataSource
14
 * @property int $shortenDescription
15
 * @property string $dateTimeFormat
16
 * @property string $generator
17
 * @property string $docs
18
 * @property string $template
19
 * @method void onBeforeGenerate(Generator $generator, array $info)
20
 * @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...
21
 * @method void onAfterGenerate(Generator $generator, array $info)
22
 */
23 1
final class Generator {
24 1
  use \Nette\SmartObject;
25
26
  /** @var string */
27
  protected $dateTimeFormat = "r";
28
  /** @var callable|null */
29
  protected $dataSource = null;
30
  /** @var int */
31
  protected $shortenDescription = 150;
32
  /** @var string */
33
  protected $generator = "Nexendrie RSS";
34
  /** @var string */
35
  protected $docs = "http://www.rssboard.org/rss-specification";
36
  /** @var string */
37
  protected $template = __DIR__ . "/template.xml";
38
  /** @var callable[] */
39
  public $onBeforeGenerate = [];
40
  /** @var callable[] */
41
  public $onAddItem = [];
42
  /** @var callable[] */
43
  public $onAfterGenerate = [];
44
45
  public function setDataSource(callable $dataSource): void {
46 1
    $this->dataSource = $dataSource;
47 1
  }
48
  
49
  public function getShortenDescription(): int {
50 1
    return $this->shortenDescription;
51
  }
52
  
53
  public function setShortenDescription(int $value): void {
54 1
    $this->shortenDescription = $value;
55 1
  }
56
  
57
  public function getDateTimeFormat(): string {
58 1
    return $this->dateTimeFormat;
59
  }
60
  
61
  public function setDateTimeFormat(string $format): void {
62 1
    $this->dateTimeFormat = $format;
63 1
  }
64
65
  public function getGenerator(): string {
66 1
    return $this->generator;
67
  }
68
69
  public function setGenerator(string $generator): void {
70 1
    $this->generator = $generator;
71 1
  }
72
73
  public function getDocs(): string {
74 1
    return $this->docs;
75
  }
76
77
  public function setDocs(string $docs): void {
78 1
    $this->docs = $docs;
79 1
  }
80
  
81
  public function getTemplate(): string {
82 1
    return $this->template;
83
  }
84
  
85
  /**
86
   * @throws \RuntimeException
87
   */
88
  public function setTemplate(string $template): void {
89 1
    if(!is_file($template)) {
90 1
      throw new \RuntimeException("File $template does not exist.");
91
    }
92 1
    $this->template = $template;
93 1
  }
94
  
95
  /**
96
   * @throws InvalidStateException
97
   * @throws \InvalidArgumentException
98
   */
99
  protected function getData(): Collection {
100 1
    if(is_null($this->dataSource)) {
0 ignored issues
show
introduced by
The condition is_null($this->dataSource) is always false.
Loading history...
101 1
      throw new InvalidStateException("Data source for RSS generator is not set.");
102
    }
103 1
    $items = call_user_func($this->dataSource);
104 1
    if(!$items instanceof Collection) {
105 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...
106
    }
107 1
    return $items;
108
  }
109
  
110
  protected function shortenDescription(string $description): string {
111 1
    if($this->shortenDescription < 1) {
112 1
      return $description;
113
    }
114 1
    $originalDescription = $description;
115 1
    $description = substr($description, 0, $this->shortenDescription);
116 1
    if($description !== $originalDescription) {
117 1
      $description .= "...";
118
    }
119 1
    return $description;
120
  }
121
  
122
  protected function writeProperty(\SimpleXMLElement &$channel, array $info, string $property): void {
123 1
    $value = Arrays::get($info, $property, "");
124 1
    if($value !== "") {
125 1
      $channel->channel->{$property}[0][0] = $value;
126
    }
127 1
  }
128
129
  protected function writeItemProperty(\SimpleXMLElement &$element, RssChannelItem $item, string $property, callable $callback = null): void {
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 142 characters
Loading history...
130 1
    if(isset($item->$property) AND $item->$property !== "") {
131 1
      $value = $item->$property;
132 1
      if(!is_null($callback)) {
133 1
        $value = $callback($value);
134
      }
135 1
      $element->addChild($property, $value);
136
    }
137 1
  }
138
139
  /**
140
   * @param Category[] $categories
141
   */
142
  protected function writeCategories(\SimpleXMLElement &$element, array $categories): void {
143 1
    foreach($categories as $category) {
144 1
      $categoryElement = $element->addChild("category", $category->identifier);
145 1
      if($category->domain !== "") {
146 1
        $categoryElement->addAttribute("domain", $category->domain);
147
      }
148
    }
149 1
  }
150
151
  protected function configureOptions(OptionsResolver $resolver): void {
152 1
    $resolver->setRequired(["title", "description", "link", "lastBuildDate", ]);
153 1
    $resolver->setAllowedTypes("title", "string");
154 1
    $resolver->setAllowedTypes("description", "string");
155 1
    $resolver->setAllowedTypes("link", "string");
156 1
    $resolver->setAllowedTypes("lastBuildDate", "callable");
157 1
    $resolver->setDefault("lastBuildDate", "time");
158 1
    $resolver->setDefined([
159 1
      "language", "copyright", "managingEditor", "webMaster", "ttl",  "pubDate", "rating", "categories",
160
    ]);
161 1
    $resolver->setAllowedTypes("language", "string");
162 1
    $resolver->setAllowedTypes("copyright", "string");
163 1
    $resolver->setAllowedTypes("managingEditor", "string");
164 1
    $resolver->setAllowedTypes("webMaster", "string");
165 1
    $resolver->setAllowedTypes("ttl", "int");
166 1
    $resolver->setAllowedValues("ttl", function(int $value) {
167 1
      return ($value >= 0);
168 1
    });
169 1
    $resolver->setAllowedTypes("pubDate", "callable");
170 1
    $resolver->setAllowedTypes("rating", "string");
171 1
    $resolver->setAllowedTypes("categories", Category::class . "[]");
172 1
  }
173
  
174
  /**
175
   * @throws InvalidStateException
176
   * @throws \InvalidArgumentException
177
   */
178
  public function generate(array $info): string {
179 1
    $this->onBeforeGenerate($this, $info);
180 1
    $items = $this->getData();
181 1
    $resolver = new OptionsResolver();
182 1
    $this->configureOptions($resolver);
183 1
    $info = $resolver->resolve($info);
184 1
    $lastBuildDate = call_user_func($info["lastBuildDate"]);
185 1
    if(!is_int($lastBuildDate)) {
186 1
      throw new \InvalidArgumentException("Callback for last build date for RSS generator has to return integer.");
187
    }
188
    /** @var \SimpleXMLElement $channel */
189 1
    $channel = simplexml_load_file($this->template);
190 1
    $channel->channel->lastBuildDate[0][0] = date($this->dateTimeFormat, $lastBuildDate);
191 1
    if(isset($info["pubDate"])) {
192 1
      $pubDate = call_user_func($info["pubDate"]);
193 1
      if(!is_int($pubDate)) {
194 1
        throw new \InvalidArgumentException("Callback for pub date for RSS generator has to return integer.");
195
      }
196 1
      $channel->channel->addChild("pubDate", date($this->dateTimeFormat, $pubDate));
197
    }
198 1
    $this->writeProperty($channel, $info, "link");
199 1
    $this->writeProperty($channel, $info, "title");
200 1
    $this->writeProperty($channel, $info, "description");
201 1
    $this->writeProperty($channel, $info, "language");
202 1
    $this->writeProperty($channel, $info, "copyright");
203 1
    $this->writeProperty($channel, $info, "managingEditor");
204 1
    $this->writeProperty($channel, $info, "webMaster");
205 1
    $this->writeProperty($channel, $info, "ttl");
206 1
    if($this->generator !== "") {
207 1
      $channel->channel->generator[0][0] = $this->generator;
208
    }
209 1
    if($this->docs !== "") {
210 1
      $channel->channel->docs[0][0] = $this->docs;
211
    }
212 1
    $this->writeProperty($channel, $info, "generator");
213 1
    $this->writeProperty($channel, $info, "docs");
214 1
    $this->writeProperty($channel, $info, "rating");
215 1
    $this->writeCategories($channel->channel, Arrays::get($info, "categories", []));
216
    /** @var RssChannelItem $item */
217 1
    foreach($items as $item) {
218
      /** @var \SimpleXMLElement $i */
219 1
      $i = $channel->channel->addChild("item");
220 1
      $this->writeItemProperty($i, $item, "title");
221 1
      $this->writeItemProperty($i, $item, "link");
222 1
      $this->writeItemProperty($i, $item, "pubDate", function($value) {
223 1
        return date($this->dateTimeFormat, $value);
224 1
      });
225 1
      $this->writeItemProperty($i, $item, "description", [$this, "shortenDescription"]);
226 1
      $this->writeItemProperty($i, $item, "author");
227 1
      $this->writeItemProperty($i, $item, "comments");
228 1
      $this->writeItemProperty($i, $item, "guid");
229 1
      $this->writeCategories($i, $item->categories->toArray());
230 1
      $this->onAddItem($this, $channel, $item, $i);
231
    }
232 1
    $this->onAfterGenerate($this, $info);
233 1
    $dom = new \DOMDocument();
234 1
    $dom->preserveWhiteSpace = false;
235 1
    $dom->formatOutput = true;
236 1
    $dom->loadXML($channel->asXML());
237 1
    return $dom->saveXML();
238
  }
239
  
240
  /**
241
   * @throws InvalidStateException
242
   * @throws \InvalidArgumentException
243
   */
244
  public function response(array $info): RssResponse {
245 1
    return new RssResponse($this->generate($info));
246
  }
247
}
248
?>