Merge branch 'install_master' of git://github.com/amosbot/moodle
authorAparup Banerjee <aparup@moodle.com>
Thu, 5 Apr 2012 08:19:27 +0000 (16:19 +0800)
committerAparup Banerjee <aparup@moodle.com>
Thu, 5 Apr 2012 08:19:27 +0000 (16:19 +0800)
391 files changed:
admin/index.php
admin/plugins.php
admin/renderer.php
admin/settings/server.php
admin/tool/generator/locallib.php
admin/tool/phpunit/cli/util.php
admin/tool/qeupgradehelper/lang/en/tool_qeupgradehelper.php
admin/tool/unittest/dbtest.php
admin/tool/unittest/ex_reporter.php
admin/tool/unittest/ex_simple_test.php
admin/tool/unittest/index.php
admin/tool/unittest/simpletestcoveragelib.php
admin/tool/unittest/simpletestlib.php
admin/webservice/forms.php
auth/cas/auth.php
auth/shibboleth/config.html
backup/controller/tests/controller_test.php [new file with mode: 0644]
backup/converter/convertlib.php
backup/converter/imscc11/backuplib.php
backup/converter/moodle1/handlerlib.php
backup/converter/moodle1/tests/lib_test.php [new file with mode: 0644]
backup/util/checks/tests/checks_test.php [new file with mode: 0644]
backup/util/dbops/tests/dbops_test.php [new file with mode: 0644]
backup/util/destinations/tests/destinations_test.php [new file with mode: 0644]
backup/util/factories/tests/factories_test.php [new file with mode: 0644]
backup/util/helper/tests/converterhelper_test.php [new file with mode: 0644]
backup/util/helper/tests/decode_test.php [new file with mode: 0644]
backup/util/helper/tests/helper_test.php [new file with mode: 0644]
backup/util/loggers/error_log_logger.class.php
backup/util/loggers/tests/logger_test.php [new file with mode: 0644]
backup/util/plan/tests/fixtures/plan_fixtures.php [new file with mode: 0644]
backup/util/plan/tests/plan_test.php [new file with mode: 0644]
backup/util/plan/tests/step_test.php [new file with mode: 0644]
backup/util/plan/tests/task_test.php [new file with mode: 0644]
backup/util/settings/tests/settings_test.php [new file with mode: 0644]
backup/util/structure/simpletest/testbackupstructures.php
backup/util/structure/tests/baseatom_test.php [new file with mode: 0644]
backup/util/structure/tests/baseattribute_test.php [new file with mode: 0644]
backup/util/structure/tests/basefinalelement_test.php [new file with mode: 0644]
backup/util/structure/tests/basenestedelement_test.php [new file with mode: 0644]
backup/util/structure/tests/baseoptiogroup_test.php [new file with mode: 0644]
backup/util/structure/tests/fixtures/structure_fixtures.php [new file with mode: 0644]
backup/util/structure/tests/structure_test.php [new file with mode: 0644]
backup/util/ui/tests/ui_test.php [new file with mode: 0644]
backup/util/xml/tests/writer_test.php [new file with mode: 0644]
blocks/moodleblock.class.php
blog/external_blog_edit.php
blog/locallib.php
blog/tests/bloglib_test.php [new file with mode: 0644]
calendar/renderer.php
comment/lib.php
config-dist.php
course/lib.php
course/moodleform_mod.php
course/tests/courselib_test.php [new file with mode: 0644]
filter/mediaplugin/tests/filter_test.php [new file with mode: 0644]
filter/urltolink/tests/filter_test.php [new file with mode: 0644]
filter/urltolink/tests/fixtures/sample.txt [new file with mode: 0644]
grade/edit/tree/grade.php
grade/grading/tests/lib_test.php [new file with mode: 0644]
grade/report/grader/lib.php
grade/simpletest/testreportlib.php [deleted file]
grade/tests/edittree_test.php [new file with mode: 0644]
index.php
lang/en/admin.php
lang/en/message.php
lang/en/moodle.php
lang/en/plugin.php
lib/accesslib.php
lib/ajax/tests/ajaxlib_test.php [new file with mode: 0644]
lib/completionlib.php
lib/componentlib.class.php
lib/cronlib.php
lib/csslib.php
lib/db/access.php
lib/db/events.php
lib/db/install.php
lib/db/log.php
lib/db/messages.php
lib/db/services.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/ddl/tests/ddl_test.php [new file with mode: 0644]
lib/ddl/tests/fixtures/invalid.xml [new file with mode: 0644]
lib/ddl/tests/fixtures/xmldb_table.xml [new file with mode: 0644]
lib/dml/mysqli_native_moodle_database.php
lib/dml/simpletest/testdml.php
lib/dml/tests/dml_test.php [new file with mode: 0644]
lib/dml/tests/fixtures/clob.txt [new file with mode: 0644]
lib/dml/tests/fixtures/randombinary [new file with mode: 0644]
lib/externallib.php
lib/form/tests/duration_test.php [new file with mode: 0644]
lib/form/yui/checkboxcontroller/checkboxcontroller.js
lib/formslib.php
lib/googleapi.php
lib/grade/simpletest/testgradecategory.php
lib/grade/simpletest/testgradescale.php
lib/installlib.php
lib/javascript-static.js
lib/mathslib.php
lib/messagelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrequirementslib.php
lib/pear/OLE.php
lib/pear/README_MOODLE.txt
lib/phpunit/bootstrap.php
lib/phpunit/generatorlib.php [new file with mode: 0644]
lib/phpunit/lib.php
lib/phpunit/readme.md
lib/pluginlib.php
lib/questionlib.php
lib/sessionlib.php
lib/setup.php
lib/setuplib.php
lib/simpletest/fixtures/gradetest.php
lib/simpletest/portfolio_testclass.php
lib/simpletest/testcompletionlib.php
lib/simpletest/testconditionlib.php
lib/simpletest/testcsslib.php
lib/simpletest/testeventslib.php
lib/simpletest/testexternallib.php
lib/simpletest/testmathslib.php
lib/simpletest/testmoodlelib.php
lib/simpletest/testnavigationlib.php
lib/simpletest/testoutputcomponents.php
lib/simpletest/testpluginlib.php [new file with mode: 0644]
lib/simpletest/testportfolioaddbutton.php [deleted file]
lib/simpletest/testportfoliolib.php [deleted file]
lib/simpletestlib/HELP_MY_TESTS_DONT_WORK_ANYMORE [changed mode: 0644->0755]
lib/simpletestlib/LICENSE [changed mode: 0644->0755]
lib/simpletestlib/README [changed mode: 0644->0755]
lib/simpletestlib/VERSION [changed mode: 0644->0755]
lib/simpletestlib/arguments.php [new file with mode: 0755]
lib/simpletestlib/authentication.php
lib/simpletestlib/autorun.php
lib/simpletestlib/browser.php
lib/simpletestlib/collector.php
lib/simpletestlib/compatibility.php [changed mode: 0644->0755]
lib/simpletestlib/cookies.php
lib/simpletestlib/default_reporter.php
lib/simpletestlib/detached.php [changed mode: 0644->0755]
lib/simpletestlib/docs/en/authentication_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/browser_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/docs.css [new file with mode: 0755]
lib/simpletestlib/docs/en/expectation_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/form_testing_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/group_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/index.html [new file with mode: 0644]
lib/simpletestlib/docs/en/mock_objects_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/overview.html [new file with mode: 0644]
lib/simpletestlib/docs/en/partial_mocks_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/reporter_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/unit_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/en/web_tester_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/authentication_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/browser_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/docs.css [new file with mode: 0755]
lib/simpletestlib/docs/fr/expectation_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/form_testing_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/group_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/index.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/mock_objects_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/overview.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/partial_mocks_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/reporter_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/unit_test_documentation.html [new file with mode: 0644]
lib/simpletestlib/docs/fr/web_tester_documentation.html [new file with mode: 0644]
lib/simpletestlib/dumper.php [changed mode: 0644->0755]
lib/simpletestlib/eclipse.php
lib/simpletestlib/encoding.php
lib/simpletestlib/errors.php
lib/simpletestlib/exceptions.php [changed mode: 0644->0755]
lib/simpletestlib/expectation.php [changed mode: 0644->0755]
lib/simpletestlib/extensions/pear_test_case.php [deleted file]
lib/simpletestlib/extensions/phpunit_test_case.php [deleted file]
lib/simpletestlib/extensions/testdox.php [deleted file]
lib/simpletestlib/extensions/testdox/test.php [deleted file]
lib/simpletestlib/form.php
lib/simpletestlib/frames.php [changed mode: 0644->0755]
lib/simpletestlib/http.php
lib/simpletestlib/invoker.php [changed mode: 0644->0755]
lib/simpletestlib/mock_objects.php [changed mode: 0644->0755]
lib/simpletestlib/page.php [changed mode: 0644->0755]
lib/simpletestlib/php_parser.php [moved from lib/simpletestlib/parser.php with 51% similarity, mode: 0755]
lib/simpletestlib/readme_moodle.txt
lib/simpletestlib/recorder.php [new file with mode: 0644]
lib/simpletestlib/reflection_php4.php
lib/simpletestlib/reflection_php5.php
lib/simpletestlib/remote.php
lib/simpletestlib/reporter.php [changed mode: 0644->0755]
lib/simpletestlib/scorer.php
lib/simpletestlib/selector.php [changed mode: 0644->0755]
lib/simpletestlib/shell_tester.php
lib/simpletestlib/simpletest.php
lib/simpletestlib/socket.php [changed mode: 0644->0755]
lib/simpletestlib/tag.php
lib/simpletestlib/test_case.php
lib/simpletestlib/tidy_parser.php [new file with mode: 0755]
lib/simpletestlib/ui/colortext_reporter.php [deleted file]
lib/simpletestlib/ui/css/webunit.css [deleted file]
lib/simpletestlib/ui/img/wait.gif [deleted file]
lib/simpletestlib/ui/js/webunit.js [deleted file]
lib/simpletestlib/ui/js/x.js [deleted file]
lib/simpletestlib/ui/webunit_reporter.php [deleted file]
lib/simpletestlib/unit_tester.php [changed mode: 0644->0755]
lib/simpletestlib/url.php
lib/simpletestlib/user_agent.php
lib/simpletestlib/web_tester.php
lib/simpletestlib/xml.php [changed mode: 0644->0755]
lib/spikephpcoverage/readme_moodle.txt
lib/spikephpcoverage/src/util/Utility.php
lib/tests/accesslib_test.php [new file with mode: 0644]
lib/tests/blocklib_test.php [new file with mode: 0644]
lib/tests/code_test.php [new file with mode: 0644]
lib/tests/componentlib_test.php [new file with mode: 0644]
lib/tests/conditionlib_test.php [new file with mode: 0644]
lib/tests/cssslib_test.php [new file with mode: 0644]
lib/tests/eventslib_test.php [new file with mode: 0644]
lib/tests/externallib_test.php [new file with mode: 0644]
lib/tests/filelib_test.php [new file with mode: 0644]
lib/tests/filter_test.php [new file with mode: 0644]
lib/tests/fixtures/events.php [new file with mode: 0644]
lib/tests/formslib_test.php [new file with mode: 0644]
lib/tests/html2text_test.php [new file with mode: 0644]
lib/tests/htmlwriter_test.php [new file with mode: 0644]
lib/tests/mathslib_test.php [new file with mode: 0644]
lib/tests/moodlelib_test.php [new file with mode: 0644]
lib/tests/navigationlib_test.php [new file with mode: 0644]
lib/tests/outputcomponents_test.php [new file with mode: 0644]
lib/tests/outputlib_test.php [new file with mode: 0644]
lib/tests/phpunit_test.php
lib/tests/questionlib_test.php [new file with mode: 0644]
lib/tests/repositorylib_test.php [new file with mode: 0644]
lib/tests/rsslib_test.php [new file with mode: 0644]
lib/tests/textlib_test.php
lib/tests/weblib_test.php [new file with mode: 0644]
lib/thirdpartylibs.xml
lib/upgradelib.php
lib/weblib.php
message/renderer.php
mod/assignment/simpletest/test_assignment_portfolio_callers.php [deleted file]
mod/chat/simpletest/test_chat_portfolio_callers.php [deleted file]
mod/choice/lib.php
mod/data/edit.php
mod/data/field/file/field.class.php
mod/data/field/picture/field.class.php
mod/data/lib.php
mod/data/simpletest/test_advanced_search_sql.php [new file with mode: 0644]
mod/data/simpletest/test_context.csv [new file with mode: 0644]
mod/data/simpletest/test_course_modules.csv [new file with mode: 0644]
mod/data/simpletest/test_data_content.csv [new file with mode: 0644]
mod/data/simpletest/test_data_fields.csv [new file with mode: 0644]
mod/data/simpletest/test_data_portfolio_callers.php [deleted file]
mod/data/simpletest/test_data_records.csv [new file with mode: 0644]
mod/data/simpletest/test_modules.csv [new file with mode: 0644]
mod/data/simpletest/test_user.csv [new file with mode: 0644]
mod/data/simpletest/testpreset.php [deleted file]
mod/data/version.php
mod/data/view.php
mod/feedback/backup/moodle2/backup_feedback_stepslib.php
mod/feedback/backup/moodle2/restore_feedback_stepslib.php
mod/feedback/complete.php
mod/feedback/complete_guest.php
mod/feedback/lib.php
mod/feedback/mod_form.php
mod/feedback/view.php
mod/folder/backup/moodle1/lib.php
mod/forum/simpletest/test_forum_portfolio_callers.php [deleted file]
mod/forum/simpletest/testmodforumlib.php [deleted file]
mod/glossary/print.php
mod/glossary/simpletest/test_glossary_portfolio_callers.php [deleted file]
mod/imscp/backup/moodle1/lib.php
mod/lti/tests/locallib_test.php [new file with mode: 0644]
mod/page/backup/moodle1/lib.php
mod/quiz/accessrule/delaybetweenattempts/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/ipaddress/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/numattempts/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/openclosedate/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/password/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/safebrowser/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/securewindow/tests/rule_test.php [new file with mode: 0644]
mod/quiz/accessrule/timelimit/tests/rule_test.php [new file with mode: 0644]
mod/quiz/addrandom.php
mod/quiz/addrandomform.php
mod/quiz/edit.php
mod/quiz/editlib.php
mod/quiz/lib.php
mod/quiz/report/overview/report.php
mod/quiz/report/statistics/tests/fixtures/mdl_question.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/fixtures/mdl_question_states.csv [new file with mode: 0644]
mod/quiz/report/statistics/tests/statistics_test.php [new file with mode: 0644]
mod/quiz/report/tests/reportlib_test.php [new file with mode: 0644]
mod/quiz/tests/editlib_test.php [new file with mode: 0644]
mod/quiz/tests/lib_test.php [new file with mode: 0644]
mod/quiz/tests/locallib_test.php [new file with mode: 0644]
mod/quiz/tests/quizdisplayoptions_test.php [new file with mode: 0644]
mod/quiz/tests/quizobj_test.php [new file with mode: 0644]
mod/resource/backup/moodle1/lib.php
mod/scorm/tests/formatduration_test.php [new file with mode: 0644]
mod/url/backup/moodle1/lib.php
mod/wiki/comments_form.php
mod/wiki/create.php
mod/wiki/create_form.php
mod/wiki/edit_form.php
mod/wiki/filesedit_form.php
mod/wiki/locallib.php
mod/wiki/mod_form.php
mod/wiki/pagelib.php
mod/wiki/renderer.php
mod/wiki/tests/fixtures/input/creole/1 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/2 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/3 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/4 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/5 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/6 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/7 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/8 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/creole/9 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/html/1 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/html/2 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/nwiki/1 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/nwiki/2 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/nwiki/3 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/nwiki/4 [new file with mode: 0644]
mod/wiki/tests/fixtures/input/nwiki/5 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/1 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/2 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/3 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/4 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/5 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/6 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/7 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/8 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/9 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/html/1 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/html/2 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/nwiki/1 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/nwiki/2 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/nwiki/3 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/nwiki/4 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/nwiki/5 [new file with mode: 0644]
mod/wiki/tests/wikiparser_test.php [new file with mode: 0644]
mod/workshop/allocation/random/tests/allocator_test.php [new file with mode: 0644]
mod/workshop/eval/best/tests/lib_test.php [new file with mode: 0644]
mod/workshop/form/accumulative/tests/lib_test.php [new file with mode: 0644]
mod/workshop/form/numerrors/tests/lib_test.php [new file with mode: 0644]
mod/workshop/form/rubric/tests/lib_test.php [new file with mode: 0644]
mod/workshop/tests/locallib_test.php [new file with mode: 0644]
phpunit.xml.dist
portfolio/boxnet/lib.php
portfolio/boxnet/simpletest/testportfoliopluginboxnet.php [deleted file]
portfolio/download/simpletest/testportfolioplugindownload.php [deleted file]
question/behaviour/adaptivenopenalty/renderer.php
question/category_class.php
question/editlib.php
question/engine/questionusage.php
question/engine/simpletest/helpers.php
question/engine/simpletest/testunitofwork.php
question/engine/upgrade/upgradelib.php
question/export.php
question/format.php
question/format/aiken/format.php
question/format/blackboard/format.php
question/format/blackboard_six/format.php
question/format/examview/format.php
question/format/learnwise/format.php
question/format/multianswer/format.php
question/format/upgrade.txt
question/format/webct/format.php
question/format/xml/format.php
question/format/xml/simpletest/testxmlformat.php
question/question.php
question/type/edit_question_form.php
question/type/match/simpletest/testquestiontype.php
question/type/multianswer/questiontype.php
question/type/multichoice/simpletest/testquestiontype.php
question/type/numerical/simpletest/testquestiontype.php
question/type/questiontypebase.php
question/type/truefalse/simpletest/testquestiontype.php
rating/tests/rating_test.php [new file with mode: 0644]
repository/picasa/lib.php
repository/wikimedia/wikimedia.php
tag/coursetagslib.php
theme/afterburner/config.php
theme/afterburner/style/afterburner_styles.css
theme/base/style/admin.css
theme/standard/style/calendar.css
version.php
webservice/lib.php

index fb02a21..3f8ab67 100644 (file)
@@ -55,6 +55,7 @@ define('NO_OUTPUT_BUFFERING', true);
 require('../config.php');
 require_once($CFG->libdir.'/adminlib.php');    // various admin-only functions
 require_once($CFG->libdir.'/upgradelib.php');  // general upgrade/install related functions
+require_once($CFG->libdir.'/pluginlib.php');   // available updates notifications
 
 $id             = optional_param('id', '', PARAM_TEXT);
 $confirmupgrade = optional_param('confirmupgrade', 0, PARAM_BOOL);
@@ -62,6 +63,7 @@ $confirmrelease = optional_param('confirmrelease', 0, PARAM_BOOL);
 $confirmplugins = optional_param('confirmplugincheck', 0, PARAM_BOOL);
 $showallplugins = optional_param('showallplugins', 0, PARAM_BOOL);
 $agreelicense   = optional_param('agreelicense', 0, PARAM_BOOL);
