Passed
Push — master ( 067151...1e051c )
by Petr
13:11
created

Pohoda::detectXmlFileHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * This file is part of riesenia/pohoda package.
5
 *
6
 * Licensed under the MIT License
7
 * (c) RIESENIA.com
8
 */
9
10
declare(strict_types=1);
11
12
namespace Riesenia;
13
14
use Riesenia\Pohoda\AbstractAgenda;
15
use Riesenia\Pohoda\Common\OneDirectionalVariablesTrait;
16
17
/**
18
 * Factory for Pohoda objects.
19
 *
20
 * @method Pohoda\AddressBook   createAddressBook(array $data = [])
21
 * @method Pohoda\Bank          createBank(array $data = [])
22
 * @method Pohoda\CashSlip      createCashSlip(array $data = [])
23
 * @method Pohoda\Category      createCategory(array $data = [])
24
 * @method Pohoda\Contract      createContract(array $data = [])
25
 * @method Pohoda\IntDoc        createIntDoc(array $data = [])
26
 * @method Pohoda\IntParam      createIntParam(array $data = [])
27
 * @method Pohoda\Invoice       createInvoice(array $data = [])
28
 * @method Pohoda\IssueSlip     createIssueSlip(array $data = [])
29
 * @method Pohoda\ListRequest   createListRequest(array $data = [])
30
 * @method Pohoda\ListStock     createListStock(array $data = [])
31
 * @method Pohoda\Offer         createOffer(array $data = [])
32
 * @method Pohoda\Order         createOrder(array $data = [])
33
 * @method Pohoda\PrintRequest  createPrintRequest(array $data = [])
34
 * @method Pohoda\Receipt       createReceipt(array $data = [])
35
 * @method Pohoda\Stock         createStock(array $data = [])
36
 * @method Pohoda\StockTransfer createStockTransfer(array $data = [])
37
 * @method Pohoda\Storage       createStorage(array $data = [])
38
 * @method Pohoda\Supplier      createSupplier(array $data = [])
39
 * @method Pohoda\UserList      createUserList(array $data = [])
40
 * @method Pohoda\Voucher       createVoucher(array $data = [])
41
 * @method \bool loadAddressBook(string $filename)
42
 * @method \bool loadBank(string $filename)
43
 * @method \bool loadCashSlip(string $filename)
44
 * @method \bool loadCategory(string $filename)
45
 * @method \bool loadContract(string $filename)
46
 * @method \bool loadIntDoc(string $filename)
47
 * @method \bool loadIntParam(string $filename)
48
 * @method \bool loadInvoice(string $filename)
49
 * @method \bool loadIssueSlip(string $filename)
50
 * @method \bool loadListRequest(string $filename)
51
 * @method \bool loadListStock(string $filename)
52
 * @method \bool loadOffer(string $filename)
53
 * @method \bool loadOrder(string $filename)
54
 * @method \bool loadPrintRequest(string $filename)
55
 * @method \bool loadReceipt(string $filename)
56
 * @method \bool loadStock(string $filename)
57
 * @method \bool loadStockTransfer(string $filename)
58
 * @method \bool loadStorage(string $filename)
59
 * @method \bool loadSupplier(string $filename)
60
 * @method \bool loadUserList(string $filename)
61
 * @method \bool loadVoucher(string $filename)
62
 *
63
 * @link https://www.stormware.cz/pohoda/xml/seznamschemat/   schemas
64
 */
