Completed
Push — master ( d8c1bc...81b29b )
by Jakub
02:46
created

Generator.php$0 ➔ generate()   B

Complexity

Conditions 9

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
dl 0
loc 40
ccs 29
cts 29
cp 1
crap 9
rs 7.7244
1
<?php
2
declare(strict_types=1);
3
4
namespace Nexendrie\Rss;
5
6
use Nexendrie\Rss\Extensions\RssCore;
7
use Symfony\Component\OptionsResolver\OptionsResolver;
8
use Nette\Utils\Arrays;
9
10
/**
11
 * RSS Channel Generator
12
 *
13
 * @author Jakub Konečný
14
 * @property-write callable $dataSource
15
 * @property int $shortenDescription
16
 * @property string $dateTimeFormat
17
 * @property string $generator
18
 * @property string $docs
19
 * @property string $template
20
 * @property \Nexendrie\Utils\Collection|IRssExtension[] $extensions
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
  private const NAMESPACE_ATTRIBUTE_HACK = "__extension_namespace__";
29
30
  /** @var string */
31
  protected $dateTimeFormat = "r";
32
  /** @var callable|null */
33
  protected $dataSource = null;
34
  /** @var int */
35
  protected $shortenDescription = 150;
36
  /** @var string */
37
  protected $generator = "Nexendrie RSS";
38
  /** @var string */
39
  protected $docs = "http://www.rssboard.org/rss-specification";
40
  /** @var string */
41
  protected $template = __DIR__ . "/template.xml";
42
  /** @var \Nexendrie\Utils\Collection|IRssExtension[] */
43
  protected $extensions;
44
  /** @var callable[] */
45
  public $onBeforeGenerate = [];
46
  /** @var callable[] */
47
  public $onAddItem = [];
48
  /** @var callable[] */
49
  public $onAfterGenerate = [];
50
51
  public function __construct() {
52 1
    $this->extensions = new class extends \Nexendrie\Utils\Collection {
53
      /** @var string */
54
      protected $class = IRssExtension::class;
55
    };
56 1
    $this->extensions[] = new RssCore();
57 1
  }
58
59
  protected function setDataSource(callable $dataSource): void {
60 1
    $this->dataSource = $dataSource;
61 1
  }
62
63
  protected function getShortenDescription(): int {
64 1
    return $this->shortenDescription;
65
  }
66
67
  protected function setShortenDescription(int $value): void {
68 1
    $this->shortenDescription = $value;
69 1
  }
70
71
  protected function getDateTimeFormat(): string {
72 1
    return $this->dateTimeFormat;
73
  }
74
75
  protected function setDateTimeFormat(string $format): void {
76 1
    $this->dateTimeFormat = $format;
77 1
  }
78
79
  protected function getGenerator(): string {
80 1
    return $this->generator;
81
  }
82
83
  protected function setGenerator(string $generator): void {
84 1
    $this->generator = $generator;
85 1
  }
86
87
  protected function getDocs(): string {
88 1
    return $this->docs;
89
  }
90
91
  protected function setDocs(string $docs): void {
92 1
    $this->docs = $docs;
93 1
  }
94
95
  protected function getTemplate(): string {
96 1
    return $this->template;
97
  }
98
  
99
  /**
100
   * @throws \RuntimeException
101
   */
102
  protected function setTemplate(string $template): void {
103 1
    if(!is_file($template) || !is_readable($template)) {
104 1
      throw new \RuntimeException("File $template does not exist or is not readable.");
105
    }
106 1
    $this->template = $template;
107 1
  }
108
109
  protected function getExtensions(): \Nexendrie\Utils\Collection {
110 1
    return $this->extensions;
111
  }
112
  
113
  /**
114
   * @throws InvalidStateException
115
   * @throws \InvalidArgumentException
116
   */
117
  protected function getData(): Collection {
118 1
    if($this->dataSource === null) {
119 1
      throw new InvalidStateException("Data source for RSS generator is not set.");
120
    }
121 1
    $items = call_user_func($this->dataSource);
122 1
    if(!$items instanceof Collection) {
123 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...
124
    }
125 1
    return $items;
126
  }
127
  
128
  protected function writeProperty(\SimpleXMLElement &$channel, array $info, string $property): void {
129 1
    $value = Arrays::get($info, $property, "");
130 1
    if(!$value instanceof IXmlConvertible) {
131 1
      $value = new GenericElement($property, $value);
132
    }
133 1
    $value->appendToXml($channel->channel);
134 1
  }
135
  
136
  /**
137
   * @throws InvalidStateException
138
   * @throws \InvalidArgumentException
139
   */
140
  public function generate(array $info): string {
141 1
    $this->onBeforeGenerate($this, $info);
142 1
    $items = $this->getData();
143 1
    $resolver = new OptionsResolver();
144 1
    foreach($this->extensions as $extension) {
145 1
      $extension->configureChannelOptions($resolver, $this);
146
    }
147 1
    $info = $resolver->resolve($info);
148
    /** @var \SimpleXMLElement $channel */
149 1
    $channel = simplexml_load_file($this->template);
150 1
    foreach($this->extensions as $extension) {
151 1
      if($extension->getName() !== "" && $extension->getNamespace() !== "") {
152 1
        $channel->addAttribute(static::NAMESPACE_ATTRIBUTE_HACK . $extension->getName(), $extension->getNamespace());
153
      }
154
    }
155 1
    $properties = $resolver->getDefinedOptions();
156 1
    foreach($properties as $property) {
157 1
      $this->writeProperty($channel, $info, $property);
158
    }
159 1
    if($this->generator !== "") {
160 1
      $channel->channel->generator = $this->generator;
161
    }
162 1
    if($this->docs !== "") {
163 1
      $channel->channel->docs = $this->docs;
164
    }
165
    /** @var RssChannelItem $item */
166 1
    foreach($items as $item) {
167
      /** @var \SimpleXMLElement $i */
168 1
      $i = $channel->channel->addChild("item");
169 1
      $item->toXml($i, $this);
170 1
      $this->onAddItem($this, $channel, $item, $i);
171
    }
172 1
    $this->onAfterGenerate($this, $info);
173 1
    $dom = new \DOMDocument();
174 1
    $dom->preserveWhiteSpace = false;
175 1
    $dom->formatOutput = true;
176 1
    $xml = $channel->asXML();
177 1
    $xml = str_replace(static::NAMESPACE_ATTRIBUTE_HACK, "xmlns:", $xml);
178 1
    $dom->loadXML($xml);
179 1
    return $dom->saveXML();
180
  }
181
  
182
  /**
183
   * @throws InvalidStateException
184
   * @throws \InvalidArgumentException
185
   */
186
  public function response(array $info): RssResponse {
187 1
    return new RssResponse($this->generate($info));
188
  }
189
}
190
?>