+$fetchupdates   = optional_param('fetchupdates', 0, PARAM_BOOL);
 
 // Check some PHP server settings
 
@@ -103,11 +105,6 @@ if (!$version or !$release) {
     print_error('withoutversion', 'debug'); // without version, stop
 }
 
-if (!isset($maturity)) {
-    // Fallback for now. Should probably be removed in the future.
-    $maturity = MATURITY_STABLE;
-}
-
 // Turn off xmlstrictheaders during upgrade.
 $origxmlstrictheaders = !empty($CFG->xmlstrictheaders);
 $CFG->xmlstrictheaders = false;
@@ -235,9 +232,17 @@ if ($version > $CFG->version) {  // upgrade
         $PAGE->set_heading($strplugincheck);
         $PAGE->set_cacheable(false);
 
+        $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1));
+
+        if ($fetchupdates) {
+            // no sesskey support guaranteed here
+            available_update_checker::instance()->fetch();
+            redirect($reloadurl);
+        }
+
         $output = $PAGE->get_renderer('core', 'admin');
-        echo $output->upgrade_plugin_check_page(plugin_manager::instance(), $version, $showallplugins,
-                new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1)),
+        echo $output->upgrade_plugin_check_page(plugin_manager::instance(), available_update_checker::instance(),
+                $version, $showallplugins, $reloadurl,
                 new moodle_url('/admin/index.php', array('confirmupgrade'=>1, 'confirmrelease'=>1, 'confirmplugincheck'=>1)));
         die();
 
@@ -268,9 +273,16 @@ if (moodle_needs_upgrading()) {
             $PAGE->set_heading($strplugincheck);
             $PAGE->set_cacheable(false);
 
+            if ($fetchupdates) {
+                // no sesskey support guaranteed here
+                available_update_checker::instance()->fetch();
+                redirect($PAGE->url);
+            }
+
             $output = $PAGE->get_renderer('core', 'admin');
-            echo $output->upgrade_plugin_check_page(plugin_manager::instance(), $version, $showallplugins,
-                    new moodle_url('/admin/index.php'),
+            echo $output->upgrade_plugin_check_page(plugin_manager::instance(), available_update_checker::instance(),
+                    $version, $showallplugins,
+                    new moodle_url($PAGE->url),
                     new moodle_url('/admin/index.php', array('confirmplugincheck'=>1)));
             die();
         }
@@ -374,7 +386,19 @@ $cronoverdue = ($lastcron < time() - 3600 * 24);
 $dbproblems = $DB->diagnose();
 $maintenancemode = !empty($CFG->maintenance_enabled);
 
+$updateschecker = available_update_checker::instance();
+$availableupdates = $updateschecker->get_update_info('core',
+    array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
+$availableupdatesfetch = $updateschecker->get_last_timefetched();
+
 admin_externalpage_setup('adminnotifications');
+
+if ($fetchupdates) {
+    require_sesskey();
+    $updateschecker->fetch();
+    redirect($PAGE->url);
+}
+
 $output = $PAGE->get_renderer('core', 'admin');
 echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
-        $cronoverdue, $dbproblems, $maintenancemode);
+        $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch);
index 0204930..943f23a 100644 (file)
@@ -30,5 +30,17 @@ require_once($CFG->libdir . '/pluginlib.php');
 
 require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
 admin_externalpage_setup('pluginsoverview');
+
+$fetchremote = optional_param('fetchremote', false, PARAM_BOOL);
+
+$pluginman = plugin_manager::instance();
+$checker = available_update_checker::instance();
+
+if ($fetchremote) {
+    require_sesskey();
+    $checker->fetch();
+    redirect($PAGE->url);
+}
+
 $output = $PAGE->get_renderer('core', 'admin');
