Completed
Push — master ( abfc1a...ad5651 )
by Marko
03:03
created

AframeDOMDocument::correctOutputFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 14
nc 1
nop 1
crap 1
1
<?php
2
/** @formatter:off
3
 * ******************************************************************
4
 * Created by   Marko Kungla on Jun 27, 2016 - 9:55:09 PM
5
 * Contact      [email protected]
6
 * @copyright   2016 Marko Kungla - https://github.com/mkungla
7
 * @license     The MIT License (MIT)
8
 * 
9
 * @category       AframeVR
10
 * @package        aframe-php
11
 * 
12
 * Lang         PHP (php version >= 7)
13
 * Encoding     UTF-8
14
 * File         AframeDOMDocument.php
15
 * Code format  PSR-2 and 12
16
 * @link        https://github.com/mkungla/aframe-php
17
 ^ @issues      https://github.com/mkungla/aframe-php/issues
18
 * ********************************************************************
19
 * Contributors:
20
 * @author Marko Kungla <[email protected]>
21
 * ********************************************************************
22
 * Comments:
23
 * @formatter:on */
24
namespace AframeVR\Core\DOM;
25
26
use \AframeVR\Core\Config;
27
use \DOMImplementation;
28
use \DOMDocumentType;
29
use \DOMDocument;
30
use \AframeVR\Core\Entity;
31
use \AframeVR\Interfaces\AssetsInterface;
32
33
final class AframeDOMDocument extends DOMImplementation
34
{
35
36
    const DEFAULT_METATAGS = array(
37
        array(
38
            'charset' => 'utf-8'
39
        ),
40
        array(
41
            'name' => 'viewport',
42
            'content' => 'width=device-width,initial-scale=1,maximum-scale=1,shrink-to-fit=no,user-scalable=no,minimal-ui'
43
        ),
44
        array(
45
            'name' => 'mobile-web-app-capable',
46
            'content' => 'yes'
47
        ),
48
        array(
49
            'name' => 'theme-color',
50
            'content' => 'black'
51
        )
52
    );
53
54
    /**
55
     * A-Frame DOM Document type
56
     *
57
     * @var \DOMDocumentType
58
     */
59
    protected $doctypeObj;
60
61
    /**
62
     * A-Frame DOM Document
63
     *
64
     * @var \DOMDocument
65
     */
66
    protected $docObj;
67
68
    /**
69
     * Scene meta tile
70
     *
71
     * @var string $scene_title
72
     */
73
    protected $scene_title = 'Untitled';
74
75
    /**
76
     * Scene meta description
77
     *
78
     * @var string $scene_description
79
     */
80
    protected $scene_description = '';
81
82
    /**
83
     * CDN Of aframe.js
84
     *
85
     * @var string
86
     */
87
    protected $aframe_cdn;
88
89
    /**
90
     * Whether to use CDN
91
     *
92
     * @var bool $use_cdn
93
     */
94
    protected $use_cdn = false;
95
96
    /**
97
     * <head>
98
     *
99
     * @var \DOMElement
100
     */
101
    protected $head;
102
103
    /**
104
     * <body>
105
     *
106
     * @var \DOMElement
107
     */
108
    protected $body;
109
110
    /**
111
     * <a-scene>
112
     *
113
     * @var \DOMElement
114
     */
115
    protected $scene;
116
117
    /**
118
     * <a-assets>
119
     *
120
     * @var \DOMElement
121
     */
122
    protected $assets;
123
124
    /**
125
     * Nicely formats output with indentation and extra space.
126
     *
127
     * @var bool
128
     */
129
    protected $formatOutput = false;
130
131
    /**
132
     * A-Frame DOM
133
     *
134
     * @param Config $config            
135
     */
136 70
    public function __construct(Config $config)
137
    {
138
        /* Config */
139 70
        $this->formatOutput = is_bool($config->get('formatOutput')) ? $config->get('formatOutput') : false;
140 70
        $this->use_cdn = is_bool($config->get('useCDN')) ? $config->get('useCDN') : false;
141
        
142
        /* Create HTML5 Document type */
143 70
        $this->createDocType('html');
144
        /* Create A-Frame DOM Document */
145 70
        $this->createAframeDocument();
146
        /* Create <head> element */
147 70
        $this->head = $this->docObj->createElement('head');
148
        /* Create <body> element */
149 70
        $this->body = $this->docObj->createElement('body', $this->formatOutput ? "\n" : '');
150
        /* Create <a-scene> element */
151 70
        $this->scene = $this->docObj->createElement('a-scene');
152
        /* Create <a-assets> element */
153 70
        $this->assets = $this->docObj->createElement('a-assets');
154
        /* Set CDN of aframe.js */
155 70
        $this->setCDN(is_string($config->get('CDN')) ? $config->get('CDN') : '');
156 70
    }
157
158
    /**
159
     * Set CDN for aframe.js or min.js
160
     *
161
     * @param string $cdn            
162
     * @return void
163
     */
164 70
    public function setCDN(string $cdn)
165
    {
166 70
        $this->aframe_cdn = $cdn;
167 70
    }
168
169
    /**
170
     * Render scene this DOM Object is attached to
171
     *
172
     * @return string
173
     */
174 5
    public function render(): string
175
    {
176 5
        $this->docObj->formatOutput = $this->formatOutput;
177 5
        $html = $this->docObj->getElementsByTagName('html')->item(0);
178
        /* Make sure we do not add duplicates when render is called multiple times */
179 5
        if (! $html->hasChildNodes()) {
180 5
            $this->renderHead();
181 5
            $html->appendChild($this->head);
182
            
183 5
            $this->renderBody();
184 5
            $html->appendChild($this->body);
185
        }
186 5
        return $this->formatOutput ? $this->correctOutputFormat($this->docObj->saveHTML()) : $this->docObj->saveHTML();
187
    }
188
189
    /**
190
     * Set Scene meta title
191
     *
192
     * @param string $title            
193
     */
194 7
    public function setTitle(string $title)
195
    {
196 7
        $this->scene_title = $title;
197 7
    }
198
199
    /**
200
     * Set Scene meta description
201
     *
202
     * @param string $description            
203
     */
204 7
    public function setDescription(string $description)
205
    {
206 7
        $this->scene_description = $description;
207 7
    }
208
209
    /**
210
     * Append entities
211
     *
212
     * @param array $entities            
213
     * @return void
214
     */
215 7
    public function appendEntities(array $entities)
216
    {
217 7
        if (! empty($entities)) {
218 2
            foreach ($entities as $entity) {
219 2
                $this->appendEntity($entity);
220
            }
221
        }
222 7
    }
223
224
    /**
225
     * Append assets
226
     *
227
     * @param array $assets            
228
     * @return void
229
     */
230 2
    public function appendAssets(array $assets)
231
    {
232 2
        if (! empty($assets)) {
233 2
            if ($this->formatOutput) {
234 2
                $com = $this->docObj->createComment('');
235 2
                $this->scene->appendChild($com);
236
            }
237 2
            foreach ($assets as $asset) {
238 2
                $this->appendAsset($asset);
239
            }
240 2
            $this->scene->appendChild($this->assets);
241
        }
242 2
    }
243
244
    /**
245
     * Append asset
246
     *
247
     * Create asset DOMElement
248
     *
249
     * @param AssetsInterface $asset            
250
     */
251 2
    public function appendAsset(AssetsInterface $asset)
252
    {
253 2
        $this->appendFormatComment('assets', "\n\t");
254 2
        $this->assets->appendChild($asset->DOMElement($this->docObj));
255 2
    }
256
257
    /**
258
     * Create entity DOMElement
259
     *
260
     * Created entity and append it to scene
261
     *
262
     * @param Entity $entity            
263
     * @return void
264
     */
265 2
    public function appendEntity(Entity $entity)
266
    {
267 2
        $this->appendFormatComment('scene', "\n");
268 2
        $this->scene->appendChild($entity->DOMElement($this->docObj));
269 2
    }
270
271
    /**
272
     * Get HTML of Scene only
273
     *
274
     * @return string
275
     */
276 3
    public function renderSceneOnly()
277
    {
278 3
        $html = new DOMDocument();
279 3
        $html->formatOutput = $this->formatOutput;
280 3
        $html_scene = $html->importNode($this->scene, true);
281 3
        $html->appendChild($html_scene);
282 3
        return $this->formatOutput ? $this->correctOutputFormat($html->saveHTML()) : $html->saveHTML();
283
    }
284
285
    /**
286
     * Add document comment for formatting
287
     *
288
     * @param string $element            
289
     * @param string $comment            
290
     */
291 4
    protected function appendFormatComment(string $element, string $comment)
292
    {
293 4
        if ($this->formatOutput) {
294 4
            $com = $this->docObj->createComment($comment);
295 4
            $this->{$element}->appendChild($com);
296
        }
297 4
    }
298
299
    /**
300
     * Correct html format for tags which are not supported by DOMDocument
301
     *
302
     * @param string $html            
303
     * @return string
304
     */
305 4
    protected function correctOutputFormat($html)
306
    {
307
        $tags = array(
308 4
            '<!--',
309
            '-->',
310
            '<a-assets>',
311
            '</a-assets>',
312
            '</a-scene>'
313
        );
314
        $values = array(
315 4
            '',
316
            "\t",
317
            "\n\t<a-assets>",
318
            "\n\t</a-assets>",
319
            "\n</a-scene>"
320
        );
321 4
        return str_ireplace($tags, $values, $html);
322
    }
323
324
    /**
325
     * Prepeare head
326
     *
327
     * @return void
328
     */
329 5
    protected function renderHead()
330
    {
331 5
        $title = $this->docObj->createElement('title', $this->scene_title);
332 5
        $this->head->appendChild($title);
333 5
        $this->appendDefaultMetaTags();
334 5
        $this->appendCDN();
335 5
    }
336
337
    /**
338
     * Append deffault metatags
339
     *
340
     * @return void
341
     */
342 5
    protected function appendDefaultMetaTags()
343
    {
344 5
        $this->appendMetaTag(array(
345 5
            'name' => 'description',
346 5
            'content' => $this->scene_description
347
        ));
348 5
        foreach ($this->getDefaultMetaTags() as $tag)
349 5
            $this->appendMetaTag($tag);
350 5
    }
351
352
    /**
353
     * Get default meta tags
354
     *
355
     * @return array
356
     */
357 5
    protected function getDefaultMetaTags(): array
358
    {
359 5
        return self::DEFAULT_METATAGS;
360
    }
361
362
    /**
363
     * If requested by user use aframe CDN
364
     *
365
     * @return void
366
     */
367 5
    protected function appendCDN()
368
    {
369 5
        if ($this->use_cdn) {
370 3
            $cdn_script = $this->docObj->createElement('script');
371 3
            $cdn_script->setAttribute('src', $this->aframe_cdn);
372 3
            $this->head->appendChild($cdn_script);
373
        }
374 5
    }
375
376
    /**
377
     * Prepare body
378
     *
379
     * @return void
380
     */
381 5
    protected function renderBody()
382
    {
383 5
        $this->body->appendChild($this->scene);
384 5
    }
385
386
    /**
387
     * Create meta tags
388
     *
389
     * @param array $attr            
390
     */
391 5
    protected function appendMetaTag(array $attr)
392
    {
393 5
        $metatag = $this->docObj->createElement('meta');
394 5
        foreach ($attr as $key => $val)
395 5
            $metatag->setAttribute($key, $val);
396 5
        $this->head->appendChild($metatag);
397 5
    }
398
399
    /**
400
     * Creates an empty DOMDocumentType object
401
     *
402
     * @param string $doctype            
403
     * @return void
404
     */
405 70
    protected function createDocType(string $doctype)
406
    {
407 70
        $this->doctypeObj = $this->createDocumentType($doctype);
408 70
    }
409
410
    /**
411
     * Creates a DOMDocument object of the specified type with its document element
412
     *
413
     * @return void
414
     */
415 70
    protected function createAframeDocument()
416
    {
417 70
        $this->docObj = $this->createDocument(null, 'html', $this->doctypeObj);
418 70
    }
419
}
420