65
class Pohoda
66
{
67
    use OneDirectionalVariablesTrait;
68
69
    protected string $application = 'Pohoda connector';
70
71
    protected bool $isInMemory;
72
73
    protected \XMLWriter $xmlWriter;
74
75
    protected \XMLReader $xmlReader;
76
77
    protected Pohoda\AgendaFactory $agendaFactory;
78
79
    protected string $elementName;
80
81
    protected bool $importRecursive = false;
82
83 18
    public function __construct(
84
        protected readonly string $companyRegistrationNumber,
85
        protected Pohoda\ValueTransformer\SanitizeEncoding $sanitizeEncoding = new Pohoda\ValueTransformer\SanitizeEncoding(new Pohoda\ValueTransformer\Listing()),
86
        protected readonly Pohoda\Common\NamespacesPaths $namespacesPaths = new Pohoda\Common\NamespacesPaths(),
87
    ) {
88 18
        $this->agendaFactory = new Pohoda\AgendaFactory($this->namespacesPaths, $this->sanitizeEncoding, $this->companyRegistrationNumber);
89
    }
90
91
    /**
92
     * Set the name of the application.
93
     *
94
     * @param string $name
95
     *
96
     * @return void
97
     */
98 1
    public function setApplicationName(string $name): void
99
    {
100 1
        $this->application = $name;
101
    }
102
103
    /**
104
     * Get class listing transformers for content serialization
105
     *
106
     * @return Pohoda\ValueTransformer\Listing
107
     */
108 2
    public function getTransformerListing(): Pohoda\ValueTransformer\Listing
109
    {
110 2
        return $this->sanitizeEncoding->getListing();
111
    }
112
113
    /**
114
     * Create and return instance of requested agenda.
115
     *
116
     * @param string              $name
117
     * @param array<string,mixed> $data
118
     *
119
     * @return AbstractAgenda
120
     */
121 3
    public function create(string $name, array $data = []): AbstractAgenda
122
    {
123 3
        return $this->agendaFactory->getAgenda($name)->setDirectionalVariable($this->useOneDirectionalVariables)->setData($data);
124
    }
125
126
    /**
127
     * Open new XML file for writing.
128
     *
129
     * @param string|null $filename path to output file or null for memory
130
     * @param string      $id
131
     * @param string      $note
132
     *
133
     * @return bool
134
     */
135 4
    public function open(?string $filename, string $id, string $note = ''): bool
136
    {
137 4
        $this->xmlWriter = new \XMLWriter();
138
139 4
        if (is_null($filename)) {
140 3
            $this->isInMemory = true;
141 3
            $this->xmlWriter->openMemory();
142
        } else {
143 1
            $this->isInMemory = false;
144
145 1
            if (!$this->xmlWriter->openUri($filename)) {
146
                // @codeCoverageIgnoreStart
147
                // I cannot test this, because it needs source file somewhere online
148
                return false;
149
            }
150
            // @codeCoverageIgnoreEnd
151
        }
152
153 4
        $this->xmlWriter->startDocument('1.0', $this->sanitizeEncoding->getEncoding());
154 4
        $this->xmlWriter->startElementNs('dat', 'dataPack', null);
155
156 4
        $this->xmlWriter->writeAttribute('id', $id);
157 4
        $this->xmlWriter->writeAttribute('ico', $this->companyRegistrationNumber);
158 4
        $this->xmlWriter->writeAttribute('application', $this->application);
159 4
        $this->xmlWriter->writeAttribute('version', '2.0');
160 4
        $this->xmlWriter->writeAttribute('note', $note);
161
162 4
        foreach ($this->namespacesPaths->allNamespaces() as $k => $v) {
163
            // put all known namespaces into base element attributes
164 4
            $this->xmlWriter->writeAttributeNs('xmlns', $k, null, $v);
165
        }
166
167 4
        return true;
168
    }
169
170
    /**
171
     * Add item.
172
     *
173
     * @param string $id
174
     * @param AbstractAgenda $agenda
175
     * @param array<string, mixed> $data
176
     *
177
     * @return void
178
     */
179 4
    public function addItem(string $id, AbstractAgenda $agenda, array $data): void
180
    {
181 4
        $this->xmlWriter->startElementNs('dat', 'dataPackItem', null);
182
183 4
        $this->xmlWriter->writeAttribute('id', $id);
184 4
        $this->xmlWriter->writeAttribute('version', '2.0');
185 4
        $this->xmlWriter->writeRaw((string) $agenda->setDirectionalVariable($this->useOneDirectionalVariables)->setData($data)->getXML()->asXML());
186 4
        $this->xmlWriter->endElement();
187
188 4
        if (!$this->isInMemory) {
189 1
            $this->xmlWriter->flush();
190
        }
191
    }
192
193
    /**
194
     * End and close XML file.
195
     *
196
     * @return int|string written bytes for file or XML string for memory
197
     */
198 8
    public function close(): int|string
199
    {
200 8
        $this->xmlWriter->endElement();
201
202 8
        return $this->xmlWriter->flush();
203
    }
204
205
    /**
206
     * Load XML file.
207
     *
208
     * @param string $name
209
     * @param string $filename filename or current xml content
210
     *
211
     * @return bool
212
     */
213 4
    public function load(string $name, string $filename): bool
214
    {
215 4
        $this->xmlReader = new \XMLReader();
216
217 4
        if ($this->detectXmlFileHeader($filename)) {
218 1
            if (!@$this->xmlReader->XML($filename)) {
219
                // @codeCoverageIgnoreStart
220
                // cannot create string which will be parsed as XML and crash itself afterward
221
                return false;
222
            }
223
            // @codeCoverageIgnoreEnd
224
        } else {
225 3
            if (!@$this->xmlReader->open($filename)) {
226 1
                return false;
227
            }
228
        }
229
230 3
        $class = $this->agendaFactory->getAgenda($name, false);
231 3
        $this->elementName = $class->getImportRoot() ?? throw new \DomainException('Not allowed entity: ' . $name);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $class->getImportRoot() targeting Riesenia\Pohoda\AbstractAgenda::getImportRoot() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
232 3
        $this->importRecursive = $class->canImportRecursive();
233
234 3
        return true;
235
    }
236
237
    /**
238
     * Detect if passed file is in fact XML and not just path
239
     * A bit simple, but it is enough for now
240
     *
241
     * @param string $string
242
     *
243
     * @return bool
244
     */
245 4
    protected function detectXmlFileHeader(string $string): bool
246
    {
247 4
        return str_contains(substr($string, 0, 64), '<?xml');
248
    }
249
250
    /**
251
     * Get next item in loaded file.
252
     *
253
     * @return \SimpleXMLElement|null
254
     */
255 3
    public function next(): ?\SimpleXMLElement
256
    {
257 3
        while (\XMLReader::ELEMENT != $this->xmlReader->nodeType || $this->xmlReader->name !== $this->elementName) {
258 3
            if (!$this->xmlReader->read()) {
259 3
                return null;
260
            }
261
        }
262
263 3
        $xml = new \SimpleXMLElement($this->xmlReader->readOuterXml());
264 3
        $this->importRecursive ? $this->xmlReader->next() : $this->xmlReader->read();
265
266 3
        return $xml;
267
    }
268
269
    /**
270
     * Handle dynamic method calls.
271
     *
272
     * @param string  $method
273
     * @param mixed[] $arguments
274
     *
275
     * @return mixed
276
     */
277 6
    public function __call(string $method, array $arguments)
278
    {
279
        // create<Agenda> method
280 6
        if (\preg_match('/create([A-Z][a-zA-Z0-9]*)/', $method, $matches)) {
281 1
            return \call_user_func([$this, 'create'], $matches[1], $arguments[0] ?? []);
282
        }
283
284
        // load<Agenda> method
285 5
        if (\preg_match('/load([A-Z][a-zA-Z0-9]*)/', $method, $matches)) {
286 4
            if (!isset($arguments[0])) {
287 1
                throw new \DomainException('Filename not set.');
288
            }
289
290 3
            return \call_user_func([$this, 'load'], $matches[1], $arguments[0]);
291
        }
292
293 1
        throw new \BadMethodCallException('Unknown method: ' . $method);
294
    }
295
}
296