Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 17.0.0 #322

Merged
merged 12 commits into from
Jul 18, 2022
Merged
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
fail-fast: false
matrix:
operating-system: [ ubuntu-latest ]
php-versions: [ '7.1', '7.2', '7.3', '7.4', '8.0' ]
php-versions: [ '7.4', '8.0', '8.1']

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"GPL-2.0-only"
],
"require" : {
"php" : ">=7.1 <8.1",
"php" : ">=7.4",
"ext-date": "*",
"ext-dom": "*",
"ext-json": "*",
Expand Down
135 changes: 124 additions & 11 deletions src/qtism/common/dom/SerializableDomDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2017-2020 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
* Copyright (c) 2017-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
*
* @author Jérôme Bogaerts <jerome@taotesting.com>
* @license GPLv2
Expand All @@ -24,27 +24,140 @@
namespace qtism\common\dom;

use DOMDocument;
use DOMDocumentType;
use DOMElement;
use DOMImplementation;
use DOMDocumentFragment;
use DOMComment;
use DOMCDATASection;
use DOMProcessingInstruction;
use DOMText;
use DOMAttr;
use DOMEntityReference;
use DOMNode;
use DOMNodeList;
use Error;

/**
* Serializable DOM Document
*
* This class is a PHP Serializable DOMDocument implementation.
*
* @property string|null $actualEncoding
* @property $config
* @property DOMDocumentType|null $doctype
* @property DOMElement|null $documentElement
* @property string|null $documentURI
* @property string|null $encoding
* @property DOMImplementation $implementation
* @property bool $preserveWhiteSpace
* @property bool $recover
* @property bool $resolveExternals
* @property bool $standalone
* @property bool $strictErrorChecking
* @property bool $substituteEntities
* @property bool $validateOnParse
* @property string|null $version
* @property string|null $xmlEncoding
* @property bool $xmlStandalone
* @property string|null $xmlVersion
* @property int $childElementCount
* @property DOMElement|null $lastElementChild
* @property DOMElement|null $firstElementChild
*
* @method createElement(string $localName, string $value)
* @method DOMDocumentFragment createDocumentFragment()
* @method DOMText|false createTextNode(string $data)
* @method DOMComment|false createComment(string $data)
* @method DOMCDATASection|false createCDATASection(string $data)
* @method DOMProcessingInstruction|false createProcessingInstruction(string $target, string $data)
* @method DOMAttr|false createAttribute(string $localName)
* @method DOMEntityReference|false createEntityReference(string $name)
* @method DOMNodeList|false getElementsByTagName(string $qualifiedName)
* @method DOMNodeList|false importNode(DOMNode $node, bool $deep = false)
* @method DOMElement|false createElementNS(string|null $namespace, string $qualifiedName, string $value)
* @method DOMAttr|false createAttributeNS(string|null $namespace, string $qualifiedName)
* @method DOMNodeList getElementsByTagNameNS(string|null $namespace, string $localName)
* @method DOMElement|null getElementById(string $elementId)
* @method DOMNode adoptNode(DOMNode $elementId)
* @method append(...$nodes)
* @method prepend(...$nodes)
* @method normalizeDocument()
* @method renameNode(DOMNode $node, $namespace, $qualifiedName)
* @method DOMDocument|bool load(string $filename, ?int $options = null)
* @method int|false save($filename, $options = null)
* @method string|false saveXML(?DOMNode $node = null, int $options = null)
* @method bool validate()
* @method int|false xinclude(int $options = null)
* @method DOMDocument|bool loadHTML(string $source, int $options=0)
* @method DOMDocument|bool loadHTMLFile(string $filename, int $options=0)
* @method string|false saveHTML(DOMNode $node = null)
* @method int|false saveHTMLFile(string $filename)
* @method bool schemaValidate($filename, $options = null)
* @method bool schemaValidateSource($source, $flags)
* @method bool relaxNGValidate(string $filename)
* @method bool relaxNGValidateSource(string $source)
* @method bool registerNodeClass(string $baseClass, string $extendedClass)
*/
class SerializableDomDocument extends DOMDocument
class SerializableDomDocument
{
private $xmlData;
private DOMDocument $dom;

public function __construct(string $version = '1.0', string $encoding = '')
{
$this->dom = new DOMDocument($version, $encoding);
}

public function __serialize(): array
{
return [
'version' => (string)$this->dom->xmlVersion,
'encoding' => (string)$this->dom->encoding,
'xmlData' => (string)$this,
];
}

public function __unserialize(array $data): void
{
$this->dom = new DOMDocument($data['version'], $data['encoding']);
$this->dom->loadXML($data['xmlData']);
}

public function __toString(): string
{
$xml = $this->dom->saveXML();
return $xml ? : '';
}

public function __call(string $name, array $arguments)
{
if (!method_exists($this->dom, $name)) {
throw new Error(sprintf('Call to undefined method %s::%s()', __CLASS__, $name));
}

return call_user_func_array([$this->dom, $name], $arguments);
}

/**
* @return array
*/
public function __sleep()
public function __get(string $name)
{
$this->xmlData = $this->saveXML();
return ['xmlData'];
if (!property_exists($this->dom, $name)) {
trigger_error(sprintf('Undefined property: %s::%s', __CLASS__, $name), E_USER_WARNING);
}

return $this->dom->$name ?? null;
}

public function __set(string $name, $value): void
{
$this->dom->$name = $value;
}

public function __wakeup()
public function __isset(string $name): bool
{
return isset($this->dom->$name);
}
public function __unset(string $name): void
{
$this->loadXML($this->xmlData);
unset($this->dom->$name);
}
}
4 changes: 2 additions & 2 deletions src/qtism/data/ExternalQtiComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ public function __construct($xmlString)
* Returns the XML representation of the external component as
* a DOMDocument object.
*
* @return SerializableDomDocument A DOMDocument (serializable) object representing the content of the external component.
* @return SerializableDomDocument|null A DOMDocument (serializable) object representing the content of the external component.
* @throws RuntimeException If the root element of the XML representation is not from the target namespace or the XML could not be parsed.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
// Build the DOMDocument object only on demand.
if ($this->xml === null) {
Expand Down
3 changes: 1 addition & 2 deletions src/qtism/data/IExternal.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ interface IExternal
*
* In case of there is no external data, the implementation may return the null value.
*
* @return SerializableDomDocument
*/
public function getXml();
public function getXml(): ?SerializableDomDocument;
}
5 changes: 3 additions & 2 deletions src/qtism/data/content/interactions/CustomInteraction.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

use DOMDocument;
use InvalidArgumentException;
use qtism\common\dom\SerializableDomDocument;
use qtism\data\content\Block;
use qtism\data\content\Flow;
use qtism\data\content\FlowTrait;
Expand Down Expand Up @@ -108,10 +109,10 @@ public function setXmlString($xmlString)
/**
* Get the XML content of the custom interaction itself and its content.
*
* @return DOMDocument A DOMDocument object representing the custom interaction.
* @return SerializableDomDocument|null A DOMDocument object representing the custom interaction.
* @throws RuntimeException If the XML content of the custom interaction and/or its content cannot be transformed into a valid DOMDocument.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
return $this->getExternalComponent()->getXml();
}
Expand Down
4 changes: 2 additions & 2 deletions src/qtism/data/expressions/operators/CustomOperator.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ public function hasDefinition()
/**
* Get the XML content of the custom operator itself and its content.
*
* @return SerializableDomDocument A DOMDocument (serializable) object representing the custom operator itself.
* @return SerializableDomDocument|null A DOMDocument (serializable) object representing the custom operator itself.
* @throws RuntimeException If the XML content of the custom operator and/or its content cannot be transformed into a valid DOMDocument.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
return $this->getExternalComponent()->getXml();
}
Expand Down
4 changes: 2 additions & 2 deletions src/qtism/data/rules/Selection.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ private function getExternalComponent()
/**
* Get the XML content of the selection itself and its content.
*
* @return SerializableDomDocument A DOMDocument (serializable) object representing the selection itself or null if there is no external component.
* @return SerializableDomDocument|null A DOMDocument (serializable) object representing the selection itself or null if there is no external component.
* @throws RuntimeException If the XML content of the selection and/or its content cannot be transformed into a valid DOMDocument.
*/
public function getXml()
public function getXml(): ?SerializableDomDocument
{
if (($externalComponent = $this->getExternalComponent()) !== null) {
return $this->getExternalComponent()->getXml();
Expand Down
96 changes: 91 additions & 5 deletions test/qtismtest/common/dom/SerializableDomDocumentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,106 @@

use qtism\common\dom\SerializableDomDocument;
use qtismtest\QtiSmTestCase;
use DOMDocument;

/**
* Class VersionTest
*/
class VersionTest extends QtiSmTestCase
class SerializableDomDocumentTest extends QtiSmTestCase
{
public function testSerialization()
{
$dom = new SerializableDomDocument('1.0', 'UTF-8');
$dom->load(self::samplesDir() . 'ims/items/2_2_1/choice.xml');

$ser = serialize($dom);
$ser = serialize($this->getSerializableDomDocument());
$dom = unserialize($ser);

$this::assertEquals('http://www.imsglobal.org/xsd/imsqti_v2p2', $dom->documentElement->namespaceURI);
}


public function testAccessingProperty()
{
$xmlVersion = '1.0';
$dom = $this->getSerializableDomDocument($xmlVersion);

$this->assertNotEmpty($dom->xmlVersion);
$this->assertEquals($xmlVersion, $dom->xmlVersion);
}

public function testAccessingInexistentProperty()
{
$dom = $this->getSerializableDomDocument();
$property = 'test';

$this->expectError();
$this->expectErrorMessage(
sprintf('Undefined property: %s::%s', SerializableDomDocument::class, $property)
);

$dom->$property;
}

public function testSettingVirtualPropertyToDom()
{
$xmlVersion = '1.0';
$dom = $this->getSerializableDomDocument($xmlVersion);

$this->assertEquals($xmlVersion, $dom->xmlVersion);

$dom->xmlVersion = '1.1';
$this->assertEquals('1.1', $dom->xmlVersion);
}

public function testCheckingIfPropertyExists()
{
$dom = $this->getSerializableDomDocument();

$this->assertTrue(isset($dom->xmlVersion));
}

public function testCallingVirtualMethods()
{
$dom = $this->getSerializableDomDocument();

$this->assertNotEmpty($dom->saveXML());
$this->assertNotEmpty((string)$dom);
}

public function testCallingNotExistedVirtualMethods()
{
$dom = $this->getSerializableDomDocument();
$method = 'saveXML2';

$this->expectError();
$this->expectErrorMessage(
sprintf('Call to undefined method %s::%s()', SerializableDomDocument::class, $method)
);

$dom->$method();
}

public function testCheckThatUnsetIsWorkingSimilarToRealDomObject()
{
$serializableDOM = $this->getSerializableDomDocument();
$coreDom = new DOMDocument($serializableDOM->xmlVersion, $serializableDOM->encoding);

$this->assertEquals($coreDom->xmlVersion, $serializableDOM->version);
$this->assertEquals($coreDom->encoding, $serializableDOM->encoding);

unset($coreDom->xmlVersion);
unset($coreDom->encoding);

unset($serializableDOM->xmlVersion);
unset($serializableDOM->encoding);

$this->assertEquals($coreDom->xmlVersion, $serializableDOM->version);
$this->assertEquals($coreDom->encoding, $serializableDOM->encoding);
}

private function getSerializableDomDocument(string $version = '1.0', string $encoding = 'UTF-8'): SerializableDomDocument
{
$dom = new SerializableDomDocument($version, $encoding);
$dom->load(self::samplesDir() . 'ims/items/2_2_1/choice.xml');

return $dom;
}
}
5 changes: 3 additions & 2 deletions test/qtismtest/data/content/MathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

namespace qtismtest\data\content;

use DOMDocument;
use qtism\common\dom\SerializableDomDocument;
use qtism\data\content\Math;
use qtismtest\QtiSmTestCase;
use RuntimeException;


/**
* Class MathTest
*/
Expand Down Expand Up @@ -34,6 +35,6 @@ public function testCorrect()
{
$xml = '<m:math xmlns:m="http://www.w3.org/1998/Math/MathML"></m:math>';
$math = new Math($xml);
$this::assertInstanceOf(DOMDocument::class, $math->getXml());
$this::assertInstanceOf(SerializableDomDocument::class, $math->getXml());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace qtismtest\data\storage\xml\marshalling;

use DOMDocument;
use qtism\common\dom\SerializableDomDocument;
use qtism\data\content\Math;
use qtismtest\QtiSmTestCase;
use RuntimeException;
Expand Down Expand Up @@ -40,7 +41,7 @@ public function testUnmarshall()
$math = $this->getMarshallerFactory('2.1.0')->createMarshaller($element)->unmarshall($element);
$this::assertInstanceOf(Math::class, $math);
$xml = $math->getXml();
$this::assertInstanceOf(DOMDocument::class, $xml);
$this::assertInstanceOf(SerializableDomDocument::class, $xml);

$mathElement = $xml->documentElement;
$this::assertEquals('m', $mathElement->prefix);
Expand Down
Loading