ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç base_atom.class.php000064400000012414152266574310010330 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Abstract class representing one atom (name/value) piece of information */ abstract class base_atom { /** @var string name of the element (maps to XML name) */ private $name; /** @var string value of the element (maps to XML content) */ private $value; /** @var bool flag to indicate when one value has been set (true) or no (false) */ private $is_set; /** * Constructor - instantiates one base_atom, specifying its basic info. * * @param string $name name of the element * @param string $value optional value of the element */ public function __construct($name) { $this->validate_name($name); // Check name $this->name = $name; $this->value = null; $this->is_set= false; } protected function validate_name($name) { // Validate various name constraints, throwing exception if needed if (empty($name)) { throw new base_atom_struct_exception('backupatomemptyname', $name); } if (preg_replace('/\s/', '', $name) != $name) { throw new base_atom_struct_exception('backupatomwhitespacename', $name); } if (preg_replace('/[^\x30-\x39\x41-\x5a\x5f\x61-\x7a]/', '', $name) != $name) { throw new base_atom_struct_exception('backupatomnotasciiname', $name); } } /// Public API starts here public function get_name() { return $this->name; } public function get_value() { return $this->value; } public function set_value($value) { if ($this->is_set) { throw new base_atom_content_exception('backupatomalreadysetvalue', $value); } $this->value = $value; $this->is_set= true; } public function clean_value() { $this->value = null; $this->is_set= false; } public function is_set() { return $this->is_set; } public function to_string($showvalue = false) { $output = $this->name; if ($showvalue) { $value = $this->is_set ? $this->value : 'not set'; $output .= ' => ' . $value; } return $output; } } /** * base_atom abstract exception class * * This exceptions will be used by all the base_atom classes * in order to detect any problem or miss-configuration */ abstract class base_atom_exception extends moodle_exception { /** * Constructor - instantiates one base_atom_exception. * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, '', '', $a, $debuginfo); } } /** * base_atom exception to control all the errors while creating the objects * * This exception will be thrown each time the base_atom class detects some * inconsistency related with the creation of objects and their attributes * (wrong names) */ class base_atom_struct_exception extends base_atom_exception { /** * Constructor - instantiates one base_atom_struct_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } /** * base_atom exception to control all the errors while setting the values * * This exception will be thrown each time the base_atom class detects some * inconsistency related with the creation of contents (values) of the objects * (bad contents, setting without cleaning...) */ class base_atom_content_exception extends base_atom_exception { /** * Constructor - instantiates one base_atom_content_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } backup_final_element.class.php000064400000005023152266574310012523 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Instantiable class representing one final element atom (name/value/parent) piece of information on backup */ class backup_final_element extends base_final_element implements processable, annotable { protected $annotationitem; // To store the item this element will be responsible to annotate public function process($processor) { if (!$processor instanceof base_processor) { // No correct processor, throw exception throw new base_element_struct_exception('incorrect_processor'); } $processor->process_final_element($this); } public function set_annotation_item($itemname) { if (!empty($this->annotationitem)) { $a = new stdclass(); $a->attribute = $this->get_name(); $a->annotating= $this->annotationitem; throw new base_element_struct_exception('element_already_used_for_annotation', $a); } $this->annotationitem = $itemname; } public function annotate($backupid) { if (empty($this->annotationitem)) { // We aren't annotating this item return; } if (!$this->is_set()) { throw new base_element_struct_exception('element_has_not_value', $this->get_name()); } backup_structure_dbops::insert_backup_ids_record($backupid, $this->annotationitem, $this->get_value()); } // Protected API starts here /** * Returns one instace of the @base_attribute class to work with * when attributes are added simply by name */ protected function get_new_attribute($name) { return new backup_attribute($name); } } backup_optigroup.class.php000064400000007531152266574310011757 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Instantiable class representing one optigroup element for conditional branching * * Objects of this class are internally nested elements, so they support having both * final elements and children (more nested elements) and are able to have one source * and all the stuff supported by nested elements. Their main differences are: * * - Support for conditional execution, using simple equality checks with outer values. * - Don't have representation in the hierarchy, so: * - Their level is the level of the parent of their enclosing optigroup. * - Act as one "path bridge" when looking for parent path values * - They don't support attributes * * Their main use is to allow conditional branching, basically for optional submodules * like question types, assignment subtypes... where different subtrees of information * must be exported. It's correct to assume that each submodule will define its own * optigroup_element for backup purposes. */ class backup_optigroup extends base_optigroup implements processable { public function add_child($element) { if (!($element instanceof backup_optigroup_element)) { // parameter must be backup_optigroup_element if (is_object($element)) { $found = get_class($element); } else { $found = 'non object'; } throw new base_optigroup_exception('optigroup_element_incorrect', $found); } parent::add_child($element); } public function process($processor) { if (!$processor instanceof base_processor) { // No correct processor, throw exception throw new base_element_struct_exception('incorrect_processor'); } // Iterate over all the children backup_optigroup_elements, delegating the process // an knowing it only handles final elements, so we'll delegate process of nested // elements below. Tricky but we need to priorize finals BEFORE nested always. foreach ($this->get_children() as $child) { if ($child->condition_matches()) { // Only if the optigroup_element condition matches $child->process($processor); if (!$this->is_multiple()) { break; // one match found and this optigroup is not multiple => break loop } } } // Now iterate again, but looking for nested elements what will go AFTER all the finals // that have been processed above foreach ($this->get_children() as $child) { if ($child->condition_matches()) { // Only if the optigroup_element condition matches foreach ($child->get_children() as $nested_element) { $nested_element->process($processor); } if (!$this->is_multiple()) { break; // one match found and this optigroup is not multiple => break loop } } } } } base_nested_element.class.php000064400000023165152266574310012370 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Abstract class representing one nestable element (non final) piece of information */ abstract class base_nested_element extends base_final_element { /** @var array final elements of the element (maps to XML final elements of the tag) */ private $final_elements; /** @var array children base_elements of this element (describes structure of the XML file) */ private $children; /** @var base_optigroup optional group of this element (branches to be processed conditionally) */ private $optigroup; /** @var array elements already used by the base_element, to avoid circular references */ private $used; /** * Constructor - instantiates one base_nested_element, specifying its basic info. * * @param string $name name of the element * @param array $attributes attributes this element will handle (optional, defaults to null) * @param array $final_elements this element will handle (optional, defaults to null) */ public function __construct($name, $attributes = null, $final_elements = null) { parent::__construct($name, $attributes); $this->final_elements = array(); if (!empty($final_elements)) { $this->add_final_elements($final_elements); } $this->children = array(); $this->optigroup = null; $this->used[] = $name; } /** * Destroy all circular references. It helps PHP 5.2 a lot! */ public function destroy() { // Before reseting anything, call destroy recursively foreach ($this->children as $child) { $child->destroy(); } foreach ($this->final_elements as $element) { $element->destroy(); } if ($this->optigroup) { $this->optigroup->destroy(); } // Everything has been destroyed recursively, now we can reset safely $this->children = array(); $this->final_elements = array(); $this->optigroup = null; // Delegate to parent to destroy other bits parent::destroy(); } protected function get_used() { return $this->used; } protected function set_used($used) { $this->used = $used; } protected function add_used($element) { $this->used = array_merge($this->used, $element->get_used()); } protected function check_and_set_used($element) { // First of all, check the element being added doesn't conflict with own final elements if (array_key_exists($element->get_name(), $this->final_elements)) { throw new base_element_struct_exception('baseelementchildnameconflict', $element->get_name()); } $grandparent = $this->get_grandoptigroupelement_or_grandparent(); if ($existing = array_intersect($grandparent->get_used(), $element->get_used())) { // Check the element isn't being used already throw new base_element_struct_exception('baseelementexisting', implode($existing)); } $grandparent->add_used($element); // If the parent is one optigroup, add the element useds to it too if ($grandparent->get_parent() instanceof base_optigroup) { $grandparent->get_parent()->add_used($element); } } /// Public API starts here public function get_final_elements() { return $this->final_elements; } public function get_final_element($name) { if (array_key_exists($name, $this->final_elements)) { return $this->final_elements[$name]; } else { return null; } } public function get_children() { return $this->children; } public function get_child($name) { if (array_key_exists($name, $this->children)) { return $this->children[$name]; } else { return null; } } public function get_optigroup() { return $this->optigroup; } public function add_final_elements($final_elements) { if ($final_elements instanceof base_final_element || is_string($final_elements)) { // Accept 1 final_element, object or string $final_elements = array($final_elements); } if (is_array($final_elements)) { foreach ($final_elements as $final_element) { if (is_string($final_element)) { // Accept string final_elements $final_element = $this->get_new_final_element($final_element); } if (!($final_element instanceof base_final_element)) { throw new base_element_struct_exception('baseelementnofinalelement', get_class($final_element)); } if (array_key_exists($final_element->get_name(), $this->final_elements)) { throw new base_element_struct_exception('baseelementexists', $final_element->get_name()); } $this->final_elements[$final_element->get_name()] = $final_element; $final_element->set_parent($this); } } else { throw new base_element_struct_exception('baseelementincorrect'); } } public function add_child($element) { if (!is_object($element) || !($element instanceof base_nested_element)) { // parameter must be a base_nested_element if (!is_object($element) || !($found = get_class($element))) { $found = 'non object'; } throw new base_element_struct_exception('nestedelementincorrect', $found); } $this->check_and_set_used($element); $this->children[$element->get_name()] = $element; $element->set_parent($this); } public function add_optigroup($optigroup) { if (!($optigroup instanceof base_optigroup)) { // parameter must be a base_optigroup if (!$found = get_class($optigroup)) { $found = 'non object'; } throw new base_element_struct_exception('optigroupincorrect', $found); } if ($this->optigroup !== null) { throw new base_element_struct_exception('optigroupalreadyset', $found); } $this->check_and_set_used($optigroup); $this->optigroup = $optigroup; $optigroup->set_parent($this); } public function get_value() { throw new base_element_struct_exception('nestedelementnotvalue'); } public function set_value($value) { throw new base_element_struct_exception('nestedelementnotvalue'); } public function clean_value() { throw new base_element_struct_exception('nestedelementnotvalue'); } public function clean_values() { parent::clean_values(); if (!empty($this->final_elements)) { foreach ($this->final_elements as $final_element) { $final_element->clean_values(); } } if (!empty($this->children)) { foreach ($this->children as $child) { $child->clean_values(); } } if (!empty($this->optigroup)) { $this->optigroup->clean_values(); } } public function to_string($showvalue = false) { $output = parent::to_string($showvalue); if (!empty($this->final_elements)) { foreach ($this->final_elements as $final_element) { $output .= PHP_EOL . $final_element->to_string($showvalue); } } if (!empty($this->children)) { foreach ($this->children as $child) { $output .= PHP_EOL . $child->to_string($showvalue); } } if (!empty($this->optigroup)) { $output .= PHP_EOL . $this->optigroup->to_string($showvalue); } return $output; } // Implementable API /** * Returns one instace of the @final_element class to work with * when final_elements are added simply by name */ abstract protected function get_new_final_element($name); } /** * base_element exception to control all the errors while building the nested tree * * This exception will be thrown each time the base_element class detects some * inconsistency related with the building of the nested tree representing one base part * (invalid objects, circular references, double parents...) */ class base_element_struct_exception extends base_atom_exception { /** * Constructor - instantiates one base_element_struct_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } restore_path_element.class.php000064400000010534152266574310012607 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Class representing one path to be restored from XML file */ class restore_path_element { /** @var string name of the element */ private $name; /** @var string path within the XML file this element will handle */ private $path; /** @var bool flag to define if this element will get child ones grouped or no */ private $grouped; /** @var object object instance in charge of processing this element. */ private $pobject; /** @var mixed last data read for this element or returned data by processing method */ private $data; /** * Constructor - instantiates one restore_path_element, specifying its basic info. * * @param string $name name of the thing being restored. This determines the name of the process_... method called. * @param string $path path of the element. * @param bool $grouped to gather information in grouped mode or no. */ public function __construct($name, $path, $grouped = false) { $this->validate_name($name); // Check name $this->name = $name; $this->path = $path; $this->grouped = $grouped; $this->pobject = null; $this->data = null; } protected function validate_name($name) { // Validate various name constraints, throwing exception if needed if (empty($name)) { throw new restore_path_element_exception('restore_path_element_emptyname', $name); } if (preg_replace('/\s/', '', $name) != $name) { throw new restore_path_element_exception('restore_path_element_whitespace', $name); } if (preg_replace('/[^\x30-\x39\x41-\x5a\x5f\x61-\x7a]/', '', $name) != $name) { throw new restore_path_element_exception('restore_path_element_notasciiname', $name); } } protected function validate_pobject($pobject) { if (!is_object($pobject)) { throw new restore_path_element_exception('restore_path_element_noobject', $pobject); } if (!method_exists($pobject, $this->get_processing_method())) { throw new restore_path_element_exception('restore_path_element_missingmethod', $this->get_processing_method()); } } /// Public API starts here public function set_processing_object($pobject) { $this->validate_pobject($pobject); $this->pobject = $pobject; } public function set_data($data) { $this->data = $data; } public function get_name() { return $this->name; } public function get_path() { return $this->path; } public function is_grouped() { return $this->grouped; } public function get_processing_object() { return $this->pobject; } public function get_processing_method() { return 'process_' . $this->name; } public function get_data() { return $this->data; } } /** * restore_path_element exception class */ class restore_path_element_exception extends moodle_exception { /** * Constructor - instantiates one restore_path_element_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, '', '', $a, $debuginfo); } } base_attribute.class.php000064400000002247152266574310011376 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Abstract class representing one attribute atom (name/value) piece of information */ abstract class base_attribute extends base_atom { public function to_string($showvalue = false) { return '@' . parent::to_string($showvalue); } } base_optigroup.class.php000064400000014531152266574310011422 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Abstract class representing one optigroup for conditional branching */ abstract class base_optigroup extends base_nested_element { /** @var boolean flag indicating if multiple branches can be processed (true) or no (false) */ private $multiple; /** * Constructor - instantiates one base_optigroup, specifying its basic info * * @param string $name name of the element * @param array $elements base_optigroup_elements of this group * @param bool $multiple to decide if the group allows multiple branches processing or no */ public function __construct($name, $elements = null, $multiple = false) { parent::__construct($name); $this->multiple = $multiple; if (!empty($elements)) { $this->add_children($elements); } } // Public API starts here /** * Return the level of this element, that will be, the level of the parent (doesn't consume level) * (note this os only a "cosmetic" effect (to_string) as fact as the real responsible for this * is the corresponding structure_processor for the final output. */ public function get_level() { return $this->get_parent() == null ? 1 : $this->get_parent()->get_level(); } public function to_string($showvalue = false) { $indent = str_repeat(' ', $this->get_level()); // Indent output based in level (4cc) $output = $indent . '!' . $this->get_name() . ' (level: ' . $this->get_level() . ')'; $children = $this->get_children(); if (!empty($children)) { foreach ($this->get_children() as $child) { $output .= PHP_EOL . $child->to_string($showvalue); } } return $output; } // Forbidden API starts here /** * Adding attributes is forbidden */ public function add_attributes($attributes) { throw new base_element_struct_exception('optigroup_not_attributes'); } /** * Instantiating attributes is forbidden */ protected function get_new_attribute($name) { throw new base_element_struct_exception('optigroup_not_attributes'); } /** * Adding final elements is forbidden */ public function add_final_elements($attributes) { throw new base_element_struct_exception('optigroup_not_final_elements'); } /** * Instantiating final elements is forbidden */ protected function get_new_final_element($name) { throw new base_element_struct_exception('optigroup_not_final_elements'); } // Protected API starts here protected function add_children($elements) { if ($elements instanceof base_nested_element) { // Accept 1 element, object $elements = array($elements); } if (is_array($elements)) { foreach ($elements as $element) { $this->add_child($element); } } else { throw new base_optigroup_exception('optigroup_elements_incorrect'); } } /** * Set the parent of the optigroup and, at the same time, process all the * condition params in all the childs */ protected function set_parent($element) { parent::set_parent($element); // Force condition param calculation in all children foreach ($this->get_children() as $child) { $child->set_condition($child->get_condition_param(), $child->get_condition_value()); } } /** * Recalculate all the used elements in the optigroup, observing * restrictions and passing the new used to outer level */ protected function add_used($element) { $newused = array(); // Iterate over all the element useds, filling $newused and // observing the multiple setting foreach ($element->get_used() as $used) { if (!in_array($used, $this->get_used())) { // it's a new one, add to $newused array $newused[] = $used; $this->set_used(array_merge($this->get_used(), array($used))); // add to the optigroup used array } else { // it's an existing one, exception on multiple optigroups if ($this->multiple) { throw new base_optigroup_exception('multiple_optigroup_duplicate_element', $used); } } } // Finally, inform about newused to the next grand(parent/optigroupelement) if ($newused && $this->get_parent()) { $element->set_used($newused); // Only about the newused $grandparent = $this->get_grandoptigroupelement_or_grandparent(); $grandparent->check_and_set_used($element); } } protected function is_multiple() { return $this->multiple; } } /** * base_optigroup_exception to control all the errors while building the optigroups * * This exception will be thrown each time the base_optigroup class detects some * inconsistency related with the building of the group */ class base_optigroup_exception extends base_atom_exception { /** * Constructor - instantiates one base_optigroup_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } backup_optigroup_element.class.php000064400000016546152266574310013476 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Instantiable class representing one optigroup element for conditional branching * * Objects of this class are internally nested elements, so they support having both * final elements and children (more nested elements) and are able to have one source * and all the stuff supported by nested elements. Their main differences are: * * - Support for conditional execution, using simple equality checks with outer values. * - Don't have representation in the hierarchy, so: * - Their level is the level of the parent of their enclosing optigroup. * - Act as one "path bridge" when looking for parent path values * - They don't support attributes * * Their main use is to allow conditional branching, basically for optional submodules * like question types, assignment subtypes... where different subtrees of information * must be exported. It's correct to assume that each submodule will define its own * optigroup_element for backup purposes. */ class backup_optigroup_element extends backup_nested_element { private $conditionparam; // Unprocessed param representing on path to look for value private $procconditionparam; // Processed base_element param to look for value private $conditionvalue; // Value to compare the the param value with /** * Constructor - instantiates one backup_optigroup_element * * @param string $name of the element * @param array $final_elements this element will handle (optional, defaults to null) * @param string $condition_param param (path) we are using as source for comparing (optional, defaults to null) * @param string $condition_value value we are comparing to (optional, defaults to null) */ public function __construct($name, $final_elements = null, $conditionparam = null, $conditionvalue = null) { parent::__construct($name, null, $final_elements); $this->set_condition($conditionparam, $conditionvalue); } // Public API starts here /** * Sets the condition for this optigroup */ public function set_condition($conditionparam, $conditionvalue) { // We only resolve the condition if the parent of the element (optigroup) already has parent // else, we'll resolve it once the optigroup parent is defined if ($this->get_parent() && $this->get_parent()->get_parent() && $conditionparam !== null) { $this->procconditionparam = $this->find_element($conditionparam); } $this->conditionparam = $conditionparam; $this->conditionvalue = $conditionvalue; } public function get_condition_param() { return $this->conditionparam; } public function get_condition_value() { return $this->conditionvalue; } /** * Evaluate the condition, returning if matches (true) or no (false) */ public function condition_matches() { $match = false; // By default no match $param = $this->procconditionparam; if ($param instanceof base_atom && $param->is_set()) { $match = ($param->get_value() == $this->conditionvalue); // blame $DB for not having === ! } else { $match = ($param == $this->conditionvalue); } return $match; } /** * Return the level of this element, that will be, the level of the parent (doesn't consume level) * (note this os only a "cosmetic" effect (to_string) as fact as the real responsible for this * is the corresponding structure_processor for the final output. */ public function get_level() { return $this->get_parent() == null ? 1 : $this->get_parent()->get_level(); } /** * process one optigroup_element * * Note that this ONLY processes the final elements in order to get all them * before processing any nested element. Pending nested elements are processed * by the optigroup caller. */ public function process($processor) { if (!$processor instanceof base_processor) { // No correct processor, throw exception throw new base_element_struct_exception('incorrect_processor'); } $iterator = $this->get_iterator($processor); // Get the iterator over backup-able data $itcounter = 0; // To check that the iterator only has 1 ocurrence foreach ($iterator as $key => $values) { // Process each "ocurrrence" of the nested element (recordset or array) // Fill the values of the attributes and final elements with the $values from the iterator $this->fill_values($values); // Delegate the process of each final_element foreach ($this->get_final_elements() as $final_element) { $final_element->process($processor); } // Everything processed, clean values before next iteration $this->clean_values(); // Increment counters for this element $this->counter++; $itcounter++; // optigroup_element, check we only have 1 element always if ($itcounter > 1) { throw new base_element_struct_exception('optigroup_element_only_one_ocurrence', $this->get_name()); } } // Close the iterator (DB recordset / array iterator) $iterator->close(); } // Forbidden API starts here /** * Adding optigroups is forbidden */ public function add_add_optigroup($optigroup) { throw new base_element_struct_exception('optigroup_element_not_optigroup'); } /** * Adding attributes is forbidden */ public function add_attributes($attributes) { throw new base_element_struct_exception('optigroup_element_not_attributes'); } /** * Instantiating attributes is forbidden */ protected function get_new_attribute($name) { throw new base_element_struct_exception('optigroup_element_not_attributes'); } // Protected API starts here /** * Returns one instace of the @final_element class to work with * when final_elements are added simply by name */ protected function get_new_final_element($name) { return new backup_final_element($name); } /** * Set the parent of the optigroup_element and, at the same time, * process the condition param */ protected function set_parent($element) { parent::set_parent($element); // Force condition param calculation $this->set_condition($this->conditionparam, $this->conditionvalue); } } backup_nested_element.class.php000064400000032157152266574310012724 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Instantiable class representing one nestable element (non final) piece of information on backup */ class backup_nested_element extends base_nested_element implements processable { /** @var array To be used in case we pass one in-memory structure */ protected $var_array; /** @var string */ protected $table; // Table (without prefix) to fetch records from /** @var string */ protected $tablesortby; // The field to sort by when using the table methods /** @var string */ protected $sql; // Raw SQL to fetch records from /** @var mixed */ protected $params; // Unprocessed params as specified in the set_source() call /** @var array */ protected $procparams;// Processed (path resolved) params array /** @var array */ protected $aliases; // Define DB->final element aliases /** @var array */ protected $fileannotations; // array of file areas to be searched by file annotations /** @var int */ protected $counter; // Number of instances of this element that have been processed /** @var array */ protected $results; // Logs the results we encounter during the process. /** @var stdClass[] */ protected $logs; // Some log messages that could be retrieved later. /** * Constructor - instantiates one backup_nested_element, specifying its basic info. * * @param string $name name of the element * @param array $attributes attributes this element will handle (optional, defaults to null) * @param array $final_elements this element will handle (optional, defaults to null) */ public function __construct($name, $attributes = null, $final_elements = null) { parent::__construct($name, $attributes, $final_elements); $this->var_array = null; $this->table = null; $this->tablesortby = null; $this->sql = null; $this->params = null; $this->procparams= null; $this->aliases = array(); $this->fileannotations = array(); $this->counter = 0; $this->results = array(); $this->logs = array(); } /** * Process the nested element * * @param object $processor the processor * @return void */ public function process($processor) { if (!$processor instanceof base_processor) { // No correct processor, throw exception throw new base_element_struct_exception('incorrect_processor'); } $iterator = $this->get_iterator($processor); // Get the iterator over backup-able data foreach ($iterator as $key => $values) { // Process each "ocurrrence" of the nested element (recordset or array) // Fill the values of the attributes and final elements with the $values from the iterator $this->fill_values($values); // Perform pre-process tasks for the nested_element $processor->pre_process_nested_element($this); // Delegate the process of each attribute foreach ($this->get_attributes() as $attribute) { $attribute->process($processor); } // Main process tasks for the nested element, once its attributes have been processed $processor->process_nested_element($this); // Delegate the process of each final_element foreach ($this->get_final_elements() as $final_element) { $final_element->process($processor); } // Delegate the process to the optigroup if ($this->get_optigroup()) { $this->get_optigroup()->process($processor); } // Delegate the process to each child nested_element foreach ($this->get_children() as $child) { $child->process($processor); } // Perform post-process tasks for the nested element $processor->post_process_nested_element($this); // Everything processed, clean values before next iteration $this->clean_values(); // Increment counter for this element $this->counter++; // For root element, check we only have 1 element if ($this->get_parent() === null && $this->counter > 1) { throw new base_element_struct_exception('root_only_one_ocurrence', $this->get_name()); } } // Close the iterator (DB recordset / array iterator) $iterator->close(); } /** * Saves a log message to an array * * @see backup_helper::log() * @param string $message to add to the logs * @param int $level level of importance {@link backup::LOG_DEBUG} and other constants * @param mixed $a to be included in $message * @param int $depth of the message * @param display $bool supporting translation via get_string() if true * @return void */ protected function add_log($message, $level, $a = null, $depth = null, $display = false) { // Adding the result to the oldest parent. if ($this->get_parent()) { $parent = $this->get_grandparent(); $parent->add_log($message, $level, $a, $depth, $display); } else { $log = new stdClass(); $log->message = $message; $log->level = $level; $log->a = $a; $log->depth = $depth; $log->display = $display; $this->logs[] = $log; } } /** * Saves the results to an array * * @param array $result associative array * @return void */ protected function add_result($result) { if (is_array($result)) { // Adding the result to the oldest parent. if ($this->get_parent()) { $parent = $this->get_grandparent(); $parent->add_result($result); } else { $this->results = array_merge($this->results, $result); } } } /** * Returns the logs * * @return array of log objects */ public function get_logs() { return $this->logs; } /** * Returns the results * * @return associative array of results */ public function get_results() { return $this->results; } public function set_source_array($arr) { // TODO: Only elements having final elements can set source $this->var_array = $arr; } public function set_source_table($table, $params, $sortby = null) { if (!is_array($params)) { // Check we are passing array throw new base_element_struct_exception('setsourcerequiresarrayofparams'); } // TODO: Only elements having final elements can set source $this->table = $table; $this->procparams = $this->convert_table_params($params); if ($sortby) { $this->tablesortby = $sortby; } } public function set_source_sql($sql, $params) { if (!is_array($params)) { // Check we are passing array throw new base_element_struct_exception('setsourcerequiresarrayofparams'); } // TODO: Only elements having final elements can set source $this->sql = $sql; $this->procparams = $this->convert_sql_params($params); } public function set_source_alias($dbname, $finalelementname) { // Get final element $finalelement = $this->get_final_element($finalelementname); if (!$finalelement) { // Final element incorrect, throw exception throw new base_element_struct_exception('incorrectaliasfinalnamenotfound', $finalelementname); } else { $this->aliases[$dbname] = $finalelement; } } public function annotate_files($component, $filearea, $elementname, $filesctxid = null) { if (!array_key_exists($component, $this->fileannotations)) { $this->fileannotations[$component] = array(); } if ($elementname !== null) { // Check elementname is valid $elementname = $this->find_element($elementname); //TODO: no warning here? (skodak) } if (array_key_exists($filearea, $this->fileannotations[$component])) { throw new base_element_struct_exception('annotate_files_duplicate_annotation', "$component/$filearea/$elementname"); } $info = new stdclass(); $info->element = $elementname; $info->contextid = $filesctxid; $this->fileannotations[$component][$filearea] = $info; } public function annotate_ids($itemname, $elementname) { $element = $this->find_element($elementname); $element->set_annotation_item($itemname); } /** * Returns one array containing the element in the * @backup_structure and the areas to be searched */ public function get_file_annotations() { return $this->fileannotations; } public function get_source_array() { return $this->var_array; } public function get_source_table() { return $this->table; } public function get_source_table_sortby() { return $this->tablesortby; } public function get_source_sql() { return $this->sql; } public function get_counter() { return $this->counter; } /** * Simple filler that, matching by name, will fill both attributes and final elements * depending of this nested element, debugging info about non-matching elements and/or * elements present in both places. Accept both arrays and objects. */ public function fill_values($values) { $values = (array)$values; foreach ($values as $key => $value) { $found = 0; if ($attribute = $this->get_attribute($key)) { // Set value for attributes $attribute->set_value($value); $found++; } if ($final = $this->get_final_element($key)) { // Set value for final elements $final->set_value($value); $found++; } if (isset($this->aliases[$key])) { // Last chance, set value by processing final element aliases $this->aliases[$key]->set_value($value); $found++; } // Found more than once, notice // TODO: Route this through backup loggers if ($found > 1) { debugging('Key found more than once ' . $key, DEBUG_DEVELOPER); } } } // Protected API starts here protected function convert_table_params($params) { return $this->convert_sql_params($params); } protected function convert_sql_params($params) { $procparams = array(); // Reset processed params foreach ($params as $key => $param) { $procparams[$key] = $this->find_element($param); } return $procparams; } protected function find_element($param) { if ($param == backup::VAR_PARENTID) { // Look for first parent having id attribute/final_element $param = $this->find_first_parent_by_name('id'); // If the param is array, with key 'sqlparam', return the value without modifications } else if (is_array($param) && array_key_exists('sqlparam', $param)) { return $param['sqlparam']; } else if (((int)$param) >= 0) { // Search by path if param isn't a backup::XXX candidate $param = $this->find_element_by_path($param); } return $param; // Return the param unmodified } /** * Returns one instace of the @base_attribute class to work with * when attributes are added simply by name */ protected function get_new_attribute($name) { return new backup_attribute($name); } /** * Returns one instace of the @final_element class to work with * when final_elements are added simply by name */ protected function get_new_final_element($name) { return new backup_final_element($name); } /** * Returns one PHP iterator over each "ocurrence" of this nested * element (array or DB recordset). Delegated to backup_structure_dbops class */ protected function get_iterator($processor) { return backup_structure_dbops::get_iterator($this, $this->procparams, $processor); } } backup_structure_processor.class.php000064400000012777152266574310014076 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Instantiable class defining the process of backup structures * * This class will process the given backup structure (nested/final/attribute) * based on its definition, triggering as many actions as necessary (pre/post * triggers, ids annotations, deciding based on settings, xml output...). Somehow * one visitor pattern to allow backup structures to work with nice decoupling */ class backup_structure_processor extends base_processor { protected $writer; // xml_writer where the processor is going to output data protected $vars; // array of backup::VAR_XXX => helper value pairs to be used by source specifications /** * @var \core\progress\base Progress tracker (null if none) */ protected $progress; /** * Constructor. * * @param xml_writer $writer XML writer to save data * @param c\core\progress\base$progress Progress tracker (optional) */ public function __construct(xml_writer $writer, ?\core\progress\base $progress = null) { $this->writer = $writer; $this->progress = $progress; $this->vars = array(); } public function set_var($key, $value) { if (isset($this->vars[$key])) { throw new backup_processor_exception('processorvariablealreadyset', $key); } $this->vars[$key] = $value; } public function get_var($key) { if (!isset($this->vars[$key])) { throw new backup_processor_exception('processorvariablenotfound', $key); } return $this->vars[$key]; } public function pre_process_nested_element(base_nested_element $nested) { // Send open tag to xml_writer $attrarr = array(); foreach ($nested->get_attributes() as $attribute) { $attrarr[$attribute->get_name()] = $attribute->get_value(); } $this->writer->begin_tag($nested->get_name(), $attrarr); } public function process_nested_element(base_nested_element $nested) { // Proceed with all the file annotations for this element $fileannotations = $nested->get_file_annotations(); if ($fileannotations) { // If there are areas to search $backupid = $this->get_var(backup::VAR_BACKUPID); foreach ($fileannotations as $component => $area) { foreach ($area as $filearea => $info) { $contextid = !is_null($info->contextid) ? $info->contextid : $this->get_var(backup::VAR_CONTEXTID); $itemid = !is_null($info->element) ? $info->element->get_value() : null; backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid, $this->progress); } } } } public function post_process_nested_element(base_nested_element $nested) { // Send close tag to xml_writer $this->writer->end_tag($nested->get_name()); if ($this->progress) { $this->progress->progress(); } } public function process_final_element(base_final_element $final) { // Send full tag to xml_writer and annotations (only if has value) if ($final->is_set()) { $attrarr = array(); foreach ($final->get_attributes() as $attribute) { $attrarr[$attribute->get_name()] = $attribute->get_value(); } $this->writer->full_tag($final->get_name(), $final->get_value(), $attrarr); if ($this->progress) { $this->progress->progress(); } // Annotate current value if configured to do so $final->annotate($this->get_var(backup::VAR_BACKUPID)); } } public function process_attribute(base_attribute $attribute) { // Annotate current value if configured to do so $attribute->annotate($this->get_var(backup::VAR_BACKUPID)); } } /** * backup_processor exception to control all the errors while working with backup_processors * * This exception will be thrown each time the backup_processors detects some * inconsistency related with the elements to process or its configuration */ class backup_processor_exception extends base_processor_exception { /** * Constructor - instantiates one backup_processor_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } tests/fixtures/structure_fixtures.php000064400000010312152266574310014271 0ustar00. /** * @package core_backup * @category phpunit * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff global $CFG; require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); /** * helper extended base_attribute class that implements some methods for instantiating and testing */ class mock_base_attribute extends base_attribute { // Nothing to do. Just allow instances to be created } /** * helper extended final_element class that implements some methods for instantiating and testing */ class mock_base_final_element extends base_final_element { /// Implementable API protected function get_new_attribute($name) { return new mock_base_attribute($name); } } /** * helper extended nested_element class that implements some methods for instantiating and testing */ class mock_base_nested_element extends base_nested_element { /// Implementable API protected function get_new_attribute($name) { return new mock_base_attribute($name); } protected function get_new_final_element($name) { return new mock_base_final_element($name); } } /** * helper extended optigroup class that implements some methods for instantiating and testing */ class mock_base_optigroup extends base_optigroup { /// Implementable API protected function get_new_attribute($name) { return new mock_base_attribute($name); } protected function get_new_final_element($name) { return new mock_base_final_element($name); } public function is_multiple() { return parent::is_multiple(); } } /** * helper class that extends backup_final_element in order to skip its value */ class mock_skip_final_element extends backup_final_element { public function set_value($value) { $this->clean_value(); } } /** * helper class that extends backup_final_element in order to modify its value */ class mock_modify_final_element extends backup_final_element { public function set_value($value) { parent::set_value('original was ' . $value . ', now changed'); } } /** * helper class that extends backup_final_element to delegate any calculation to another class */ class mock_final_element_interceptor extends backup_final_element { public function set_value($value) { // Get grandparent name $gpname = $this->get_grandparent()->get_name(); // Get parent name $pname = $this->get_parent()->get_name(); // Get my name $myname = $this->get_name(); // Define class and function name $classname = 'mock_' . $gpname . '_' . $pname . '_interceptor'; $methodname= 'intercept_' . $pname . '_' . $myname; // Invoke the interception method $result = call_user_func(array($classname, $methodname), $value); // Finally set it parent::set_value($result); } } /** * test interceptor class (its methods are called from interceptor) */ abstract class mock_forum_forum_interceptor { static function intercept_forum_completionposts($element) { return 'intercepted!'; } } /** * Instantiable class extending base_atom in order to be able to perform tests */ class mock_base_atom extends base_atom { // Nothing new in this class, just an instantiable base_atom class // with the is_set() method public for testing purposes public function is_set() { return parent::is_set(); } } tests/baseoptigroup_test.php000064400000014006152266574310012355 0ustar00. namespace core_backup; use base_atom_struct_exception; use base_element_struct_exception; use mock_base_attribute; use mock_base_final_element; use mock_base_nested_element; use mock_base_optigroup; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff require_once(__DIR__.'/fixtures/structure_fixtures.php'); /** * Unit test case the base_optigroup class. * * Note: highly imbricated with nested/final base elements * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class baseoptigroup_test extends \basic_testcase { /** * Correct creation tests (s) */ function test_creation(): void { $instance = new mock_base_optigroup('optigroup', null, true); $this->assertInstanceOf('base_optigroup', $instance); $this->assertEquals($instance->get_name(), 'optigroup'); $this->assertNull($instance->get_parent()); $this->assertEquals($instance->get_children(), array()); $this->assertEquals($instance->get_level(), 1); $this->assertTrue($instance->is_multiple()); // Get to_string() results (with values) $child1 = new mock_base_nested_element('child1', null, new mock_base_final_element('four')); $child2 = new mock_base_nested_element('child2', null, new mock_base_final_element('five')); $instance->add_child($child1); $instance->add_child($child2); $children = $instance->get_children(); $final_elements = $children['child1']->get_final_elements(); $final_elements['four']->set_value('final4value'); $final_elements['four']->add_attributes('attr4'); $grandchild = new mock_base_nested_element('grandchild', new mock_base_attribute('attr5')); $child2->add_child($grandchild); $attrs = $grandchild->get_attributes(); $attrs['attr5']->set_value('attr5value'); $tostring = $instance->to_string(true); $this->assertTrue(strpos($tostring, '!optigroup (level: 1)') !== false); $this->assertTrue(strpos($tostring, '?child2 (level: 2) =>') !== false); $this->assertTrue(strpos($tostring, ' => ') !== false); $this->assertTrue(strpos($tostring, '#four (level: 3) => final4value') !== false); $this->assertTrue(strpos($tostring, '@attr5 => attr5value') !== false); $this->assertTrue(strpos($tostring, '#five (level: 3) => not set') !== false); } /** * Incorrect creation tests (attributes and final elements) */ function test_wrong_creation(): void { // Create instance with invalid name try { $instance = new mock_base_nested_element(''); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } // Create instance with incorrect (object) final element try { $obj = new \stdClass; $obj->name = 'test_attr'; $instance = new mock_base_nested_element('TEST', null, $obj); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Create instance with array containing incorrect (object) final element try { $obj = new \stdClass; $obj->name = 'test_attr'; $instance = new mock_base_nested_element('TEST', null, array($obj)); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Create instance with array containing duplicate final elements try { $instance = new mock_base_nested_element('TEST', null, array('VAL1', 'VAL2', 'VAL1')); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Try to get value of base_nested_element $instance = new mock_base_nested_element('TEST'); try { $instance->get_value(); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Try to set value of base_nested_element $instance = new mock_base_nested_element('TEST'); try { $instance->set_value('some_value'); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Try to clean one value of base_nested_element $instance = new mock_base_nested_element('TEST'); try { $instance->clean_value('some_value'); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } } } tests/basefinalelement_test.php000064400000016455152266574310013002 0ustar00. namespace core_backup; use base_atom_struct_exception; use base_attribute; use base_element_attribute_exception; use mock_base_attribute; use mock_base_final_element; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff require_once(__DIR__.'/fixtures/structure_fixtures.php'); /** * Unit test case the base_final_element class. * * Note: highly imbricated with base_nested_element class * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class basefinalelement_test extends \basic_testcase { /** * Correct base_final_element tests */ function test_base_final_element(): void { // Create instance with name $instance = new mock_base_final_element('TEST'); $this->assertInstanceOf('base_final_element', $instance); $this->assertEquals($instance->get_name(), 'TEST'); $this->assertNull($instance->get_value()); $this->assertEquals($instance->get_attributes(), array()); $this->assertNull($instance->get_parent()); $this->assertEquals($instance->get_level(), 1); // Set value $instance->set_value('value'); $this->assertEquals($instance->get_value(), 'value'); // Create instance with name and one object attribute $instance = new mock_base_final_element('TEST', new mock_base_attribute('ATTR1')); $attrs = $instance->get_attributes(); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 1); $this->assertTrue($attrs['ATTR1'] instanceof base_attribute); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertNull($attrs['ATTR1']->get_value()); // Create instance with name and various object attributes $attr1 = new mock_base_attribute('ATTR1'); $attr1->set_value('attr1_value'); $attr2 = new mock_base_attribute('ATTR2'); $instance = new mock_base_final_element('TEST', array($attr1, $attr2)); $attrs = $instance->get_attributes(); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 2); $this->assertTrue($attrs['ATTR1'] instanceof base_attribute); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertEquals($attrs['ATTR1']->get_value(), 'attr1_value'); $this->assertTrue($attrs['ATTR2'] instanceof base_attribute); $this->assertEquals($attrs['ATTR2']->get_name(), 'ATTR2'); $this->assertNull($attrs['ATTR2']->get_value()); // Create instance with name and one string attribute $instance = new mock_base_final_element('TEST', 'ATTR1'); $attrs = $instance->get_attributes(); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 1); $this->assertTrue($attrs['ATTR1'] instanceof base_attribute); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertNull($attrs['ATTR1']->get_value()); // Create instance with name and various object attributes $instance = new mock_base_final_element('TEST', array('ATTR1', 'ATTR2')); $attrs = $instance->get_attributes(); $attrs['ATTR1']->set_value('attr1_value'); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 2); $this->assertTrue($attrs['ATTR1'] instanceof base_attribute); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertEquals($attrs['ATTR1']->get_value(), 'attr1_value'); $this->assertTrue($attrs['ATTR2'] instanceof base_attribute); $this->assertEquals($attrs['ATTR2']->get_name(), 'ATTR2'); $this->assertNull($attrs['ATTR2']->get_value()); // Clean values $instance = new mock_base_final_element('TEST', array('ATTR1', 'ATTR2')); $instance->set_value('instance_value'); $attrs = $instance->get_attributes(); $attrs['ATTR1']->set_value('attr1_value'); $this->assertEquals($instance->get_value(), 'instance_value'); $this->assertEquals($attrs['ATTR1']->get_value(), 'attr1_value'); $instance->clean_values(); $this->assertNull($instance->get_value()); $this->assertNull($attrs['ATTR1']->get_value()); // Get to_string() results (with values) $instance = new mock_base_final_element('TEST', array('ATTR1', 'ATTR2')); $instance->set_value('final element value'); $attrs = $instance->get_attributes(); $attrs['ATTR1']->set_value('attr1 value'); $tostring = $instance->to_string(true); $this->assertTrue(strpos($tostring, '#TEST (level: 1)') !== false); $this->assertTrue(strpos($tostring, ' => ') !== false); $this->assertTrue(strpos($tostring, 'final element value') !== false); $this->assertTrue(strpos($tostring, 'attr1 value') !== false); } /** * Exception base_final_element tests */ function test_base_final_element_exceptions(): void { // Create instance with invalid name try { $instance = new mock_base_final_element(''); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } // Create instance with incorrect (object) attribute try { $obj = new \stdClass; $obj->name = 'test_attr'; $instance = new mock_base_final_element('TEST', $obj); $this->fail("Expecting base_element_attribute_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_attribute_exception); } // Create instance with array containing incorrect (object) attribute try { $obj = new \stdClass; $obj->name = 'test_attr'; $instance = new mock_base_final_element('TEST', array($obj)); $this->fail("Expecting base_element_attribute_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_attribute_exception); } // Create instance with array containing duplicate attributes try { $instance = new mock_base_final_element('TEST', array('ATTR1', 'ATTR2', 'ATTR1')); $this->fail("Expecting base_element_attribute_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_attribute_exception); } } } tests/baseattribute_test.php000064400000004524152266574310012334 0ustar00. namespace core_backup; use mock_base_attribute; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff require_once(__DIR__.'/fixtures/structure_fixtures.php'); /** * Unit test case the base_attribute class. * * Note: No really much to test here as attribute is 100% * atom extension without new functionality (name/value) * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class baseattribute_test extends \basic_testcase { /** * Correct base_attribute tests */ function test_base_attribute(): void { $name_with_all_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'; $value_to_test = 'Some to test'; // Create instance with correct names $instance = new mock_base_attribute($name_with_all_chars); $this->assertInstanceOf('base_attribute', $instance); $this->assertEquals($instance->get_name(), $name_with_all_chars); $this->assertNull($instance->get_value()); // Set value $instance->set_value($value_to_test); $this->assertEquals($instance->get_value(), $value_to_test); // Get to_string() results (with values) $instance = new mock_base_attribute($name_with_all_chars); $instance->set_value($value_to_test); $tostring = $instance->to_string(true); $this->assertTrue(strpos($tostring, '@' . $name_with_all_chars) !== false); $this->assertTrue(strpos($tostring, ' => ') !== false); $this->assertTrue(strpos($tostring, $value_to_test) !== false); } } tests/basenestedelement_test.php000064400000045330152266574310013165 0ustar00. namespace core_backup; use base_atom_struct_exception; use base_element_parent_exception; use base_element_struct_exception; use mock_base_attribute; use mock_base_final_element; use mock_base_nested_element; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff require_once(__DIR__.'/fixtures/structure_fixtures.php'); /** * Unit test case the base_nested_element class. * * Note: highly imbricated with base_final_element class * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class basenestedelement_test extends \basic_testcase { /** * Correct creation tests (attributes and final elements) */ public function test_creation(): void { // Create instance with name, attributes and values and check all them $instance = new mock_base_nested_element('NAME', array('ATTR1', 'ATTR2'), array('VAL1', 'VAL2', 'VAL3')); $this->assertInstanceOf('base_nested_element', $instance); $this->assertEquals($instance->get_name(), 'NAME'); $attrs = $instance->get_attributes(); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 2); $this->assertInstanceOf('base_attribute', $attrs['ATTR1']); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertNull($attrs['ATTR1']->get_value()); $this->assertEquals($attrs['ATTR2']->get_name(), 'ATTR2'); $this->assertNull($attrs['ATTR2']->get_value()); $finals = $instance->get_final_elements(); $this->assertTrue(is_array($finals)); $this->assertEquals(count($finals), 3); $this->assertInstanceOf('base_final_element', $finals['VAL1']); $this->assertEquals($finals['VAL1']->get_name(), 'VAL1'); $this->assertNull($finals['VAL1']->get_value()); $this->assertEquals($finals['VAL1']->get_level(), 2); $this->assertInstanceOf('base_nested_element', $finals['VAL1']->get_parent()); $this->assertEquals($finals['VAL2']->get_name(), 'VAL2'); $this->assertNull($finals['VAL2']->get_value()); $this->assertEquals($finals['VAL2']->get_level(), 2); $this->assertInstanceOf('base_nested_element', $finals['VAL1']->get_parent()); $this->assertEquals($finals['VAL3']->get_name(), 'VAL3'); $this->assertNull($finals['VAL3']->get_value()); $this->assertEquals($finals['VAL3']->get_level(), 2); $this->assertInstanceOf('base_nested_element', $finals['VAL1']->get_parent()); $this->assertNull($instance->get_parent()); $this->assertEquals($instance->get_children(), array()); $this->assertEquals($instance->get_level(), 1); // Create instance with name only $instance = new mock_base_nested_element('NAME'); $this->assertInstanceOf('base_nested_element', $instance); $this->assertEquals($instance->get_name(), 'NAME'); $this->assertEquals($instance->get_attributes(), array()); $this->assertEquals($instance->get_final_elements(), array()); $this->assertNull($instance->get_parent()); $this->assertEquals($instance->get_children(), array()); $this->assertEquals($instance->get_level(), 1); // Add some attributes $instance->add_attributes(array('ATTR1', 'ATTR2')); $attrs = $instance->get_attributes(); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 2); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertNull($attrs['ATTR1']->get_value()); $this->assertEquals($attrs['ATTR2']->get_name(), 'ATTR2'); $this->assertNull($attrs['ATTR2']->get_value()); // And some more atributes $instance->add_attributes(array('ATTR3', 'ATTR4')); $attrs = $instance->get_attributes(); $this->assertTrue(is_array($attrs)); $this->assertEquals(count($attrs), 4); $this->assertEquals($attrs['ATTR1']->get_name(), 'ATTR1'); $this->assertNull($attrs['ATTR1']->get_value()); $this->assertEquals($attrs['ATTR2']->get_name(), 'ATTR2'); $this->assertNull($attrs['ATTR2']->get_value()); $this->assertEquals($attrs['ATTR3']->get_name(), 'ATTR3'); $this->assertNull($attrs['ATTR3']->get_value()); $this->assertEquals($attrs['ATTR4']->get_name(), 'ATTR4'); $this->assertNull($attrs['ATTR4']->get_value()); // Add some final elements $instance->add_final_elements(array('VAL1', 'VAL2', 'VAL3')); $finals = $instance->get_final_elements(); $this->assertTrue(is_array($finals)); $this->assertEquals(count($finals), 3); $this->assertEquals($finals['VAL1']->get_name(), 'VAL1'); $this->assertNull($finals['VAL1']->get_value()); $this->assertEquals($finals['VAL2']->get_name(), 'VAL2'); $this->assertNull($finals['VAL2']->get_value()); $this->assertEquals($finals['VAL3']->get_name(), 'VAL3'); $this->assertNull($finals['VAL3']->get_value()); // Add some more final elements $instance->add_final_elements('VAL4'); $finals = $instance->get_final_elements(); $this->assertTrue(is_array($finals)); $this->assertEquals(count($finals), 4); $this->assertEquals($finals['VAL1']->get_name(), 'VAL1'); $this->assertNull($finals['VAL1']->get_value()); $this->assertEquals($finals['VAL2']->get_name(), 'VAL2'); $this->assertNull($finals['VAL2']->get_value()); $this->assertEquals($finals['VAL3']->get_name(), 'VAL3'); $this->assertNull($finals['VAL3']->get_value()); $this->assertEquals($finals['VAL4']->get_name(), 'VAL4'); $this->assertNull($finals['VAL4']->get_value()); // Get to_string() results (with values) $instance = new mock_base_nested_element('PARENT', array('ATTR1', 'ATTR2'), array('FINAL1', 'FINAL2', 'FINAL3')); $child1 = new mock_base_nested_element('CHILD1', null, new mock_base_final_element('FINAL4')); $child2 = new mock_base_nested_element('CHILD2', null, new mock_base_final_element('FINAL5')); $instance->add_child($child1); $instance->add_child($child2); $children = $instance->get_children(); $final_elements = $children['CHILD1']->get_final_elements(); $final_elements['FINAL4']->set_value('final4value'); $final_elements['FINAL4']->add_attributes('ATTR4'); $grandchild = new mock_base_nested_element('GRANDCHILD', new mock_base_attribute('ATTR5')); $child2->add_child($grandchild); $attrs = $grandchild->get_attributes(); $attrs['ATTR5']->set_value('attr5value'); $tostring = $instance->to_string(true); $this->assertTrue(strpos($tostring, 'PARENT (level: 1)') !== false); $this->assertTrue(strpos($tostring, ' => ') !== false); $this->assertTrue(strpos($tostring, '#FINAL4 (level: 3) => final4value') !== false); $this->assertTrue(strpos($tostring, '@ATTR5 => attr5value') !== false); $this->assertTrue(strpos($tostring, '#FINAL5 (level: 3) => not set') !== false); // Clean values $instance = new mock_base_nested_element('PARENT', array('ATTR1', 'ATTR2'), array('FINAL1', 'FINAL2', 'FINAL3')); $child1 = new mock_base_nested_element('CHILD1', null, new mock_base_final_element('FINAL4')); $child2 = new mock_base_nested_element('CHILD2', null, new mock_base_final_element('FINAL4')); $instance->add_child($child1); $instance->add_child($child2); $children = $instance->get_children(); $final_elements = $children['CHILD1']->get_final_elements(); $final_elements['FINAL4']->set_value('final4value'); $final_elements['FINAL4']->add_attributes('ATTR4'); $grandchild = new mock_base_nested_element('GRANDCHILD', new mock_base_attribute('ATTR4')); $child2->add_child($grandchild); $attrs = $grandchild->get_attributes(); $attrs['ATTR4']->set_value('attr4value'); $this->assertEquals($final_elements['FINAL4']->get_value(), 'final4value'); $this->assertEquals($attrs['ATTR4']->get_value(), 'attr4value'); $instance->clean_values(); $this->assertNull($final_elements['FINAL4']->get_value()); $this->assertNull($attrs['ATTR4']->get_value()); } /** * Incorrect creation tests (attributes and final elements) */ function test_wrong_creation(): void { // Create instance with invalid name try { $instance = new mock_base_nested_element(''); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } // Create instance with incorrect (object) final element try { $obj = new \stdClass; $obj->name = 'test_attr'; $instance = new mock_base_nested_element('TEST', null, $obj); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Create instance with array containing incorrect (object) final element try { $obj = new \stdClass; $obj->name = 'test_attr'; $instance = new mock_base_nested_element('TEST', null, array($obj)); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Create instance with array containing duplicate final elements try { $instance = new mock_base_nested_element('TEST', null, array('VAL1', 'VAL2', 'VAL1')); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Try to get value of base_nested_element $instance = new mock_base_nested_element('TEST'); try { $instance->get_value(); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Try to set value of base_nested_element $instance = new mock_base_nested_element('TEST'); try { $instance->set_value('some_value'); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Try to clean one value of base_nested_element $instance = new mock_base_nested_element('TEST'); try { $instance->clean_value('some_value'); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } } /** * Correct tree tests (children stuff) */ function test_tree(): void { // Create parent and child instances, tree-ing them $parent = new mock_base_nested_element('PARENT'); $child = new mock_base_nested_element('CHILD'); $parent->add_child($child); $this->assertEquals($parent->get_children(), array('CHILD' => $child)); $this->assertEquals($child->get_parent(), $parent); $check_children = $parent->get_children(); $check_child = $check_children['CHILD']; $check_parent = $check_child->get_parent(); $this->assertEquals($check_child->get_name(), 'CHILD'); $this->assertEquals($check_parent->get_name(), 'PARENT'); $this->assertEquals($check_child->get_level(), 2); $this->assertEquals($check_parent->get_level(), 1); $this->assertEquals($check_parent->get_children(), array('CHILD' => $child)); $this->assertEquals($check_child->get_parent(), $parent); // Add parent to grandparent $grandparent = new mock_base_nested_element('GRANDPARENT'); $grandparent->add_child($parent); $this->assertEquals($grandparent->get_children(), array('PARENT' => $parent)); $this->assertEquals($parent->get_parent(), $grandparent); $this->assertEquals($parent->get_children(), array('CHILD' => $child)); $this->assertEquals($child->get_parent(), $parent); $this->assertEquals($child->get_level(), 3); $this->assertEquals($parent->get_level(), 2); $this->assertEquals($grandparent->get_level(), 1); // Add grandchild to child $grandchild = new mock_base_nested_element('GRANDCHILD'); $child->add_child($grandchild); $this->assertEquals($child->get_children(), array('GRANDCHILD' => $grandchild)); $this->assertEquals($grandchild->get_parent(), $child); $this->assertEquals($grandchild->get_level(), 4); $this->assertEquals($child->get_level(), 3); $this->assertEquals($parent->get_level(), 2); $this->assertEquals($grandparent->get_level(), 1); // Add another child to parent $child2 = new mock_base_nested_element('CHILD2'); $parent->add_child($child2); $this->assertEquals($parent->get_children(), array('CHILD' => $child, 'CHILD2' => $child2)); $this->assertEquals($child2->get_parent(), $parent); $this->assertEquals($grandchild->get_level(), 4); $this->assertEquals($child->get_level(), 3); $this->assertEquals($child2->get_level(), 3); $this->assertEquals($parent->get_level(), 2); $this->assertEquals($grandparent->get_level(), 1); } /** * Incorrect tree tests (children stuff) */ function test_wrong_tree(): void { // Add null object child $parent = new mock_base_nested_element('PARENT'); $child = null; try { $parent->add_child($child); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Add non base_element object child $parent = new mock_base_nested_element('PARENT'); $child = new \stdClass(); try { $parent->add_child($child); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Add existing element (being parent) $parent = new mock_base_nested_element('PARENT'); $child = new mock_base_nested_element('PARENT'); try { $parent->add_child($child); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Add existing element (being grandparent) $grandparent = new mock_base_nested_element('GRANDPARENT'); $parent = new mock_base_nested_element('PARENT'); $child = new mock_base_nested_element('GRANDPARENT'); $grandparent->add_child($parent); try { $parent->add_child($child); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Add existing element (being grandchild) $grandparent = new mock_base_nested_element('GRANDPARENT'); $parent = new mock_base_nested_element('PARENT'); $child = new mock_base_nested_element('GRANDPARENT'); $parent->add_child($child); try { $grandparent->add_child($parent); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Add existing element (being cousin) $grandparent = new mock_base_nested_element('GRANDPARENT'); $parent1 = new mock_base_nested_element('PARENT1'); $parent2 = new mock_base_nested_element('PARENT2'); $child1 = new mock_base_nested_element('CHILD1'); $child2 = new mock_base_nested_element('CHILD1'); $grandparent->add_child($parent1); $parent1->add_child($child1); $parent2->add_child($child2); try { $grandparent->add_child($parent2); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); } // Add element to two parents $parent1 = new mock_base_nested_element('PARENT1'); $parent2 = new mock_base_nested_element('PARENT2'); $child = new mock_base_nested_element('CHILD'); $parent1->add_child($child); try { $parent2->add_child($child); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_parent_exception); } // Add child element already used by own final elements $nested = new mock_base_nested_element('PARENT1', null, array('FINAL1', 'FINAL2')); $child = new mock_base_nested_element('FINAL2', null, array('FINAL3', 'FINAL4')); try { $nested->add_child($child); $this->fail("Expecting base_element_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'baseelementchildnameconflict'); $this->assertEquals($e->a, 'FINAL2'); } } } tests/baseatom_test.php000064400000011016152266574310011263 0ustar00. namespace core_backup; use base_atom_content_exception; use base_atom_struct_exception; use mock_base_atom; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff require_once(__DIR__.'/fixtures/structure_fixtures.php'); /** * Unit test case the base_atom class. * * Note: as it's abstract we are testing mock_base_atom instantiable class instead * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class baseatom_test extends \basic_testcase { /** * Correct base_atom_tests */ function test_base_atom(): void { $name_with_all_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'; $value_to_test = 'Some to test'; // Create instance with correct names $instance = new mock_base_atom($name_with_all_chars); $this->assertInstanceOf('base_atom', $instance); $this->assertEquals($instance->get_name(), $name_with_all_chars); $this->assertFalse($instance->is_set()); $this->assertNull($instance->get_value()); // Set value $instance->set_value($value_to_test); $this->assertEquals($instance->get_value(), $value_to_test); $this->assertTrue($instance->is_set()); // Clean value $instance->clean_value(); $this->assertFalse($instance->is_set()); $this->assertNull($instance->get_value()); // Get to_string() results (with values) $instance = new mock_base_atom($name_with_all_chars); $instance->set_value($value_to_test); $tostring = $instance->to_string(true); $this->assertTrue(strpos($tostring, $name_with_all_chars) !== false); $this->assertTrue(strpos($tostring, ' => ') !== false); $this->assertTrue(strpos($tostring, $value_to_test) !== false); // Get to_string() results (without values) $tostring = $instance->to_string(false); $this->assertTrue(strpos($tostring, $name_with_all_chars) !== false); $this->assertFalse(strpos($tostring, ' => ')); $this->assertFalse(strpos($tostring, $value_to_test)); } /** * Throwing exception base_atom tests */ function test_base_atom_exceptions(): void { // empty names try { $instance = new mock_base_atom(''); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } // whitespace names try { $instance = new mock_base_atom('TESTING ATOM'); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } // ascii names try { $instance = new mock_base_atom('TESTING-ATOM'); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } try { $instance = new mock_base_atom('TESTING_ATOM_Á'); $this->fail("Expecting base_atom_struct_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_struct_exception); } // setting already set value $instance = new mock_base_atom('TEST'); $instance->set_value('test'); try { $instance->set_value('test'); $this->fail("Expecting base_atom_content_exception exception, none occurred"); } catch (\Exception $e) { $this->assertTrue($e instanceof base_atom_content_exception); } } } tests/structure_test.php000064400000100137152266574310011533 0ustar00. namespace core_backup; use backup; use backup_attribute; use backup_controller_dbops; use backup_final_element; use backup_nested_element; use backup_optigroup; use backup_optigroup_element; use backup_processor_exception; use backup_structure_processor; use base_element_struct_exception; use base_optigroup_exception; use base_processor; use memory_xml_output; use xml_writer; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff. require_once(__DIR__.'/fixtures/structure_fixtures.php'); global $CFG; require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php'); /** * Unit test case the all the backup structure classes. Note: Uses database * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class structure_test extends \advanced_testcase { /** @var int Store the inserted forum->id for use in test functions */ protected $forumid; /** @var int Store the inserted discussion1->id for use in test functions */ protected $discussionid1; /** @var int Store the inserted discussion2->id for use in test functions */ protected $discussionid2; /** @var int Store the inserted post1->id for use in test functions */ protected $postid1; /** @var int Store the inserted post2->id for use in test functions */ protected $postid2; /** @var int Store the inserted post3->id for use in test functions */ protected $postid3; /** @var int Store the inserted post4->id for use in test functions */ protected $postid4; /** @var int Official contextid for these tests */ protected $contextid; protected function setUp(): void { parent::setUp(); $this->resetAfterTest(true); $this->contextid = 666; // Let's assume this is the context for the forum $this->fill_records(); // Add common stuff needed by various test methods } private function fill_records() { global $DB; // Create one forum $forum_data = (object)array('course' => 1, 'name' => 'Test forum', 'intro' => 'Intro forum'); $this->forumid = $DB->insert_record('forum', $forum_data); // With two related file $f1_forum_data = (object)array( 'contenthash' => 'testf1', 'contextid' => $this->contextid, 'component'=>'mod_forum', 'filearea' => 'intro', 'filename' => 'tf1', 'itemid' => 0, 'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0, 'pathnamehash' => 'testf1' ); $DB->insert_record('files', $f1_forum_data); $f2_forum_data = (object)array( 'contenthash' => 'tesft2', 'contextid' => $this->contextid, 'component'=>'mod_forum', 'filearea' => 'intro', 'filename' => 'tf2', 'itemid' => 0, 'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0, 'pathnamehash' => 'testf2' ); $DB->insert_record('files', $f2_forum_data); // Create two discussions $discussion1 = (object)array('course' => 1, 'forum' => $this->forumid, 'name' => 'd1', 'userid' => 100, 'groupid' => 200); $this->discussionid1 = $DB->insert_record('forum_discussions', $discussion1); $discussion2 = (object)array('course' => 1, 'forum' => $this->forumid, 'name' => 'd2', 'userid' => 101, 'groupid' => 201); $this->discussionid2 = $DB->insert_record('forum_discussions', $discussion2); // Create four posts $post1 = (object)array('discussion' => $this->discussionid1, 'userid' => 100, 'subject' => 'p1', 'message' => 'm1'); $this->postid1 = $DB->insert_record('forum_posts', $post1); $post2 = (object)array('discussion' => $this->discussionid1, 'parent' => $this->postid1, 'userid' => 102, 'subject' => 'p2', 'message' => 'm2'); $this->postid2 = $DB->insert_record('forum_posts', $post2); $post3 = (object)array('discussion' => $this->discussionid1, 'parent' => $this->postid2, 'userid' => 103, 'subject' => 'p3', 'message' => 'm3'); $this->postid3 = $DB->insert_record('forum_posts', $post3); $post4 = (object)array('discussion' => $this->discussionid2, 'userid' => 101, 'subject' => 'p4', 'message' => 'm4'); $this->postid4 = $DB->insert_record('forum_posts', $post4); // With two related file $f1_post1 = (object)array( 'contenthash' => 'testp1', 'contextid' => $this->contextid, 'component'=>'mod_forum', 'filearea' => 'post', 'filename' => 'tp1', 'itemid' => $this->postid1, 'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0, 'pathnamehash' => 'testp1' ); $DB->insert_record('files', $f1_post1); $f1_post2 = (object)array( 'contenthash' => 'testp2', 'contextid' => $this->contextid, 'component'=>'mod_forum', 'filearea' => 'attachment', 'filename' => 'tp2', 'itemid' => $this->postid2, 'filesize' => 123, 'timecreated' => 0, 'timemodified' => 0, 'pathnamehash' => 'testp2' ); $DB->insert_record('files', $f1_post2); // Create two ratings $rating1 = (object)array( 'contextid' => $this->contextid, 'userid' => 104, 'itemid' => $this->postid1, 'rating' => 2, 'scaleid' => -1, 'timecreated' => time(), 'timemodified' => time()); $r1id = $DB->insert_record('rating', $rating1); $rating2 = (object)array( 'contextid' => $this->contextid, 'userid' => 105, 'itemid' => $this->postid1, 'rating' => 3, 'scaleid' => -1, 'timecreated' => time(), 'timemodified' => time()); $r2id = $DB->insert_record('rating', $rating2); // Create 1 reads $read1 = (object)array('userid' => 102, 'forumid' => $this->forumid, 'discussionid' => $this->discussionid2, 'postid' => $this->postid4); $DB->insert_record('forum_read', $read1); } /** * Backup structures tests (construction, definition and execution) */ function test_backup_structure_construct(): void { global $DB; $backupid = 'Testing Backup ID'; // Official backupid for these tests // Create all the elements that will conform the tree $forum = new backup_nested_element('forum', array('id'), array( 'type', 'name', 'intro', 'introformat', 'assessed', 'assesstimestart', 'assesstimefinish', 'scale', 'maxbytes', 'maxattachments', 'forcesubscribe', 'trackingtype', 'rsstype', 'rssarticles', 'timemodified', 'warnafter', 'blockafter', new backup_final_element('blockperiod'), new \mock_skip_final_element('completiondiscussions'), new \mock_modify_final_element('completionreplies'), new \mock_final_element_interceptor('completionposts')) ); $discussions = new backup_nested_element('discussions'); $discussion = new backup_nested_element('discussion', array('id'), array( 'forum', 'name', 'firstpost', 'userid', 'groupid', 'assessed', 'timemodified', 'usermodified', 'timestart', 'timeend') ); $posts = new backup_nested_element('posts'); $post = new backup_nested_element('post', array('id'), array( 'discussion', 'parent', 'userid', 'created', 'modified', 'mailed', 'subject', 'message', 'messageformat', 'messagetrust', 'attachment', 'totalscore', 'mailnow') ); $ratings = new backup_nested_element('ratings'); $rating = new backup_nested_element('rating', array('id'), array('userid', 'itemid', 'time', 'post_rating') ); $reads = new backup_nested_element('readposts'); $read = new backup_nested_element('read', array('id'), array( 'userid', 'discussionid', 'postid', 'firstread', 'lastread') ); $inventeds = new backup_nested_element('invented_elements', array('reason', 'version') ); $invented = new backup_nested_element('invented', null, array('one', 'two', 'three') ); $one = $invented->get_final_element('one'); $one->add_attributes(array('attr1', 'attr2')); // Build the tree $forum->add_child($discussions); $discussions->add_child($discussion); $discussion->add_child($posts); $posts->add_child($post); $post->add_child($ratings); $ratings->add_child($rating); $forum->add_child($reads); $reads->add_child($read); $forum->add_child($inventeds); $inventeds->add_child($invented); // Let's add 1 optigroup with 4 elements $alternative1 = new backup_optigroup_element('alternative1', array('name', 'value'), '../../id', $this->postid1); $alternative2 = new backup_optigroup_element('alternative2', array('name', 'value'), backup::VAR_PARENTID, $this->postid2); $alternative3 = new backup_optigroup_element('alternative3', array('name', 'value'), '/forum/discussions/discussion/posts/post/id', $this->postid3); $alternative4 = new backup_optigroup_element('alternative4', array('forumtype', 'forumname')); // Alternative without conditions // Create the optigroup, adding one element $optigroup = new backup_optigroup('alternatives', $alternative1, false); // Add second opti element $optigroup->add_child($alternative2); // Add optigroup to post element $post->add_optigroup($optigroup); // Add third opti element, on purpose after the add_optigroup() line above to check param evaluation works ok $optigroup->add_child($alternative3); // Add 4th opti element (the one without conditions, so will be present always) $optigroup->add_child($alternative4); /// Create some new nested elements, both named 'dupetest1', and add them to alternative1 and alternative2 /// (not problem as far as the optigroup in not unique) $dupetest1 = new backup_nested_element('dupetest1', null, array('field1', 'field2')); $dupetest2 = new backup_nested_element('dupetest2', null, array('field1', 'field2')); $dupetest3 = new backup_nested_element('dupetest3', null, array('field1', 'field2')); $dupetest4 = new backup_nested_element('dupetest1', null, array('field1', 'field2')); $dupetest1->add_child($dupetest3); $dupetest2->add_child($dupetest4); $alternative1->add_child($dupetest1); $alternative2->add_child($dupetest2); // Define sources $forum->set_source_table('forum', array('id' => backup::VAR_ACTIVITYID)); $discussion->set_source_sql('SELECT * FROM {forum_discussions} WHERE forum = ?', array('/forum/id') ); $post->set_source_table('forum_posts', array('discussion' => '/forum/discussions/discussion/id')); $rating->set_source_sql('SELECT * FROM {rating} WHERE itemid = ?', array(backup::VAR_PARENTID) ); $read->set_source_table('forum_read', array('forumid' => '../../id')); $inventeds->set_source_array(array((object)array('reason' => 'I love Moodle', 'version' => '1.0'), (object)array('reason' => 'I love Moodle', 'version' => '2.0'))); // 2 object array $invented->set_source_array(array((object)array('one' => 1, 'two' => 2, 'three' => 3), (object)array('one' => 11, 'two' => 22, 'three' => 33))); // 2 object array // Set optigroup_element sources $alternative1->set_source_array(array((object)array('name' => 'alternative1', 'value' => 1))); // 1 object array // Skip alternative2 source definition on purpose (will be tested) // $alternative2->set_source_array(array((object)array('name' => 'alternative2', 'value' => 2))); // 1 object array $alternative3->set_source_array(array((object)array('name' => 'alternative3', 'value' => 3))); // 1 object array // Alternative 4 source is the forum type and name, so we'll get that in ALL posts (no conditions) that // have not another alternative (post4 in our testing data in the only not matching any other alternative) $alternative4->set_source_sql('SELECT type AS forumtype, name AS forumname FROM {forum} WHERE id = ?', array('/forum/id') ); // Set children of optigroup_element source $dupetest1->set_source_array(array((object)array('field1' => '1', 'field2' => 1))); // 1 object array $dupetest2->set_source_array(array((object)array('field1' => '2', 'field2' => 2))); // 1 object array $dupetest3->set_source_array(array((object)array('field1' => '3', 'field2' => 3))); // 1 object array $dupetest4->set_source_array(array((object)array('field1' => '4', 'field2' => 4))); // 1 object array // Define some aliases $rating->set_source_alias('rating', 'post_rating'); // Map the 'rating' value from DB to 'post_rating' final element // Mark to detect files of type 'forum_intro' in forum (and not item id) $forum->annotate_files('mod_forum', 'intro', null); // Mark to detect file of type 'forum_post' and 'forum_attachment' in post (with itemid being post->id) $post->annotate_files('mod_forum', 'post', 'id'); $post->annotate_files('mod_forum', 'attachment', 'id'); // Mark various elements to be annotated $discussion->annotate_ids('user1', 'userid'); $post->annotate_ids('forum_post', 'id'); $rating->annotate_ids('user2', 'userid'); $rating->annotate_ids('forum_post', 'itemid'); // Create the backup_ids_temp table backup_controller_dbops::create_backup_ids_temp_table($backupid); // Instantiate in memory xml output $xo = new memory_xml_output(); // Instantiate xml_writer and start it $xw = new xml_writer($xo); $xw->start(); // Instantiate the backup processor $processor = new backup_structure_processor($xw); // Set some variables $processor->set_var(backup::VAR_ACTIVITYID, $this->forumid); $processor->set_var(backup::VAR_BACKUPID, $backupid); $processor->set_var(backup::VAR_CONTEXTID,$this->contextid); // Process the backup structure with the backup processor $forum->process($processor); // Stop the xml_writer $xw->stop(); // Check various counters $this->assertEquals($forum->get_counter(), $DB->count_records('forum')); $this->assertEquals($discussion->get_counter(), $DB->count_records('forum_discussions')); $this->assertEquals($rating->get_counter(), $DB->count_records('rating')); $this->assertEquals($read->get_counter(), $DB->count_records('forum_read')); $this->assertEquals($inventeds->get_counter(), 2); // Array // Perform some validations with the generated XML $dom = new \DomDocument(); $dom->loadXML($xo->get_allcontents()); $xpath = new \DOMXPath($dom); // Some more counters $query = '/forum/discussions/discussion/posts/post'; $posts = $xpath->query($query); $this->assertEquals($posts->length, $DB->count_records('forum_posts')); $query = '/forum/invented_elements/invented'; $inventeds = $xpath->query($query); $this->assertEquals($inventeds->length, 2*2); // Check ratings information against DB $ratings = $dom->getElementsByTagName('rating'); $this->assertEquals($ratings->length, $DB->count_records('rating')); foreach ($ratings as $rating) { $ratarr = array(); $ratarr['id'] = $rating->getAttribute('id'); foreach ($rating->childNodes as $node) { if ($node->nodeType != XML_TEXT_NODE) { $ratarr[$node->nodeName] = $node->nodeValue; } } $this->assertEquals($DB->get_field('rating', 'userid', array('id' => $ratarr['id'])), $ratarr['userid']); $this->assertEquals($DB->get_field('rating', 'itemid', array('id' => $ratarr['id'])), $ratarr['itemid']); $this->assertEquals($DB->get_field('rating', 'rating', array('id' => $ratarr['id'])), $ratarr['post_rating']); } // Check forum has "blockeperiod" with value 0 (was declared by object instead of name) $query = '/forum[blockperiod="0"]'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check forum is missing "completiondiscussions" (as we are using mock_skip_final_element) $query = '/forum/completiondiscussions'; $result = $xpath->query($query); $this->assertEquals(0, $result->length); // Check forum has "completionreplies" with value "original was 0, now changed" (because of mock_modify_final_element) $query = '/forum[completionreplies="original was 0, now changed"]'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check forum has "completionposts" with value "intercepted!" (because of mock_final_element_interceptor) $query = '/forum[completionposts="intercepted!"]'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check there isn't any alternative2 tag, as far as it hasn't source defined $query = '//alternative2'; $result = $xpath->query($query); $this->assertEquals(0, $result->length); // Check there are 4 "field1" elements $query = '/forum/discussions/discussion/posts/post//field1'; $result = $xpath->query($query); $this->assertEquals(4, $result->length); // Check first post has one name element with value "alternative1" $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid1.'"][name="alternative1"]'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check there are two "dupetest1" elements $query = '/forum/discussions/discussion/posts/post//dupetest1'; $result = $xpath->query($query); $this->assertEquals(2, $result->length); // Check second post has one name element with value "dupetest2" $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid2.'"]/dupetest2'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check element "dupetest2" of second post has one field1 element with value "2" $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid2.'"]/dupetest2[field1="2"]'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check forth post has no name element $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid4.'"]/name'; $result = $xpath->query($query); $this->assertEquals(0, $result->length); // Check 1st, 2nd and 3rd posts have no forumtype element $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid1.'"]/forumtype'; $result = $xpath->query($query); $this->assertEquals(0, $result->length); $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid2.'"]/forumtype'; $result = $xpath->query($query); $this->assertEquals(0, $result->length); $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid3.'"]/forumtype'; $result = $xpath->query($query); $this->assertEquals(0, $result->length); // Check 4th post has one forumtype element with value "general" // (because it doesn't matches alternatives 1, 2, 3, then alternative 4, // the one without conditions is being applied) $query = '/forum/discussions/discussion/posts/post[@id="'.$this->postid4.'"][forumtype="general"]'; $result = $xpath->query($query); $this->assertEquals(1, $result->length); // Check annotations information against DB // Count records in original tables $c_postsid = $DB->count_records_sql('SELECT COUNT(DISTINCT id) FROM {forum_posts}'); $c_dissuserid = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {forum_discussions}'); $c_ratuserid = $DB->count_records_sql('SELECT COUNT(DISTINCT userid) FROM {rating}'); // Count records in backup_ids_table $f_forumpost = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'forum_post')); $f_user1 = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'user1')); $f_user2 = $DB->count_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'user2')); $c_notbackupid = $DB->count_records_select('backup_ids_temp', 'backupid != ?', array($backupid)); // Peform tests by comparing counts $this->assertEquals($c_notbackupid, 0); // there isn't any record with incorrect backupid $this->assertEquals($c_postsid, $f_forumpost); // All posts have been registered $this->assertEquals($c_dissuserid, $f_user1); // All users coming from discussions have been registered $this->assertEquals($c_ratuserid, $f_user2); // All users coming from ratings have been registered // Check file annotations against DB $fannotations = $DB->get_records('backup_ids_temp', array('backupid' => $backupid, 'itemname' => 'file')); $ffiles = $DB->get_records('files', array('contextid' => $this->contextid)); $this->assertEquals(count($fannotations), count($ffiles)); // Same number of recs in both (all files have been annotated) foreach ($fannotations as $annotation) { // Check ids annotated $this->assertTrue($DB->record_exists('files', array('id' => $annotation->itemid))); } // Drop the backup_ids_temp table backup_controller_dbops::drop_backup_ids_temp_table('testingid'); } /** * Backup structures wrong tests (trying to do things the wrong way) */ function test_backup_structure_wrong(): void { // Instantiate the backup processor $processor = new backup_structure_processor(new xml_writer(new memory_xml_output())); $this->assertTrue($processor instanceof base_processor); // Set one var twice $processor->set_var('onenewvariable', 999); try { $processor->set_var('onenewvariable', 999); $this->assertTrue(false, 'backup_processor_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof backup_processor_exception); $this->assertEquals($e->errorcode, 'processorvariablealreadyset'); $this->assertEquals($e->a, 'onenewvariable'); } // Get non-existing var try { $var = $processor->get_var('nonexistingvar'); $this->assertTrue(false, 'backup_processor_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof backup_processor_exception); $this->assertEquals($e->errorcode, 'processorvariablenotfound'); $this->assertEquals($e->a, 'nonexistingvar'); } // Create nested element and try ro get its parent id (doesn't exisit => exception) $ne = new backup_nested_element('test', 'one', 'two', 'three'); try { $ne->set_source_table('forum', array('id' => backup::VAR_PARENTID)); $ne->process($processor); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'cannotfindparentidforelement'); } // Try to process one nested/final/attribute elements without processor $ne = new backup_nested_element('test', 'one', 'two', 'three'); try { $ne->process(new \stdClass()); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'incorrect_processor'); } $fe = new backup_final_element('test'); try { $fe->process(new \stdClass()); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'incorrect_processor'); } $at = new backup_attribute('test'); try { $at->process(new \stdClass()); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'incorrect_processor'); } // Try to put an incorrect alias $ne = new backup_nested_element('test', 'one', 'two', 'three'); try { $ne->set_source_alias('last', 'nonexisting'); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'incorrectaliasfinalnamenotfound'); $this->assertEquals($e->a, 'nonexisting'); } // Try various incorrect paths specifying source $ne = new backup_nested_element('test', 'one', 'two', 'three'); try { $ne->set_source_table('forum', array('/test/subtest')); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'baseelementincorrectfinalorattribute'); $this->assertEquals($e->a, 'subtest'); } try { $ne->set_source_table('forum', array('/wrongtest')); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'baseelementincorrectgrandparent'); $this->assertEquals($e->a, 'wrongtest'); } try { $ne->set_source_table('forum', array('../nonexisting')); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'baseelementincorrectparent'); $this->assertEquals($e->a, '..'); } // Try various incorrect file annotations $ne = new backup_nested_element('test', 'one', 'two', 'three'); $ne->annotate_files('test', 'filearea', null); try { $ne->annotate_files('test', 'filearea', null); // Try to add annotations twice $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'annotate_files_duplicate_annotation'); $this->assertEquals($e->a, 'test/filearea/'); } $ne = new backup_nested_element('test', 'one', 'two', 'three'); try { $ne->annotate_files('test', 'filearea', 'four'); // Incorrect element $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'baseelementincorrectfinalorattribute'); $this->assertEquals($e->a, 'four'); } // Try to add incorrect element to backup_optigroup $bog = new backup_optigroup('test'); try { $bog->add_child(new backup_nested_element('test2')); $this->assertTrue(false, 'base_optigroup_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_optigroup_exception); $this->assertEquals($e->errorcode, 'optigroup_element_incorrect'); $this->assertEquals($e->a, 'backup_nested_element'); } $bog = new backup_optigroup('test'); try { $bog->add_child('test2'); $this->assertTrue(false, 'base_optigroup_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_optigroup_exception); $this->assertEquals($e->errorcode, 'optigroup_element_incorrect'); $this->assertEquals($e->a, 'non object'); } try { $bog = new backup_optigroup('test', new \stdClass()); $this->assertTrue(false, 'base_optigroup_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_optigroup_exception); $this->assertEquals($e->errorcode, 'optigroup_elements_incorrect'); } // Try a wrong processor with backup_optigroup $bog = new backup_optigroup('test'); try { $bog->process(new \stdClass()); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'incorrect_processor'); } // Try duplicating used elements with backup_optigroup // Adding top->down $bog = new backup_optigroup('test', null, true); $boge1 = new backup_optigroup_element('boge1'); $boge2 = new backup_optigroup_element('boge2'); $ne1 = new backup_nested_element('ne1'); $ne2 = new backup_nested_element('ne1'); $bog->add_child($boge1); $bog->add_child($boge2); $boge1->add_child($ne1); try { $boge2->add_child($ne2); $this->assertTrue(false, 'base_optigroup_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_optigroup_exception); $this->assertEquals($e->errorcode, 'multiple_optigroup_duplicate_element'); $this->assertEquals($e->a, 'ne1'); } // Adding down->top $bog = new backup_optigroup('test', null, true); $boge1 = new backup_optigroup_element('boge1'); $boge2 = new backup_optigroup_element('boge2'); $ne1 = new backup_nested_element('ne1'); $ne2 = new backup_nested_element('ne1'); $boge1->add_child($ne1); $boge2->add_child($ne2); $bog->add_child($boge1); try { $bog->add_child($boge2); $this->assertTrue(false, 'base_element_struct_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof base_element_struct_exception); $this->assertEquals($e->errorcode, 'baseelementexisting'); $this->assertEquals($e->a, 'ne1'); } } } base_processor.class.php000064400000004161152266574310011407 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Abstract class representing the required implementation for classes able to process structure classes */ abstract class base_processor { abstract function pre_process_nested_element(base_nested_element $nested); abstract function process_nested_element(base_nested_element $nested); abstract function post_process_nested_element(base_nested_element $nested); abstract function process_final_element(base_final_element $final); abstract function process_attribute(base_attribute $attribute); } /** * base_processor abstract exception class * * This exceptions will be used by all the processor classes * in order to detect any problem or miss-configuration */ abstract class base_processor_exception extends moodle_exception { /** * Constructor - instantiates one base_processor_exception. * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, '', '', $a, $debuginfo); } } backup_attribute.class.php000064400000004371152266574310011731 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Instantiable class representing one attribute atom (name/value) piece of information on backup */ class backup_attribute extends base_attribute implements processable, annotable { protected $annotationitem; // To store the item this element will be responsible to annotate public function process($processor) { if (!$processor instanceof base_processor) { // No correct processor, throw exception throw new base_element_struct_exception('incorrect_processor'); } $processor->process_attribute($this); } public function set_annotation_item($itemname) { if (!empty($this->annotationitem)) { $a = new stdclass(); $a->attribute = $this->get_name(); $a->annotating= $this->annotationitem; throw new base_element_struct_exception('attribute_already_used_for_annotation', $a); } $this->annotationitem = $itemname; } public function annotate($backupid) { if (empty($this->annotationitem)) { // We aren't annotating this item return; } if (!$this->is_set()) { throw new base_element_struct_exception('attribute_has_not_value', $this->get_name()); } backup_structure_dbops::insert_backup_ids_record($backupid, $this->annotationitem, $this->get_value()); } } base_final_element.class.php000064400000024657152266574310012206 0ustar00. /** * @package moodlecore * @subpackage backup-structure * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * * TODO: Finish phpdocs */ /** * Abstract class representing one final element atom (name/value/parent) piece of information */ abstract class base_final_element extends base_atom { /** @var array base_attributes of the element (maps to XML attributes of the tag) */ private $attributes; /** @var base_nested_element parent of this element (describes structure of the XML file) */ private $parent; /** * Constructor - instantiates one base_final_element, specifying its basic info. * * @param string $name name of the element * @param array $attributes attributes this element will handle (optional, defaults to null) */ public function __construct($name, $attributes = null) { parent::__construct($name); $this->attributes = array(); if (!empty($attributes)) { $this->add_attributes($attributes); } $this->parent = null; } /** * Destroy all circular references. It helps PHP 5.2 a lot! */ public function destroy() { // No need to destroy anything recursively here, direct reset $this->attributes = array(); $this->parent = null; } protected function set_parent($element) { if ($this->parent) { $info = new stdClass(); $info->currparent= $this->parent->get_name(); $info->newparent = $element->get_name(); $info->element = $this->get_name(); throw new base_element_parent_exception('baseelementhasparent', $info); } $this->parent = $element; } protected function get_grandparent() { $parent = $this->parent; if ($parent instanceof base_nested_element) { return $parent->get_grandparent(); } else { return $this; } } protected function get_grandoptigroupelement_or_grandparent() { $parent = $this->parent; if ($parent instanceof base_optigroup) { return $this; // Have found one parent optigroup, so I (first child of optigroup) am } else if ($parent instanceof base_nested_element) { return $parent->get_grandoptigroupelement_or_grandparent(); // Continue searching } else { return $this; } } protected function find_element_by_path($path) { $patharr = explode('/', trim($path, '/')); // Split the path trimming slashes if (substr($path, 0, 1) == '/') { // Absolute path, go to grandparent and process if (!$this->get_grandparent() instanceof base_nested_element) { throw new base_element_struct_exception('baseelementincorrectgrandparent', $patharr[0]); } else if ($this->get_grandparent()->get_name() !== $patharr[0]) { throw new base_element_struct_exception('baseelementincorrectgrandparent', $patharr[0]); } else { $newpath = implode('/', array_slice($patharr, 1)); // Take out 1st element return $this->get_grandparent()->find_element_by_path($newpath); // Process as relative in grandparent } } else { if ($patharr[0] == '..') { // Go to parent if (!$this->get_parent() instanceof base_nested_element) { throw new base_element_struct_exception('baseelementincorrectparent', $patharr[0]); } else { $newpath = implode('/', array_slice($patharr, 1)); // Take out 1st element return $this->get_parent()->find_element_by_path($newpath); // Process as relative in parent } } else if (count($patharr) > 1) { // Go to next child if (!$this->get_child($patharr[0]) instanceof base_nested_element) { throw new base_element_struct_exception('baseelementincorrectchild', $patharr[0]); } else { $newpath = implode('/', array_slice($patharr, 1)); // Take out 1st element return $this->get_child($patharr[0])->find_element_by_path($newpath); // Process as relative in parent } } else { // Return final element or attribute if ($this->get_final_element($patharr[0]) instanceof base_final_element) { return $this->get_final_element($patharr[0]); } else if ($this->get_attribute($patharr[0]) instanceof base_attribute) { return $this->get_attribute($patharr[0]); } else { throw new base_element_struct_exception('baseelementincorrectfinalorattribute', $patharr[0]); } } } } protected function find_first_parent_by_name($name) { if ($parent = $this->get_parent()) { // If element has parent $element = $parent->get_final_element($name); // Look for name into parent finals $attribute = $parent->get_attribute($name); // Look for name into parent attrs if ($element instanceof base_final_element) { return $element; } else if ($attribute instanceof base_attribute) { return $attribute; } else { // Not found, go up 1 level and continue searching return $parent->find_first_parent_by_name($name); } } else { // No more parents available, return the original backup::VAR_PARENTID, exception throw new base_element_struct_exception('cannotfindparentidforelement', $name); } } /// Public API starts here public function get_attributes() { return $this->attributes; } public function get_attribute($name) { if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } else { return null; } } public function get_parent() { return $this->parent; } public function get_level() { return $this->parent == null ? 1 : $this->parent->get_level() + 1; } public function add_attributes($attributes) { if ($attributes instanceof base_attribute || is_string($attributes)) { // Accept 1 attribute, object or string $attributes = array($attributes); } if (is_array($attributes)) { foreach ($attributes as $attribute) { if (is_string($attribute)) { // Accept string attributes $attribute = $this->get_new_attribute($attribute); } if (!($attribute instanceof base_attribute)) { throw new base_element_attribute_exception('baseelementnoattribute', get_class($attribute)); } if (array_key_exists($attribute->get_name(), $this->attributes)) { throw new base_element_attribute_exception('baseelementattributeexists', $attribute->get_name()); } $this->attributes[$attribute->get_name()] = $attribute; } } else { throw new base_element_attribute_exception('baseelementattributeincorrect'); } } public function clean_values() { parent::clean_value(); if (!empty($this->attributes)) { foreach ($this->attributes as $attribute) { $attribute->clean_value(); } } } public function to_string($showvalue = false) { // Decide the correct prefix $prefix = '#'; // default if ($this->parent instanceof base_optigroup) { $prefix = '?'; } else if ($this instanceof base_nested_element) { $prefix = ''; } $indent = str_repeat(' ', $this->get_level()); // Indent output based in level (4cc) $output = $indent . $prefix . $this->get_name() . ' (level: ' . $this->get_level() . ')'; if ($showvalue) { $value = $this->is_set() ? $this->get_value() : 'not set'; $output .= ' => ' . $value; } if (!empty($this->attributes)) { foreach ($this->attributes as $attribute) { $output .= PHP_EOL . $indent . ' ' . $attribute->to_string($showvalue); } } return $output; } // Implementable API /** * Returns one instace of the @base_attribute class to work with * when attributes are added simply by name */ abstract protected function get_new_attribute($name); } /** * base_element exception to control all the errors related with parents handling */ class base_element_parent_exception extends base_atom_exception { /** * Constructor - instantiates one base_element_parent_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } /** * base_element exception to control all the errors related with attributes handling */ class base_element_attribute_exception extends base_atom_exception { /** * Constructor - instantiates one base_element_attribute_exception * * @param string $errorcode key for the corresponding error string * @param object $a extra words and phrases that might be required in the error string * @param string $debuginfo optional debugging information */ public function __construct($errorcode, $a = null, $debuginfo = null) { parent::__construct($errorcode, $a, $debuginfo); } } slot_random.php000064400000017022152267017410007605 0ustar00. namespace mod_quiz\local\structure; use context_module; /** * Class slot_random, represents a random question slot type. * * @package mod_quiz * @copyright 2018 Shamim Rezaie * @author 2021 Safat Shahin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class slot_random { /** @var \stdClass Slot's properties. A record retrieved from the quiz_slots table. */ protected $record; /** * @var \stdClass set reference record */ protected $referencerecord; /** * @var \stdClass The quiz this question slot belongs to. */ protected $quiz = null; /** * @var \core_tag_tag[] List of tags for this slot. */ protected $tags = []; /** * @var string filter condition */ protected $filtercondition = null; /** * slot_random constructor. * * @param \stdClass $slotrecord Represents a record in the quiz_slots table. */ public function __construct($slotrecord = null) { $this->record = new \stdClass(); $this->referencerecord = new \stdClass(); $slotproperties = ['id', 'slot', 'quizid', 'page', 'requireprevious', 'maxmark']; $setreferenceproperties = ['usingcontextid', 'questionscontextid']; foreach ($slotproperties as $property) { if (isset($slotrecord->$property)) { $this->record->$property = $slotrecord->$property; } } foreach ($setreferenceproperties as $referenceproperty) { if (isset($slotrecord->$referenceproperty)) { $this->referencerecord->$referenceproperty = $slotrecord->$referenceproperty; } } } /** * Returns the quiz for this question slot. * The quiz is fetched the first time it is requested and then stored in a member variable to be returned each subsequent time. * * @return mixed * @throws \coding_exception */ public function get_quiz() { global $DB; if (empty($this->quiz)) { if (empty($this->record->quizid)) { throw new \coding_exception('quizid is not set.'); } $this->quiz = $DB->get_record('quiz', ['id' => $this->record->quizid]); } return $this->quiz; } /** * Sets the quiz object for the quiz slot. * It is not mandatory to set the quiz as the quiz slot can fetch it the first time it is accessed, * however it helps with the performance to set the quiz if you already have it. * * @param \stdClass $quiz The qui object. */ public function set_quiz($quiz) { $this->quiz = $quiz; $this->record->quizid = $quiz->id; } /** * Set some tags for this quiz slot. * * @param \core_tag_tag[] $tags * * @deprecated since Moodle 4.3 * @todo Final deprecation on Moodle 4.7 MDL-78091 */ public function set_tags($tags) { debugging('Method set_tags() is deprecated, ' . 'please do not use this function.', DEBUG_DEVELOPER); $this->tags = []; foreach ($tags as $tag) { // We use $tag->id as the key for the array so not only it handles duplicates of the same tag being given, // but also it is consistent with the behaviour of set_tags_by_id() below. $this->tags[$tag->id] = $tag; } } /** * Set some tags for this quiz slot. This function uses tag ids to find tags. * * @param int[] $tagids * @deprecated since Moodle 4.3 * @todo Final deprecation on Moodle 4.7 MDL-78091 */ public function set_tags_by_id($tagids) { debugging( 'Method set_tags_by_id() is deprecated, please do not use this function.', DEBUG_DEVELOPER ); $this->tags = \core_tag_tag::get_bulk($tagids, 'id, name'); } /** * Set filter condition. * * @param \string $filters */ public function set_filter_condition(string $filters): void { $this->filtercondition = $filters; } /** * Inserts the quiz slot at the $page page. * It is required to call this function if you are building a quiz slot object from scratch. * * @param int $page The page that this slot will be inserted at. */ public function insert($page) { global $DB; $slots = $DB->get_records('quiz_slots', ['quizid' => $this->record->quizid], 'slot', 'id, slot, page'); $quiz = $this->get_quiz(); $trans = $DB->start_delegated_transaction(); $maxpage = 1; $numonlastpage = 0; foreach ($slots as $slot) { if ($slot->page > $maxpage) { $maxpage = $slot->page; $numonlastpage = 1; } else { $numonlastpage += 1; } } if (is_int($page) && $page >= 1) { // Adding on a given page. $lastslotbefore = 0; foreach (array_reverse($slots) as $otherslot) { if ($otherslot->page > $page) { $DB->set_field('quiz_slots', 'slot', $otherslot->slot + 1, ['id' => $otherslot->id]); } else { $lastslotbefore = $otherslot->slot; break; } } $this->record->slot = $lastslotbefore + 1; $this->record->page = min($page, $maxpage + 1); quiz_update_section_firstslots($this->record->quizid, 1, max($lastslotbefore, 1)); } else { $lastslot = end($slots); if ($lastslot) { $this->record->slot = $lastslot->slot + 1; } else { $this->record->slot = 1; } if ($quiz->questionsperpage && $numonlastpage >= $quiz->questionsperpage) { $this->record->page = $maxpage + 1; } else { $this->record->page = $maxpage; } } $this->record->id = $DB->insert_record('quiz_slots', $this->record); $this->referencerecord->component = 'mod_quiz'; $this->referencerecord->questionarea = 'slot'; $this->referencerecord->itemid = $this->record->id; $this->referencerecord->filtercondition = $this->filtercondition; $DB->insert_record('question_set_references', $this->referencerecord); $trans->allow_commit(); // Log slot created event. $cm = get_coursemodule_from_instance('quiz', $quiz->id); $event = \mod_quiz\event\slot_created::create([ 'context' => context_module::instance($cm->id), 'objectid' => $this->record->id, 'other' => [ 'quizid' => $quiz->id, 'slotnumber' => $this->record->slot, 'page' => $this->record->page ] ]); $event->trigger(); } }