-echo $output->plugin_management_page(plugin_manager::instance());
+echo $output->plugin_management_page($pluginman, $checker);
index cecd725..f6a2c11 100644 (file)
@@ -165,23 +165,41 @@ class core_admin_renderer extends plugin_renderer_base {
     /**
      * Display the upgrade page that lists all the plugins that require attention.
      * @param plugin_manager $pluginman provides information about the plugins.
+     * @param available_update_checker $checker provides information about available updates.
      * @param int $version the version of the Moodle code from version.php.
      * @param bool $showallplugins
      * @param moodle_url $reloadurl
      * @param moodle_url $continueurl
      * @return string HTML to output.
      */
-    public function upgrade_plugin_check_page(plugin_manager $pluginman, $version, $showallplugins, $reloadurl, $continueurl) {
+    public function upgrade_plugin_check_page(plugin_manager $pluginman, available_update_checker $checker,
+            $version, $showallplugins, $reloadurl, $continueurl) {
+
         $output = '';
 
         $output .= $this->header();
         $output .= $this->box_start('generalbox');
-        $output .= $this->container(get_string('pluginchecknotice', 'core_plugin'), 'generalbox', 'notice');
+        $output .= $this->container_start('generalbox', 'notice');
+        $output .= html_writer::tag('p', get_string('pluginchecknotice', 'core_plugin'));
+        $output .= $this->container_start('checkforupdates');
+        $output .= $this->single_button(new moodle_url($reloadurl, array('fetchupdates' => 1)), get_string('checkforupdates', 'core_plugin'));
+        if ($timefetched = $checker->get_last_timefetched()) {
+            $output .= $this->container(get_string('checkforupdateslast', 'core_plugin',
+                userdate($timefetched, get_string('strftimedatetime', 'core_langconfig'))));
+        }
+        $output .= $this->container_end();
+        $output .= $this->container_end();
+
         $output .= $this->plugins_check_table($pluginman, $version, array('full' => $showallplugins));
         $output .= $this->box_end();
         $output .= $this->upgrade_reload($reloadurl);
 
         if ($pluginman->all_plugins_ok($version)) {
+            if ($pluginman->some_plugins_updatable()) {
+                $output .= $this->container_start('upgradepluginsinfo');
+                $output .= $this->help_icon('upgradepluginsinfo', 'core_admin', get_string('upgradepluginsfirst', 'core_admin'));
+                $output .= $this->container_end();
+            }
             $button = new single_button($continueurl, get_string('upgradestart', 'admin'), 'get');
             $button->class = 'continuebutton';
             $output .= $this->render($button);
@@ -202,14 +220,18 @@ class core_admin_renderer extends plugin_renderer_base {
      * @param bool $cronoverdue warn cron not running
      * @param bool $dbproblems warn db has problems
      * @param bool $maintenancemode warn in maintenance mode
+     * @param array|null $availableupdates array of available_update_info objects or null
+     * @param int|null $availableupdatesfetch timestamp of the most recent updates fetch or null (unknown)
+     *
      * @return string HTML to output.
      */
     public function admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
-            $cronoverdue, $dbproblems, $maintenancemode) {
+            $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch) {
         $output = '';
 
         $output .= $this->header();
         $output .= $this->maturity_info($maturity);
+        $output .= $this->available_updates($availableupdates, $availableupdatesfetch);
         $output .= $this->insecure_dataroot_warning($insecuredataroot);
         $output .= $this->display_errors_warning($errorsdisplayed);
         $output .= $this->cron_overdue_warning($cronoverdue);
@@ -228,17 +250,27 @@ class core_admin_renderer extends plugin_renderer_base {
 
     /**
      * Display the plugin management page (admin/plugins.php).
+     *
      * @param plugin_manager $pluginman
+     * @param available_update_checker $checker
      * @return string HTML to output.
      */
-    public function plugin_management_page(plugin_manager $pluginman) {
+    public function plugin_management_page(plugin_manager $pluginman, available_update_checker $checker) {
         $output = '';
 
         $output .= $this->header();
         $output .= $this->heading(get_string('pluginsoverview', 'core_admin'));
-        $output .= $this->box_start('generalbox');
-        $output .= $this->plugins_control_panel($pluginman);
-        $output .= $this->box_end();
+        $output .= $this->plugins_overview_panel($pluginman);
+
+        $output .= $this->container_start('checkforupdates');
+        $output .= $this->single_button(new moodle_url($this->page->url, array('fetchremote' => 1)), get_string('checkforupdates', 'core_plugin'));
+        if ($timefetched = $checker->get_last_timefetched()) {
+            $output .= $this->container(get_string('checkforupdateslast', 'core_plugin',
+                userdate($timefetched, get_string('strftimedatetime', 'core_langconfig'))));
+        }
+        $output .= $this->container_end();
+
+        $output .= $this->box($this->plugins_control_panel($pluginman), 'generalbox');
         $output .= $this->footer();
 
         return $output;
@@ -415,7 +447,81 @@ class core_admin_renderer extends plugin_renderer_base {
         return $this->box(
                     get_string('maturitycoreinfo', 'admin', $maturitylevel) . ' ' .
                     $this->doc_link('admin/versions', get_string('morehelp')),
-                'generalbox adminwarning maturityinfo');
+                'generalbox adminwarning maturityinfo maturity'.$maturity);
+    }
+
+    /**
+     * Displays the info about available Moodle updates
+     *
+     * @param array|null $updates array of available_update_info objects or null
+     * @param int|null $fetch timestamp of the most recent updates fetch or null (unknown)
+     * @return string
+     */
+    protected function available_updates($updates, $fetch) {
+
+        $updateinfo = $this->box_start('generalbox adminwarning availableupdatesinfo');
+        if (is_array($updates)) {
+            $updateinfo .= $this->heading(get_string('updateavailable', 'core_admin'), 3);
+            foreach ($updates as $update) {
+                $updateinfo .= $this->moodle_available_update_info($update);
+            }
+        } else {
+            $updateinfo .= $this->heading(get_string('updateavailablenot', 'core_admin'), 3);
+        }
+
+        $updateinfo .= $this->container_start('checkforupdates');
+        $updateinfo .= $this->single_button(new moodle_url($this->page->url, array('fetchupdates' => 1)), get_string('checkforupdates', 'core_plugin'));
+        if ($fetch) {
+            $updateinfo .= $this->container(get_string('checkforupdateslast', 'core_plugin',
+                userdate($fetch, get_string('strftimedatetime', 'core_langconfig'))));
+        }
+        $updateinfo .= $this->container_end();
+
+        $updateinfo .= $this->box_end();
+
+        return $updateinfo;
+    }
+
+    /**
+     * Helper method to render the information about the available Moodle update
+     *
+     * @param available_update_info $updateinfo information about the available Moodle core update
+     */
+    protected function moodle_available_update_info(available_update_info $updateinfo) {
+
+        $boxclasses = 'moodleupdateinfo';
+        $info = array();
+
+        if (isset($updateinfo->release)) {
+            $info[] = html_writer::tag('span', get_string('updateavailable_release', 'core_admin', $updateinfo->release),
+                array('class' => 'info release'));
+        }
+
+        if (isset($updateinfo->version)) {
+            $info[] = html_writer::tag('span', get_string('updateavailable_version', 'core_admin', $updateinfo->version),
+                array('class' => 'info version'));
+        }
+
+        if (isset($updateinfo->maturity)) {
+            $info[] = html_writer::tag('span', get_string('maturity'.$updateinfo->maturity, 'core_admin'),
+                array('class' => 'info maturity'));
+            $boxclasses .= ' maturity'.$updateinfo->maturity;
+        }
+
+        if (isset($updateinfo->download)) {
+            $info[] = html_writer::link($updateinfo->download, get_string('download'), array('class' => 'info download'));
+        }
+
+        if (isset($updateinfo->url)) {
+            $info[] = html_writer::link($updateinfo->url, get_string('updateavailable_moreinfo', 'core_plugin'),
+                array('class' => 'info more'));
+        }
+
+        $box  = $this->output->box_start($boxclasses);
+        $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '');
+        $box .= $this->output->box_end();
+
+        return $box;
     }
 
     /**
@@ -532,8 +638,16 @@ class core_admin_renderer extends plugin_renderer_base {
 
                 $statuscode = $plugin->get_status();
                 $row->attributes['class'] .= ' status-' . $statuscode;
+                $status = get_string('status_' . $statuscode, 'core_plugin');
 
-                $status = new html_table_cell(get_string('status_' . $statuscode, 'core_plugin'));
+                $availableupdates = $plugin->available_updates();
+                if (!empty($availableupdates)) {
+                    foreach ($availableupdates as $availableupdate) {
+                        $status .= $this->plugin_available_update_info($availableupdate);
+                    }
+                }
+
+                $status = new html_table_cell($status);
 
                 $requires = new html_table_cell($this->required_column($plugin, $pluginman, $version));
 
@@ -541,7 +655,7 @@ class core_admin_renderer extends plugin_renderer_base {
                         plugin_manager::PLUGIN_STATUS_NODB, plugin_manager::PLUGIN_STATUS_UPTODATE));
                 $dependenciesok = $pluginman->are_dependencies_satisfied(
                         $plugin->get_other_required_plugins());
-                if ($isstandard and $statusisboring and $dependenciesok) {
+                if ($isstandard and $statusisboring and $dependenciesok and empty($availableupdates)) {
                     if (empty($options['full'])) {
                         continue;
                     }
@@ -581,6 +695,10 @@ class core_admin_renderer extends plugin_renderer_base {
                 $out .= html_writer::link(new moodle_url('/admin/index.php',
                     array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 1)),
                     get_string('somehighlightedinfo', 'core_plugin'));
+            } else {
+                $out .= html_writer::link(new moodle_url('/admin/index.php',
+                    array('confirmupgrade' => 1, 'confirmrelease' => 1, 'showallplugins' => 0)),
+                    get_string('somehighlightedonly', 'core_plugin'));
             }
             $out .= $this->output->container_end();
         }
@@ -594,12 +712,12 @@ class core_admin_renderer extends plugin_renderer_base {
 
     /**
      * Formats the information that needs to go in the 'Requires' column.
-     * @param plugin_information $plugin the plugin we are rendering the row for.
+     * @param plugininfo_base $plugin the plugin we are rendering the row for.
      * @param plugin_manager $pluginman provides data on all the plugins.
      * @param string $version
      * @return string HTML code
      */
-    protected function required_column(plugin_information $plugin, plugin_manager $pluginman, $version) {
+    protected function required_column(plugininfo_base $plugin, plugin_manager $pluginman, $version) {
         $requires = array();
 
         if (!empty($plugin->versionrequires)) {
@@ -646,6 +764,46 @@ class core_admin_renderer extends plugin_renderer_base {
         return html_writer::tag('ul', implode("\n", $requires));
     }
 
+    /**
+     * Prints an overview about the plugins - number of installed, number of extensions etc.
+     *
+     * @param plugin_manager $pluginman provides information about the plugins
+     * @return string as usually
+     */
+    public function plugins_overview_panel(plugin_manager $pluginman) {
+        $plugininfo = $pluginman->get_plugins();
+
+        $numtotal = $numdisabled = $numextension = $numupdatable = 0;
+
+        foreach ($plugininfo as $type => $plugins) {
+            foreach ($plugins as $name => $plugin) {
+                if ($plugin->get_status() === plugin_manager::PLUGIN_STATUS_MISSING) {
+                    continue;
+                }
+                $numtotal++;
+                if ($plugin->is_enabled() === false) {
+                    $numdisabled++;
+                }
+                if (!$plugin->is_standard()) {
+                    $numextension++;
+                }
+                if ($plugin->available_updates()) {
+                    $numupdatable++;
+                }
+            }
+        }
+
+        $info = array();
+        $info[] = html_writer::tag('span', get_string('numtotal', 'core_plugin', $numtotal), array('class' => 'info total'));
+        $info[] = html_writer::tag('span', get_string('numdisabled', 'core_plugin', $numdisabled), array('class' => 'info disabled'));
+        $info[] = html_writer::tag('span', get_string('numextension', 'core_plugin', $numextension), array('class' => 'info extension'));
+        if ($numupdatable > 0) {
+            $info[] = html_writer::tag('span', get_string('numupdatable', 'core_plugin', $numupdatable), array('class' => 'info updatable'));
+        }
+
+        return $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '', 'plugins-overview-panel');
+    }
+
     /**
      * Displays all known plugins and links to manage them
      *
@@ -665,15 +823,14 @@ class core_admin_renderer extends plugin_renderer_base {
         $table->id = 'plugins-control-panel';
         $table->head = array(
             get_string('displayname', 'core_plugin'),
-            get_string('systemname', 'core_plugin'),
             get_string('source', 'core_plugin'),
             get_string('version', 'core_plugin'),
             get_string('availability', 'core_plugin'),
-            get_string('settings', 'core_plugin'),
-            get_string('uninstall','core_plugin'),
+            get_string('actions', 'core_plugin'),
+            get_string('notes','core_plugin'),
         );
         $table->colclasses = array(
-            'displayname', 'systemname', 'source', 'version', 'availability', 'settings', 'uninstall',
+            'pluginname', 'source', 'version', 'availability', 'actions', 'notes'
         );
 
         foreach ($plugininfo as $type => $plugins) {
@@ -709,10 +866,9 @@ class core_admin_renderer extends plugin_renderer_base {
                 } else {
                     $msg = '';
                 }
-                $displayname  = $icon . ' ' . $plugin->displayname . ' ' . $msg;
-                $displayname = new html_table_cell($displayname);
-
-                $systemname = new html_table_cell($plugin->type . '_' . $plugin->name);
+                $pluginname  = html_writer::tag('div', $icon . ' ' . $plugin->displayname . ' ' . $msg, array('class' => 'displayname')).
+                               html_writer::tag('div', $plugin->component, array('class' => 'componentname'));
+                $pluginname  = new html_table_cell($pluginname);
 
                 if ($plugin->is_standard()) {
                     $row->attributes['class'] .= ' standard';
@@ -737,28 +893,39 @@ class core_admin_renderer extends plugin_renderer_base {
                     $availability = new html_table_cell($icon . ' ' . get_string('plugindisabled', 'core_plugin'));
                 }
 
+                $actions = array();
+
                 $settingsurl = $plugin->get_settings_url();
-                if (is_null($settingsurl)) {
-                    $settings = new html_table_cell('');
-                } else {
-                    $settings = html_writer::link($settingsurl, get_string('settings', 'core_plugin'));
-                    $settings = new html_table_cell($settings);
+                if (!is_null($settingsurl)) {
+                    $actions[] = html_writer::link($settingsurl, get_string('settings', 'core_plugin'), array('class' => 'settings'));
                 }
 
                 $uninstallurl = $plugin->get_uninstall_url();
+                if (!is_null($uninstallurl)) {
+                    $actions[] = html_writer::link($uninstallurl, get_string('uninstall', 'core_plugin'), array('class' => 'uninstall'));
+                }
+
+                $actions = new html_table_cell(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $actions));
+
                 $requriedby = $pluginman->other_plugins_that_require($plugin->component);
-                if (is_null($uninstallurl)) {
-                    $uninstall = new html_table_cell('');
-                } else if ($requriedby) {
-                    $uninstall = new html_table_cell(get_string('requiredby', 'core_plugin', implode(', ', $requriedby)));
-                    $uninstall->attributes['class'] = 'requiredby';
+                if ($requriedby) {
+                    $requiredby = html_writer::tag('div', get_string('requiredby', 'core_plugin', implode(', ', $requriedby)),
+                        array('class' => 'requiredby'));
                 } else {
-                    $uninstall = html_writer::link($uninstallurl, get_string('uninstall', 'core_plugin'));
-                    $uninstall = new html_table_cell($uninstall);
+                    $requiredby = '';
+                }
+
+                $updateinfo = '';
+                if (is_array($plugin->available_updates())) {
+                    foreach ($plugin->available_updates() as $availableupdate) {
+                        $updateinfo .= $this->plugin_available_update_info($availableupdate);
+                    }
                 }
 
+                $notes = new html_table_cell($requiredby.$updateinfo);
+
                 $row->cells = array(
-                    $displayname, $systemname, $source, $version, $availability, $settings, $uninstall
+                    $pluginname, $source, $version, $availability, $actions, $notes
                 );
                 $table->data[] = $row;
             }
@@ -767,6 +934,47 @@ class core_admin_renderer extends plugin_renderer_base {
         return html_writer::table($table);
     }
 
+    /**
+     * Helper method to render the information about the available plugin update
+     *
+     * The passed objects always provides at least the 'version' property containing
+     * the (higher) version of the plugin available.
+     *
+     * @param available_update_info $updateinfo information about the available update for the plugin
+     */
+    protected function plugin_available_update_info(available_update_info $updateinfo) {
+
+        $boxclasses = 'pluginupdateinfo';
+        $info = array();
+
+        if (isset($updateinfo->release)) {
+            $info[] = html_writer::tag('span', get_string('updateavailable_release', 'core_plugin', $updateinfo->release),
+                array('class' => 'info release'));
+        }
+
+        if (isset($updateinfo->maturity)) {
+            $info[] = html_writer::tag('span', get_string('maturity'.$updateinfo->maturity, 'core_admin'),
+                array('class' => 'info maturity'));
+            $boxclasses .= ' maturity'.$updateinfo->maturity;
+        }
+
+        if (isset($updateinfo->download)) {
+            $info[] = html_writer::link($updateinfo->download, get_string('download'), array('class' => 'info download'));
+        }
+
+        if (isset($updateinfo->url)) {
+            $info[] = html_writer::link($updateinfo->url, get_string('updateavailable_moreinfo', 'core_plugin'),
+                array('class' => 'info more'));
+        }
+
+        $box  = $this->output->box_start($boxclasses);
+        $box .= html_writer::tag('div', get_string('updateavailable', 'core_plugin', $updateinfo->version), array('class' => 'version'));
+        $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '');
+        $box .= $this->output->box_end();
+
+        return $box;
+    }
+
     /**
      * This function will render one beautiful table with all the environmental
      * configuration and how it suits Moodle needs.
index 90c8832..b8b674c 100644 (file)
@@ -221,4 +221,20 @@ $ADMIN->add('server', $temp);
 
 $ADMIN->add('server', new admin_externalpage('adminregistration', new lang_string('registration','admin'), "$CFG->wwwroot/$CFG->admin/registration/index.php"));
 
+// "update notifications" settingpage
+$temp = new admin_settingpage('updatenotifications', new lang_string('updatenotifications', 'core_admin'));
+$temp->add(new admin_setting_configcheckbox('updateautocheck', new lang_string('updateautocheck', 'core_admin'),
+                                            new lang_string('updateautocheck_desc', 'core_admin'), 1));
+$temp->add(new admin_setting_configselect('updateminmaturity', new lang_string('updateminmaturity', 'core_admin'),
+                                          new lang_string('updateminmaturity_desc', 'core_admin'), MATURITY_STABLE,
+                                          array(
+                                              MATURITY_ALPHA  => new lang_string('maturity'.MATURITY_ALPHA, 'core_admin'),
+                                              MATURITY_BETA   => new lang_string('maturity'.MATURITY_BETA, 'core_admin'),
+                                              MATURITY_RC     => new lang_string('maturity'.MATURITY_RC, 'core_admin'),
+                                              MATURITY_STABLE => new lang_string('maturity'.MATURITY_STABLE, 'core_admin'),
+                                          )));
+$temp->add(new admin_setting_configcheckbox('updatenotifybuilds', new lang_string('updatenotifybuilds', 'core_admin'),
+                                            new lang_string('updatenotifybuilds_desc', 'core_admin'), 0));
+$ADMIN->add('server', $temp);
+
 } // end of speedup
index c7d25da..1eceb1a 100644 (file)
@@ -348,7 +348,7 @@ class generator {
         $base_course->summary = 'Blah Blah';
         $base_course->format = 'weeks';
         $base_course->numsections = '10';
-        $base_course->startdate = mktime();
+        $base_course->startdate = time();
         $base_course->id = '0';
 
         $courses_count = 0;
@@ -461,7 +461,7 @@ class generator {
                             case 'assignment':
                                 $module->intro = $description;
                                 $module->assignmenttype = $this->get_module_type('assignment');
-                                $module->timedue = mktime() + 89487321;
+                                $module->timedue = time() + 89487321;
                                 $module->grade = rand(50,100);
                                 break;
                             case 'chat':
@@ -505,8 +505,8 @@ class generator {
                                 break;
                             case 'lesson':
                                 $module->lessondefault = 1;
-                                $module->available = mktime();
-                                $module->deadline = mktime() + 719891987;
+                                $module->available = time();
+                                $module->deadline = time() + 719891987;
                                 $module->grade = 100;
                                 break;
                             case 'quiz':
index c270e17..18a08db 100644 (file)
@@ -29,7 +29,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-define('PHPUNIT_CLI_UTIL', true);
+define('PHPUNIT_UTIL', true);
 
 require(__DIR__ . '/../../../../lib/phpunit/bootstrap.php');
 require_once($CFG->libdir.'/phpunit/lib.php');
index 3010666..7bd7c13 100644 (file)
@@ -48,16 +48,16 @@ $string['gotoindex'] = 'Back to the list of quizzes that can be upgraded';
 $string['gotoquizreport'] = 'Go to the reports for this quiz, to check the upgrade';
 $string['gotoresetlink'] = 'Go to the list of quizzes that can be reset';
 $string['includedintheupgrade'] = 'Included in the upgrade?';
-$string['invalidquizid'] = 'Invaid quiz id. Either the quiz does not exist, or it has no attempts to convert.';
+$string['invalidquizid'] = 'Invalid quiz id. Either the quiz does not exist, or it has no attempts to convert.';
 $string['listpreupgrade'] = 'List quizzes and attempts';
 $string['listpreupgrade_desc'] = 'This will show a report of all the quizzes on the system and how many attempts they have. This will give you an idea of the scope of the upgrade you have to do.';
 $string['listpreupgradeintro'] = 'These are the number of quiz attempts that will need to be processed when you upgrade your site. A few tens of thousands is no worry. Much beyond that and you need to think about how long the upgrade will take.';
 $string['listtodo'] = 'List quizzes still to upgrade';
 $string['listtodo_desc'] = 'This will show a report of all the quizzes on the system (if any) that have attempts that still need to be upgraded to the new question engine.';
 $string['listtodointro'] = 'These are all the quizzes with attempt data that still needs to be converted. You can convert the attempts by clicking the link.';
-$string['listupgraded'] = 'List already upgrade quizzes that can be reset';
+$string['listupgraded'] = 'List already upgraded quizzes that can be reset';
 $string['listupgraded_desc'] = 'This will show a report of all the quizzes on the system whose attempts have been upgraded, and where the old data is still present so the upgrade could be reset and redone.';
-$string['listupgradedintro'] = 'These are all the quizzes that have attempts that were upgraded, and where the old attempt data is so there, so they could be reset, and the upgrade re-done.';
+$string['listupgradedintro'] = 'These are all the quizzes that have attempts that were upgraded, and where the old attempt data is still there, so they could be reset, and the upgrade re-done.';
 $string['noquizattempts'] = 'Your site does not have any quiz attempts at all!';
 $string['nothingupgradedyet'] = 'No upgraded attempts that can be reset';
 $string['notupgradedsiterequired'] = 'This script can only work before the site has been upgraded.';
@@ -80,4 +80,4 @@ $string['resettingquizattemptsprogress'] = 'Resetting attempt {$a->done} / {$a->
 $string['upgradingquizattempts'] = 'Upgrading the attempts for quiz \'{$a->name}\' in course {$a->shortname}';
 $string['upgradedsitedetected'] = 'This appears to be a site that has been upgraded to include the new question engine.';
 $string['upgradedsiterequired'] = 'This script can only work after the site has been upgraded.';
-$string['veryoldattemtps'] = 'Your site has {$a} quiz attempts that were never completely updated during the upgrade from Moodle 1.4 to Moodle 1.5. These attempts will be dealt wiht before the main upgrade. You need to to consider the extra time required for this.';
+$string['veryoldattemtps'] = 'Your site has {$a} quiz attempts that were never completely updated during the upgrade from Moodle 1.4 to Moodle 1.5. These attempts will be dealt with before the main upgrade. You need to to consider the extra time required for this.';
index e5e2eb1..b3f060c 100644 (file)
@@ -28,10 +28,6 @@ define('NO_OUTPUT_BUFFERING', true);
 require(dirname(__FILE__) . '/../../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 
-// unfortunately outdated SimpleTest is not E_STRICT compatible
-$CFG->debug = ($CFG->debug & ~E_STRICT);
-error_reporting($CFG->debug);
-
 require_once('simpletestlib.php');
 require_once('simpletestcoveragelib.php');
 require_once('ex_simple_test.php');
index 5d440f3..b7b296c 100644 (file)
@@ -55,8 +55,8 @@ class ExHtmlReporter extends HtmlReporter {
      *
      * @param bool $showpasses Whether this reporter should output anything for passes.
      */
-    function ExHtmlReporter($showpasses) {
-        $this->HtmlReporter();
+    function __construct($showpasses) {
+        parent::__construct('UTF-8');
         $this->showpasses = $showpasses;
 
         $this->strrunonlyfolder = $this->get_string('runonlyfolder');
@@ -254,7 +254,7 @@ class ExHtmlReporter extends HtmlReporter {
         echo '<div class="performanceinfo">',
                 $this->get_string('runat', userdate($this->timestart)), ' ',
                 $this->get_string('timetakes', format_time(time() - $this->timestart)), ' ',
-                $this->get_string('version', SimpleTestOptions::getVersion()),
+                $this->get_string('version', SimpleTest::getVersion()),
                 '</div>';
     }
 
index 1428a01..dd6ee8a 100644 (file)
@@ -46,7 +46,7 @@ class AutoGroupTest extends TestSuite {
         $this->showsearch = $showsearch;
     }
 
-    function run(&$reporter) {
+    function run($reporter) {
         global $UNITTEST;
 
         $UNITTEST->running = true;
@@ -85,7 +85,7 @@ class AutoGroupTest extends TestSuite {
 
                 $s_count++;
                 // OK, found: this shows as a 'Notice' for any 'simpletest/test*.php' file.
-                $this->addTestCase(new FindFileNotice($file_path, 'Found unit test file, '. $s_count));
+                $this->add(new FindFileNotice($file_path, 'Found unit test file, '. $s_count));
 
                 // addTestFile: Unfortunately this doesn't return fail/success (bool).
                 $this->addTestFile($file_path, true);
@@ -105,9 +105,9 @@ class AutoGroupTest extends TestSuite {
         $path = $dir;
         $count = $this->_recurseFolders($path);
         if ($count <= 0) {
-            $this->addTestCase(new BadAutoGroupTest($path, 'Search complete. No unit test files found'));
+            $this->add(new BadAutoGroupTest($path, 'Search complete. No unit test files found'));
         } else {
-            $this->addTestCase(new AutoGroupTestNotice($path, 'Search complete. Total unit test files found: '. $count));
+            $this->add(new AutoGroupTestNotice($path, 'Search complete. Total unit test files found: '. $count));
         }
         if ($this->showsearch) {
                 echo '</ul>';
@@ -116,6 +116,7 @@ class AutoGroupTest extends TestSuite {
     }
 
     function addTestFile($file, $internalcall = false) {
+
         if ($this->showsearch) {
             if ($internalcall) {
                 echo '<li><b>' . basename($file) . '</b></li>';
@@ -126,10 +127,10 @@ class AutoGroupTest extends TestSuite {
             // get blank screens because evil people turn down error_reporting elsewhere.
             error_reporting(E_ALL);
         }
-        if(!is_file($file) ){
-            parent::addTestCase(new BadTest($file, 'Not a file or does not exist'));
+        if (!is_file($file) ){
+            parent::add(new BadTest($file, 'Not a file or does not exist'));
         }
-        parent::addTestFile($file);
+        parent::addFile($file);
     }
 }
 
index 62a60a7..93057b5 100644 (file)
@@ -28,13 +28,6 @@ define('NO_OUTPUT_BUFFERING', true);
 
 require(dirname(__FILE__) . '/../../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
-
-// Always run the unit tests in developer debug mode.
-// unfortunately outdated SimpleTest is not E_STRICT compatible
-$CFG->debug = (DEBUG_DEVELOPER & ~E_STRICT);
-error_reporting($CFG->debug);
-raise_memory_limit(MEMORY_EXTRA);
-
 require_once('simpletestlib.php');
 require_once('simpletestcoveragelib.php');
 require_once('ex_simple_test.php');
@@ -48,6 +41,8 @@ $showsearch   = optional_param('showsearch', false, PARAM_BOOL);
 
 admin_externalpage_setup('toolsimpletest', '', array('showpasses'=>$showpasses, 'showsearch'=>$showsearch));
 
+raise_memory_limit(MEMORY_EXTRA);
+
 $unittest = true;
 
 global $UNITTEST;
index 88943c7..306bf29 100644 (file)
@@ -123,7 +123,7 @@ class autogroup_test_coverage extends AutoGroupTest {
      * automatically generating the coverage report. Only supports one instrumentation
      * to be executed and reported.
      */
-    public function run(&$simpletestreporter) {
+    public function run($simpletestreporter) {
         global $CFG;
 
         if (moodle_coverage_recorder::can_run_codecoverage() && $this->performcoverage) {
@@ -135,6 +135,7 @@ class autogroup_test_coverage extends AutoGroupTest {
             $covrecorder->start_instrumentation();
             parent::run($simpletestreporter);
             $covrecorder->stop_instrumentation();
+            set_time_limit(60*10); // it may take a long time to generate the report
             $covrecorder->generate_report();
             moodle_coverage_reporter::print_summary_info(basename($this->coveragedir));
         } else {
@@ -148,7 +149,7 @@ class autogroup_test_coverage extends AutoGroupTest {
      * allowing further process of coverage data once tests are over. Supports multiple
      * instrumentations (code coverage gathering sessions) to be executed.
      */
-    public function run_with_external_coverage(&$simpletestreporter, &$covrecorder) {
+    public function run_with_external_coverage($simpletestreporter, $covrecorder) {
 
         if (moodle_coverage_recorder::can_run_codecoverage() && $this->performcoverage) {
             $covrecorder->setIncludePaths($this->includecoverage);
index e2c573f..e60faf7 100644 (file)
@@ -82,8 +82,8 @@ function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false
 class IgnoreWhitespaceExpectation extends SimpleExpectation {
     var $expect;
 
-    function IgnoreWhitespaceExpectation($content, $message = '%s') {
-        $this->SimpleExpectation($message);
+    function __construct($content, $message = '%s') {
+        parent::__construct($message);
         $this->expect=$this->normalise($content);
     }
 
@@ -111,8 +111,8 @@ class IgnoreWhitespaceExpectation extends SimpleExpectation {
 class ArraysHaveSameValuesExpectation extends SimpleExpectation {
     var $expect;
 
-    function ArraysHaveSameValuesExpectation($expected, $message = '%s') {
-        $this->SimpleExpectation($message);
+    function __construct($expected, $message = '%s') {
+        parent::__construct($message);
         if (!is_array($expected)) {
             trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' .
                     'with an expected value that is not an array.');
@@ -149,8 +149,8 @@ class ArraysHaveSameValuesExpectation extends SimpleExpectation {
 class CheckSpecifiedFieldsExpectation extends SimpleExpectation {
     var $expect;
 
-    function CheckSpecifiedFieldsExpectation($expected, $message = '%s') {
-        $this->SimpleExpectation($message);
+    function __construct($expected, $message = '%s') {
+        parent::__construct($message);
         if (!is_object($expected)) {
             trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' .
                     'with an expected value that is not an object.');
@@ -651,7 +651,7 @@ class UnitTestCaseUsingDatabase extends UnitTestCase {
         }
 
         // Only do this after the above text.
-        parent::UnitTestCase($label);
+        parent::__construct($label);
 
         // Create the test DB instance.
         $this->realdb = $DB;
@@ -974,7 +974,7 @@ class FakeDBUnitTestCase extends UnitTestCase {
             return;
         }
 
-        parent::UnitTestCase($label);
+        parent::__construct($label);
         // MDL-16483 Get PKs and save data to text file
 
         $this->pkfile = $CFG->dataroot.'/testtablespks.csv';
index 96e53c6..91aa058 100644 (file)
@@ -254,7 +254,7 @@ class web_service_token_form extends moodleform {
         return $data;
     }
 
-    function validation(&$data, $files) {
+    function validation($data, $files) {
         global $DB;
 
         $errors = parent::validation($data, $files);
index d8c9523..94ea981 100644 (file)
@@ -150,7 +150,7 @@ class auth_plugin_cas extends auth_plugin_ldap {
     function prelogout_hook() {
         global $CFG;
 
-        if ($this->config->logoutcas) {
+        if (!empty($this->config->logoutcas)) {
             $backurl = $CFG->wwwroot;
             $this->connectCAS();
             phpCAS::logoutWithURL($backurl);
index 8eff5f2..18dfdea 100644 (file)
@@ -41,7 +41,7 @@
 
         ?>
     </td>
-    <td><?php print_string("auth_shib_convert_data_description", "auth_shibboleth"); echo $config->alt_login ?></td>
+    <td><?php print_string("auth_shib_convert_data_description", "auth_shibboleth"); echo (isset($config->alt_login) ? $config->alt_login : '') ?></td>
 </tr>
 
 <tr valign="top">
diff --git a/backup/controller/tests/controller_test.php b/backup/controller/tests/controller_test.php
new file mode 100644 (file)
index 0000000..b9ff372
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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');
+
+/*
+ * controller tests (all)
+ */
+class backup_controller_testcase extends advanced_testcase {
+
+    protected $moduleid;  // course_modules id used for testing
+    protected $sectionid; // course_sections id used for testing
+    protected $courseid;  // course id used for testing
+    protected $userid;    // user used if for testing
+
+    protected function setUp() {
+        global $DB, $CFG;
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
+        $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
+
+        $this->moduleid  = $coursemodule->id;
+        $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
+        $this->courseid  = $coursemodule->course;
+        $this->userid = 2; // admin
+
+        // Disable all loggers
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_output_indented_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /*
+     * test base_setting class
+     */
+    public function test_backup_controller() {
+        // Instantiate non interactive backup_controller
+        $bc = new mock_backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        $this->assertTrue($bc instanceof backup_controller);
+        $this->assertEquals($bc->get_status(), backup::STATUS_AWAITING);
+        // Instantiate interactive backup_controller
+        $bc = new mock_backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_YES, backup::MODE_GENERAL, $this->userid);
+        $this->assertTrue($bc instanceof backup_controller);
+        $this->assertEquals($bc->get_status(), backup::STATUS_SETTING_UI);
+        $this->assertEquals(strlen($bc->get_backupid()), 32); // is one md5
+
+        // Save and load one backup controller to check everything is in place
+        $bc = new mock_backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        $recid = $bc->save_controller();
+        $newbc = mock_backup_controller::load_controller($bc->get_backupid());
+        $this->assertTrue($newbc instanceof backup_controller); // This means checksum and load worked ok
+    }
+}
+
+
+/*
+ * helper extended @backup_controller class that makes some methods public for testing
+ */
+class mock_backup_controller extends backup_controller {
+
+    public function save_controller() {
+        parent::save_controller();
+    }
+}
index 61b148b..ef37f5e 100644 (file)
@@ -112,7 +112,8 @@ abstract class base_converter implements loggable {
      * @return string the system name of the converter
      */
     public function get_name() {
-        return array_shift(explode('_', get_class($this)));
+        $parts = explode('_', get_class($this));
+        return array_shift($parts);
     }
 
     /**
index 52ca776..81b9657 100644 (file)
@@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die();
 require_once($CFG->dirroot . '/backup/converter/convertlib.php');
 
 class imscc11_export_converter extends base_converter {
-    public function get_deps() {
+    static public function get_deps() {
         global $CFG;
         require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
         return array(
index da87824..51cad64 100644 (file)
@@ -863,6 +863,7 @@ class moodle1_course_outline_handler extends moodle1_xml_handler {
         // host...
         $versionfile = $CFG->dirroot.'/mod/'.$data['modulename'].'/version.php';
         if (file_exists($versionfile)) {
+            $module = new stdClass();
             include($versionfile);
             $data['version'] = $module->version;
         } else {
@@ -1852,7 +1853,7 @@ abstract class moodle1_qtype_handler extends moodle1_plugin_handler {
     /**
      * Question type handlers cannot open the xml_writer
      */
-    final protected function open_xml_writer() {
+    final protected function open_xml_writer($filename) {
         throw new moodle1_convert_exception('opening_xml_writer_forbidden');
     }
 
@@ -1971,7 +1972,7 @@ abstract class moodle1_resource_successor_handler extends moodle1_mod_handler {
      * @param array $data pre-cooked legacy resource data
      * @param array $raw raw legacy resource data
      */
-    public function process_legacy_resource(array $data, array $raw) {
+    public function process_legacy_resource(array $data, array $raw = null) {
     }
 
     /**
diff --git a/backup/converter/moodle1/tests/lib_test.php b/backup/converter/moodle1/tests/lib_test.php
new file mode 100644 (file)
index 0000000..4b31157
--- /dev/null
@@ -0,0 +1,475 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the moodle1 converter
+ *
+ * @package    core_backup
+ * @subpackage backup-convert
+ * @category   phpunit
+ * @copyright  2011 Mark Nielsen <mark@moodlerooms.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/backup/converter/moodle1/lib.php');
+
+
+class moodle1_converter_testcase extends advanced_testcase {
+
+    /** @var string the name of the directory containing the unpacked Moodle 1.9 backup */
+    protected $tempdir;
+
+    protected function setUp() {
+        global $CFG;
+
+        $this->tempdir = convert_helper::generate_id('simpletest');
+        check_dir_exists("$CFG->tempdir/backup/$this->tempdir/course_files/sub1");
+        check_dir_exists("$CFG->tempdir/backup/$this->tempdir/moddata/unittest/4/7");
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/moodle.xml",
+            "$CFG->tempdir/backup/$this->tempdir/moodle.xml"
+        );
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/icon.gif",
+            "$CFG->tempdir/backup/$this->tempdir/course_files/file1.gif"
+        );
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/icon.gif",
+            "$CFG->tempdir/backup/$this->tempdir/course_files/sub1/file2.gif"
+        );
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/icon.gif",
+            "$CFG->tempdir/backup/$this->tempdir/moddata/unittest/4/file1.gif"
+        );
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/icon.gif",
+            "$CFG->tempdir/backup/$this->tempdir/moddata/unittest/4/icon.gif"
+        );
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/icon.gif",
+            "$CFG->tempdir/backup/$this->tempdir/moddata/unittest/4/7/icon.gif"
+        );
+    }
+
+    protected function tearDown() {
+        global $CFG;
+        if (empty($CFG->keeptempdirectoriesonbackup)) {
+            fulldelete("$CFG->tempdir/backup/$this->tempdir");
+        }
+    }
+
+    public function test_detect_format() {
+        $detected = moodle1_converter::detect_format($this->tempdir);
+        $this->assertEquals(backup::FORMAT_MOODLE1, $detected);
+    }
+
+    public function test_convert_factory() {
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $this->assertInstanceOf('moodle1_converter', $converter);
+    }
+
+    public function test_stash_storage_not_created() {
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $this->setExpectedException('moodle1_convert_storage_exception');
+        $converter->set_stash('tempinfo', 12);
+    }
+
+    public function test_stash_requiring_empty_stash() {
+        $this->resetAfterTest(true);
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->create_stash_storage();
+        $converter->set_stash('tempinfo', 12);
+        $this->setExpectedException('moodle1_convert_empty_storage_exception');
+        try {
+            $converter->get_stash('anothertempinfo');
+
+        } catch (moodle1_convert_empty_storage_exception $e) {
+            // we must drop the storage here so we are able to re-create it in the next test
+            $converter->drop_stash_storage();
+            throw new moodle1_convert_empty_storage_exception('rethrowing');
+        }
+    }
+
+    public function test_stash_storage() {
+        $this->resetAfterTest(true);
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->create_stash_storage();
+
+        // no implicit stashes
+        $stashes = $converter->get_stash_names();
+        $this->assertEquals(gettype($stashes), 'array');
+        $this->assertTrue(empty($stashes));
+
+        // test stashes without itemid
+        $converter->set_stash('tempinfo1', 12);
+        $converter->set_stash('tempinfo2', array('a' => 2, 'b' => 3));
+        $stashes = $converter->get_stash_names();
+        $this->assertEquals('array', gettype($stashes));
+        $this->assertEquals(2, count($stashes));
+        $this->assertTrue(in_array('tempinfo1', $stashes));
+        $this->assertTrue(in_array('tempinfo2', $stashes));
+        $this->assertEquals(12, $converter->get_stash('tempinfo1'));
+        $this->assertEquals(array('a' => 2, 'b' => 3), $converter->get_stash('tempinfo2'));
+
+        // overwriting a stashed value is allowed
+        $converter->set_stash('tempinfo1', '13');
+        $this->assertNotSame(13, $converter->get_stash('tempinfo1'));
+        $this->assertSame('13', $converter->get_stash('tempinfo1'));
+
+        // repeated reading is allowed
+        $this->assertEquals('13', $converter->get_stash('tempinfo1'));
+
+        // storing empty array
+        $converter->set_stash('empty_array_stash', array());
+        $restored = $converter->get_stash('empty_array_stash');
+        //$this->assertEquals(gettype($restored), 'array'); // todo return null now, this needs MDL-27713 to be fixed, then uncomment
+        $this->assertTrue(empty($restored));
+
+        // test stashes with itemid
+        $converter->set_stash('tempinfo', 'Hello', 1);
+        $converter->set_stash('tempinfo', 'World', 2);
+        $this->assertSame('Hello', $converter->get_stash('tempinfo', 1));
+        $this->assertSame('World', $converter->get_stash('tempinfo', 2));
+
+        // test get_stash_itemids()
+        $ids = $converter->get_stash_itemids('course_fileref');
+        $this->assertEquals(gettype($ids), 'array');
+        $this->assertTrue(empty($ids));
+
+        $converter->set_stash('course_fileref', null, 34);
+        $converter->set_stash('course_fileref', null, 52);
+        $ids = $converter->get_stash_itemids('course_fileref');
+        $this->assertEquals(2, count($ids));
+        $this->assertTrue(in_array(34, $ids));
+        $this->assertTrue(in_array(52, $ids));
+
+        $converter->drop_stash_storage();
+    }
+
+    public function test_get_stash_or_default() {
+        $this->resetAfterTest(true);
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->create_stash_storage();
+
+        $this->assertTrue(is_null($converter->get_stash_or_default('stashname')));
+        $this->assertTrue(is_null($converter->get_stash_or_default('stashname', 7)));
+        $this->assertTrue('default' === $converter->get_stash_or_default('stashname', 0, 'default'));
+        $this->assertTrue(array('foo', 'bar') === $converter->get_stash_or_default('stashname', 42, array('foo', 'bar')));
+
+        //$converter->set_stash('stashname', 0);
+        //$this->assertFalse(is_null($converter->get_stash_or_default('stashname'))); // todo returns true now, this needs MDL-27713 to be fixed
+
+        //$converter->set_stash('stashname', '');
+        //$this->assertFalse(is_null($converter->get_stash_or_default('stashname'))); // todo returns true now, this needs MDL-27713 to be fixed
+
+        //$converter->set_stash('stashname', array());
+        //$this->assertFalse(is_null($converter->get_stash_or_default('stashname'))); // todo returns true now, this needs MDL-27713 to be fixed
+
+        $converter->set_stash('stashname', 42);
+        $this->assertTrue(42 === $converter->get_stash_or_default('stashname'));
+        $this->assertTrue(is_null($converter->get_stash_or_default('stashname', 1)));
+        $this->assertTrue(42 === $converter->get_stash_or_default('stashname', 0, 61));
+
+        $converter->set_stash('stashname', array(42 => (object)array('id' => 42)), 18);
+        $stashed = $converter->get_stash_or_default('stashname', 18, 1984);
+        $this->assertEquals(gettype($stashed), 'array');
+        $this->assertTrue(is_object($stashed[42]));
+        $this->assertTrue($stashed[42]->id === 42);
+
+        $converter->drop_stash_storage();
+    }
+
+    public function test_get_contextid() {
+        $this->resetAfterTest(true);
+
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+
+        // stash storage must be created in advance
+        $converter->create_stash_storage();
+
+        // ids are generated on the first call
+        $id1 = $converter->get_contextid(CONTEXT_BLOCK, 10);
+        $id2 = $converter->get_contextid(CONTEXT_BLOCK, 11);
+        $id3 = $converter->get_contextid(CONTEXT_MODULE, 10);
+
+        $this->assertNotEquals($id1, $id2);
+        $this->assertNotEquals($id1, $id3);
+        $this->assertNotEquals($id2, $id3);
+
+        // and then re-used if called with the same params
+        $this->assertEquals($id1, $converter->get_contextid(CONTEXT_BLOCK, 10));
+        $this->assertEquals($id2, $converter->get_contextid(CONTEXT_BLOCK, 11));
+        $this->assertEquals($id3, $converter->get_contextid(CONTEXT_MODULE, 10));
+
+        // for system and course level, the instance is irrelevant
+        // as we need only one system and one course
+        $id1 = $converter->get_contextid(CONTEXT_COURSE);
+        $id2 = $converter->get_contextid(CONTEXT_COURSE, 10);
+        $id3 = $converter->get_contextid(CONTEXT_COURSE, 14);
+
+        $this->assertEquals($id1, $id2);
+        $this->assertEquals($id1, $id3);
+
+        $id1 = $converter->get_contextid(CONTEXT_SYSTEM);
+        $id2 = $converter->get_contextid(CONTEXT_SYSTEM, 11);
+        $id3 = $converter->get_contextid(CONTEXT_SYSTEM, 15);
+
+        $this->assertEquals($id1, $id2);
+        $this->assertEquals($id1, $id3);
+
+        $converter->drop_stash_storage();
+    }
+
+    public function test_get_nextid() {
+        $this->resetAfterTest(true);
+
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+
+        $id1 = $converter->get_nextid();
+        $id2 = $converter->get_nextid();
+        $id3 = $converter->get_nextid();
+
+        $this->assertTrue(0 < $id1);
+        $this->assertTrue($id1 < $id2);
+        $this->assertTrue($id2 < $id3);
+    }
+
+    public function test_migrate_file() {
+        $this->resetAfterTest(true);
+
+        // set-up the file manager
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->create_stash_storage();
+        $contextid = $converter->get_contextid(CONTEXT_MODULE, 32);
+        $fileman   = $converter->get_file_manager($contextid, 'mod_unittest', 'testarea');
+        // this fileman has not converted anything yet
+        $fileids = $fileman->get_fileids();
+        $this->assertEquals(gettype($fileids), 'array');
+        $this->assertEquals(0, count($fileids));
+        // try to migrate a non-existing directory
+        $returned = $fileman->migrate_directory('not/existing/directory');
+        $this->assertEquals(gettype($returned), 'array');
+        $this->assertEquals(0, count($returned));
+        $fileids = $fileman->get_fileids();
+        $this->assertEquals(gettype($fileids), 'array');
+        $this->assertEquals(0, count($fileids));
+        // migrate a single file
+        $fileman->itemid = 4;
+        $fileman->migrate_file('moddata/unittest/4/icon.gif');
+        $this->assertTrue(is_file($converter->get_workdir_path().'/files/4e/4ea114b0558f53e3af8dd9afc0e0810a95c2a724'));
+        // get the file id
+        $fileids = $fileman->get_fileids();
+        $this->assertEquals(gettype($fileids), 'array');
+        $this->assertEquals(1, count($fileids));
+        // migrate another single file into another file area
+        $fileman->filearea = 'anotherarea';
+        $fileman->itemid = 7;
+        $fileman->migrate_file('moddata/unittest/4/7/icon.gif', '/', 'renamed.gif');
+        // get the file records
+        $filerecordids = $converter->get_stash_itemids('files');
+        foreach ($filerecordids as $filerecordid) {
+            $filerecord = $converter->get_stash('files', $filerecordid);
+            $this->assertEquals('4ea114b0558f53e3af8dd9afc0e0810a95c2a724', $filerecord['contenthash']);
+            $this->assertEquals($contextid, $filerecord['contextid']);
+            $this->assertEquals('mod_unittest', $filerecord['component']);
+            if ($filerecord['filearea'] === 'testarea') {
+                $this->assertEquals(4, $filerecord['itemid']);
+                $this->assertEquals('icon.gif', $filerecord['filename']);
+            }
+        }
+        // explicitly clear the list of migrated files
+        $this->assertTrue(count($fileman->get_fileids()) > 0);
+        $fileman->reset_fileids();
+        $this->assertTrue(count($fileman->get_fileids()) == 0);
+        $converter->drop_stash_storage();
+    }
+
+    public function test_convert_path() {
+        $path = new convert_path('foo_bar', '/ROOT/THINGS/FOO/BAR');
+        $this->assertEquals('foo_bar', $path->get_name());
+        $this->assertEquals('/ROOT/THINGS/FOO/BAR', $path->get_path());
+        $this->assertEquals('process_foo_bar', $path->get_processing_method());
+        $this->assertEquals('on_foo_bar_start', $path->get_start_method());
+        $this->assertEquals('on_foo_bar_end', $path->get_end_method());
+    }
+
+    public function test_convert_path_implicit_recipes() {
+        $path = new convert_path('foo_bar', '/ROOT/THINGS/FOO/BAR');
+        $data = array(
+            'ID' => 76,
+            'ELOY' => 'stronk7',
+            'MARTIN' => 'moodler',
+            'EMPTY' => null,
+        );
+        // apply default recipes (converting keys to lowercase)
+        $data = $path->apply_recipes($data);
+        $this->assertEquals(4, count($data));
+        $this->assertEquals(76, $data['id']);
+        $this->assertEquals('stronk7', $data['eloy']);
+        $this->assertEquals('moodler', $data['martin']);
+        $this->assertSame(null, $data['empty']);
+    }
+
+    public function test_convert_path_explicit_recipes() {
+        $path = new convert_path(
+            'foo_bar', '/ROOT/THINGS/FOO/BAR',
+            array(
+                'newfields' => array(
+                    'david' => 'mudrd8mz',
+                    'petr'  => 'skodak',
+                ),
+                'renamefields' => array(
+                    'empty' => 'nothing',
+                ),
+                'dropfields' => array(
+                    'id'
+                ),
+            )
+        );
+        $data = array(
+            'ID' => 76,
+            'ELOY' => 'stronk7',
+            'MARTIN' => 'moodler',
+            'EMPTY' => null,
+        );
+        $data = $path->apply_recipes($data);
+
+        $this->assertEquals(5, count($data));
+        $this->assertFalse(array_key_exists('id', $data));
+        $this->assertEquals('stronk7', $data['eloy']);
+        $this->assertEquals('moodler', $data['martin']);
+        $this->assertEquals('mudrd8mz', $data['david']);
+        $this->assertEquals('skodak', $data['petr']);
+        $this->assertSame(null, $data['nothing']);
+    }
+
+    public function test_grouped_data_on_nongrouped_convert_path() {
+        // prepare some grouped data
+        $data = array(
+            'ID' => 77,
+            'NAME' => 'Pale lagers',
+            'BEERS' => array(
+                array(
+                    'BEER' => array(
+                        'ID' => 67,
+                        'NAME' => 'Pilsner Urquell',
+                    )
+                ),
+                array(
+                    'BEER' => array(
+                        'ID' => 34,
+                        'NAME' => 'Heineken',
+                    )
+                ),
+            )
+        );
+
+        // declare a non-grouped path
+        $path = new convert_path('beer_style', '/ROOT/BEER_STYLES/BEER_STYLE');
+
+        // an attempt to apply recipes throws exception because we do not expect grouped data
+        $this->setExpectedException('convert_path_exception');
+        $data = $path->apply_recipes($data);
+    }
+
+    public function test_grouped_convert_path_with_recipes() {
+        // prepare some grouped data
+        $data = array(
+            'ID' => 77,
+            'NAME' => 'Pale lagers',
+            'BEERS' => array(
+                array(
+                    'BEER' => array(
+                        'ID' => 67,
+                        'NAME' => 'Pilsner Urquell',
+                    )
+                ),
+                array(
+                    'BEER' => array(
+                        'ID' => 34,
+                        'NAME' => 'Heineken',
+                    )
+                ),
+            )
+        );
+
+        // implict recipes work for grouped data if the path is declared as grouped
+        $path = new convert_path('beer_style', '/ROOT/BEER_STYLES/BEER_STYLE', array(), true);
+        $data = $path->apply_recipes($data);
+        $this->assertEquals('Heineken', $data['beers'][1]['beer']['name']);
+
+        // an attempt to provide explicit recipes on grouped elements throws exception
+        $this->setExpectedException('convert_path_exception');
+        $path = new convert_path(
+            'beer_style', '/ROOT/BEER_STYLES/BEER_STYLE',
+            array(
+                'renamefields' => array(
+                    'name' => 'beername',   // note this is confusing recipe because the 'name' is used for both
+                    // beer-style name ('Pale lagers') and beer name ('Pilsner Urquell')
+                )
+            ), true);
+    }
+
+    public function test_referenced_course_files() {
+
+        $text = 'This is a text containing links to file.php
+as it is parsed from the backup file. <br /><br /><img border="0" width="110" vspace="0" hspace="0" height="92" title="News" alt="News" src="$@FILEPHP@$$@SLASH@$pics$@SLASH@$news.gif" /><a href="$@FILEPHP@$$@SLASH@$pics$@SLASH@$news.gif$@FORCEDOWNLOAD@$">download image</a><br />
+    <br /><a href=\'$@FILEPHP@$$@SLASH@$MANUAL.DOC$@FORCEDOWNLOAD@$\'>download manual</a><br />';
+
+        $files = moodle1_converter::find_referenced_files($text);
+        $this->assertEquals(gettype($files), 'array');
+        $this->assertEquals(2, count($files));
+        $this->assertTrue(in_array('/pics/news.gif', $files));
+        $this->assertTrue(in_array('/MANUAL.DOC', $files));
+
+        $text = moodle1_converter::rewrite_filephp_usage($text, array('/pics/news.gif', '/another/file/notused.txt'), $files);
+        $this->assertEquals($text, 'This is a text containing links to file.php
+as it is parsed from the backup file. <br /><br /><img border="0" width="110" vspace="0" hspace="0" height="92" title="News" alt="News" src="@@PLUGINFILE@@/pics/news.gif" /><a href="@@PLUGINFILE@@/pics/news.gif?forcedownload=1">download image</a><br />
+    <br /><a href=\'$@FILEPHP@$$@SLASH@$MANUAL.DOC$@FORCEDOWNLOAD@$\'>download manual</a><br />');
+    }
+
+    public function test_question_bank_conversion() {
+        global $CFG;
+
+        $this->resetAfterTest(true);
+
+        copy(
+            "$CFG->dirroot/backup/converter/moodle1/simpletest/files/questions.xml",
+            "$CFG->tempdir/backup/$this->tempdir/moodle.xml"
+        );
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->convert();
+    }
+
+    public function test_convert_run_convert() {
+        $this->resetAfterTest(true);
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $converter->convert();
+    }
+
+    public function test_inforef_manager() {
+        $converter = convert_factory::get_converter('moodle1', $this->tempdir);
+        $inforef = $converter->get_inforef_manager('unittest');
+        $inforef->add_ref('file', 45);
+        $inforef->add_refs('file', array(46, 47));
+        // todo test the write_refs() via some dummy xml_writer
+        $this->setExpectedException('coding_exception');
+        $inforef->add_ref('unknown_referenced_item_name', 76);
+    }
+}
diff --git a/backup/util/checks/tests/checks_test.php b/backup/util/checks/tests/checks_test.php
new file mode 100644 (file)
index 0000000..ccee3ba
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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');
+
+
+/*
+ * check tests (all)
+ */
+class backup_check_testcase extends advanced_testcase {
+
+    protected $moduleid;  // course_modules id used for testing
+    protected $sectionid; // course_sections id used for testing
+    protected $courseid;  // course id used for testing
+    protected $userid;    // user record id
+
+    protected function setUp() {
+        global $DB, $CFG;
+        parent::setUp();
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
+        $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
+
+        $this->moduleid  = $coursemodule->id;
+        $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
+        $this->courseid  = $coursemodule->course;
+        $this->userid = 2; // admin
+
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_output_indented_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        unset($CFG->backup_file_logger_extra);
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /*
+     * test backup_check class
+     */
+    public function test_backup_check() {
+
+        // Check against existing course module/section course or fail
+        $this->assertTrue(backup_check::check_id(backup::TYPE_1ACTIVITY, $this->moduleid));
+        $this->assertTrue(backup_check::check_id(backup::TYPE_1SECTION, $this->sectionid));
+        $this->assertTrue(backup_check::check_id(backup::TYPE_1COURSE, $this->courseid));
+        $this->assertTrue(backup_check::check_user($this->userid));
+
+        // Check against non-existing course module/section/course (0)
+        try {
+            backup_check::check_id(backup::TYPE_1ACTIVITY, 0);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_module_not_exists');
+        }
+        try {
+            backup_check::check_id(backup::TYPE_1SECTION, 0);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_section_not_exists');
+        }
+        try {
+            backup_check::check_id(backup::TYPE_1COURSE, 0);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_course_not_exists');
+        }
+
+        // Try wrong type
+        try {
+            backup_check::check_id(12345678,0);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_incorrect_type');
+        }
+
+        // Test non-existing user
+        $userid = 0;
+        try {
+            backup_check::check_user($userid);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_user_not_exists');
+        }
+
+        // Security check tests
+        // Try to pass wrong controller
+        try {
+            backup_check::check_security(new stdclass(), true);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_security_requires_backup_controller');
+        }
+
+        // Pass correct controller, check must return true in any case with $apply enabled
+        // and $bc must continue being mock_backup_controller
+        $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        $this->assertTrue(backup_check::check_security($bc, true));
+        $this->assertTrue($bc instanceof backup_controller);
+
+    }
+}
diff --git a/backup/util/dbops/tests/dbops_test.php b/backup/util/dbops/tests/dbops_test.php
new file mode 100644 (file)
index 0000000..43682a5
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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');
+
+
+/*
+ * dbops tests (all)
+ */
+class backup_dbops_testcase extends advanced_testcase {
+
+    protected $moduleid;  // course_modules id used for testing
+    protected $sectionid; // course_sections id used for testing
+    protected $courseid;  // course id used for testing
+    protected $userid;      // user record used for testing
+
+    protected function setUp() {
+        global $DB, $CFG;
+        parent::setUp();
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
+        $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
+
+        $this->moduleid  = $coursemodule->id;
+        $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
+        $this->courseid  = $coursemodule->course;
+        $this->userid = 2; // admin
+
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_output_indented_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        unset($CFG->backup_file_logger_extra);
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /*
+     * test backup_ops class
+     */
+    function test_backup_dbops() {
+        // Nothing to do here, abstract class + exception, will be tested by the rest
+    }
+
+    /*
+     * test backup_controller_dbops class
+     */
+    function test_backup_controller_dbops() {
+        global $DB;
+
+        $dbman = $DB->get_manager(); // Going to use some database_manager services for testing
+
+        // Instantiate non interactive backup_controller
+        $bc = new mock_backup_controller4dbops(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        $this->assertTrue($bc instanceof backup_controller);
+        // Calculate checksum
+        $checksum = $bc->calculate_checksum();
+        $this->assertEquals(strlen($checksum), 32); // is one md5
+
+        // save controller
+        $recid = backup_controller_dbops::save_controller($bc, $checksum);
+        $this->assertNotEmpty($recid);
+        // save it again (should cause update to happen)
+        $recid2 = backup_controller_dbops::save_controller($bc, $checksum);
+        $this->assertNotEmpty($recid2);
+        $this->assertEquals($recid, $recid2); // Same record in both save operations
+
+        // Try incorrect checksum
+        $bc = new mock_backup_controller4dbops(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        $checksum = $bc->calculate_checksum();
+        try {
+            $recid = backup_controller_dbops::save_controller($bc, 'lalala');
+            $this->assertTrue(false, 'backup_dbops_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_dbops_exception);
+            $this->assertEquals($e->errorcode, 'backup_controller_dbops_saving_checksum_mismatch');
+        }
+
+        // Try to save non backup_controller object
+        $bc = new stdclass();
+        try {
+            $recid = backup_controller_dbops::save_controller($bc, 'lalala');
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_controller_expected');
+        }
+
+        // save and load controller (by backupid). Then compare
+        $bc = new mock_backup_controller4dbops(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        $checksum = $bc->calculate_checksum(); // Calculate checksum
+        $backupid = $bc->get_backupid();
+        $this->assertEquals(strlen($backupid), 32); // is one md5
+        $recid = backup_controller_dbops::save_controller($bc, $checksum); // save controller
+        $newbc = backup_controller_dbops::load_controller($backupid); // load controller
+        $this->assertTrue($newbc instanceof backup_controller);
+        $newchecksum = $newbc->calculate_checksum();
+        $this->assertEquals($newchecksum, $checksum);
+
+        // try to load non-existing controller
+        try {
+            $bc = backup_controller_dbops::load_controller('1234567890');
+            $this->assertTrue(false, 'backup_dbops_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_dbops_exception);
+            $this->assertEquals($e->errorcode, 'backup_controller_dbops_nonexisting');
+        }
+
+        // backup_ids_temp table tests
+        // If, for any reason table exists, drop it
+        if ($dbman->table_exists('backup_ids_temp')) {
+            $dbman->drop_temp_table(new xmldb_table('backup_ids_temp'));
+        }
+        // Check backup_ids_temp table doesn't exist
+        $this->assertFalse($dbman->table_exists('backup_ids_temp'));
+        // Create and check it exists
+        backup_controller_dbops::create_backup_ids_temp_table('testingid');
+        $this->assertTrue($dbman->table_exists('backup_ids_temp'));
+        // Drop and check it doesn't exists anymore
+        backup_controller_dbops::drop_backup_ids_temp_table('testingid');
+        $this->assertFalse($dbman->table_exists('backup_ids_temp'));
+    }
+}
+
+class mock_backup_controller4dbops extends backup_controller {
+
+    /**
+     * Change standard behavior so the checksum is also stored and not onlt calculated
+     */
+    public function calculate_checksum() {
+        $this->checksum = parent::calculate_checksum();
+        return $this->checksum;
+    }
+}
diff --git a/backup/util/destinations/tests/destinations_test.php b/backup/util/destinations/tests/destinations_test.php
new file mode 100644 (file)
index 0000000..42da381
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/helper/backup_helper.class.php');
+
+/**
+ * dbops tests (all)
+ */
+class backup_destinations_testcase extends basic_testcase {
+
+    /**
+     * test backup_destination class
+     */
+    function test_backup_destination() {
+    }
+
+    /**
+     * test backup_destination_osfs class
+     */
+    function test_backup_destination_osfs() {
+    }
+}
+
diff --git a/backup/util/factories/tests/factories_test.php b/backup/util/factories/tests/factories_test.php
new file mode 100644 (file)
index 0000000..9e3a482
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/interfaces/checksumable.class.php');
+require_once($CFG->dirroot . '/backup/backup.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/base_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
+
+
+/**
+ * backup_factory tests (all)
+ */
+class backup_factories_testcase extends advanced_testcase {
+
+    function setUp() {
+        global $CFG;
+        parent::setUp();
+
+        $this->resetAfterTest(true);
+
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_output_indented_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        unset($CFG->backup_file_logger_extra);
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /**
+     * test get_logger_chain() method
+     */
+    function test_backup_factory() {
+        global $CFG;
+
+        // Default instantiate, all levels = backup::LOG_NONE
+        // With debugdisplay enabled
+        $CFG->debugdisplay = true;
+        $logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
+        $this->assertTrue($logger1 instanceof error_log_logger);  // 1st logger is error_log_logger
+        $this->assertEquals($logger1->get_level(), backup::LOG_NONE);
+        $logger2 = $logger1->get_next();
+        $this->assertTrue($logger2 instanceof output_indented_logger);  // 2nd logger is output_indented_logger
+        $this->assertEquals($logger2->get_level(), backup::LOG_NONE);
+        $logger3 = $logger2->get_next();
+        $this->assertTrue($logger3 instanceof file_logger);  // 3rd logger is file_logger
+        $this->assertEquals($logger3->get_level(), backup::LOG_NONE);
+        $logger4 = $logger3->get_next();
+        $this->assertTrue($logger4 instanceof database_logger);  // 4th logger is database_logger
+        $this->assertEquals($logger4->get_level(), backup::LOG_NONE);
+        $logger5 = $logger4->get_next();
+        $this->assertTrue($logger5 === null);
+
+        // With debugdisplay disabled
+        $CFG->debugdisplay = false;
+        $logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
+        $this->assertTrue($logger1 instanceof error_log_logger);  // 1st logger is error_log_logger
+        $this->assertEquals($logger1->get_level(), backup::LOG_NONE);
+        $logger2 = $logger1->get_next();
+        $this->assertTrue($logger2 instanceof file_logger);  // 2nd logger is file_logger
+        $this->assertEquals($logger2->get_level(), backup::LOG_NONE);
+        $logger3 = $logger2->get_next();
+        $this->assertTrue($logger3 instanceof database_logger);  // 3rd logger is database_logger
+        $this->assertEquals($logger3->get_level(), backup::LOG_NONE);
+        $logger4 = $logger3->get_next();
+        $this->assertTrue($logger4 === null);
+
+        // Instantiate with debugging enabled and $CFG->backup_error_log_logger_level not set
+        $CFG->debugdisplay = true;
+        $CFG->debug = DEBUG_DEVELOPER;
+        unset($CFG->backup_error_log_logger_level);
+        $logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
+        $this->assertTrue($logger1 instanceof error_log_logger);  // 1st logger is error_log_logger
+        $this->assertEquals($logger1->get_level(), backup::LOG_DEBUG); // and must have backup::LOG_DEBUG level
+        // Set $CFG->backup_error_log_logger_level to backup::LOG_WARNING and test again
+        $CFG->backup_error_log_logger_level = backup::LOG_WARNING;
+        $logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
+        $this->assertTrue($logger1 instanceof error_log_logger);  // 1st logger is error_log_logger
+        $this->assertEquals($logger1->get_level(), backup::LOG_WARNING); // and must have backup::LOG_WARNING level
+
+        // Instantiate in non-interactive mode, output_indented_logger must be out
+        $logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_NO, backup::EXECUTION_INMEDIATE, 'test');
+        $logger2 = $logger1->get_next();
+        $this->assertTrue($logger2 instanceof file_logger);  // 2nd logger is file_logger (output_indented_logger skiped)
+
+        // Define extra file logger and instantiate, should be 5th and last logger
+        $CFG->backup_file_logger_extra = '/tmp/test.html';
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+        $logger1 = backup_factory::get_logger_chain(backup::INTERACTIVE_YES, backup::EXECUTION_INMEDIATE, 'test');
+        $logger2 = $logger1->get_next();
+        $logger3 = $logger2->get_next();
+        $logger4 = $logger3->get_next();
+        $logger5 = $logger4->get_next();
+        $this->assertTrue($logger5 instanceof file_logger);  // 5rd logger is file_logger (extra)
+        $this->assertEquals($logger3->get_level(), backup::LOG_NONE);
+        $logger6 = $logger5->get_next();
+        $this->assertTrue($logger6 === null);
+    }
+}
diff --git a/backup/util/helper/tests/converterhelper_test.php b/backup/util/helper/tests/converterhelper_test.php
new file mode 100644 (file)
index 0000000..7824ab2
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/helper/convert_helper.class.php');
+
+
+/**
+ * Provides access to the protected methods we need to test
+ */
+class testable_convert_helper extends convert_helper {
+
+    public static function choose_conversion_path($format, array $descriptions) {
+        return parent::choose_conversion_path($format, $descriptions);
+    }
+}
+
+
+/**
+ * Defines the test methods
+ */
+class backup_convert_helper_testcase extends basic_testcase {
+
+    public function test_choose_conversion_path() {
+
+        // no converters available
+        $descriptions = array();
+        $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+        $this->assertEquals($path, array());
+
+        // missing source and/or targets
+        $descriptions = array(
+            // some custom converter
+            'exporter' => array(
+                'from'  => backup::FORMAT_MOODLE1,
+                'to'    => 'some_custom_format',
+                'cost'  => 10,
+            ),
+            // another custom converter
+            'converter' => array(
+                'from'  => 'yet_another_crazy_custom_format',
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 10,
+            ),
+        );
+        $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+        $this->assertEquals($path, array());
+
+        $path = testable_convert_helper::choose_conversion_path('some_other_custom_format', $descriptions);
+        $this->assertEquals($path, array());
+
+        // single step conversion
+        $path = testable_convert_helper::choose_conversion_path('yet_another_crazy_custom_format', $descriptions);
+        $this->assertEquals($path, array('converter'));
+
+        // no conversion needed - this is supposed to be detected by the caller
+        $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE, $descriptions);
+        $this->assertEquals($path, array());
+
+        // two alternatives
+        $descriptions = array(
+            // standard moodle 1.9 -> 2.x converter
+            'moodle1' => array(
+                'from'  => backup::FORMAT_MOODLE1,
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 10,
+            ),
+            // alternative moodle 1.9 -> 2.x converter
+            'alternative' => array(
+                'from'  => backup::FORMAT_MOODLE1,
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 8,
+            )
+        );
+        $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+        $this->assertEquals($path, array('alternative'));
+
+        // complex case
+        $descriptions = array(
+            // standard moodle 1.9 -> 2.x converter
+            'moodle1' => array(
+                'from'  => backup::FORMAT_MOODLE1,
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 10,
+            ),
+            // alternative moodle 1.9 -> 2.x converter
+            'alternative' => array(
+                'from'  => backup::FORMAT_MOODLE1,
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 8,
+            ),
+            // custom converter from 1.9 -> custom 'CFv1' format
+            'cc1' => array(
+                'from'  => backup::FORMAT_MOODLE1,
+                'to'    => 'CFv1',
+                'cost'  => 2,
+            ),
+            // custom converter from custom 'CFv1' format -> moodle 2.0 format
+            'cc2' => array(
+                'from'  => 'CFv1',
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 5,
+            ),
+            // custom converter from CFv1 -> CFv2 format
+            'cc3' => array(
+                'from'  => 'CFv1',
+                'to'    => 'CFv2',
+                'cost'  => 2,
+            ),
+            // custom converter from CFv2 -> moodle 2.0 format
+            'cc4' => array(
+                'from'  => 'CFv2',
+                'to'    => backup::FORMAT_MOODLE,
+                'cost'  => 2,
+            ),
+        );
+
+        // ask the helper to find the most effective way
+        $path = testable_convert_helper::choose_conversion_path(backup::FORMAT_MOODLE1, $descriptions);
+        $this->assertEquals($path, array('cc1', 'cc3', 'cc4'));
+    }
+}
diff --git a/backup/util/helper/tests/decode_test.php b/backup/util/helper/tests/decode_test.php
new file mode 100644 (file)
index 0000000..b55178a
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/restore_includes.php');
+
+
+/**
+ * restore_decode tests (both rule and content)
+ */
+class backup_restore_decode_testcase extends basic_testcase {
+
+    /**
+     * test restore_decode_rule class
+     */
+    function test_restore_decode_rule() {
+
+        // Test various incorrect constructors
+        try {
+            $dr = new restore_decode_rule('28 HJH', '/index.php', array());
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_incorrect_name');
+            $this->assertEquals($e->a, '28 HJH');
+        }
+
+        try {
+            $dr = new restore_decode_rule('HJHJhH', '/index.php', array());
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_incorrect_name');
+            $this->assertEquals($e->a, 'HJHJhH');
+        }
+
+        try {
+            $dr = new restore_decode_rule('', '/index.php', array());
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_incorrect_name');
+            $this->assertEquals($e->a, '');
+        }
+
+        try {
+            $dr = new restore_decode_rule('TESTRULE', 'index.php', array());
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_incorrect_urltemplate');
+            $this->assertEquals($e->a, 'index.php');
+        }
+
+        try {
+            $dr = new restore_decode_rule('TESTRULE', '', array());
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_incorrect_urltemplate');
+            $this->assertEquals($e->a, '');
+        }
+
+        try {
+            $dr = new restore_decode_rule('TESTRULE', '/course/view.php?id=$1&c=$2$3', array('test1', 'test2'));
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_mappings_incorrect_count');
+            $this->assertEquals($e->a->placeholders, 3);
+            $this->assertEquals($e->a->mappings, 2);
+        }
+
+        try {
+            $dr = new restore_decode_rule('TESTRULE', '/course/view.php?id=$5&c=$4$1', array('test1', 'test2', 'test3'));
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_nonconsecutive_placeholders');
+            $this->assertEquals($e->a, '1, 4, 5');
+        }
+
+        try {
+            $dr = new restore_decode_rule('TESTRULE', '/course/view.php?id=$0&c=$3$2', array('test1', 'test2', 'test3'));
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_nonconsecutive_placeholders');
+            $this->assertEquals($e->a, '0, 2, 3');
+        }
+
+        try {
+            $dr = new restore_decode_rule('TESTRULE', '/course/view.php?id=$1&c=$3$3', array('test1', 'test2', 'test3'));
+            $this->assertTrue(false, 'restore_decode_rule_exception exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof restore_decode_rule_exception);
+            $this->assertEquals($e->errorcode, 'decode_rule_duplicate_placeholders');
+            $this->assertEquals($e->a, '1, 3, 3');
+        }
+
+        // Provide some example content and test the regexp is calculated ok
+        $content    = '$@TESTRULE*22*33*44@$';
+        $linkname   = 'TESTRULE';
+        $urltemplate= '/course/view.php?id=$1&c=$3$2';
+        $mappings   = array('test1', 'test2', 'test3');
+        $result     = '1/course/view.php?id=44&c=8866';
+        $dr = new mock_restore_decode_rule($linkname, $urltemplate, $mappings);
+        $this->assertEquals($dr->decode($content), $result);
+
+        $content    = '$@TESTRULE*22*33*44@$ñ$@TESTRULE*22*33*44@$';
+        $linkname   = 'TESTRULE';
+        $urltemplate= '/course/view.php?id=$1&c=$3$2';
+        $mappings   = array('test1', 'test2', 'test3');
+        $result     = '1/course/view.php?id=44&c=8866ñ1/course/view.php?id=44&c=8866';
+        $dr = new mock_restore_decode_rule($linkname, $urltemplate, $mappings);
+        $this->assertEquals($dr->decode($content), $result);
+
+        $content    = 'ñ$@TESTRULE*22*0*44@$ñ$@TESTRULE*22*33*44@$ñ';
+        $linkname   = 'TESTRULE';
+        $urltemplate= '/course/view.php?id=$1&c=$3$2';
+        $mappings   = array('test1', 'test2', 'test3');
+        $result     = 'ñ0/course/view.php?id=22&c=440ñ1/course/view.php?id=44&c=8866ñ';
+        $dr = new mock_restore_decode_rule($linkname, $urltemplate, $mappings);
+        $this->assertEquals($dr->decode($content), $result);
+    }
+
+    /**
+     * test restore_decode_content class
+     */
+    function test_restore_decode_content() {
+        // TODO: restore_decode_content tests
+    }
+
+    /**
+     * test restore_decode_processor class
+     */
+    function test_restore_decode_processor() {
+        // TODO: restore_decode_processor tests
+    }
+}
+
+/**
+ * Mockup restore_decode_rule for testing purposes
+ */
+class mock_restore_decode_rule extends restore_decode_rule {
+
+    /**
+     * Originally protected, make it public
+     */
+    public function get_calculated_regexp() {
+        return parent::get_calculated_regexp();
+    }
+
+    /**
+     * Simply map each itemid by its double
+     */
+    protected function get_mapping($itemname, $itemid) {
+        return $itemid * 2;
+    }
+
+    /**
+     * Simply prefix with '0' non-mapped results and with '1' mapped ones
+     */
+    protected function apply_modifications($toreplace, $mappingsok) {
+        return ($mappingsok ? '1' : '0') . $toreplace;
+    }
+}
diff --git a/backup/util/helper/tests/helper_test.php b/backup/util/helper/tests/helper_test.php
new file mode 100644 (file)
index 0000000..27d193f
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/interfaces/checksumable.class.php');
+require_once($CFG->dirroot . '/backup/backup.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
+require_once($CFG->dirroot . '/backup/util/helper/backup_general_helper.class.php');
+
+/*
+ * backup_helper tests (all)
+ */
+class backup_helper_testcase extends basic_testcase {
+
+    /*
+     * test backup_helper class
+     */
+    function test_backup_helper() {
+    }
+
+    /*
+     * test backup_general_helper class
+     */
+    function test_backup_general_helper() {
+    }
+}
index a303cd1..899e951 100644 (file)
@@ -32,6 +32,10 @@ class error_log_logger extends base_logger {
 // Protected API starts here
 
     protected function action($message, $level, $options = null) {
+        if (PHPUNIT_TEST) {
+            // no logging from PHPUnit, it is admins fault if it does not work!!!
+            return true;
+        }
         return error_log($message);
     }
 }
diff --git a/backup/util/loggers/tests/logger_test.php b/backup/util/loggers/tests/logger_test.php
new file mode 100644 (file)
index 0000000..799632f
--- /dev/null
@@ -0,0 +1,389 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/interfaces/checksumable.class.php');
+require_once($CFG->dirroot . '/backup/backup.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/base_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/error_log_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/output_text_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
+require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
+
+
+/**
+ * logger tests (all)
+ */
+class backup_logger_testcase extends basic_testcase {
+
+    /**
+     * test base_logger class
+     */
+    function test_base_logger() {
+        // Test logger with simple action (message * level)
+        $lo = new mock_base_logger1(backup::LOG_ERROR);
+        $msg = 13;
+        $this->assertEquals($lo->process($msg, backup::LOG_ERROR), $msg * backup::LOG_ERROR);
+        // With lowest level must return true
+        $lo = new mock_base_logger1(backup::LOG_ERROR);
+        $msg = 13;
+        $this->assertTrue($lo->process($msg, backup::LOG_DEBUG));
+
+        // Chain 2 loggers, we must get as result the result of the inner one
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        $lo2 = new mock_base_logger2(backup::LOG_ERROR);
+        $lo1->set_next($lo2);
+        $msg = 13;
+        $this->assertEquals($lo1->process($msg, backup::LOG_ERROR), $msg + backup::LOG_ERROR);
+
+        // Try circular reference
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        try {
+            $lo1->set_next($lo1); //self
+            $this->assertTrue(false, 'base_logger_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_logger_exception);
+            $this->assertEquals($e->errorcode, 'logger_circular_reference');
+            $this->assertTrue($e->a instanceof stdclass);
+            $this->assertEquals($e->a->main, get_class($lo1));
+            $this->assertEquals($e->a->alreadyinchain, get_class($lo1));
+        }
+
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        $lo2 = new mock_base_logger2(backup::LOG_ERROR);
+        $lo3 = new mock_base_logger3(backup::LOG_ERROR);
+        $lo1->set_next($lo2);
+        $lo2->set_next($lo3);
+        try {
+            $lo3->set_next($lo1);
+            $this->assertTrue(false, 'base_logger_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_logger_exception);
+            $this->assertEquals($e->errorcode, 'logger_circular_reference');
+            $this->assertTrue($e->a instanceof stdclass);
+            $this->assertEquals($e->a->main, get_class($lo1));
+            $this->assertEquals($e->a->alreadyinchain, get_class($lo3));
+        }
+
+        // Test stopper logger
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        $lo2 = new mock_base_logger2(backup::LOG_ERROR);
+        $lo3 = new mock_base_logger3(backup::LOG_ERROR);
+        $lo1->set_next($lo2);
+        $lo2->set_next($lo3);
+        $this->assertFalse($lo1->process('test', backup::LOG_ERROR));
+
+        // Test checksum correct
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        $lo1->is_checksum_correct(get_class($lo1) . '-' . backup::LOG_ERROR);
+
+        // Test get_levelstr()
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        $this->assertEquals($lo1->get_levelstr(backup::LOG_NONE), 'undefined');
+        $this->assertEquals($lo1->get_levelstr(backup::LOG_ERROR), 'error');
+        $this->assertEquals($lo1->get_levelstr(backup::LOG_WARNING), 'warn');
+        $this->assertEquals($lo1->get_levelstr(backup::LOG_INFO), 'info');
+        $this->assertEquals($lo1->get_levelstr(backup::LOG_DEBUG), 'debug');
+    }
+
+    /**
+     * test error_log_logger class
+     */
+    function test_error_log_logger() {
+        // Not much really to test, just instantiate and execute, should return true
+        $lo = new error_log_logger(backup::LOG_ERROR);
+        $this->assertTrue($lo instanceof error_log_logger);
+        $message = 'This log exists because you have run Moodle unit tests: Ignore it';
+        $result = $lo->process($message, backup::LOG_ERROR);
+        $this->assertTrue($result);
+    }
+
+    /**
+     * test output_text_logger class
+     */
+    function test_output_text_logger() {
+        // Instantiate without date nor level output
+        $lo = new output_text_logger(backup::LOG_ERROR);
+        $this->assertTrue($lo instanceof output_text_logger);
+        $message = 'testing output_text_logger';
+        ob_start(); // Capture output
+        $result = $lo->process($message, backup::LOG_ERROR);
+        $contents = ob_get_contents();
+        ob_end_clean(); // End capture and discard
+        $this->assertTrue($result);
+        $this->assertTrue(strpos($contents, $message) !== false);
+
+        // Instantiate with date and level output
+        $lo = new output_text_logger(backup::LOG_ERROR, true, true);
+        $this->assertTrue($lo instanceof output_text_logger);
+        $message = 'testing output_text_logger';
+        ob_start(); // Capture output
+        $result = $lo->process($message, backup::LOG_ERROR);
+        $contents = ob_get_contents();
+        ob_end_clean(); // End capture and discard
+        $this->assertTrue($result);
+        $this->assertTrue(strpos($contents,'[') === 0);
+        $this->assertTrue(strpos($contents,'[error]') !== false);
+        $this->assertTrue(strpos($contents, $message) !== false);
+        $this->assertTrue(substr_count($contents , '] ') >= 2);
+    }
+
+    /**
+     * test output_indented_logger class
+     */
+    function test_output_indented_logger() {
+        // Instantiate without date nor level output
+        $options = array('depth' => 2);
+        $lo = new output_indented_logger(backup::LOG_ERROR);
+        $this->assertTrue($lo instanceof output_indented_logger);
+        $message = 'testing output_indented_logger';
+        ob_start(); // Capture output
+        $result = $lo->process($message, backup::LOG_ERROR, $options);
+        $contents = ob_get_contents();
+        ob_end_clean(); // End capture and discard
+        $this->assertTrue($result);
+        if (defined('STDOUT')) {
+            $check = '  ';
+        } else {
+            $check = '&nbsp;&nbsp;';
+        }
+        $this->assertTrue(strpos($contents, str_repeat($check, $options['depth']) . $message) !== false);
+
+        // Instantiate with date and level output
+        $options = array('depth' => 3);
+        $lo = new output_indented_logger(backup::LOG_ERROR, true, true);
+        $this->assertTrue($lo instanceof output_indented_logger);
+        $message = 'testing output_indented_logger';
+        ob_start(); // Capture output
+        $result = $lo->process($message, backup::LOG_ERROR, $options);
+        $contents = ob_get_contents();
+        ob_end_clean(); // End capture and discard
+        $this->assertTrue($result);
+        $this->assertTrue(strpos($contents,'[') === 0);
+        $this->assertTrue(strpos($contents,'[error]') !== false);
+        $this->assertTrue(strpos($contents, $message) !== false);
+        $this->assertTrue(substr_count($contents , '] ') >= 2);
+        if (defined('STDOUT')) {
+            $check = '  ';
+        } else {
+            $check = '&nbsp;&nbsp;';
+        }
+        $this->assertTrue(strpos($contents, str_repeat($check, $options['depth']) . $message) !== false);
+    }
+
+    /**
+     * test database_logger class
+     */
+    function test_database_logger() {
+        // Instantiate with date and level output (and with specs from the global moodle "log" table so checks will pass
+        $now = time();
+        $datecol = 'time';
+        $levelcol = 'action';
+        $messagecol = 'info';
+        $logtable = 'log';
+        $columns = array('url' => 'http://127.0.0.1');
+        $loglevel = backup::LOG_ERROR;
+        $lo = new mock_database_logger(backup::LOG_ERROR, $datecol, $levelcol, $messagecol, $logtable, $columns);
+        $this->assertTrue($lo instanceof database_logger);
+        $message = 'testing database_logger';
+        $result = $lo->process($message, $loglevel);
+        // Check everything is ready to be inserted to DB
+        $this->assertEquals($result['table'], $logtable);
+        $this->assertTrue($result['columns'][$datecol] >= $now);
+        $this->assertEquals($result['columns'][$levelcol], $loglevel);
+        $this->assertEquals($result['columns'][$messagecol], $message);
+        $this->assertEquals($result['columns']['url'], $columns['url']);
+    }
+
+    /**
+     * test file_logger class
+     */
+    function test_file_logger() {
+        global $CFG;
+
+        $file = $CFG->tempdir . '/test/test_file_logger.txt';
+        // Remove the test dir and any content
+        @remove_dir(dirname($file));
+        // Recreate test dir
+        if (!check_dir_exists(dirname($file), true, true)) {
+            throw new moodle_exception('error_creating_temp_dir', 'error', dirname($file));
+        }
+
+        // Instantiate with date and level output, and also use the depth option
+        $options = array('depth' => 3);
+        $lo1 = new file_logger(backup::LOG_ERROR, true, true, $file);
+        $this->assertTrue($lo1 instanceof file_logger);
+        $message1 = 'testing file_logger';
+        $result = $lo1->process($message1, backup::LOG_ERROR, $options);
+        $this->assertTrue($result);
+
+        // Another file_logger is going towrite there too without closing
+        $options = array();
+        $lo2 = new file_logger(backup::LOG_WARNING, true, true, $file);
+        $this->assertTrue($lo2 instanceof file_logger);
+        $message2 = 'testing file_logger2';
+        $result = $lo2->process($message2, backup::LOG_WARNING, $options);
+        $this->assertTrue($result);
+
+        // Destruct loggers
+        $lo1 = null;
+        $lo2 = null;
+
+        // Load file results to analyze them
+        $fcontents = file_get_contents($file);
+        $acontents = explode(PHP_EOL, $fcontents); // Split by line
+        $this->assertTrue(strpos($acontents[0], $message1) !== false);
+        $this->assertTrue(strpos($acontents[0], '[error]') !== false);
+        $this->assertTrue(strpos($acontents[0], '      ') !== false);
+        $this->assertTrue(substr_count($acontents[0] , '] ') >= 2);
+        $this->assertTrue(strpos($acontents[1], $message2) !== false);
+        $this->assertTrue(strpos($acontents[1], '[warn]') !== false);
+        $this->assertTrue(strpos($acontents[1], '      ') === false);
+        $this->assertTrue(substr_count($acontents[1] , '] ') >= 2);
+        unlink($file); // delete file
+
+        // Try one html file
+        $file = $CFG->tempdir . '/test/test_file_logger.html';
+        $options = array('depth' => 1);
+        $lo = new file_logger(backup::LOG_ERROR, true, true, $file);
+        $this->assertTrue($lo instanceof file_logger);
+        $this->assertTrue(file_exists($file));
+        $message = 'testing file_logger';
+        $result = $lo->process($message, backup::LOG_ERROR, $options);
+        // Get file contents and inspect them
+        $fcontents = file_get_contents($file);
+        $this->assertTrue($result);
+        $this->assertTrue(strpos($fcontents, $message) !== false);
+        $this->assertTrue(strpos($fcontents, '[error]') !== false);
+        $this->assertTrue(strpos($fcontents, '&nbsp;&nbsp;') !== false);
+        $this->assertTrue(substr_count($fcontents , '] ') >= 2);
+        unlink($file); // delete file
+
+        // Instantiate, write something, force deletion, try to write again
+        $file = $CFG->tempdir . '/test/test_file_logger.html';
+        $lo = new mock_file_logger(backup::LOG_ERROR, true, true, $file);
+        $this->assertTrue(file_exists($file));
+        $message = 'testing file_logger';
+        $result = $lo->process($message, backup::LOG_ERROR);
+        fclose($lo->get_fhandle()); // close file
+        try {
+            $result = @$lo->process($message, backup::LOG_ERROR); // Try to write again
+            $this->assertTrue(false, 'base_logger_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_logger_exception);
+            $this->assertEquals($e->errorcode, 'error_writing_file');
+        }
+
+        // Instantiate without file
+        try {
+            $lo = new file_logger(backup::LOG_WARNING, true, true, '');
+            $this->assertTrue(false, 'base_logger_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_logger_exception);
+            $this->assertEquals($e->errorcode, 'missing_fullpath_parameter');
+        }
+
+        // Instantiate in (near) impossible path
+        $file =  $CFG->tempdir . '/test_azby/test_file_logger.txt';
+        try {
+            $lo = new file_logger(backup::LOG_WARNING, true, true, $file);
+            $this->assertTrue(false, 'base_logger_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_logger_exception);
+            $this->assertEquals($e->errorcode, 'file_not_writable');
+            $this->assertEquals($e->a, $file);
+        }
+
+        // Instantiate one file logger with level = backup::LOG_NONE
+        $file =  $CFG->tempdir . '/test/test_file_logger.txt';
+        $lo = new file_logger(backup::LOG_NONE, true, true, $file);
+        $this->assertTrue($lo instanceof file_logger);
+        $this->assertFalse(file_exists($file));
+
+        // Remove the test dir and any content
+        @remove_dir(dirname($file));
+    }
+}
+
+
+/**
+ * helper extended base_logger class that implements some methods for testing
+ * Simply return the product of message and level
+ */
+class mock_base_logger1 extends base_logger {
+
+    protected function action($message, $level, $options = null) {
+        return $message * $level; // Simply return that, for testing
+    }
+    public function get_levelstr($level) {
+        return parent::get_levelstr($level);
+    }
+}
+
+/**
+ * helper extended base_logger class that implements some methods for testing
+ * Simply return the sum of message and level
+ */
+class mock_base_logger2 extends base_logger {
+
+    protected function action($message, $level, $options = null) {
+        return $message + $level; // Simply return that, for testing
+    }
+}
+
+/**
+ * helper extended base_logger class that implements some methods for testing
+ * Simply return 8
+ */
+class mock_base_logger3 extends base_logger {
+
+    protected function action($message, $level, $options = null) {
+        return false; // Simply return false, for testing stopper
+    }
+}
+
+/**
+ * helper extended database_logger class that implements some methods for testing
+ * Returns the complete info that normally will be used by insert record calls
+ */
+class mock_database_logger extends database_logger {
+
+    protected function insert_log_record($table, $columns) {
+        return array('table' => $table, 'columns' => $columns);
+    }
+}
+
+/**
+ * helper extended file_logger class that implements some methods for testing
+ * Returns the, usually protected, handle
+ */
+class mock_file_logger extends file_logger {
+
+    function get_fhandle() {
+        return $this->fhandle;
+    }
+}
diff --git a/backup/util/plan/tests/fixtures/plan_fixtures.php b/backup/util/plan/tests/fixtures/plan_fixtures.php
new file mode 100644 (file)
index 0000000..daf1499
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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');
+
+
+/**
+ * Instantiable class extending base_plan in order to be able to perform tests
+ */
+class mock_base_plan extends base_plan {
+    public function build() {
+    }
+}
+
+/**
+ * Instantiable class extending base_step in order to be able to perform tests
+ */
+class mock_base_step extends base_step {
+    public function execute() {
+    }
+}
+
+/**
+ * Instantiable class extending backup_step in order to be able to perform tests
+ */
+class mock_backup_step extends backup_step {
+    public function execute() {
+    }
+}
+
+/**
+ * Instantiable class extending backup_task in order to mockup get_taskbasepath()
+ */
+class mock_backup_task_basepath extends backup_task {
+
+    public function build() {
+        // Nothing to do
+    }
+
+    public function define_settings() {
+        // Nothing to do
+    }
+
+    public function get_taskbasepath() {
+        global $CFG;
+        return $CFG->tempdir . '/test';
+    }
+}
+
+/**
+ * Instantiable class extending backup_structure_step in order to be able to perform tests
+ */
+class mock_backup_structure_step extends backup_structure_step {
+
+    protected function define_structure() {
+
+        // Create really simple structure (1 nested with 1 attr and 2 fields)
+        $test = new backup_nested_element('test',
+            array('id'),
+            array('field1', 'field2')
+        );
+        $test->set_source_array(array(array('id' => 1, 'field1' => 'value1', 'field2' => 'value2')));
+
+        return $test;
+    }
+}
+
+/**
+ * Instantiable class extending activity_backup_setting to be added to task and perform tests
+ */
+class mock_fullpath_activity_setting extends activity_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Nothing to do
+    }
+}
+
+/**
+ * Instantiable class extending activity_backup_setting to be added to task and perform tests
+ */
+class mock_backupid_activity_setting extends activity_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Nothing to do
+    }
+}
+
+/**
+ * Instantiable class extending base_task in order to be able to perform tests
+ */
+class mock_base_task extends base_task {
+    public function build() {
+    }
+
+    public function define_settings() {
+    }
+}
+
+/**
+ * Instantiable class extending backup_task in order to be able to perform tests
+ */
+class mock_backup_task extends backup_task {
+    public function build() {
+    }
+
+    public function define_settings() {
+    }
+}
diff --git a/backup/util/plan/tests/plan_test.php b/backup/util/plan/tests/plan_test.php
new file mode 100644 (file)
index 0000000..bfc98f3
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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();
+
+require_once(__DIR__.'/fixtures/plan_fixtures.php');
+
+
+/**
+ * plan tests (all)
+ */
+class backup_plan_testcase extends advanced_testcase {
+
+    protected $moduleid;  // course_modules id used for testing
+    protected $sectionid; // course_sections id used for testing
+    protected $courseid;  // course id used for testing
+    protected $userid;      // user record used for testing
+
+    protected function setUp() {
+        global $DB, $CFG;
+        parent::setUp();
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
+        $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
+
+        $this->moduleid  = $coursemodule->id;
+        $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
+        $this->courseid  = $coursemodule->course;
+        $this->userid = 2; // admin
+
+        // Disable all loggers
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /**
+     * test base_plan class
+     */
+    function test_base_plan() {
+
+        // Instantiate
+        $bp = new mock_base_plan('name');
+        $this->assertTrue($bp instanceof base_plan);
+        $this->assertEquals($bp->get_name(), 'name');
+        $this->assertTrue(is_array($bp->get_settings()));
+        $this->assertEquals(count($bp->get_settings()), 0);
+        $this->assertTrue(is_array($bp->get_tasks()));
+        $this->assertEquals(count($bp->get_tasks()), 0);
+    }
+
+    /**
+     * test backup_plan class
+     */
+    function test_backup_plan() {
+
+        // We need one (non interactive) controller for instantiating plan
+        $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        // Instantiate one backup plan
+        $bp = new backup_plan($bc);
+        $this->assertTrue($bp instanceof backup_plan);
+        $this->assertEquals($bp->get_name(), 'backup_plan');
+
+        // Calculate checksum and check it
+        $checksum = $bp->calculate_checksum();
+        $this->assertTrue($bp->is_checksum_correct($checksum));
+    }
+
+    /**
+     * wrong base_plan class tests
+     */
+    function test_base_plan_wrong() {
+
+        // We need one (non interactive) controller for instantiating plan
+        $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        // Instantiate one backup plan
+        $bp = new backup_plan($bc);
+        // Add wrong task
+        try {
+            $bp->add_task(new stdclass());
+            $this->assertTrue(false, 'base_plan_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_plan_exception);
+            $this->assertEquals($e->errorcode, 'wrong_base_task_specified');
+        }
+    }
+
+    /**
+     * wrong backup_plan class tests
+     */
+    function test_backup_plan_wrong() {
+
+        // Try to pass one wrong controller
+        try {
+            $bp = new backup_plan(new stdclass());
+            $this->assertTrue(false, 'backup_plan_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_plan_exception);
+            $this->assertEquals($e->errorcode, 'wrong_backup_controller_specified');
+        }
+        try {
+            $bp = new backup_plan(null);
+            $this->assertTrue(false, 'backup_plan_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_plan_exception);
+            $this->assertEquals($e->errorcode, 'wrong_backup_controller_specified');
+        }
+
+        // Try to build one non-existent format plan (when creating the controller)
+        // We need one (non interactive) controller for instatiating plan
+        try {
+            $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, 'non_existing_format',
+                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+            $this->assertTrue(false, 'backup_controller_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_controller_exception);
+            $this->assertEquals($e->errorcode, 'backup_check_unsupported_format');
+            $this->assertEquals($e->a, 'non_existing_format');
+        }
+    }
+}
+
diff --git a/backup/util/plan/tests/step_test.php b/backup/util/plan/tests/step_test.php
new file mode 100644 (file)
index 0000000..44afd8d
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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();
+
+require_once(__DIR__.'/fixtures/plan_fixtures.php');
+
+
+/*
+ * step tests (all)
+ */
+class backup_step_testcase extends advanced_testcase {
+
+    protected $moduleid;  // course_modules id used for testing
+    protected $sectionid; // course_sections id used for testing
+    protected $courseid;  // course id used for testing
+    protected $userid;      // user record used for testing
+
+    protected function setUp() {
+        global $DB, $CFG;
+        parent::setUp();
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
+        $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
+
+        $this->moduleid  = $coursemodule->id;
+        $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
+        $this->courseid  = $coursemodule->course;
+        $this->userid = 2; // admin
+
+        // Disable all loggers
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /**
+     * test base_step class
+     */
+    function test_base_step() {
+
+        $bp = new mock_base_plan('planname'); // We need one plan
+        $bt = new mock_base_task('taskname', $bp); // We need one task
+        // Instantiate
+        $bs = new mock_base_step('stepname', $bt);
+        $this->assertTrue($bs instanceof base_step);
+        $this->assertEquals($bs->get_name(), 'stepname');
+    }
+
+    /**
+     * test backup_step class
+     */
+    function test_backup_step() {
+
+        // We need one (non interactive) controller for instatiating plan
+        $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        // We need one plan
+        $bp = new backup_plan($bc);
+        // We need one task
+        $bt = new mock_backup_task('taskname', $bp);
+        // Instantiate step
+        $bs = new mock_backup_step('stepname', $bt);
+        $this->assertTrue($bs instanceof backup_step);
+        $this->assertEquals($bs->get_name(), 'stepname');
+
+    }
+
+    /**
+     * test backup_structure_step class
+     */
+    function test_backup_structure_step() {
+        global $CFG;
+
+        $file = $CFG->tempdir . '/test/test_backup_structure_step.txt';
+        // Remove the test dir and any content
+        @remove_dir(dirname($file));
+        // Recreate test dir
+        if (!check_dir_exists(dirname($file), true, true)) {
+            throw new moodle_exception('error_creating_temp_dir', 'error', dirname($file));
+        }
+
+        // We need one (non interactive) controller for instatiating plan
+        $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        // We need one plan
+        $bp = new backup_plan($bc);
+        // We need one task with mocked basepath
+        $bt = new mock_backup_task_basepath('taskname');
+        $bp->add_task($bt);
+        // Instantiate backup_structure_step (and add it to task)
+        $bs = new mock_backup_structure_step('steptest', basename($file), $bt);
+        // Execute backup_structure_step
+        $bs->execute();
+
+        // Test file has been created
+        $this->assertTrue(file_exists($file));
+
+        // Some simple tests with contents
+        $contents = file_get_contents($file);
+        $this->assertTrue(strpos($contents, '<?xml version="1.0"') !== false);
+        $this->assertTrue(strpos($contents, '<test id="1">') !== false);
+        $this->assertTrue(strpos($contents, '<field1>value1</field1>') !== false);
+        $this->assertTrue(strpos($contents, '<field2>value2</field2>') !== false);
+        $this->assertTrue(strpos($contents, '</test>') !== false);
+
+        unlink($file); // delete file
+
+        // Remove the test dir and any content
+        @remove_dir(dirname($file));
+    }
+
+    /**
+     * wrong base_step class tests
+     */
+    function test_base_step_wrong() {
+
+        // Try to pass one wrong task
+        try {
+            $bt = new mock_base_step('teststep', new stdclass());
+            $this->assertTrue(false, 'base_step_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_step_exception);
+            $this->assertEquals($e->errorcode, 'wrong_base_task_specified');
+        }
+    }
+
+    /**
+     * wrong backup_step class tests
+     */
+    function test_backup_test_wrong() {
+
+        // Try to pass one wrong task
+        try {
+            $bt = new mock_backup_step('teststep', new stdclass());
+            $this->assertTrue(false, 'backup_step_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_step_exception);
+            $this->assertEquals($e->errorcode, 'wrong_backup_task_specified');
+        }
+    }
+}
diff --git a/backup/util/plan/tests/task_test.php b/backup/util/plan/tests/task_test.php
new file mode 100644 (file)
index 0000000..efc1b60
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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();
+
+require_once(__DIR__.'/fixtures/plan_fixtures.php');
+
+
+/**
+ * task tests (all)
+ */
+class backup_task_testcase extends advanced_testcase {
+
+    protected $moduleid;  // course_modules id used for testing
+    protected $sectionid; // course_sections id used for testing
+    protected $courseid;  // course id used for testing
+    protected $userid;      // user record used for testing
+
+    protected function setUp() {
+        global $DB, $CFG;
+        parent::setUp();
+
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course();
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
+        $coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
+
+        $this->moduleid  = $coursemodule->id;
+        $this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
+        $this->courseid  = $coursemodule->course;
+        $this->userid = 2; // admin
+
+        // Disable all loggers
+        $CFG->backup_error_log_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level = backup::LOG_NONE;
+        $CFG->backup_database_logger_level = backup::LOG_NONE;
+        $CFG->backup_file_logger_level_extra = backup::LOG_NONE;
+    }
+
+    /**
+     * test base_task class
+     */
+    function test_base_task() {
+
+        $bp = new mock_base_plan('planname'); // We need one plan
+        // Instantiate
+        $bt = new mock_base_task('taskname', $bp);
+        $this->assertTrue($bt instanceof base_task);
+        $this->assertEquals($bt->get_name(), 'taskname');
+        $this->assertTrue(is_array($bt->get_settings()));
+        $this->assertEquals(count($bt->get_settings()), 0);
+        $this->assertTrue(is_array($bt->get_steps()));
+        $this->assertEquals(count($bt->get_steps()), 0);
+    }
+
+    /**
+     * test backup_task class
+     */
+    function test_backup_task() {
+
+        // We need one (non interactive) controller for instatiating plan
+        $bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
+            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
+        // We need one plan
+        $bp = new backup_plan($bc);
+        // Instantiate task
+        $bt = new mock_backup_task('taskname', $bp);
+        $this->assertTrue($bt instanceof backup_task);
+        $this->assertEquals($bt->get_name(), 'taskname');
+
+        // Calculate checksum and check it
+        $checksum = $bt->calculate_checksum();
+        $this->assertTrue($bt->is_checksum_correct($checksum));
+
+    }
+
+    /**
+     * wrong base_task class tests
+     */
+    function test_base_task_wrong() {
+
+        // Try to pass one wrong plan
+        try {
+            $bt = new mock_base_task('tasktest', new stdclass());
+            $this->assertTrue(false, 'base_task_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_task_exception);
+            $this->assertEquals($e->errorcode, 'wrong_base_plan_specified');
+        }
+
+        // Add wrong step to task
+        $bp = new mock_base_plan('planname'); // We need one plan
+        // Instantiate
+        $bt = new mock_base_task('taskname', $bp);
+        try {
+            $bt->add_step(new stdclass());
+            $this->assertTrue(false, 'base_task_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_task_exception);
+            $this->assertEquals($e->errorcode, 'wrong_base_step_specified');
+        }
+
+    }
+
+    /**
+     * wrong backup_task class tests
+     */
+    function test_backup_task_wrong() {
+
+        // Try to pass one wrong plan
+        try {
+            $bt = new mock_backup_task('tasktest', new stdclass());
+            $this->assertTrue(false, 'backup_task_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_task_exception);
+            $this->assertEquals($e->errorcode, 'wrong_backup_plan_specified');
+        }
+    }
+}
diff --git a/backup/util/settings/tests/settings_test.php b/backup/util/settings/tests/settings_test.php
new file mode 100644 (file)
index 0000000..201d29e
--- /dev/null
@@ -0,0 +1,463 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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/interfaces/checksumable.class.php');
+require_once($CFG->dirroot . '/backup/backup.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/root/root_backup_setting.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/activity/activity_backup_setting.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/section/section_backup_setting.class.php');
+require_once($CFG->dirroot . '/backup/util/settings/course/course_backup_setting.class.php');
+require_once($CFG->dirroot . '/backup/util/ui/backup_ui_setting.class.php');
+
+
+/**
+ * setting tests (all)
+ */
+class backp_settings_testcase extends basic_testcase {
+
+    /**
+     * test base_setting class
+     */
+    function test_base_setting() {
+        // Instantiate base_setting and check everything
+        $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
+        $this->assertTrue($bs instanceof base_setting);
+        $this->assertEquals($bs->get_name(), 'test');
+        $this->assertEquals($bs->get_vtype(), base_setting::IS_BOOLEAN);
+        $this->assertTrue(is_null($bs->get_value()));
+        $this->assertEquals($bs->get_visibility(), base_setting::VISIBLE);
+        $this->assertEquals($bs->get_status(), base_setting::NOT_LOCKED);
+
+        // Instantiate base_setting with explicit nulls
+        $bs = new mock_base_setting('test', base_setting::IS_FILENAME, 'filename.txt', null, null);
+        $this->assertEquals($bs->get_value() , 'filename.txt');
+        $this->assertEquals($bs->get_visibility(), base_setting::VISIBLE);
+        $this->assertEquals($bs->get_status(), base_setting::NOT_LOCKED);
+
+        // Instantiate base_setting and set value, visibility and status
+        $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
+        $bs->set_value(true);
+        $this->assertNotEmpty($bs->get_value());
+        $bs->set_visibility(base_setting::HIDDEN);
+        $this->assertEquals($bs->get_visibility(), base_setting::HIDDEN);
+        $bs->set_status(base_setting::LOCKED_BY_HIERARCHY);
+        $this->assertEquals($bs->get_status(), base_setting::LOCKED_BY_HIERARCHY);
+
+        // Instantiate with wrong vtype
+        try {
+            $bs = new mock_base_setting('test', 'one_wrong_type');
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_invalid_type');
+        }
+
+        // Instantiate with wrong integer value
+        try {
+            $bs = new mock_base_setting('test', base_setting::IS_INTEGER, 99.99);
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_invalid_integer');
+        }
+
+        // Instantiate with wrong filename value
+        try {
+            $bs = new mock_base_setting('test', base_setting::IS_FILENAME, '../../filename.txt');
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_invalid_filename');
+        }
+
+        // Instantiate with wrong visibility
+        try {
+            $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, 'one_wrong_visibility');
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_invalid_visibility');
+        }
+
+        // Instantiate with wrong status
+        try {
+            $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, 'one_wrong_status');
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_invalid_status');
+        }
+
+        // Instantiate base_setting and try to set wrong ui_type
+        // We need a custom error handler to catch the type hinting error
+        // that should return incorrect_object_passed
+        $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
+        set_error_handler('backup_setting_error_handler', E_RECOVERABLE_ERROR);
+        try {
+            $bs->set_ui('one_wrong_ui_type', 'label', array(), array());
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'incorrect_object_passed');
+        }
+        restore_error_handler();
+
+        // Instantiate base_setting and try to set wrong ui_label
+        // We need a custom error handler to catch the type hinting error
+        // that should return incorrect_object_passed
+        $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
+        set_error_handler('backup_setting_error_handler', E_RECOVERABLE_ERROR);
+        try {
+            $bs->set_ui(base_setting::UI_HTML_CHECKBOX, 'one/wrong/label', array(), array());
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'incorrect_object_passed');
+        }
+        restore_error_handler();
+
+        // Try to change value of locked setting by permission
+        $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, base_setting::LOCKED_BY_PERMISSION);
+        try {
+            $bs->set_value(true);
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_locked_by_permission');
+        }
+
+        // Try to change value of locked setting by config
+        $bs = new mock_base_setting('test', base_setting::IS_BOOLEAN, null, null, base_setting::LOCKED_BY_CONFIG);
+        try {
+            $bs->set_value(true);
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_locked_by_config');
+        }
+
+        // Try to add same setting twice
+        $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
+        $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
+        $bs1->add_dependency($bs2, null, array('value'=>0));
+        try {
+            $bs1->add_dependency($bs2);
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_already_added');
+        }
+
+        // Try to create one circular reference
+        $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
+        try {
+            $bs1->add_dependency($bs1); // self
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_circular_reference');
+            $this->assertTrue($e->a instanceof stdclass);
+            $this->assertEquals($e->a->main, 'test1');
+            $this->assertEquals($e->a->alreadydependent, 'test1');
+        }
+
+        $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
+        $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
+        $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
+        $bs4 = new mock_base_setting('test4', base_setting::IS_INTEGER, null);
+        $bs1->add_dependency($bs2, null, array('value'=>0));
+        $bs2->add_dependency($bs3, null, array('value'=>0));
+        $bs3->add_dependency($bs4, null, array('value'=>0));
+        try {
+            $bs4->add_dependency($bs1, null, array('value'=>0));
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_circular_reference');
+            $this->assertTrue($e->a instanceof stdclass);
+            $this->assertEquals($e->a->main, 'test1');
+            $this->assertEquals($e->a->alreadydependent, 'test4');
+        }
+
+        $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
+        $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
+        $bs1->register_dependency(new setting_dependency_disabledif_empty($bs1, $bs2));
+        try {
+            // $bs1 is already dependent on $bs2 so this should fail.
+            $bs2->register_dependency(new setting_dependency_disabledif_empty($bs2, $bs1));
+            $this->assertTrue(false, 'base_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof base_setting_exception);
+            $this->assertEquals($e->errorcode, 'setting_circular_reference');
+            $this->assertTrue($e->a instanceof stdclass);
+            $this->assertEquals($e->a->main, 'test1');
+            $this->assertEquals($e->a->alreadydependent, 'test2');
+        }
+
+        // Create 3 settings and observe between them, last one must
+        // automatically inherit all the settings defined in the main one
+        $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
+        $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
+        $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
+        $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
+        $bs2->add_dependency($bs3, setting_dependency::DISABLED_NOT_EMPTY);
+        // Check values are spreaded ok
+        $bs1->set_value(123);
+        $this->assertEquals($bs1->get_value(), 123);
+        $this->assertEquals($bs2->get_value(), $bs1->get_value());
+        $this->assertEquals($bs3->get_value(), $bs1->get_value());
+
+        // Add one more setting and set value again
+        $bs4 = new mock_base_setting('test4', base_setting::IS_INTEGER, null);
+        $bs2->add_dependency($bs4, setting_dependency::DISABLED_NOT_EMPTY);
+        $bs2->set_value(321);
+        // The above change should change
+        $this->assertEquals($bs1->get_value(), 123);
+        $this->assertEquals($bs2->get_value(), 321);
+        $this->assertEquals($bs3->get_value(), 321);
+        $this->assertEquals($bs4->get_value(), 321);
+
+        // Check visibility is spreaded ok
+        $bs1->set_visibility(base_setting::HIDDEN);
+        $this->assertEquals($bs2->get_visibility(), $bs1->get_visibility());
+        $this->assertEquals($bs3->get_visibility(), $bs1->get_visibility());
+        // Check status is spreaded ok
+        $bs1->set_status(base_setting::LOCKED_BY_HIERARCHY);
+        $this->assertEquals($bs2->get_status(), $bs1->get_status());
+        $this->assertEquals($bs3->get_status(), $bs1->get_status());
+
+        // Create 3 settings and observe between them, put them in one array,
+        // force serialize/deserialize to check the observable pattern continues
+        // working after that
+        $bs1 = new mock_base_setting('test1', base_setting::IS_INTEGER, null);
+        $bs2 = new mock_base_setting('test2', base_setting::IS_INTEGER, null);
+        $bs3 = new mock_base_setting('test3', base_setting::IS_INTEGER, null);
+        $bs1->add_dependency($bs2, null, array('value'=>0));
+        $bs2->add_dependency($bs3, null, array('value'=>0));
+        // Serialize
+        $arr = array($bs1, $bs2, $bs3);
+        $ser = base64_encode(serialize($arr));
+        // Unserialize and copy to new objects
+        $newarr = unserialize(base64_decode($ser));
+        $ubs1 = $newarr[0];
+        $ubs2 = $newarr[1];
+        $ubs3 = $newarr[2];
+        // Must continue being base settings
+        $this->assertTrue($ubs1 instanceof base_setting);
+        $this->assertTrue($ubs2 instanceof base_setting);
+        $this->assertTrue($ubs3 instanceof base_setting);
+        // Set parent setting
+        $ubs1->set_value(1234);
+        $ubs1->set_visibility(base_setting::HIDDEN);
+        $ubs1->set_status(base_setting::LOCKED_BY_HIERARCHY);
+        // Check changes have been spreaded
+        $this->assertEquals($ubs2->get_visibility(), $ubs1->get_visibility());
+        $this->assertEquals($ubs3->get_visibility(), $ubs1->get_visibility());
+        $this->assertEquals($ubs2->get_status(), $ubs1->get_status());
+        $this->assertEquals($ubs3->get_status(), $ubs1->get_status());
+    }
+
+    /**
+     * test backup_setting class
+     */
+    function test_backup_setting() {
+        // Instantiate backup_setting class and set level
+        $bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
+        $bs->set_level(1);
+        $this->assertEquals($bs->get_level(), 1);
+
+        // Instantiate backup setting class and try to add one non backup_setting dependency
+        set_error_handler('backup_setting_error_handler', E_RECOVERABLE_ERROR);
+        $bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
+        try {
+            $bs->add_dependency(new stdclass());
+            $this->assertTrue(false, 'backup_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_setting_exception);
+            $this->assertEquals($e->errorcode, 'incorrect_object_passed');
+        }
+        restore_error_handler();
+
+        // Try to assing upper level dependency
+        $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, null);
+        $bs1->set_level(1);
+        $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, null);
+        $bs2->set_level(2);
+        try {
+            $bs2->add_dependency($bs1);
+            $this->assertTrue(false, 'backup_setting_exception expected');
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof backup_setting_exception);
+            $this->assertEquals($e->errorcode, 'cannot_add_upper_level_dependency');
+        }
+
+        // Check dependencies are working ok
+        $bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, null);
+        $bs1->set_level(1);
+        $bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, null);
+        $bs2->set_level(1); // Same level *must* work
+        $bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
+        $bs1->set_value(123456);
+        $this->assertEquals($bs2->get_value(), $bs1->get_value());
+    }
+
+    /**
+     * test activity_backup_setting class
+     */
+    function test_activity_backup_setting() {
+        $bs = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
+        $this->assertEquals($bs->get_level(), backup_setting::ACTIVITY_LEVEL);
+
+        // Check checksum implementation is working
+        $bs1 = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
+        $bs1->set_value(123);
+        $checksum = $bs1->calculate_checksum();
+        $this->assertNotEmpty($checksum);
+        $this->assertTrue($bs1->is_checksum_correct($checksum));
+    }
+
+    /**
+     * test section_backup_setting class
+     */
+    function test_section_backup_setting() {
+        $bs = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
+        $this->assertEquals($bs->get_level(), backup_setting::SECTION_LEVEL);
+
+        // Check checksum implementation is working
+        $bs1 = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
+        $bs1->set_value(123);
+        $checksum = $bs1->calculate_checksum();
+        $this->assertNotEmpty($checksum);
+        $this->assertTrue($bs1->is_checksum_correct($checksum));
+    }
+
+    /**
+     * test course_backup_setting class
+     */
+    function test_course_backup_setting() {
+        $bs = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
+        $this->assertEquals($bs->get_level(), backup_setting::COURSE_LEVEL);
+
+        // Check checksum implementation is working
+        $bs1 = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
+        $bs1->set_value(123);
+        $checksum = $bs1->calculate_checksum();
+        $this->assertNotEmpty($checksum);
+        $this->assertTrue($bs1->is_checksum_correct($checksum));
+    }
+}
+
+/**
+ * helper extended base_setting class that makes some methods public for testing
+ */
+class mock_base_setting extends base_setting {
+    public function get_vtype() {
+        return $this->vtype;
+    }
+
+    public function process_change($setting, $ctype, $oldv) {
+        // Simply, inherit from the main object
+        $this->set_value($setting->get_value());
+        $this->set_visibility($setting->get_visibility());
+        $this->set_status($setting->get_status());
+    }
+
+    public function get_ui_info() {
+        // Return an array with all the ui info to be tested
+        return array($this->ui_type, $this->ui_label, $this->ui_values, $this->ui_options);
+    }
+}
+
+/**
+ * helper extended backup_setting class that makes some methods public for testing
+ */
+class mock_backup_setting extends backup_setting {
+    public function set_level($level) {
+        $this->level = $level;
+    }
+
+    public function process_change($setting, $ctype, $oldv) {
+        // Simply, inherit from the main object
+        $this->set_value($setting->get_value());
+        $this->set_visibility($setting->get_visibility());
+        $this->set_status($setting->get_status());
+    }
+}
+
+/**
+ * helper extended activity_backup_setting class that makes some methods public for testing
+ */
+class mock_activity_backup_setting extends activity_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Do nothing
+    }
+}
+
+/**
+ * helper extended section_backup_setting class that makes some methods public for testing
+ */
+class mock_section_backup_setting extends section_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Do nothing
+    }
+}
+
+/**
+ * helper extended course_backup_setting class that makes some methods public for testing
+ */
+class mock_course_backup_setting extends course_backup_setting {
+    public function process_change($setting, $ctype, $oldv) {
+        // Do nothing
+    }
+}
+
+/**
+ * This error handler is used to convert errors to excpetions so that simepltest can
+ * catch them.
+ *
+ * This is required in order to catch type hint mismatches that result in a error
+ * being thrown. It should only ever be used to catch E_RECOVERABLE_ERROR's.
+ *
+ * It throws a backup_setting_exception with 'incorrect_object_passed'
+ *
+ * @param int $errno E_RECOVERABLE_ERROR
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @param array $errcontext
+ * @return null
+ */
+function backup_setting_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
+    if ($errno !== E_RECOVERABLE_ERROR) {
+        // Currently we only want to deal with type hinting errors
+        return false;
+    }
+    throw new backup_setting_exception('incorrect_object_passed');
+}
index 5b98144..5677932 100644 (file)
@@ -337,7 +337,8 @@ class backup_structure_test extends UnitTestCaseUsingDatabase {
         $this->assertEqual($inventeds->get_counter(), 2); // Array
 
         // Perform some validations with the generated XML
-        $dom = DOMDocument::loadXML($xo->get_allcontents());
+        $dom = new DomDocument();
+        $dom->loadXML($xo->get_allcontents());
         $xpath = new DOMXPath($dom);
         // Some more counters
         $query = '/forum/discussions/discussion/posts/post';
diff --git a/backup/util/structure/tests/baseatom_test.php b/backup/util/structure/tests/baseatom_test.php
new file mode 100644 (file)
index 0000000..0f829e8
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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
+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
+ */
+class backup_base_atom_testcase extends basic_testcase {
+
+    /**
+     * Correct base_atom_tests
+     */
+    function test_base_atom() {
+        $name_with_all_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
+        $value_to_test = 'Some <value> 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() {
+        // 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);
+        }
+    }
+}
diff --git a/backup/util/structure/tests/baseattribute_test.php b/backup/util/structure/tests/baseattribute_test.php
new file mode 100644 (file)
index 0000000..e32d7ab
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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
+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)
+ */
+class backup_base_attribute_testcase extends basic_testcase {
+
+    /**
+     * Correct base_attribute tests
+     */
+    function test_base_attribute() {
+        $name_with_all_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
+        $value_to_test = 'Some <value> 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);
+    }
+}
diff --git a/backup/util/structure/tests/basefinalelement_test.php b/backup/util/structure/tests/basefinalelement_test.php
new file mode 100644 (file)
index 0000000..f4156db
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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
+require_once(__DIR__.'/fixtures/structure_fixtures.php');
+
+
+/**
+ * Unit test case the base_final_element class. Note: highly imbricated with base_nested_element class
+ */
+class backup_base_final_element_testcase extends basic_testcase {
+
+    /**
+     * Correct base_final_element tests
+     */
+    function test_base_final_element() {
+
+        // 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() {
+
+        // 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);
+        }
+    }
+}
diff --git a/backup/util/structure/tests/basenestedelement_test.php b/backup/util/structure/tests/basenestedelement_test.php
new file mode 100644 (file)
index 0000000..cdafd83
--- /dev/null
@@ -0,0 +1,395 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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
+require_once(__DIR__.'/fixtures/structure_fixtures.php');
+
+
+/**
+ * Unit test case the base_nested_element class. Note: highly imbricated with base_final_element class
+ */
+class backup_base_nested_element_testcase extends basic_testcase {
+
+    /**
+     * Correct creation tests (attributes and final elements)
+     */
+    public function test_creation() {
+        // 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() {
+
+        // 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() {
+
+        // 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() {
+
+        // 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');
+        }
+    }
+}
diff --git a/backup/util/structure/tests/baseoptiogroup_test.php b/backup/util/structure/tests/baseoptiogroup_test.php
new file mode 100644 (file)
index 0000000..7d70be9
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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
+require_once(__DIR__.'/fixtures/structure_fixtures.php');
+
+
+/**
+ * Unit test case the base_optigroup class. Note: highly imbricated with nested/final base elements
+ */
+class backup_base_optigroup_testcase extends basic_testcase {
+
+    /**
+     * Correct creation tests (s)
+     */
+    function test_creation() {
+        $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 itest_wrong_creation() {
+
+        // 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);
+        }
+    }
+}
diff --git a/backup/util/structure/tests/fixtures/structure_fixtures.php b/backup/util/structure/tests/fixtures/structure_fixtures.php
new file mode 100644 (file)
index 0000000..4379d87
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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();
+    }
+}
diff --git a/backup/util/structure/tests/structure_test.php b/backup/util/structure/tests/structure_test.php
new file mode 100644 (file)
index 0000000..c8870cc
--- /dev/null
@@ -0,0 +1,646 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @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
+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
+ */
+class backup_structure_testcase extends advanced_testcase {
+
+    protected $forumid;   // To store the inserted forum->id
+    protected $contextid; // Official contextid for these tests
+
+
+    protected function setUp() {
+        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);
+        $d1id = $DB->insert_record('forum_discussions', $discussion1);
+        $discussion2 = (object)array('course' => 1, 'forum' => $this->forumid, 'name' => 'd2', 'userid' => 101, 'groupid' => 201);
+        $d2id = $DB->insert_record('forum_discussions', $discussion2);
+
+        // Create four posts
+        $post1 = (object)array('discussion' => $d1id, 'userid' => 100, 'subject' => 'p1', 'message' => 'm1');
+        $p1id = $DB->insert_record('forum_posts', $post1);
+        $post2 = (object)array('discussion' => $d1id, 'parent' => $p1id, 'userid' => 102, 'subject' => 'p2', 'message' => 'm2');
+        $p2id = $DB->insert_record('forum_posts', $post2);
+        $post3 = (object)array('discussion' => $d1id, 'parent' => $p2id, 'userid' => 103, 'subject' => 'p3', 'message' => 'm3');
+        $p3id = $DB->insert_record('forum_posts', $post3);
+        $post4 = (object)array('discussion' => $d2id, 'userid' => 101, 'subject' => 'p4', 'message' => 'm4');
+        $p4id = $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' => $p1id,
+            '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' => $p2id,
+            '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' => $p1id, 'rating' => 2,
+            'scaleid' => -1, 'timecreated' => time(), 'timemodified' => time());
+        $r1id = $DB->insert_record('rating', $rating1);
+        $rating2 = (object)array(
+            'contextid' => $this->contextid, 'userid' => 105, 'itemid' => $p1id, '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' => $d2id, 'postid' => $p4id);
+        $DB->insert_record('forum_read', $read1);
+    }
+
+    /**
+     * Backup structures tests (construction, definition and execution)
+     */
+    function test_backup_structure_construct() {
+        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', 1);
+        $alternative2 = new backup_optigroup_element('alternative2',
+            array('name', 'value'), backup::VAR_PARENTID, 2);
+        $alternative3 = new backup_optigroup_element('alternative3',
+            array('name', 'value'), '/forum/discussions/discussion/posts/post/id', 3);
+        $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('id' => '../../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($ratarr['userid'], $DB->get_field('rating', 'userid', array('id' => $ratarr['id'])));
+            $this->assertEquals($ratarr['itemid'], $DB->get_field('rating', 'itemid', array('id' => $ratarr['id'])));
+            $this->assertEquals($ratarr['post_rating'], $DB->get_field('rating', 'rating', array('id' => $ratarr['id'])));
+        }
+
+        // Check forum has "blockeperiod" with value 0 (was declared by object instead of name)
+        $query = '/forum[blockperiod="0"]';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 1);
+
+        // Check forum is missing "completiondiscussions" (as we are using mock_skip_final_element)
+        $query = '/forum/completiondiscussions';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 0);
+
+        // 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($result->length, 1);
+
+        // Check forum has "completionposts" with value "intercepted!" (because of mock_final_element_interceptor)
+        $query = '/forum[completionposts="intercepted!"]';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 1);
+
+        // Check there isn't any alternative2 tag, as far as it hasn't source defined
+        $query = '//alternative2';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 0);
+
+        // Check there are 4 "field1" elements
+        $query = '/forum/discussions/discussion/posts/post//field1';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 4);
+
+        // Check first post has one name element with value "alternative1"
+        $query = '/forum/discussions/discussion/posts/post[@id="1"][name="alternative1"]';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 1);
+
+        // Check there are two "dupetest1" elements
+        $query = '/forum/discussions/discussion/posts/post//dupetest1';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 2);
+
+        // Check second post has one name element with value "dupetest2"
+        $query = '/forum/discussions/discussion/posts/post[@id="2"]/dupetest2';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 1);
+
+        // Check element "dupetest2" of second post has one field1 element with value "2"
+        $query = '/forum/discussions/discussion/posts/post[@id="2"]/dupetest2[field1="2"]';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 1);
+
+        // Check forth post has no name element
+        $query = '/forum/discussions/discussion/posts/post[@id="4"]/name';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 0);
+
+        // Check 1st, 2nd and 3rd posts have no forumtype element
+        $query = '/forum/discussions/discussion/posts/post[@id="1"]/forumtype';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 0);
+        $query = '/forum/discussions/discussion/posts/post[@id="2"]/forumtype';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 0);
+        $query = '/forum/discussions/discussion/posts/post[@id="3"]/forumtype';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 0);
+
+        // 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="4"][forumtype="general"]';
+        $result = $xpath->query($query);
+        $this->assertEquals($result->length, 1);
+
+        // 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() {
+
+        // 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');