Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
23.44% covered (danger)
23.44%
15 / 64
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
XIncludeLoader
23.44% covered (danger)
23.44%
15 / 64
14.29% covered (danger)
14.29%
1 / 7
305.50
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 load
33.33% covered (danger)
33.33%
5 / 15
0.00% covered (danger)
0.00%
0 / 1
16.67
 parseCustomElementMarkup
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
 loadXInclude
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 loadNonQtiAttributes
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getXIncludes
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 getCustomElements
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
1<?php
2
3/*
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2015 (original work) Open Assessment Technologies SA
19 *
20 */
21
22namespace oat\taoQtiItem\model\qti;
23
24use DOMAttr;
25use DOMDocument;
26use DOMElement;
27use oat\taoItems\model\media\ItemMediaResolver;
28use oat\taoQtiItem\model\qti\Item;
29use oat\taoQtiItem\model\qti\XInclude;
30use oat\taoQtiItem\model\qti\interaction\PortableCustomInteraction;
31use oat\taoQtiItem\model\qti\interaction\CustomInteraction;
32use oat\taoQtiItem\model\qti\ParserFactory;
33use oat\taoQtiItem\model\qti\exception\XIncludeException;
34use oat\taoQtiItem\model\qti\exception\ParsingException;
35
36/**
37 *
38 * @access public
39 * @author Sam, <sam@taotesting.com>
40 * @package taoQTI
41
42 */
43class XIncludeLoader
44{
45    protected $qtiItem = null;
46    protected $resolver = null;
47
48    public function __construct(Item $qtiItem, ItemMediaResolver $resolver)
49    {
50        $this->qtiItem = $qtiItem;
51        $this->resolver = $resolver;
52    }
53
54    /**
55     * Load parse the item and resolve all xinclude
56     *
57     * @param boolean $removeUnfoundHref
58     * @return array
59     * @throws XIncludeException when the href cannot be resolved
60     */
61    public function load($removeUnfoundHref = false)
62    {
63
64        $xincludes = $this->getXIncludes();
65
66        //load xincludes in standard element
67        foreach ($xincludes as $xinclude) {
68            //retrive the xinclude from href
69            $href = $xinclude->attr('href');
70            if (!empty($href)) {
71                try {
72                    $asset = $this->resolver->resolve($href);
73                    $filePath = $asset->getMediaSource()->download($asset->getMediaIdentifier());
74                    $this->loadXInclude($xinclude, $filePath);
75                } catch (\tao_models_classes_FileNotFoundException $exception) {
76                    if ($removeUnfoundHref) {
77                        $xinclude->attr('href', '');
78                    } else {
79                        throw new XIncludeException('The file referenced by href does not exist : ' . $href, $xinclude);
80                    }
81                }
82            }
83        }
84
85        //load xinclude in portable element markup
86        $customElements = $this->getCustomElements();
87        foreach ($customElements as $customElement) {
88            $xincludes = array_merge($xincludes, $this->parseCustomElementMarkup($customElement));
89        }
90
91        return $xincludes;
92    }
93
94    /**
95     * Parse and load xinclude located in custom element markup
96     *
97     * @param CustomInteraction $customElement
98     * @return array
99     * @throws XIncludeException when the file in href cannot be resolved
100     * @throws ParsingException when the markup cannot be loaded as xml document
101     */
102    private function parseCustomElementMarkup(CustomInteraction $customElement)
103    {
104
105        $xincludes = [];
106        $xml = new DOMDocument();
107        $xml->formatOutput = true;
108        $loadSuccess = $xml->loadXML($customElement->getMarkup());
109        $node = $xml->documentElement;
110
111        if ($loadSuccess && !is_null($node)) {
112            $parser = new ParserFactory($xml);
113            $xincludesNodes = $parser->queryXPath(".//*[name(.)='include']");
114            foreach ($xincludesNodes as $xincludeNode) {
115                $href = $xincludeNode->getAttribute('href');
116                $asset = $this->resolver->resolve($href);
117                $filePath = $asset->getMediaSource()->download($asset->getMediaIdentifier());
118                if (file_exists($filePath)) {
119                    $fileContent = file_get_contents($filePath);
120                    $xmlInclude = new DOMDocument();
121                    $xmlInclude->formatOutput = true;
122                    $xmlInclude->loadXML($fileContent);
123                    foreach ($xmlInclude->documentElement->childNodes as $node) {
124                        $importNode = $xml->importNode($node, true);
125                        $xincludeNode->parentNode->insertBefore($importNode, $xincludeNode);
126                    }
127                } else {
128                    throw new XIncludeException('The file referenced by href does not exist : ' . $href, $xincludeNode);
129                }
130                $xincludeNode->parentNode->removeChild($xincludeNode);
131                $xincludes[] = $href;
132            }
133        } else {
134            throw new ParsingException('cannot parse pci markup');
135        }
136
137        $customElement->setMarkup($xml->saveXML($xml->documentElement, LIBXML_NOEMPTYTAG));
138
139        return $xincludes;
140    }
141
142    /**
143     * load an xml string into the body of the XInclude
144     *
145     * @param \oat\taoQtiItem\model\qti\XInclude $xinclude
146     * @param string $filePath
147     * @throws XIncludeException
148     */
149    private function loadXInclude(XInclude $xinclude, $filePath)
150    {
151        //load DOMDocument
152        $xml = new DOMDocument();
153        $loadSuccess = $xml->load($filePath);
154        $node = $xml->documentElement;
155        if ($loadSuccess && !is_null($node)) {
156            $this->loadNonQtiAttributes($node, $xinclude);
157            //parse the href content
158            $parser = new ParserFactory($xml);
159            $parser->loadContainerStatic($node, $xinclude->getBody());
160        } else {
161            throw new XIncludeException('Cannot load the XInclude DOM XML', $xinclude);
162        }
163    }
164
165    /**
166     * loads XML attributes related to passage styling to Xinclude QTI model
167     *
168     * @throws exception\QtiModelException
169     */
170    private function loadNonQtiAttributes(DOMElement $xIncludeDOMElement, XInclude $xInclude): void
171    {
172        /** @var  $attrNode DOMAttr */
173        foreach ($xIncludeDOMElement->attributes as $attrName => $attrNode) {
174            if (in_array($attrName, $xInclude->listOfNonQtiAttributes())) {
175                $xInclude->setAttribute($attrName, $attrNode->value);
176            }
177        }
178    }
179
180    /**
181     * Find the xinclude elements in the qti item
182     *
183     * @return \oat\taoQtiItem\model\qti\XInclude[]
184     */
185    private function getXIncludes()
186    {
187        $xincludes = [];
188        foreach ($this->qtiItem->getComposingElements() as $element) {
189            if ($element instanceof XInclude) {
190                $xincludes[] = $element;
191            }
192        }
193        return $xincludes;
194    }
195
196    /**
197     * Find the custom elements in the qti item
198     *
199     * @return \oat\taoQtiItem\model\qti\interaction\PortableCustomInteraction[]
200     */
201    private function getCustomElements()
202    {
203        $customElements = [];
204        foreach ($this->qtiItem->getComposingElements() as $element) {
205            if ($element instanceof PortableCustomInteraction) {
206                $customElements[] = $element;
207            }
208        }
209        return $customElements;
210    }
211}