 .travis.yml                                        |   78 +
 DEVELOPER.txt                                      |   21 +-
 README.md                                          |   73 +
 README.txt                                         |   71 -
 images/arrow-right_808080_64.png                   |  Bin 906 -> 0 bytes
 images/check_008000_64.png                         |  Bin 745 -> 0 bytes
 images/check_808080_64.png                         |  Bin 750 -> 0 bytes
 images/check_ffff00_64.png                         |  Bin 658 -> 0 bytes
 images/circle-o_808080_64.png                      |  Bin 1419 -> 0 bytes
 images/circle_808080_64.png                        |  Bin 937 -> 0 bytes
 images/times_808080_64.png                         |  Bin 869 -> 0 bytes
 images/times_ff0000_64.png                         |  Bin 641 -> 0 bytes
 includes/views/defaults/questions_by_quiz.view.inc |   58 +-
 .../defaults/questions_by_quiz_version.view.inc    |  107 +-
 includes/views/defaults/quiz.view.inc              |  170 +
 includes/views/defaults/quiz_by_author.view.inc    |   18 +-
 includes/views/defaults/quiz_by_taker.view.inc     |   78 +-
 includes/views/defaults/quiz_results.view.inc      |   20 +-
 includes/views/defaults/quiz_user_results.view.inc |   34 +-
 .../quiz_views_handler_argument_quiz_nid.inc       |  158 -
 .../quiz_views_handler_argument_quiz_qid.inc       |  160 +
 .../quiz_views_handler_field_node_link_take.inc    |    2 +-
 .../quiz_views_handler_field_number_questions.inc  |   18 +-
 .../handlers/quiz_views_handler_field_time.inc     |    6 +-
 .../quiz_views_handler_filter_quiz_nid.inc         |  142 -
 .../quiz_views_handler_filter_quiz_qid.inc         |  143 +
 .../quiz_views_handler_relationship_quiz_nid.inc   |   10 -
 .../quiz_views_handler_relationship_quiz_qid.inc   |   10 +
 includes/views/quiz.views.inc                      |  208 +-
 js/quiz.auto-title.js                              |   31 -
 js/quiz.jumper.js                                  |   10 -
 misc/images/arrow-right_808080_64.png              |  Bin 0 -> 906 bytes
 misc/images/check_008000_64.png                    |  Bin 0 -> 745 bytes
 misc/images/check_808080_64.png                    |  Bin 0 -> 750 bytes
 misc/images/check_ffff00_64.png                    |  Bin 0 -> 658 bytes
 misc/images/circle-o_808080_64.png                 |  Bin 0 -> 1419 bytes
 misc/images/circle_808080_64.png                   |  Bin 0 -> 937 bytes
 misc/images/times_808080_64.png                    |  Bin 0 -> 869 bytes
 misc/images/times_ff0000_64.png                    |  Bin 0 -> 641 bytes
 misc/js/quiz.admin.categorized-form.js             |   20 +
 misc/js/quiz.answering.confirm.js                  |   16 +
 misc/js/quiz.auto-title.js                         |   31 +
 misc/js/quiz.entity-form.js                        |   32 +
 misc/js/quiz.jumper.js                             |   17 +
 misc/js/quiz.take.count-down.js                    |   26 +
 misc/js/quiz_take.js                               |   19 +
 misc/quiz.css                                      |  356 +
 modules/ajax_quiz/ajax_quiz.admin.inc              |   58 -
 modules/ajax_quiz/ajax_quiz.info                   |    8 -
 modules/ajax_quiz/ajax_quiz.js                     |  113 -
 modules/ajax_quiz/ajax_quiz.module                 |   71 -
 .../long_answer/long_answer.admin.inc              |  186 +
 .../long_answer/long_answer.classes.inc            |  394 +
 .../question_types/long_answer/long_answer.info    |    9 +
 .../question_types/long_answer/long_answer.install |  139 +
 .../question_types/long_answer/long_answer.module  |  309 +
 .../long_answer/tests/LongAnswerUnitTest.test      |   97 +
 .../theme/long-answer-answering-form.tpl.php       |   12 +
 .../long_answer/theme/long_answer.theme.inc        |   47 +
 .../question_types/matching/matching.classes.inc   |  560 ++
 modules/question_types/matching/matching.info      |    9 +
 modules/question_types/matching/matching.install   |  185 +
 modules/question_types/matching/matching.module    |  125 +
 .../matching/tests/MatchingUnitTest.test           |   60 +
 .../matching/theme/matching-answering-form.tpl.php |   12 +
 .../matching/theme/matching.theme.inc              |   99 +
 modules/question_types/multichoice/README.txt      |   45 +
 .../multichoice/js/multichoice.alternative.js      |   25 +
 .../question_types/multichoice/js/multichoice.js   |   91 +
 .../multichoice/multichoice.classes.inc            | 1024 +++
 .../question_types/multichoice/multichoice.info    |    9 +
 .../question_types/multichoice/multichoice.install |  231 +
 .../question_types/multichoice/multichoice.module  |  197 +
 .../multichoice/multichoice.views.inc              |  244 +
 .../multichoice/multichoice_update_6400.inc        |  402 +
 .../multichoice/tests/MultichoiceTestCase.test     |  175 +
 .../theme/multichoice-alternative-creation.js      |   34 +
 .../theme/multichoice-alternative-creation.tpl.php |   39 +
 .../theme/multichoice-alternative.tpl.php          |   36 +
 .../theme/multichoice-answering-form.tpl.php       |   11 +
 .../multichoice/theme/multichoice.theme.inc        |  140 +
 .../question_types/quiz_ddlines/js/colorwheel.js   |  310 +
 .../question_types/quiz_ddlines/js/quiz_ddlines.js | 1650 ++++
 .../question_types/quiz_ddlines/js/raphael-min.js  |   10 +
 modules/question_types/quiz_ddlines/js/raphael.js  | 7955 ++++++++++++++++++++
 .../quiz_ddlines/quiz_ddlines.classes.inc          |  565 ++
 .../question_types/quiz_ddlines/quiz_ddlines.info  |   10 +
 .../quiz_ddlines/quiz_ddlines.install              |  218 +
 .../quiz_ddlines/quiz_ddlines.module               |  183 +
 .../quiz_ddlines/theme/images/close.png            |  Bin 0 -> 1119 bytes
 .../quiz_ddlines/theme/images/icon_ok.gif          |  Bin 0 -> 263 bytes
 .../quiz_ddlines/theme/images/icon_wrong.gif       |  Bin 0 -> 258 bytes
 .../quiz_ddlines/theme/images/icons.png            |  Bin 0 -> 3484 bytes
 .../quiz_ddlines/theme/quiz_ddlines.css            |  147 +
 .../quiz_ddlines/theme/quiz_ddlines.theme.inc      |   37 +
 .../quiz_directions/quiz_directions.classes.inc    |  176 +
 .../quiz_directions/quiz_directions.info           |    9 +
 .../quiz_directions/quiz_directions.install        |   14 +
 .../quiz_directions/quiz_directions.module         |   52 +
 .../theme/quiz-directions-answering-form.tpl.php   |   12 +
 .../question_types/quiz_page/quiz_page.classes.inc |  135 +
 modules/question_types/quiz_page/quiz_page.info    |    9 +
 modules/question_types/quiz_page/quiz_page.install |   14 +
 modules/question_types/quiz_page/quiz_page.module  |   38 +
 .../quiz_page/tests/QuizPageTestCase.test          |  102 +
 modules/question_types/scale/README.txt            |   44 +
 modules/question_types/scale/scale.classes.inc     |  638 ++
 modules/question_types/scale/scale.info            |    9 +
 modules/question_types/scale/scale.install         |  193 +
 modules/question_types/scale/scale.js              |   28 +
 modules/question_types/scale/scale.module          |  391 +
 .../question_types/scale/tests/ScaleTestCase.test  |   85 +
 .../scale/theme/scale-answering-form.tpl.php       |   12 +
 modules/question_types/scale/theme/scale.theme.inc |   49 +
 .../short_answer/short_answer.admin.inc            |  179 +
 .../short_answer/short_answer.classes.inc          |  485 ++
 .../question_types/short_answer/short_answer.info  |    9 +
 .../short_answer/short_answer.install              |  161 +
 .../short_answer/short_answer.module               |  252 +
 .../short_answer/tests/ShortAnswerTestCase.test    |  197 +
 .../theme/short-answer-answering-form.tpl.php      |   12 +
 .../short_answer/theme/short_answer.theme.inc      |   64 +
 .../truefalse/tests/TrueFalseUnitTest.test         |  106 +
 .../theme/truefalse-answering-form.tpl.php         |   12 +
 .../question_types/truefalse/truefalse.classes.inc |  314 +
 modules/question_types/truefalse/truefalse.info    |    9 +
 modules/question_types/truefalse/truefalse.install |  137 +
 modules/question_types/truefalse/truefalse.module  |   94 +
 modules/quiz_question/quiz_question.hooks.inc      |  387 +
 modules/quiz_question/quiz_question.info           |    7 +
 modules/quiz_question/quiz_question.install        |  125 +
 modules/quiz_question/quiz_question.module         |  446 ++
 modules/quiz_question/quiz_question.pages.inc      |  267 +
 modules/quiz_question/src/QuizQuestion.php         |  495 ++
 modules/quiz_question/src/QuizQuestionResponse.php |  437 ++
 .../quiz_question/tests/QuizQuestionTestCase.test  |   36 +
 modules/quiz_stats/quiz_stats.admin.inc            |  361 +-
 modules/quiz_stats/quiz_stats.module               |  131 +-
 .../src/Controller/RevisionListingController.php   |   51 +
 .../src/Controller/RevisionStatsController.php     |  201 +
 modules/quiz_stats/theme/quiz_stats_charts.tpl.php |    9 +-
 plugins/content_types/quiztake_pane.inc            |   50 +-
 plugins/page_manager/tasks/quiztake.inc            |   50 +-
 question_types/long_answer/long_answer.admin.inc   |  184 -
 question_types/long_answer/long_answer.classes.inc |  383 -
 question_types/long_answer/long_answer.info        |    9 -
 question_types/long_answer/long_answer.install     |  139 -
 question_types/long_answer/long_answer.module      |  307 -
 question_types/long_answer/long_answer.test        |  107 -
 .../theme/long-answer-answering-form.tpl.php       |   12 -
 .../long_answer/theme/long_answer.theme.inc        |   47 -
 question_types/matching/matching.classes.inc       |  557 --
 question_types/matching/matching.info              |    9 -
 question_types/matching/matching.install           |  185 -
 question_types/matching/matching.module            |  125 -
 question_types/matching/matching.test              |   65 -
 .../matching/theme/matching-answering-form.tpl.php |   12 -
 question_types/matching/theme/matching.theme.inc   |   99 -
 question_types/multichoice/README.txt              |   45 -
 question_types/multichoice/multichoice.classes.inc | 1022 ---
 question_types/multichoice/multichoice.info        |    8 -
 question_types/multichoice/multichoice.install     |  233 -
 question_types/multichoice/multichoice.js          |   89 -
 question_types/multichoice/multichoice.module      |  195 -
 question_types/multichoice/multichoice.test        |  184 -
 question_types/multichoice/multichoice.views.inc   |  244 -
 .../multichoice/multichoice_update_6400.inc        |  404 -
 .../theme/multichoice-alternative-creation.js      |   34 -
 .../theme/multichoice-alternative-creation.tpl.php |   42 -
 .../theme/multichoice-alternative.tpl.php          |   63 -
 .../theme/multichoice-answering-form.tpl.php       |   12 -
 .../multichoice/theme/multichoice.theme.inc        |  140 -
 question_types/quiz_ddlines/js/colorwheel.js       |  310 -
 question_types/quiz_ddlines/js/quiz_ddlines.js     | 1650 ----
 question_types/quiz_ddlines/js/raphael-min.js      |   10 -
 question_types/quiz_ddlines/js/raphael.js          | 7955 --------------------
 .../quiz_ddlines/quiz_ddlines.classes.inc          |  562 --
 question_types/quiz_ddlines/quiz_ddlines.info      |    8 -
 question_types/quiz_ddlines/quiz_ddlines.install   |  218 -
 question_types/quiz_ddlines/quiz_ddlines.module    |  183 -
 question_types/quiz_ddlines/theme/images/close.png |  Bin 1119 -> 0 bytes
 .../quiz_ddlines/theme/images/icon_ok.gif          |  Bin 263 -> 0 bytes
 .../quiz_ddlines/theme/images/icon_wrong.gif       |  Bin 258 -> 0 bytes
 question_types/quiz_ddlines/theme/images/icons.png |  Bin 3484 -> 0 bytes
 question_types/quiz_ddlines/theme/quiz_ddlines.css |  147 -
 .../quiz_ddlines/theme/quiz_ddlines.theme.inc      |   37 -
 .../quiz_directions/quiz_directions.classes.inc    |  167 -
 .../quiz_directions/quiz_directions.info           |   10 -
 .../quiz_directions/quiz_directions.install        |   14 -
 .../quiz_directions/quiz_directions.module         |   52 -
 .../theme/quiz-directions-answering-form.tpl.php   |   12 -
 question_types/quiz_page/QuizPageTestCase.test     |  105 -
 question_types/quiz_page/quiz_page.classes.inc     |  132 -
 question_types/quiz_page/quiz_page.info            |    8 -
 question_types/quiz_page/quiz_page.install         |   14 -
 question_types/quiz_page/quiz_page.module          |   38 -
 .../quiz_question/quiz_question.core.inc           |  888 ---
 question_types/quiz_question/quiz_question.info    |    8 -
 question_types/quiz_question/quiz_question.install |  143 -
 question_types/quiz_question/quiz_question.module  | 1173 ---
 .../quiz_question/quiz_question.pages.inc          |  247 -
 question_types/quiz_question/quiz_question.test    |   43 -
 .../quiz_question/quiz_question.theme.inc          |   25 -
 question_types/scale/README.txt                    |   44 -
 question_types/scale/scale.classes.inc             |  635 --
 question_types/scale/scale.info                    |    9 -
 question_types/scale/scale.install                 |  193 -
 question_types/scale/scale.js                      |   28 -
 question_types/scale/scale.module                  |  391 -
 question_types/scale/scale.test                    |   93 -
 .../scale/theme/scale-answering-form.tpl.php       |   12 -
 question_types/scale/theme/scale.theme.inc         |   49 -
 question_types/short_answer/short_answer.admin.inc |  174 -
 .../short_answer/short_answer.classes.inc          |  482 --
 question_types/short_answer/short_answer.info      |    9 -
 question_types/short_answer/short_answer.install   |  161 -
 question_types/short_answer/short_answer.module    |  252 -
 question_types/short_answer/short_answer.test      |  220 -
 .../theme/short-answer-answering-form.tpl.php      |   12 -
 .../short_answer/theme/short_answer.theme.inc      |   64 -
 .../theme/truefalse-answering-form.tpl.php         |   12 -
 question_types/truefalse/truefalse.classes.inc     |  309 -
 question_types/truefalse/truefalse.info            |    9 -
 question_types/truefalse/truefalse.install         |  139 -
 question_types/truefalse/truefalse.module          |   95 -
 question_types/truefalse/truefalse.test            |  119 -
 quiz.admin.inc                                     | 1588 ----
 quiz.api.php                                       |   42 +-
 quiz.css                                           |  367 -
 quiz.help.inc                                      |   30 -
 quiz.hooks.inc                                     |  561 ++
 quiz.info                                          |   28 +-
 quiz.install                                       |  981 +--
 quiz.module                                        | 4167 +---------
 quiz.pages.inc                                     |  682 +-
 quiz_datetime.inc                                  |   99 -
 .../Admin/QuizAdminSettingsController.php          |  337 +
 .../Admin/QuizQuestionAdminController.php          |   71 +
 .../Admin/QuizResultsAdminController.php           |   13 +
 .../Admin/QuizRevisionsAdminController.php         |   43 +
 src/Controller/Legacy/QuizTakeLegacyController.php |   62 +
 src/Controller/QuizEntityAddController.php         |   24 +
 src/Controller/QuizEntityViewController.php        |   14 +
 src/Controller/QuizMyResultsController.php         |   14 +
 src/Controller/QuizQuestionFeedbackController.php  |   54 +
 src/Controller/QuizResultBaseController.php        |  174 +
 src/Controller/QuizResultController.php            |   45 +
 src/Controller/QuizTakeController.php              |  216 +
 src/Controller/QuizTakeQuestionController.php      |  116 +
 src/Controller/QuizUserResultController.php        |   65 +
 src/Entity/AnswerMetadataController.php            |   23 +
 src/Entity/QuizEntity.php                          |  107 +
 src/Entity/QuizEntity/DefaultPropertiesIO.php      |  157 +
 src/Entity/QuizEntity/QuestionLoader.php           |  258 +
 src/Entity/QuizEntityController.php                |  164 +
 src/Entity/QuizEntityMetadataController.php        |   35 +
 src/Entity/QuizEntityType.php                      |   29 +
 src/Entity/QuizTypeEntityController.php            |   47 +
 src/Entity/QuizTypeUIController.php                |   18 +
 src/Entity/QuizUIController.php                    |  184 +
 src/Entity/QuizViewsController.php                 |   33 +
 src/Entity/RelationshipMetadataController.php      |   21 +
 src/Entity/Result.php                              |   87 +
 src/Entity/Result/ScoreCalculator.php              |   85 +
 src/Entity/ResultController.php                    |  108 +
 src/Entity/ResultMetadataController.php            |   32 +
 src/Entity/Views/QuizEntityDeleteLink.php          |   17 +
 src/Entity/Views/QuizEntityEditLink.php            |   17 +
 src/Entity/Views/QuizEntityLink.php                |   19 +
 src/Form/QuizAdminEntityForm.php                   |   66 +
 src/Form/QuizAnsweringForm.php                     |  209 +
 src/Form/QuizAnsweringForm/FormSubmission.php      |  296 +
 src/Form/QuizCategorizedForm.php                   |  303 +
 src/Form/QuizEntityForm.php                        |   86 +
 src/Form/QuizEntityForm/FormDefinition.php         |  488 ++
 src/Form/QuizEntityForm/FormValidation.php         |  123 +
 src/Form/QuizQuestionsForm.php                     |  555 ++
 src/Form/QuizReportForm.php                        |  218 +
 src/Form/QuizTypeForm.php                          |   82 +
 src/Helper/FormHelper.php                          |   92 +
 src/Helper/HookImplementation.php                  |   96 +
 src/Helper/HookImplementation/HookEntityInfo.php   |  123 +
 .../HookImplementation/HookFieldExtraFields.php    |   76 +
 src/Helper/HookImplementation/HookMenu.php         |  106 +
 src/Helper/HookImplementation/HookQuizScored.php   |   68 +
 src/Helper/HookImplementation/HookUserCancel.php   |   29 +
 src/Helper/MailHelper.php                          |   68 +
 src/Helper/Node/NodeHelper.php                     |  112 +
 src/Helper/Node/NodeInsertHelper.php               |   41 +
 src/Helper/Node/NodeUpdateHelper.php               |   92 +
 src/Helper/Node/NodeValidateHelper.php             |   98 +
 src/Helper/NodeHelper.php                          |   72 +
 src/Helper/Quiz/AccessHelper.php                   |  175 +
 src/Helper/Quiz/BaseForm.php                       |   43 +
 src/Helper/Quiz/FeedbackHelper.php                 |   76 +
 src/Helper/Quiz/ResultHelper.php                   |  164 +
 src/Helper/Quiz/TakeJumperHelper.php               |   80 +
 src/Helper/Quiz/TakingHelper.php                   |   20 +
 src/Helper/QuizHelper.php                          |  724 ++
 src/Quiz.php                                       |  178 +
 tests/QuizCreation.test                            |  110 -
 tests/QuizEntityFeedbackTestCase.test              |  136 +
 tests/QuizEntityNavigationTestCase.test            |  175 +
 tests/QuizEntityTakingTestCase.test                |  167 +
 tests/QuizEntityTestCase.test                      |  174 +
 tests/QuizFeedback.test                            |  139 -
 tests/QuizNavigation.test                          |  188 -
 tests/QuizResult.test                              |  212 -
 tests/QuizResultTestCase.test                      |  111 +
 tests/QuizTaking.test                              |  197 -
 tests/QuizTestCase.test                            |  159 +-
 tests/QuizTimer.test                               |   59 -
 tests/QuizTimerTestCase.test                       |   50 +
 theme/question_bg.png                              |  Bin 3789 -> 0 bytes
 theme/quiz-report-form.tpl.php                     |   30 +-
 theme/quiz_confirm.js                              |   13 -
 theme/quiz_node_form.js                            |   24 -
 theme/quiz_take.js                                 |   19 -
 318 files changed, 34130 insertions(+), 32169 deletions(-)
 318 files changed, 34130 insertions(+), 32169 deletions(-)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fdad84a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,78 @@
+language: php
+php:
+  - 5.3
+  - 5.4
+  - 5.5
+
+mysql:
+  database: drupal
+  username: root
+  encoding: utf8
+
+install:
+  # ---------------------
+  # Install Drush
+  # ---------------------
+  - export PATH="$HOME/.composer/vendor/bin:$PATH"
+  - composer global require drush/drush:dev-master
+
+  # ---------------------
+  # Setup DB
+  # ---------------------
+  - mysql -e 'SET GLOBAL wait_timeout = 5400;'
+  - mysql -e 'create database drupal;'
+
+before_script:
+  # ---------------------
+  # Install apache server
+  # ---------------------
+  - sudo apt-get install apache2 libapache2-mod-fastcgi
+  - sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
+  - sudo a2enmod rewrite actions fastcgi alias
+  - echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+  - ~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
+  - wget https://cdn.rawgit.com/backdrop/backdrop/fe6e16b6114995eed008596aa7c733bc907a8fca/core/misc/travis-ci/vhost.conf -O /tmp/vhost.conf
+  - sudo cp -f /tmp/vhost.conf /etc/apache2/sites-available/default
+
+  # ---------------------
+  # Variables
+  # ---------------------
+  - cd ..
+  - export VENDOR=`pwd`
+  - cd ..
+  - export DRUPAL=`pwd`'/drupal-7.x/'
+
+  # ---------------------
+  # Download the codebase
+  # ---------------------
+  - wget https://github.com/drupal/drupal/archive/7.x.zip -O drupal.zip
+  - unzip -qq drupal.zip
+  - cd $DRUPAL
+  - drush si testing --db-url=mysql://root:@localhost/drupal --yes
+  - mv $VENDOR/* sites/all/modules/
+
+  # ---------------------
+  # Change doc-root
+  # ---------------------
+  - sudo sed -i -e "s,/var/www,$DRUPAL,g" /etc/apache2/sites-available/default
+  - sudo service apache2 restart
+
+  # ---------------------
+  # Prepare env for the module
+  # ---------------------
+  - drush vset --yes file_temporary_path /tmp
+  - drush en -y simpletest long_answer matching multichoice quiz_ddlines quiz_directions quiz_page quiz_question scale short_answer truefalse
+  - drush vset --yes simpletest_verbose FALSE
+
+  # ---------------------
+  # No cache data!
+  # ---------------------
+  - drush cc all
+
+script:
+  - drush test-run --dirty --field-labels=0 'Quiz'  --uri=http://localhost:80
+
+notifications:
+  email: false
+  hipchat: 76d833fa0b6bae8860f279a0e7dba0@me.Andy
+  flowdock: 8e80a6538fa73fda0f7dd60bf2d44356
diff --git a/DEVELOPER.txt b/DEVELOPER.txt
index 8bdde5b..eccecd0 100644
--- a/DEVELOPER.txt
+++ b/DEVELOPER.txt
@@ -19,34 +19,32 @@ Here are the steps:
 3. Make sure you module implements hook_quiz_question_info()
 4. Define classes that extends QuizQuestion and QuizQuestionResponse.
    For a complete example, see multichoice.classes.inc.
-  
-  
-  
+
   
 ==================================================
 How to clean up the database during development
 ==================================================
 
 # see what questions are deleted but still in quizzes
-SELECT qnr.* FROM quiz_node_relationship qnr
-LEFT JOIN node n ON qnr.child_nid = n.nid 
+SELECT qnr.* FROM quiz_relationship qnr
+LEFT JOIN node n ON qnr.question_nid = n.nid 
 WHERE n.nid IS NULL
 
 # delete questions that are deleted but still in quizzes
 DELETE qnr
-FROM quiz_node_relationship qnr
-LEFT JOIN node n ON qnr.child_nid = n.nid 
+FROM quiz_relationship qnr
+LEFT JOIN node n ON qnr.question_nid = n.nid 
 WHERE n.nid IS NULL
 
 # see what quizzes are deleted but still have questions
-SELECT qnr.* FROM quiz_node_relationship qnr
-LEFT JOIN node n ON qnr.parent_nid = n.nid 
+SELECT qnr.* FROM quiz_relationship qnr
+LEFT JOIN node n ON qnr.quiz_qid = n.nid 
 WHERE n.nid IS NULL
 
 # delete quizzes that are deleted but still have questions
 DELETE qnr
-FROM quiz_node_relationship qnr
-LEFT JOIN node n ON qnr.parent_nid = n.nid 
+FROM quiz_relationship qnr
+LEFT JOIN node n ON qnr.quiz_qid = n.nid 
 WHERE n.nid IS NULL
 
 # see what revisions are stored for nodes that are deleted
@@ -59,4 +57,3 @@ DELETE nr
 FROM node_revisions nr
 LEFT JOIN node n ON nr.nid = n.nid 
 WHERE n.nid IS NULL
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c9eb18d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,73 @@
+Quiz.module [![Build Status](https://travis-ci.org/v3kwip/quiz.svg?branch=dev-master)](https://travis-ci.org/v3kwip/quiz)
+====
+
+Overview
+--------
+The quiz.module is a framework which allows you to create interactive quizzes 
+for your visitors. It allows for the creation of questions of varying types, and
+to collect those questions into quizzes. 
+
+
+Requirements
+------------
+Drupal 6.x
+PHP 5.1 (for OOP code introduced in Quiz 3.0)
+MySQL 5
+
+The module consists of two types of modules: the Quiz module itself 
+(quiz.module), and various question types (example: multichoice.module). The 
+main Quiz module, the Quiz Question module and at least one question type module 
+are required to be both installed and enabled for this module to function properly.
+
+
+Features
+--------
+This list isn't complete(not even close)
+
+ - Administrative features:
+    o Assign feedback to responses to help point out places for further study
+    o Supports multiple answers to quiz questions (on supporting question types)
+    o Limit the number of takes users are allowed
+    o Extensibility allows for additional question types to be added
+    o Permissions (create/edit)
+    o Randomize questions during the quiz
+    o Assign only specific questions from the question bank
+
+ - User features:
+   o Can create/edit own quizzes if have 'create quizzes' permission
+   o Can take a quiz if have 'view quizzes' permissions, and receive score
+
+
+Installation
+------------
+Please refer to the INSTALL file for installation directions.
+
+
+Support
+-------
+- Visit the Quiz group at http://groups.drupal.org/quiz
+
+
+Credits
+-------
+- Specification:      Robert Douglass
+- Original author:    Károly Négyesi
+- Update to Drupal 5: Wim Mostrey and riverfr0zen
+- Maintainers: Angela Byron, westwesterson, mbutcher, sivaji, turadg, falcon
+
+
+Acknowledgements
+----------------
+I'd like to extend a heart-felt thank you to the folks at Google for their 
+Summer of Code program, without which my working on this module would not have 
+been possible. I'd also like to extend my thanks to Robert Douglass as my mentor 
+on this project, for his tireless patience and faith in my abilities, and the 
+entire Drupal development community for their support while I struggled with
+foreign concepts like 'node' and 'taxonomy.' You guys all provided me with 
+support, encouragement, and fun times and I sincerely hope to remain a part of 
+the Drupal community for a long time to come!
+
+Also huge thanks go to Ejovi Nuwere of http://www.securitylab.net/ for 
+sponsoring further module development after Summer of Code.
+
+-Angela Byron/'webchick'
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 16a90f2..0000000
--- a/README.txt
+++ /dev/null
@@ -1,71 +0,0 @@
-
-Overview
---------
-The quiz.module is a framework which allows you to create interactive quizzes 
-for your visitors. It allows for the creation of questions of varying types, and
-to collect those questions into quizzes. 
-
-
-Requirements
-------------
-Drupal 6.x
-PHP 5.1 (for OOP code introduced in Quiz 3.0)
-MySQL 5
-
-The module consists of two types of modules: the Quiz module itself 
-(quiz.module), and various question types (example: multichoice.module). The 
-main Quiz module, the Quiz Question module and at least one question type module 
-are required to be both installed and enabled for this module to function properly.
-
-
-Features
---------
-This list isn't complete(not even close)
-
- - Administrative features:
-    o Assign feedback to responses to help point out places for further study
-    o Supports multiple answers to quiz questions (on supporting question types)
-    o Limit the number of takes users are allowed
-    o Extensibility allows for additional question types to be added
-    o Permissions (create/edit)
-    o Randomize questions during the quiz
-    o Assign only specific questions from the question bank
-
- - User features:
-   o Can create/edit own quizzes if have 'create quizzes' permission
-   o Can take a quiz if have 'view quizzes' permissions, and receive score
-
-
-Installation
-------------
-Please refer to the INSTALL file for installation directions.
-
-
-Support
--------
-- Visit the Quiz group at http://groups.drupal.org/quiz
-
-
-Credits
--------
-- Specification:      Robert Douglass
-- Original author:    Károly Négyesi
-- Update to Drupal 5: Wim Mostrey and riverfr0zen
-- Maintainers: Angela Byron, westwesterson, mbutcher, sivaji, turadg, falcon
-
-
-Acknowledgements
-----------------
-I'd like to extend a heart-felt thank you to the folks at Google for their 
-Summer of Code program, without which my working on this module would not have 
-been possible. I'd also like to extend my thanks to Robert Douglass as my mentor 
-on this project, for his tireless patience and faith in my abilities, and the 
-entire Drupal development community for their support while I struggled with
-foreign concepts like 'node' and 'taxonomy.' You guys all provided me with 
-support, encouragement, and fun times and I sincerely hope to remain a part of 
-the Drupal community for a long time to come!
-
-Also huge thanks go to Ejovi Nuwere of http://www.securitylab.net/ for 
-sponsoring further module development after Summer of Code.
-
--Angela Byron/'webchick'
diff --git a/images/arrow-right_808080_64.png b/images/arrow-right_808080_64.png
deleted file mode 100644
index f410b1d29a3abe2c7ac1c57f3025b747710637b2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 906
zcmV;519kj~P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0009~Nkl<Zc%1Fp
zO^B6M9LMpGR=CO*sZ(Q-lx0qDL~WE7Woki-406$`U4gi0TWtnf1Z`Tlh$skLNwg6`
z3d*8|uW6R16(u1qMw_O|HbTj?INrtNoO|cq^LAzY!LzyNh2QV{{O>)_IrmvKK0a<C
zYjJA>t#uthYXhxS8^BiV$2893eT-K2FiSN7yo-;q4!7V=yn<tRq@s^mss&&NUW>Gi
z;B!1&$;&L&0I&nwd-Pd`FYr)#JF`_9z*~BU?`RqJsSKecfP3(IpJS?5LP-G6;)Vgn
zEf*j>nAgtSDhXg|!r0})*WrOoUZN@q;HSj#RUniEuou52&apy(FqZ5lqLKhE;RWHT
zFGVZxr9u!&FN4Ri8Gj|t)2W2DsfPZq8V^E8ut_?E5up;YLa6m9d_<l~$PS_AX6Ued
z5cc8zWJl_6hY#Z!TuhwjHlY&Mq#9~*AaY2kgrR`Y>O|;}Je6>}0O7ttCun&jcF+(&
zXniJlP}ns6ojA{^0U@*o00)Fh$OYlveg<^|FoKUTiJ8LJe%#!T@rXw8l|CTc82NlD
zzLxOg(omgBcoM(#>Kv<pH(Z<HJ6b7J!s=cfV*p;v>86oZ3YBnow@xttcV)NJMt5Kj
zZi;n?0f_xJT#FjR^RdQZSO#n8@mRw`0T9(f0q{+%AqL>zLPlEXG<L@tV*t*U@lstA
z*n)pz9by1JDdDEFCh!z~?$#*=;BEYx+fhZG#|He^t78nnG@igtOl7lGN9Tn*e|kQh
zyR8Bfcp0~1QQ>R7^zATQ5bpfE!ZRGp;5vLJeLH+Xxbvs_8Ppm;&hQ)b8J@NPa)tk0
z2>-(%gIfU@itw}nkURXzlu2p>$Q6DPPfH9>T>!bmpGbD39)R58zfX2_MKu8A3jc@9
z;VBQ`F+&`lvH+F{yUJYQze{#Bhe`r?4yzL9n8HSd!c!JN>dOnA@W<1w#8nbN%BwJc
z3gN%a<Rz+-06xX#0mfAePgwwG@J^p&I^mDyw==g&FN1I5gL(F?9-i_5X7H-s5T=Ef
zDXWI3&XZsV!h4bSNg@2v3SO>Kt-Zhuw&5k=m6(e-hY#_nF8|z=R_%X*cU`%LMy<~T
go9jA&)&^SZKUP$-sHDN@fdBvi07*qoM6N<$g2G{|NdN!<

diff --git a/images/check_008000_64.png b/images/check_008000_64.png
deleted file mode 100644
index 408fd2b7805513c83a7e50428b47143d302dedd0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 745
zcmV<F0v7#=P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00088Nkl<Zc%1Fp
zJFg8<7>DuSL2^VyqtK9O6hfk>Kon>+f<mS60nVjxIqtW@<pYRHP^d^GqL3g_AtF?q
zM1$^BDv3g|&qOk2?>(0_vu2$6mut<;dY@+(YxcWl!g)(N%VHN0JH3F|1;kD-Aa((<
z(+h}QK<xAaViyoQ|BHZqc!95Yj4gR@{2v6&;wg4wBM#uLft0c7x`0_+&lq2cM{-E$
zvVdXmNhalx&{YBB;FFA%&_w}x;gcj1_GV3XO~8y|@JS}|5F64HT@o;Z8x`(fgX8JJ
zt_Y|KpJaV{pbG**!NdFXK-&d`g#U!w>A^M&2nqk$U>Eq6o@lFpX<^}a<44wH5dk~!
z8b9$7M=O_>ooU<*^sJHa<QH%RFR>k~u@(1ltzv=cGzw2X0f%uP%QNOq;A-L0veP&`
z;{}X@PjV7ha+j8!DDaFHaL53DMULm3!sR>*W+Wv17X{!MF5n;@$OoU~G%gjm9;Sqa
z-=zRN*#b7<u43>>=H!<!g<B=o$Vw@AvIQK&stU!-;o_KUS^-bCfOS=>S<D$+7&6xy
zc(Mh&Df_Tg&hpRK2d#o9TfiORB%JeCLhImRV#pUFYw;Z0sxGjYXNI(-N+j9pzK3|&
zux)Swd+;H|B3cU1=og{T5~?ORJmX)5+ajSBc=G)jiY=iwc=G=pjwPX1c#1VdnwBuH
zW_XIX#F~;YZ?MnT3QxJ7VBHcLgQt8`v@Qva!c(m)99lx-@KkS$hmsHlo-pSERV74&
zC)~kESqY77b)ypUWT>cw1pZsV8VV=$(O6yyt$?SVv%zr^S_4mghogyvSE=!LS_DtI
z)_2}47{KnwW`ke;34YhOX5kELi7lD5_4|Ov(hG=PK<xAaViyoQy@1#S#7-|Db^)=o
b<OTcz&tRem>ZYiz00000NkvXXu0mjf`)gKX

diff --git a/images/check_808080_64.png b/images/check_808080_64.png
deleted file mode 100644
index aee7c23a4c048ace00c49a33a519db9ed26ff57c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 750
zcmV<K0ulX*P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0008DNkl<Zc%1Fp
zJ&z4h9LMpGhh#-Wt0B=SgjYa;DA-0LC{zkBU>^z(%U;ighZi6!L7^fclt@&F2o;-X
z&~2rXh$!w(B$L^D=i$tp89Tq^Hs{Qo`G3E+ICKB!t{MymR&rLyE+BS#0kI2+onAof
z0%E5Z5W9fb=>^0tAa?#20SEB{U-1|_^4|DA2$;n)?7>zX!dpWrW7Bm3v$&oyz7CJ%
zkkDlT%fTm^kV8UO1&o7FGFCzt1>}WKl1Mm^HQ6-*Gm60{nZQGANl$c1zzl9wxPK!~
zrU$zspelTl&FO(I2nYoa@6!Wq7Z4Ku6K<ym+bkd?{Aa^m;8%L0tpcWnh2MuCS(8Ns
z?8a;S#77*jTv~RfaWl|bBjL#};22(F7dBug?%`U+0@G;}o_qq1;y%`7%$>s3!lh-W
zad^fH7zLl?G_K?>Ejv-*87<(5A^h4L>zu*mJP*uBNcb-bz_Yx7!+0Pce3G-cRN#7;
z5*B{10`O!D*oM1`!6%uMU&0h_m3T&0O2LyY-~`rJC}s{9$6V72c(Mg-s#48T&f&r`
zbFG0VTfm#Li=}cNgVb27;K>$nM>q-d{z+&ZJlO(%V-g>#F20m`JQo(e&{j7p%WZ>;
z*e{%f%zj*nB=Fw@tvB*TD71vC2@cQrSK+ous0E&UKZasUs12U{KZj#Us1=@K4UwiL
zET|ct;w`bJBrF*2^R>cLt|wTxgvQ`0-xRG&LZk3h>k5aK&^SER+v1@lM1d#Fxj<D3
z(clSpFj7`RBU|05gghB4Dj|XY7O;lG34Jt{S3)b`spo8PoP^fEQ{UleBH>kP{GArT
zQ?B)$H;aa_lh|(f%|9{hIJG8q3V=U&y7Xyf+WP;1#?lLjT|n&g0%8{sJH3F|1;kD-
gAa((<v*HE(1s!juQT{3=>i_@%07*qoM6N<$f^rH@)c^nh

diff --git a/images/check_ffff00_64.png b/images/check_ffff00_64.png
deleted file mode 100644
index bc17f6f6b788297ab847947bf6c97d4e235761ad..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 658
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV2bo~aSW-L^LEzWEYUy_xA3Nf
z^vUPuUvko^X)x((-6)ovsCy&p6T`n_$HG4-vT#c~vPe6Ithv(AJ55Y!QNn}G9Lz?~
z?o^+fyElFQ<TLEMC1+SxudRO0_x(-eB#R4?eR&F2lN(;JEi&fpKxO`RJs|Z#<_C`r
zn_v2TP1E?<La(mscW#~YdGYM;4ewu-_qAj&=>N#!yeKg5L!{6m$2|}KPc2Ba=eQBU
zuy6HyA@yI^b6y(mV6>4b_$T-v>VJYZQ`@0(%X{`0ni#Yz8qEL8u6(fX+pg!ozs5Z(
z@V0N7_Ca2y@7-2Elay6?eT?y1389ac9^rpmH+S7OzIdevT}RHzaoc1_+JzseV%oQQ
zZP>$NmNnjgPDXsF>;CYWCFWeq`&V(SALP9sq;u{F;kbYGuG0_ci61l>)m7{GGE~By
zENZ+COmlQDk81GetVp?X<L#}7!A$F<%_>%>{g~3=EVN!mC4_yxP{Wa~BhiQ3=1U!x
z-`p@g!PDzsc^u<B#`K<l-O*2k3;2Fyl^%?gR=VHt`GEE6qin}y<et6fxT<uJRqoTa
zBa2jb9CZFSFJTHxc!kDo5!vWNX6HTx?9;ZH){^@`=iG-4H@5BA-g5Cl;>Vp0(^~=`
zCf-Om5_Ras2ZI`;6jenQxg57-XVsR2&3AJO0y!7kyh=M-WD%vcu=#(|4d=o&%Mb4q
zTGhZl|KkrE&Z`HS@Be0;+akXA@%oh<a%uS;5h;>8IOZgMpIp!MX2-e+iG0SZ%Ui=!
kVld(r9NWbYSpTr{{5>mm$>Z-GU|M7FboFyt=akR{0P1KiA^-pY

diff --git a/images/circle-o_808080_64.png b/images/circle-o_808080_64.png
deleted file mode 100644
index c659d2e2edfb649a53937fb0ca06ce9f3e220429..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1419
zcmV;61$6p}P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF000G2Nkl<Zc%1E<
zU2GLa6vuzpw0%&N+gIBn&jMOnDj$B4DB4yZjWr=LJZb#G#1|D6izY;PF`_(Z6w$<o
z5>1SWZLQH5`~am~Q>(s$wEEgBG??5r=);-nO`P3xc6WBSN%<uk%FgV}IsZE|J9B2X
zGCVxYO6H2P4Jez-0+ek)*|Zc;M<>whu8r>hRd=ld=7GAqW`UZ!CV}7cHZCs#)&lnf
zLqLy`x9To{Zs6Xgw9~){a18h@pnd-mx(jRuMu9WH0d(gzdVzzkyib9?jOG20Py%cO
z#(|T-K%g?4sQ`n(4~P&qhAKF#0Ik5wh+%hUH^;;vGCu5c^=BfB0PCG0&r{4`+Dr?u
z7ZHA4mV(j>umkuGxFJjB0qAyi+>y3`Gy?2G`r*}SD-Fywz!>mgnun7%-Ce-DDe9>s
zZ-+_b)i&eqT|k7a0$s?c--q<`)d|XP1>SRizn{dgBm(RJ-bzw9FaaD!-Vlw5Jag_k
z0~~kv+JJswH_4j!+sNeU3+n^cF0c;x5c{b6HLwkNGkij{^HDV1b9Vx_ApP7zEASC;
zy|p5X0Ik3WNMF;q2s{bg4xF$Wq3|tmEASNXmm&XZ#9q^nWf9;7Y!i=JU<+^v_}fx>
z0xkis0$Z`I54w@pvx+7_H}I^XoL>;<r!AFcV9M3=t0C_m3_qMEz<%_Bau(_9xv$)s
z>gxyoQ1Z1P_Qw(@z)k4$`isabt0?D}m~r)LW5OWH0xC{`7Y&9yL;lS^iYeeZL%!j-
z#3%u-L)44oIPiwSfIN8({S`TY_~Ru?fFVQWPZNH$Ps$}{M;sL-IO8QsfCrR9KSS33
z%YkpuXMU0X_yp)g0vvI?uA~>uAtk*B=<p@@1lXh$HiyjdmlNZ_pGqFDUp@h}INm6*
zU@)pk8psko4zFKn1UO}}s01dJbh7}RN<lS~ImL5YN%#8Y6QHUTbk1T;3Ct+zRo~4t
z0@QU|N};Z#SA92q0<2OBnl~6z1{aicuU|d^GzT?-%0dg$dj0YVP*)178jLA}s*+y!
z-Ap6E8r_ys=u*<_zMDP)W|e}nZg}QJuafTd%O}8(N<qPQXak{5;k|zO1kffFg9O-=
z3``q({KD&(PXKL7*^X?IEGPQW2SKl2QDcDC4IbBRDwM~R^coRy%cy|Jh*IDH5;R>7
zYy<96@<jTRRsfJAFcm|-JgGQ4G>%0jL<w*f2{Od79eBi0d7kV>_73A1L;Ud)7Z%@V
zFyJ5=PiSOd1BQ(c6hGnwIEe&5<G2b@_*#P@A?ZLOQ&%ea#)0qSGH6?4dw@kHPZuIo
zQ6hA>`n15!B4U3mngBIygUotlJ3R01umNeAmU>=6f1R7Q$6vy>{j~;ZZjTcF?N9-p
zK%Ccpd4Qh~yHzX#G=QDx0SAyV;V|$e;pPcjfUl6)zt-mg&Lj4kBG=X<GSk3)=;Of>
z+mY5VO1iZ}8*mRYh@Aj#Gd{O~*!i=yq9li;CV+>5cM?1^;I27Dj8n+Cat>La&m$40
zD$<2|`0tchdxCnFcnJ7B$pcA`$sKY3zM1gZcBEPEOjw_l1;qKINgqseQ1S@UJS}5F
z5u8WtIhy9-v`1wpfSW1raKsW*$YLl_vu`HLVd-fk3bP;k;24TU<dk|JazHCJS&z#%
zkYg&F(WlL!7(;~LOMp$<P#;Xxkf_X7^i^b9MiHS0k)|Jn(C7S@z8G1H9J3ihKWq_4
z4T(*RkkdCqd41?`7Fk0aK&GynNcYv47NXBg5np=Dy6fWt>PpOt|8VlM0A(9cHY;9$
Ze*mx#^MUl7b*TUV002ovPDHLkV1is@lgj`A

diff --git a/images/circle_808080_64.png b/images/circle_808080_64.png
deleted file mode 100644
index 576483f27f299ca6414b3c2fd9a50af7babc94b1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 937
zcmV;a16KTrP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF000AUNkl<Zc%1E<
z&r1|x7{@<`d$ZbmLY^~f{Z&-QkaFu783bVj^*3Z?Yk_2cLWEh+K`H2xn%%=fH_AF^
zV0ST3>Y>9k#X|4Qyfg2wb$37Tg3iqQzTfA&^S(3l&NFS5N`+=-UuXfLX&De&Kxo=M
z^wA0QMWrYDzZjJcU<=rY$~sVu$};fFwK!J>bOD!vVW2nV*kV)wO2EaMeJj8WFbn*&
zsP7-Phrmf-4)_5~k{)~-eZb?Wy|=)CVSE2!%YYtW0ayZtEVWrn8&C#5Ax0dx)v&1n
z1>hkf?3}qAEy~FJFdp@9goy!1BO!P3Gnv-X4vZnjcbhbn*MLFb18~fwa|@Ir9cS}4
zkjH=#WE}3#+o@#^0Q11LJe9Max+B1|JRLcv06dTWevzeOmNQ@wc#@^@06Zn*aL_fN
z8+e6<S=TWI;5BeWw}OrV1>hwz*7gDi5xrVJmW~1Uuz2I)i4t;oP9@EN5^&p4OejXt
z_~FtFn85!{jxE{|{fQ(QZ~`~?^*$++@&J`&z<s+~f>BA@8E3#@#Jxsf2#H5YoB_jb
zv;?6IS)5T4XTTLVT7nT9kIH~fWPziR=mid`cBl;KcdI2B%D7Ypn6Bg1E6TW91}L_5
zIu;TNWn3x)if(lTqo~^0GC;Ad!>x{BDC1HYu;o}tD3o!j4A^k1BN!X1jV%Kd+t%Ic
z2!=8)l>wg}3kiiXE|mc*j)jCm8JEg{WyeB7p^PhT4rsJtP$eeaCT>Aw#+{ZR#KzMs
z2F%9oh%;afNqK1m=8<@mBrS`NTbB}s#5j@+SOVVJ)#9B6;A7GOX|Kjcfn95I{IZMa
zPbAHNDw6NF7kPxn<y`0W_yb_oSdL%5BDz!2F<=|G3j8)v<dIE8?~cyFx+yZNz$NN`
zKG;EYe$%axB}r-#xRq6rXKn)TvsB2MCpZP%p#IK;9pE}JowZ_~M9C@O0`)T|Y$AH5
z^Hk28D!T}rA^g?h3vil@r+qCZ$<nLH3&#ok4~bnQOML+NZqkrxy8Je92l-IK*KPAW
zV*D7Im%1cueK1i4&I6}#_Y^sY7+pqAeJj{bf&2T#NEec3GmKfXMHN|_n8DFEL#}cF
z*N|t3N#w0;Kl#2IbG}6HBMEXnDsMdWm6@jhaI$4UXaS*VdISCd*At6#GU2`#00000
LNkvXXu0mjf-6^U2

diff --git a/images/times_808080_64.png b/images/times_808080_64.png
deleted file mode 100644
index 59fb9e9b6837cb0fa6f435e421d4dc9159809b09..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 869
zcmV-r1DgDaP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0009lNkl<Zc%1Fp
zzl+>O7{~FK@dsk3h@=o3L1Mrn|H6@gO;EHD4Qec68?caoq9aPW)Itb|r#7~t5X9Kn
z2sUDoU}sAI00{&Vi|-BW<#L<vGxO}u!R;HC<!<LY`+VN)E;G+}9xA0c%p4hAz-TrC
zMi(%ejeyYwjAkQXbOEE;2pC<!XdX$L=M>K3Dg1^@xRo^bB5&e#Jcg_I5_ftg%n<Mi
zK3n+vWxRot_%~sGrcde>ytnYb(|851;=i7Wdnk5?ue^j`@l5CZOf`OGclgRF{D3EV
zCU2$S(<QGT*Ds-le`m><EgceC1r*D}S9&DO314ZJ&?2Ci7rxRWArijQBq1u`ZL#o`
zh=i8#l`UM3To4uTMR5Egb0qYHubjb)a|cHRJdWqW<?LpegpBZ&7v>I(2)Kot@mlt9
zKM9%P;qSQvqhEoKgX8yOQ9^e3?{IzY;OI*58$7S=hwxsigg;G7+zhVa{m2C^OX0Hf
zoF(CS!aPjjPYW)Kdlo|XTLO=wDLnm4;e#NdIXtV|46Tc<@Z>Z^iZK$p!;{w$D%L9@
zB|Nz;v0^<E(!!J96D(FMAvHX!X^IxBk&qsq)pdnG$6tx_-Nre|;aQD<hw-(kFPO!n
z_z_1F=S04M8vac3yl~94cSx0xDPWc1E9nxl1gs-`B~?O(fOUqiq)F%%Fd}><MM95&
zk>M-d5?Tcugz%Lv2`vH+Qus=<gs6Zca>HN4#iY5KBt(0HAIc41*Y>yN^catsy22-M
zcg_XTPrwJ!iuU2&jZKlaf!e}}xr3v3hMo?WvzPsLbw!?pXXg%#-Wj?PuVohx+R_&N
z67HDpHOwOV6*#Z`OwQc>e9<T268@PxI4a<0(@~YEglIFPXp^vw3y}+2)&bjM5?VVs
zMNC4pF0`)%=e}8Q8>^Tnp(#9ldw~bNX;u%fST3PCJpH@DZM<vmp~#=)HZTi5vNz;q
z9pTA2DETe^z*~6IbW-$tGgo2(F5(xwX6g}M-FbA8byW6->BQ)&`F-b+#-Tqfy%8|F
vfYEFOj4og_8v&yW7|lk&=mJJ_=mq=_UP!MYYFtXe00000NkvXXu0mjf=u@59

diff --git a/images/times_ff0000_64.png b/images/times_ff0000_64.png
deleted file mode 100644
index 2d7367ec6f94180480b2839e180ecf1c4283002f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 641
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVDj*EaSW-L^Y+$8ub4oA_K)X%
zd6K4!B&jdyTHEWNkgX8LrflctA((TC<C>zcgFyEeO`aP+SsGPJqv9PnUo45xXNkOS
zoBnTeasD^HIp5yY*o&3#eKW`Kw(*HLh3cw?H>{hMv!gNPy5HSo_|}kqz_NfNrt{su
z@@<W}4>$|BYFP4O%xC@IW4^)q`R;?09=v<T!(-Q9F*l#Bd*1H7-EaFNR$1D+a4<J7
zHe6r)%(0^@T+v44y<11mcO@UG_f8#s>s>W^*Sl%-)(3A4(R9kKDehBdtg{xI&2mus
z{+z2E4Xg)~9t(tgO#85D?fZt`0#iOFzGrxUAIQ4$abZ34hF$)Rg&gXKejl=DRT8-L
z$d)13dB!jEgJz0L{_0;~V+r5GrNDhqc0J1svnG`|UISg0$Ql+0lO_{8E(I<}IeE?x
zC)&=lhJLK)ndkmSO0-_)1K*F-4R^{KR{St%(L36%P@)^xcj0J-rc?OiRF2rb=?W#b
z_j*6rAK5M7^_cz9594P3nH)<Q@{fKzTmHND<KE&x264uZX9JbixH9NS++jJoTF`9q
zJw|)x$Dth8*|Y;0q_^t}h^}|A(8$zcJ&>@SY2mX(H{L@NdF@v^F?tEw_!oHzbUn6Z
zn5?8Bm+adVAo`xQg2VAAPri&(=^iGAX4&S`*50+E$IPGK<#^*iuj27H>B$Lk4C@=0
z^VDnpTFNMP-s%I_jPxJg7lYroYBiiYcjkY@F7&tr$8h>H#(zvUug_>}^>=LtCM^a}
LS3j3^P6<r_p&|{J

diff --git a/includes/views/defaults/questions_by_quiz.view.inc b/includes/views/defaults/questions_by_quiz.view.inc
index 4487a87..8daa4d3 100644
--- a/includes/views/defaults/questions_by_quiz.view.inc
+++ b/includes/views/defaults/questions_by_quiz.view.inc
@@ -3,7 +3,7 @@ $view = new view();
 $view->name = 'questions_by_quiz';
 $view->description = 'View of all the questions associated with a quiz';
 $view->tag = 'quiz';
-$view->base_table = 'quiz_node_properties';
+$view->base_table = 'quiz_entity_revision';
 $view->human_name = '';
 $view->core = 0;
 $view->api_version = '3.0';
@@ -20,16 +20,16 @@ $handler->display->display_options['query']['type'] = 'views_query';
 $handler->display->display_options['exposed_form']['type'] = 'basic';
 $handler->display->display_options['pager']['type'] = 'none';
 $handler->display->display_options['style_plugin'] = 'table';
-$handler->display->display_options['style_options']['grouping'] = 'parent_vid';
+$handler->display->display_options['style_options']['grouping'] = 'quiz_vid';
 $handler->display->display_options['style_options']['columns'] = array(
-  'timestamp' => 'parent_nid',
+  'timestamp' => 'quiz_qid',
   'title' => 'title',
   'type' => 'type',
-  'child_nid' => 'child_nid',
-  'child_vid' => 'child_vid',
+  'question_nid' => 'question_nid',
+  'question_vid' => 'question_vid',
   'question_status' => 'question_status',
-  'parent_nid' => 'timestamp',
-  'parent_vid' => 'parent_vid',
+  'quiz_qid' => 'timestamp',
+  'quiz_vid' => 'quiz_vid',
 );
 $handler->display->display_options['style_options']['default'] = 'title';
 $handler->display->display_options['style_options']['info'] = array(
@@ -45,11 +45,11 @@ $handler->display->display_options['style_options']['info'] = array(
     'sortable' => 0,
     'separator' => '',
   ),
-  'child_nid' => array(
+  'question_nid' => array(
     'sortable' => 0,
     'separator' => '',
   ),
-  'child_vid' => array(
+  'question_vid' => array(
     'sortable' => 0,
     'separator' => '',
   ),
@@ -57,48 +57,48 @@ $handler->display->display_options['style_options']['info'] = array(
     'sortable' => 0,
     'separator' => '',
   ),
-  'parent_nid' => array(
+  'quiz_qid' => array(
     'sortable' => 0,
     'separator' => '',
   ),
-  'parent_vid' => array(
+  'quiz_vid' => array(
     'sortable' => 0,
     'separator' => '',
   ),
 );
 /* Relationship: Quiz Questions: Quiz Node VID */
-$handler->display->display_options['relationships']['parent_vid']['id'] = 'parent_vid';
-$handler->display->display_options['relationships']['parent_vid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['relationships']['parent_vid']['field'] = 'parent_vid';
-$handler->display->display_options['relationships']['parent_vid']['label'] = 'node';
-$handler->display->display_options['relationships']['parent_vid']['required'] = TRUE;
+$handler->display->display_options['relationships']['quiz_vid']['id'] = 'quiz_vid';
+$handler->display->display_options['relationships']['quiz_vid']['table'] = 'quiz_relationship';
+$handler->display->display_options['relationships']['quiz_vid']['field'] = 'quiz_vid';
+$handler->display->display_options['relationships']['quiz_vid']['label'] = 'node';
+$handler->display->display_options['relationships']['quiz_vid']['required'] = TRUE;
 /* Relationship: Quiz Questions: Question Node VID */
-$handler->display->display_options['relationships']['child_vid']['id'] = 'child_vid';
-$handler->display->display_options['relationships']['child_vid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['relationships']['child_vid']['field'] = 'child_vid';
-$handler->display->display_options['relationships']['child_vid']['label'] = 'question node revision';
+$handler->display->display_options['relationships']['question_vid']['id'] = 'question_vid';
+$handler->display->display_options['relationships']['question_vid']['table'] = 'quiz_relationship';
+$handler->display->display_options['relationships']['question_vid']['field'] = 'question_vid';
+$handler->display->display_options['relationships']['question_vid']['label'] = 'question node revision';
 /* Field: Quiz Questions: Question Node ID */
-$handler->display->display_options['fields']['child_nid']['id'] = 'child_nid';
-$handler->display->display_options['fields']['child_nid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['fields']['child_nid']['field'] = 'child_nid';
-$handler->display->display_options['fields']['child_nid']['exclude'] = TRUE;
-$handler->display->display_options['fields']['child_nid']['separator'] = '';
+$handler->display->display_options['fields']['question_nid']['id'] = 'question_nid';
+$handler->display->display_options['fields']['question_nid']['table'] = 'quiz_relationship';
+$handler->display->display_options['fields']['question_nid']['field'] = 'question_nid';
+$handler->display->display_options['fields']['question_nid']['exclude'] = TRUE;
+$handler->display->display_options['fields']['question_nid']['separator'] = '';
 /* Field: Content revision: Title */
 $handler->display->display_options['fields']['title']['id'] = 'title';
 $handler->display->display_options['fields']['title']['table'] = 'node_revision';
 $handler->display->display_options['fields']['title']['field'] = 'title';
-$handler->display->display_options['fields']['title']['relationship'] = 'child_vid';
+$handler->display->display_options['fields']['title']['relationship'] = 'question_vid';
 $handler->display->display_options['fields']['title']['label'] = 'Question Title';
 $handler->display->display_options['fields']['title']['alter']['make_link'] = TRUE;
-$handler->display->display_options['fields']['title']['alter']['path'] = 'node/[child_nid]';
+$handler->display->display_options['fields']['title']['alter']['path'] = 'node/[question_nid]';
 $handler->display->display_options['fields']['title']['link_to_node'] = TRUE;
 /* Field: Quiz Questions: Question Status */
 $handler->display->display_options['fields']['question_status']['id'] = 'question_status';
-$handler->display->display_options['fields']['question_status']['table'] = 'quiz_node_relationship';
+$handler->display->display_options['fields']['question_status']['table'] = 'quiz_relationship';
 $handler->display->display_options['fields']['question_status']['field'] = 'question_status';
 /* Contextual filter: Quiz: Quiz Node NID */
 $handler->display->display_options['arguments']['nid']['id'] = 'nid';
-$handler->display->display_options['arguments']['nid']['table'] = 'quiz_node_properties';
+$handler->display->display_options['arguments']['nid']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['arguments']['nid']['field'] = 'nid';
 $handler->display->display_options['arguments']['nid']['default_action'] = 'not found';
 $handler->display->display_options['arguments']['nid']['exception']['title_enable'] = TRUE;
diff --git a/includes/views/defaults/questions_by_quiz_version.view.inc b/includes/views/defaults/questions_by_quiz_version.view.inc
index 38d3062..31d2511 100644
--- a/includes/views/defaults/questions_by_quiz_version.view.inc
+++ b/includes/views/defaults/questions_by_quiz_version.view.inc
@@ -1,9 +1,10 @@
 <?php
+
 $view = new view();
 $view->name = 'questions_by_quiz_version';
 $view->description = 'View of all the questions associated with a particular revision of a quiz';
 $view->tag = 'quiz';
-$view->base_table = 'quiz_node_properties';
+$view->base_table = 'quiz_entity_revision';
 $view->human_name = '';
 $view->core = 0;
 $view->api_version = '3.0';
@@ -20,103 +21,103 @@ $handler->display->display_options['query']['type'] = 'views_query';
 $handler->display->display_options['exposed_form']['type'] = 'basic';
 $handler->display->display_options['pager']['type'] = 'none';
 $handler->display->display_options['style_plugin'] = 'table';
-$handler->display->display_options['style_options']['grouping'] = 'parent_vid';
+$handler->display->display_options['style_options']['grouping'] = 'quiz_vid';
 $handler->display->display_options['style_options']['columns'] = array(
-  'timestamp' => 'parent_nid',
-  'title' => 'title',
-  'type' => 'type',
-  'child_nid' => 'child_nid',
-  'child_vid' => 'child_vid',
+  'timestamp'       => 'quiz_qid',
+  'title'           => 'title',
+  'type'            => 'type',
+  'question_nid'    => 'question_nid',
+  'question_vid'    => 'question_vid',
   'question_status' => 'question_status',
-  'parent_nid' => 'timestamp',
-  'parent_vid' => 'parent_vid',
+  'quiz_qid'        => 'timestamp',
+  'quiz_vid'        => 'quiz_vid',
 );
 $handler->display->display_options['style_options']['default'] = 'title';
 $handler->display->display_options['style_options']['info'] = array(
-  'timestamp' => array(
-    'sortable' => 0,
+  'timestamp'       => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
-  'title' => array(
-    'sortable' => 0,
+  'title'           => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
-  'type' => array(
-    'sortable' => 0,
+  'type'            => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
-  'child_nid' => array(
-    'sortable' => 0,
+  'question_nid'    => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
-  'child_vid' => array(
-    'sortable' => 0,
+  'question_vid'    => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
   'question_status' => array(
-    'sortable' => 0,
+    'sortable'  => 0,
     'separator' => '',
   ),
-  'parent_nid' => array(
-    'sortable' => 0,
+  'quiz_qid'        => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
-  'parent_vid' => array(
-    'sortable' => 0,
+  'quiz_vid'        => array(
+    'sortable'  => 0,
     'separator' => '',
   ),
 );
 /* Relationship: Quiz Questions: Quiz Node VID */
-$handler->display->display_options['relationships']['parent_vid']['id'] = 'parent_vid';
-$handler->display->display_options['relationships']['parent_vid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['relationships']['parent_vid']['field'] = 'parent_vid';
-$handler->display->display_options['relationships']['parent_vid']['label'] = 'node';
-$handler->display->display_options['relationships']['parent_vid']['required'] = TRUE;
+$handler->display->display_options['relationships']['quiz_vid']['id'] = 'quiz_vid';
+$handler->display->display_options['relationships']['quiz_vid']['table'] = 'quiz_relationship';
+$handler->display->display_options['relationships']['quiz_vid']['field'] = 'quiz_vid';
+$handler->display->display_options['relationships']['quiz_vid']['label'] = 'node';
+$handler->display->display_options['relationships']['quiz_vid']['required'] = TRUE;
 /* Relationship: Quiz Questions: Question Node VID */
-$handler->display->display_options['relationships']['child_vid']['id'] = 'child_vid';
-$handler->display->display_options['relationships']['child_vid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['relationships']['child_vid']['field'] = 'child_vid';
-$handler->display->display_options['relationships']['child_vid']['label'] = 'question node revision';
+$handler->display->display_options['relationships']['question_vid']['id'] = 'question_vid';
+$handler->display->display_options['relationships']['question_vid']['table'] = 'quiz_relationship';
+$handler->display->display_options['relationships']['question_vid']['field'] = 'question_vid';
+$handler->display->display_options['relationships']['question_vid']['label'] = 'question node revision';
 /* Field: Quiz Questions: Quiz Node ID */
-$handler->display->display_options['fields']['parent_nid']['id'] = 'parent_nid';
-$handler->display->display_options['fields']['parent_nid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['fields']['parent_nid']['field'] = 'parent_nid';
-$handler->display->display_options['fields']['parent_nid']['separator'] = '';
+$handler->display->display_options['fields']['quiz_qid']['id'] = 'quiz_qid';
+$handler->display->display_options['fields']['quiz_qid']['table'] = 'quiz_relationship';
+$handler->display->display_options['fields']['quiz_qid']['field'] = 'quiz_qid';
+$handler->display->display_options['fields']['quiz_qid']['separator'] = '';
 /* Field: Quiz Questions: Quiz Node VID */
-$handler->display->display_options['fields']['parent_vid']['id'] = 'parent_vid';
-$handler->display->display_options['fields']['parent_vid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['fields']['parent_vid']['field'] = 'parent_vid';
-$handler->display->display_options['fields']['parent_vid']['separator'] = '';
+$handler->display->display_options['fields']['quiz_vid']['id'] = 'quiz_vid';
+$handler->display->display_options['fields']['quiz_vid']['table'] = 'quiz_relationship';
+$handler->display->display_options['fields']['quiz_vid']['field'] = 'quiz_vid';
+$handler->display->display_options['fields']['quiz_vid']['separator'] = '';
 /* Field: Content revision: Updated date */
 $handler->display->display_options['fields']['timestamp']['id'] = 'timestamp';
 $handler->display->display_options['fields']['timestamp']['table'] = 'node_revision';
 $handler->display->display_options['fields']['timestamp']['field'] = 'timestamp';
-$handler->display->display_options['fields']['timestamp']['relationship'] = 'parent_vid';
+$handler->display->display_options['fields']['timestamp']['relationship'] = 'quiz_vid';
 $handler->display->display_options['fields']['timestamp']['label'] = 'Quiz Created';
 /* Field: Content revision: Title */
 $handler->display->display_options['fields']['title']['id'] = 'title';
 $handler->display->display_options['fields']['title']['table'] = 'node_revision';
 $handler->display->display_options['fields']['title']['field'] = 'title';
-$handler->display->display_options['fields']['title']['relationship'] = 'child_vid';
+$handler->display->display_options['fields']['title']['relationship'] = 'question_vid';
 $handler->display->display_options['fields']['title']['label'] = 'Question Title';
 $handler->display->display_options['fields']['title']['link_to_node'] = TRUE;
 /* Field: Quiz Questions: Question Node ID */
-$handler->display->display_options['fields']['child_nid']['id'] = 'child_nid';
-$handler->display->display_options['fields']['child_nid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['fields']['child_nid']['field'] = 'child_nid';
-$handler->display->display_options['fields']['child_nid']['separator'] = '';
+$handler->display->display_options['fields']['question_nid']['id'] = 'question_nid';
+$handler->display->display_options['fields']['question_nid']['table'] = 'quiz_relationship';
+$handler->display->display_options['fields']['question_nid']['field'] = 'question_nid';
+$handler->display->display_options['fields']['question_nid']['separator'] = '';
 /* Field: Quiz Questions: Question Node VID */
-$handler->display->display_options['fields']['child_vid']['id'] = 'child_vid';
-$handler->display->display_options['fields']['child_vid']['table'] = 'quiz_node_relationship';
-$handler->display->display_options['fields']['child_vid']['field'] = 'child_vid';
-$handler->display->display_options['fields']['child_vid']['separator'] = '';
+$handler->display->display_options['fields']['question_vid']['id'] = 'question_vid';
+$handler->display->display_options['fields']['question_vid']['table'] = 'quiz_relationship';
+$handler->display->display_options['fields']['question_vid']['field'] = 'question_vid';
+$handler->display->display_options['fields']['question_vid']['separator'] = '';
 /* Field: Quiz Questions: Question Status */
 $handler->display->display_options['fields']['question_status']['id'] = 'question_status';
-$handler->display->display_options['fields']['question_status']['table'] = 'quiz_node_relationship';
+$handler->display->display_options['fields']['question_status']['table'] = 'quiz_relationship';
 $handler->display->display_options['fields']['question_status']['field'] = 'question_status';
 /* Contextual filter: Quiz: Quiz Node VID */
 $handler->display->display_options['arguments']['vid']['id'] = 'vid';
-$handler->display->display_options['arguments']['vid']['table'] = 'quiz_node_properties';
+$handler->display->display_options['arguments']['vid']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['arguments']['vid']['field'] = 'vid';
 $handler->display->display_options['arguments']['vid']['exception']['title_enable'] = TRUE;
 $handler->display->display_options['arguments']['vid']['title_enable'] = TRUE;
diff --git a/includes/views/defaults/quiz.view.inc b/includes/views/defaults/quiz.view.inc
new file mode 100644
index 0000000..7c8b4f5
--- /dev/null
+++ b/includes/views/defaults/quiz.view.inc
@@ -0,0 +1,170 @@
+<?php
+
+$view = new view();
+$view->name = 'quiz';
+$view->description = '';
+$view->tag = 'default';
+$view->base_table = 'quiz_entity';
+$view->human_name = 'Quiz';
+$view->core = 7;
+$view->api_version = '3.0';
+$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+/* Display: Master */
+$handler = $view->new_display('default', 'Master', 'default');
+$handler->display->display_options['title'] = 'Quiz';
+$handler->display->display_options['use_ajax'] = TRUE;
+$handler->display->display_options['use_more_always'] = FALSE;
+$handler->display->display_options['access']['type'] = 'perm';
+$handler->display->display_options['access']['perm'] = 'administer quiz configuration';
+$handler->display->display_options['cache']['type'] = 'none';
+$handler->display->display_options['query']['type'] = 'views_query';
+$handler->display->display_options['exposed_form']['type'] = 'basic';
+$handler->display->display_options['pager']['type'] = 'full';
+$handler->display->display_options['pager']['options']['items_per_page'] = '50';
+$handler->display->display_options['pager']['options']['offset'] = '0';
+$handler->display->display_options['pager']['options']['id'] = '0';
+$handler->display->display_options['pager']['options']['quantity'] = '9';
+$handler->display->display_options['pager']['options']['expose']['items_per_page'] = TRUE;
+$handler->display->display_options['pager']['options']['expose']['items_per_page_options'] = '5, 10, 20, 40, 50, 75, 100';
+$handler->display->display_options['style_plugin'] = 'table';
+$handler->display->display_options['style_options']['columns'] = array(
+    'url'         => 'url',
+    'title'       => 'title',
+    'name'        => 'name',
+    'type'        => 'type',
+    'changed'     => 'changed',
+    'created'     => 'created',
+    'view_node'   => 'view_node',
+    'delete_node' => 'view_node',
+    'edit_node'   => 'view_node',
+);
+$handler->display->display_options['style_options']['default'] = '-1';
+$handler->display->display_options['style_options']['info'] = array(
+    'url'         => array(
+        'align'        => '',
+        'separator'    => '',
+        'empty_column' => 0,
+    ),
+    'title'       => array(
+        'sortable'           => 0,
+        'default_sort_order' => 'asc',
+        'align'              => '',
+        'separator'          => '',
+        'empty_column'       => 0,
+    ),
+    'name'        => array(
+        'sortable'           => 0,
+        'default_sort_order' => 'asc',
+        'align'              => '',
+        'separator'          => '',
+        'empty_column'       => 0,
+    ),
+    'type'        => array(
+        'sortable'           => 0,
+        'default_sort_order' => 'asc',
+        'align'              => '',
+        'separator'          => '',
+        'empty_column'       => 0,
+    ),
+    'changed'     => array(
+        'sortable'           => 0,
+        'default_sort_order' => 'asc',
+        'align'              => '',
+        'separator'          => '',
+        'empty_column'       => 0,
+    ),
+    'created'     => array(
+        'sortable'           => 0,
+        'default_sort_order' => 'asc',
+        'align'              => '',
+        'separator'          => '',
+        'empty_column'       => 0,
+    ),
+    'view_node'   => array(
+        'align'        => '',
+        'separator'    => ' | ',
+        'empty_column' => 0,
+    ),
+    'delete_node' => array(
+        'align'        => '',
+        'separator'    => '',
+        'empty_column' => 0,
+    ),
+    'edit_node'   => array(
+        'align'        => '',
+        'separator'    => '',
+        'empty_column' => 0,
+    ),
+);
+/* Relationship: Quiz properties: Uid */
+$handler->display->display_options['relationships']['uid']['id'] = 'uid';
+$handler->display->display_options['relationships']['uid']['table'] = 'quiz_entity';
+$handler->display->display_options['relationships']['uid']['field'] = 'uid';
+$handler->display->display_options['relationships']['uid']['label'] = 'Author';
+/* Field: Quiz properties: URL */
+$handler->display->display_options['fields']['url']['id'] = 'url';
+$handler->display->display_options['fields']['url']['table'] = 'views_entity_quiz_entity';
+$handler->display->display_options['fields']['url']['field'] = 'url';
+$handler->display->display_options['fields']['url']['exclude'] = TRUE;
+$handler->display->display_options['fields']['url']['display_as_link'] = FALSE;
+$handler->display->display_options['fields']['url']['link_to_entity'] = 0;
+/* Field: Quiz properties: Label */
+$handler->display->display_options['fields']['title']['id'] = 'title';
+$handler->display->display_options['fields']['title']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['title']['field'] = 'title';
+$handler->display->display_options['fields']['title']['alter']['make_link'] = TRUE;
+$handler->display->display_options['fields']['title']['alter']['path'] = '[url]';
+/* Field: User: Name */
+$handler->display->display_options['fields']['name']['id'] = 'name';
+$handler->display->display_options['fields']['name']['table'] = 'users';
+$handler->display->display_options['fields']['name']['field'] = 'name';
+$handler->display->display_options['fields']['name']['relationship'] = 'uid';
+/* Field: Quiz properties: Type */
+$handler->display->display_options['fields']['type']['id'] = 'type';
+$handler->display->display_options['fields']['type']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['type']['field'] = 'type';
+/* Field: Quiz properties: Changed */
+$handler->display->display_options['fields']['changed']['id'] = 'changed';
+$handler->display->display_options['fields']['changed']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['changed']['field'] = 'changed';
+/* Field: Quiz properties: Created */
+$handler->display->display_options['fields']['created']['id'] = 'created';
+$handler->display->display_options['fields']['created']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['created']['field'] = 'created';
+/* Field: Quiz properties: Link */
+$handler->display->display_options['fields']['view_node']['id'] = 'view_node';
+$handler->display->display_options['fields']['view_node']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['view_node']['field'] = 'view_node';
+/* Field: Quiz properties: Delete link */
+$handler->display->display_options['fields']['delete_node']['id'] = 'delete_node';
+$handler->display->display_options['fields']['delete_node']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['delete_node']['field'] = 'delete_node';
+/* Field: Quiz properties: Edit link */
+$handler->display->display_options['fields']['edit_node']['id'] = 'edit_node';
+$handler->display->display_options['fields']['edit_node']['table'] = 'quiz_entity';
+$handler->display->display_options['fields']['edit_node']['field'] = 'edit_node';
+/* Filter criterion: Quiz properties: Type */
+$handler->display->display_options['filters']['type']['id'] = 'type';
+$handler->display->display_options['filters']['type']['table'] = 'quiz_entity';
+$handler->display->display_options['filters']['type']['field'] = 'type';
+$handler->display->display_options['filters']['type']['exposed'] = TRUE;
+$handler->display->display_options['filters']['type']['expose']['operator_id'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['label'] = 'Quiz Type';
+$handler->display->display_options['filters']['type']['expose']['operator'] = 'type_op';
+$handler->display->display_options['filters']['type']['expose']['identifier'] = 'type';
+/* Filter criterion: Quiz properties: Status */
+$handler->display->display_options['filters']['status']['id'] = 'status';
+$handler->display->display_options['filters']['status']['table'] = 'quiz_entity';
+$handler->display->display_options['filters']['status']['field'] = 'status';
+$handler->display->display_options['filters']['status']['operator'] = '>=';
+$handler->display->display_options['filters']['status']['value']['value'] = '0';
+
+/* Display: Page */
+$handler = $view->new_display('page', 'Page', 'page');
+$handler->display->display_options['path'] = 'admin/content/quiz';
+$handler->display->display_options['menu']['type'] = 'tab';
+$handler->display->display_options['menu']['title'] = 'Quiz';
+$handler->display->display_options['menu']['weight'] = '0';
+$handler->display->display_options['menu']['context'] = 0;
+$handler->display->display_options['menu']['context_only_inline'] = 0;
diff --git a/includes/views/defaults/quiz_by_author.view.inc b/includes/views/defaults/quiz_by_author.view.inc
index 08a74cc..7e01f77 100644
--- a/includes/views/defaults/quiz_by_author.view.inc
+++ b/includes/views/defaults/quiz_by_author.view.inc
@@ -4,7 +4,7 @@ $view = new view();
 $view->name = 'quiz_by_author';
 $view->description = 'View of all the quizzes owned by a particular user (\'author\').';
 $view->tag = 'quiz';
-$view->base_table = 'quiz_node_properties';
+$view->base_table = 'quiz_entity_revision';
 $view->human_name = '';
 $view->core = 0;
 $view->api_version = '3.0';
@@ -53,38 +53,38 @@ $handler->display->display_options['style_options']['info'] = array(
 );
 /* Relationship: Quiz: Quiz Node NID */
 $handler->display->display_options['relationships']['nid']['id'] = 'nid';
-$handler->display->display_options['relationships']['nid']['table'] = 'quiz_node_properties';
+$handler->display->display_options['relationships']['nid']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['relationships']['nid']['field'] = 'nid';
 $handler->display->display_options['relationships']['nid']['required'] = TRUE;
 /* Field: Quiz: Quiz Node NID */
 $handler->display->display_options['fields']['nid']['id'] = 'nid';
-$handler->display->display_options['fields']['nid']['table'] = 'quiz_node_properties';
+$handler->display->display_options['fields']['nid']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['fields']['nid']['field'] = 'nid';
 /* Field: Quiz: Quiz Node VID */
 $handler->display->display_options['fields']['vid']['id'] = 'vid';
-$handler->display->display_options['fields']['vid']['table'] = 'quiz_node_properties';
+$handler->display->display_options['fields']['vid']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['fields']['vid']['field'] = 'vid';
 /* Field: Quiz: Close time */
 $handler->display->display_options['fields']['quiz_close']['id'] = 'quiz_close';
-$handler->display->display_options['fields']['quiz_close']['table'] = 'quiz_node_properties';
+$handler->display->display_options['fields']['quiz_close']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['fields']['quiz_close']['field'] = 'quiz_close';
 $handler->display->display_options['fields']['quiz_close']['label'] = 'Quiz close time';
 /* Field: Quiz: Open time */
 $handler->display->display_options['fields']['quiz_open']['id'] = 'quiz_open';
-$handler->display->display_options['fields']['quiz_open']['table'] = 'quiz_node_properties';
+$handler->display->display_options['fields']['quiz_open']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['fields']['quiz_open']['field'] = 'quiz_open';
 $handler->display->display_options['fields']['quiz_open']['label'] = 'Quiz open time';
 /* Field: Quiz: Time limit */
 $handler->display->display_options['fields']['time_limit']['id'] = 'time_limit';
-$handler->display->display_options['fields']['time_limit']['table'] = 'quiz_node_properties';
+$handler->display->display_options['fields']['time_limit']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['fields']['time_limit']['field'] = 'time_limit';
 /* Field: Quiz: Allowed takes */
 $handler->display->display_options['fields']['takes']['id'] = 'takes';
-$handler->display->display_options['fields']['takes']['table'] = 'quiz_node_properties';
+$handler->display->display_options['fields']['takes']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['fields']['takes']['field'] = 'takes';
 /* Contextual filter: Quiz Results: User */
 $handler->display->display_options['arguments']['uid']['id'] = 'uid';
-$handler->display->display_options['arguments']['uid']['table'] = 'quiz_node_results';
+$handler->display->display_options['arguments']['uid']['table'] = 'quiz_results';
 $handler->display->display_options['arguments']['uid']['field'] = 'uid';
 $handler->display->display_options['arguments']['uid']['default_action'] = 'not found';
 $handler->display->display_options['arguments']['uid']['exception']['title_enable'] = TRUE;
diff --git a/includes/views/defaults/quiz_by_taker.view.inc b/includes/views/defaults/quiz_by_taker.view.inc
index 55d56e4..20cba18 100644
--- a/includes/views/defaults/quiz_by_taker.view.inc
+++ b/includes/views/defaults/quiz_by_taker.view.inc
@@ -1,9 +1,10 @@
 <?php
+
 $view = new view();
 $view->name = 'quiz_by_taker';
 $view->description = 'View of all the quizzes a user has taken';
 $view->tag = 'quiz';
-$view->base_table = 'quiz_node_properties';
+$view->base_table = 'quiz_entity_revision';
 $view->human_name = '';
 $view->core = 0;
 $view->api_version = '3.0';
@@ -21,43 +22,43 @@ $handler->display->display_options['pager']['type'] = 'none';
 $handler->display->display_options['style_plugin'] = 'table';
 $handler->display->display_options['style_options']['grouping'] = 'vid';
 $handler->display->display_options['style_options']['columns'] = array(
-  'time_end' => 'time_end',
-  'nid' => 'nid',
-  'vid' => 'vid',
-  'time_start' => 'time_start',
-  'score' => 'score',
-  'uid' => 'uid',
+    'time_end'   => 'time_end',
+    'nid'        => 'nid',
+    'vid'        => 'vid',
+    'time_start' => 'time_start',
+    'score'      => 'score',
+    'uid'        => 'uid',
 );
 $handler->display->display_options['style_options']['default'] = '-1';
 $handler->display->display_options['style_options']['info'] = array(
-  'time_end' => array(
-    'sortable' => 0,
-    'separator' => '',
-  ),
-  'nid' => array(
-    'sortable' => 0,
-    'separator' => '',
-  ),
-  'vid' => array(
-    'sortable' => 0,
-    'separator' => '',
-  ),
-  'time_start' => array(
-    'sortable' => 0,
-    'separator' => '',
-  ),
-  'score' => array(
-    'sortable' => 0,
-    'separator' => '',
-  ),
-  'uid' => array(
-    'sortable' => 0,
-    'separator' => '',
-  ),
+    'time_end'   => array(
+        'sortable'  => 0,
+        'separator' => '',
+    ),
+    'nid'        => array(
+        'sortable'  => 0,
+        'separator' => '',
+    ),
+    'vid'        => array(
+        'sortable'  => 0,
+        'separator' => '',
+    ),
+    'time_start' => array(
+        'sortable'  => 0,
+        'separator' => '',
+    ),
+    'score'      => array(
+        'sortable'  => 0,
+        'separator' => '',
+    ),
+    'uid'        => array(
+        'sortable'  => 0,
+        'separator' => '',
+    ),
 );
 /* Relationship: Quiz Results: Quiz Node VID */
 $handler->display->display_options['relationships']['vid']['id'] = 'vid';
-$handler->display->display_options['relationships']['vid']['table'] = 'quiz_node_results';
+$handler->display->display_options['relationships']['vid']['table'] = 'quiz_results';
 $handler->display->display_options['relationships']['vid']['field'] = 'vid';
 $handler->display->display_options['relationships']['vid']['label'] = 'quiz revision';
 $handler->display->display_options['relationships']['vid']['required'] = TRUE;
@@ -68,24 +69,24 @@ $handler->display->display_options['fields']['title']['field'] = 'title';
 $handler->display->display_options['fields']['title']['relationship'] = 'vid';
 /* Field: Quiz Results: Score */
 $handler->display->display_options['fields']['score']['id'] = 'score';
-$handler->display->display_options['fields']['score']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['score']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['score']['field'] = 'score';
 /* Field: Quiz Results: Quiz Start Time */
 $handler->display->display_options['fields']['time_start']['id'] = 'time_start';
-$handler->display->display_options['fields']['time_start']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['time_start']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['time_start']['field'] = 'time_start';
 $handler->display->display_options['fields']['time_start']['label'] = 'Quiz Start Time.';
 /* Field: Quiz Results: Quiz End Time */
 $handler->display->display_options['fields']['time_end']['id'] = 'time_end';
-$handler->display->display_options['fields']['time_end']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['time_end']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['time_end']['field'] = 'time_end';
 /* Field: Quiz Results: Quiz State */
 $handler->display->display_options['fields']['quiz_state']['id'] = 'quiz_state';
-$handler->display->display_options['fields']['quiz_state']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['quiz_state']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['quiz_state']['field'] = 'quiz_state';
 /* Contextual filter: Quiz Results: User */
 $handler->display->display_options['arguments']['uid']['id'] = 'uid';
-$handler->display->display_options['arguments']['uid']['table'] = 'quiz_node_results';
+$handler->display->display_options['arguments']['uid']['table'] = 'quiz_results';
 $handler->display->display_options['arguments']['uid']['field'] = 'uid';
 $handler->display->display_options['arguments']['uid']['default_action'] = 'not found';
 $handler->display->display_options['arguments']['uid']['exception']['title_enable'] = TRUE;
@@ -95,9 +96,10 @@ $handler->display->display_options['arguments']['uid']['default_argument_type']
 $handler->display->display_options['arguments']['uid']['summary']['format'] = 'default_summary';
 $handler->display->display_options['arguments']['uid']['specify_validation'] = TRUE;
 $handler->display->display_options['arguments']['uid']['allow_null'] = 1;
+
 /* Filter criterion: Quiz: Quiz Version */
 $handler->display->display_options['filters']['nid']['id'] = 'nid';
-$handler->display->display_options['filters']['nid']['table'] = 'quiz_node_properties';
+$handler->display->display_options['filters']['nid']['table'] = 'quiz_entity_revision';
 $handler->display->display_options['filters']['nid']['field'] = 'nid';
 $handler->display->display_options['filters']['nid']['group'] = '0';
 $handler->display->display_options['filters']['nid']['expose']['operator'] = FALSE;
diff --git a/includes/views/defaults/quiz_results.view.inc b/includes/views/defaults/quiz_results.view.inc
index de40cc8..48386ff 100644
--- a/includes/views/defaults/quiz_results.view.inc
+++ b/includes/views/defaults/quiz_results.view.inc
@@ -4,7 +4,7 @@ $view = new view();
 $view->name = 'quiz_results';
 $view->description = '';
 $view->tag = 'default';
-$view->base_table = 'quiz_node_results';
+$view->base_table = 'quiz_results';
 $view->human_name = 'quiz_results';
 $view->core = 7;
 $view->api_version = '3.0';
@@ -30,11 +30,11 @@ $handler->display->display_options['empty']['area']['content'] = 'There are no a
 $handler->display->display_options['empty']['area']['format'] = 'filtered_html';
 /* Relationship: Quiz result: Uid */
 $handler->display->display_options['relationships']['uid']['id'] = 'uid';
-$handler->display->display_options['relationships']['uid']['table'] = 'quiz_node_results';
+$handler->display->display_options['relationships']['uid']['table'] = 'quiz_results';
 $handler->display->display_options['relationships']['uid']['field'] = 'uid';
 /* Field: Bulk operations: Quiz result */
 $handler->display->display_options['fields']['views_bulk_operations']['id'] = 'views_bulk_operations';
-$handler->display->display_options['fields']['views_bulk_operations']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['views_bulk_operations']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['views_bulk_operations']['field'] = 'views_bulk_operations';
 $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['display_type'] = '1';
 $handler->display->display_options['fields']['views_bulk_operations']['vbo_settings']['enable_select_all_pages'] = 1;
@@ -102,32 +102,32 @@ $handler->display->display_options['fields']['name']['label'] = 'Username';
 $handler->display->display_options['fields']['name']['link_to_user'] = FALSE;
 /* Field: Quiz result: Date started */
 $handler->display->display_options['fields']['time_start']['id'] = 'time_start';
-$handler->display->display_options['fields']['time_start']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['time_start']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['time_start']['field'] = 'time_start';
 $handler->display->display_options['fields']['time_start']['date_format'] = 'short';
 /* Field: Quiz result: Date finished */
 $handler->display->display_options['fields']['time_end']['id'] = 'time_end';
-$handler->display->display_options['fields']['time_end']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['time_end']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['time_end']['field'] = 'time_end';
 $handler->display->display_options['fields']['time_end']['date_format'] = 'short';
 /* Field: Quiz result: Score */
 $handler->display->display_options['fields']['score']['id'] = 'score';
-$handler->display->display_options['fields']['score']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['score']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['score']['field'] = 'score';
 /* Field: Quiz result: Evaluated */
 $handler->display->display_options['fields']['is_evaluated']['id'] = 'is_evaluated';
-$handler->display->display_options['fields']['is_evaluated']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['is_evaluated']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['is_evaluated']['field'] = 'is_evaluated';
 $handler->display->display_options['fields']['is_evaluated']['not'] = 0;
 /* Field: Quiz result: Nid */
 $handler->display->display_options['fields']['nid']['id'] = 'nid';
-$handler->display->display_options['fields']['nid']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['nid']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['nid']['field'] = 'nid';
 $handler->display->display_options['fields']['nid']['exclude'] = TRUE;
 $handler->display->display_options['fields']['nid']['separator'] = '';
 /* Field: Quiz result: Quiz result ID */
 $handler->display->display_options['fields']['result_id']['id'] = 'result_id';
-$handler->display->display_options['fields']['result_id']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['result_id']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['result_id']['field'] = 'result_id';
 $handler->display->display_options['fields']['result_id']['exclude'] = TRUE;
 /* Field: Global: Custom text */
@@ -140,7 +140,7 @@ $handler->display->display_options['fields']['nothing']['alter']['make_link'] =
 $handler->display->display_options['fields']['nothing']['alter']['path'] = 'node/[nid]/quiz/results/[result_id]/view';
 /* Contextual filter: Quiz result: Nid */
 $handler->display->display_options['arguments']['nid']['id'] = 'nid';
-$handler->display->display_options['arguments']['nid']['table'] = 'quiz_node_results';
+$handler->display->display_options['arguments']['nid']['table'] = 'quiz_results';
 $handler->display->display_options['arguments']['nid']['field'] = 'nid';
 $handler->display->display_options['arguments']['nid']['default_action'] = 'access denied';
 $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
diff --git a/includes/views/defaults/quiz_user_results.view.inc b/includes/views/defaults/quiz_user_results.view.inc
index 63aca55..5fd69b0 100644
--- a/includes/views/defaults/quiz_user_results.view.inc
+++ b/includes/views/defaults/quiz_user_results.view.inc
@@ -4,7 +4,7 @@ $view = new view();
 $view->name = 'quiz_user_results';
 $view->description = '';
 $view->tag = 'default';
-$view->base_table = 'quiz_node_results';
+$view->base_table = 'quiz_results';
 $view->human_name = 'quiz_results';
 $view->core = 7;
 $view->api_version = '3.0';
@@ -31,31 +31,25 @@ $handler->display->display_options['empty']['area']['content'] = 'There are no a
 $handler->display->display_options['empty']['area']['format'] = 'filtered_html';
 /* Relationship: Quiz result: Uid */
 $handler->display->display_options['relationships']['uid']['id'] = 'uid';
-$handler->display->display_options['relationships']['uid']['table'] = 'quiz_node_results';
+$handler->display->display_options['relationships']['uid']['table'] = 'quiz_results';
 $handler->display->display_options['relationships']['uid']['field'] = 'uid';
 /* Field: Quiz result: Date started */
 $handler->display->display_options['fields']['time_start']['id'] = 'time_start';
-$handler->display->display_options['fields']['time_start']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['time_start']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['time_start']['field'] = 'time_start';
 $handler->display->display_options['fields']['time_start']['date_format'] = 'short';
 /* Field: Quiz result: Date finished */
 $handler->display->display_options['fields']['time_end']['id'] = 'time_end';
-$handler->display->display_options['fields']['time_end']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['time_end']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['time_end']['field'] = 'time_end';
 $handler->display->display_options['fields']['time_end']['date_format'] = 'short';
 /* Field: Quiz result: Score */
 $handler->display->display_options['fields']['score']['id'] = 'score';
-$handler->display->display_options['fields']['score']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['score']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['score']['field'] = 'score';
-/* Field: Quiz result: Nid */
-$handler->display->display_options['fields']['nid']['id'] = 'nid';
-$handler->display->display_options['fields']['nid']['table'] = 'quiz_node_results';
-$handler->display->display_options['fields']['nid']['field'] = 'nid';
-$handler->display->display_options['fields']['nid']['exclude'] = TRUE;
-$handler->display->display_options['fields']['nid']['separator'] = '';
 /* Field: Quiz result: Quiz result ID */
 $handler->display->display_options['fields']['result_id']['id'] = 'result_id';
-$handler->display->display_options['fields']['result_id']['table'] = 'quiz_node_results';
+$handler->display->display_options['fields']['result_id']['table'] = 'quiz_results';
 $handler->display->display_options['fields']['result_id']['field'] = 'result_id';
 $handler->display->display_options['fields']['result_id']['exclude'] = TRUE;
 /* Field: Global: Custom text */
@@ -65,10 +59,10 @@ $handler->display->display_options['fields']['nothing']['field'] = 'nothing';
 $handler->display->display_options['fields']['nothing']['label'] = 'Operations';
 $handler->display->display_options['fields']['nothing']['alter']['text'] = 'View';
 $handler->display->display_options['fields']['nothing']['alter']['make_link'] = TRUE;
-$handler->display->display_options['fields']['nothing']['alter']['path'] = 'node/[nid]/quiz-results/[result_id]/view';
-/* Contextual filter: Quiz result: Nid */
+$handler->display->display_options['fields']['nothing']['alter']['path'] = 'quiz-result/[result_id]';
+/* Contextual filter: Broken/missing handler */
 $handler->display->display_options['arguments']['nid']['id'] = 'nid';
-$handler->display->display_options['arguments']['nid']['table'] = 'quiz_node_results';
+$handler->display->display_options['arguments']['nid']['table'] = 'quiz_results';
 $handler->display->display_options['arguments']['nid']['field'] = 'nid';
 $handler->display->display_options['arguments']['nid']['default_action'] = 'access denied';
 $handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
@@ -78,13 +72,13 @@ $handler->display->display_options['arguments']['nid']['summary_options']['items
 $handler->display->display_options['arguments']['nid']['specify_validation'] = TRUE;
 $handler->display->display_options['arguments']['nid']['validate']['type'] = 'node';
 $handler->display->display_options['arguments']['nid']['validate_options']['types'] = array(
-  'quiz' => 'quiz',
+    'quiz' => 'quiz',
 );
 $handler->display->display_options['arguments']['nid']['validate_options']['access'] = TRUE;
 $handler->display->display_options['arguments']['nid']['validate_options']['access_op'] = 'update';
 /* Contextual filter: Quiz result: Uid */
 $handler->display->display_options['arguments']['uid']['id'] = 'uid';
-$handler->display->display_options['arguments']['uid']['table'] = 'quiz_node_results';
+$handler->display->display_options['arguments']['uid']['table'] = 'quiz_results';
 $handler->display->display_options['arguments']['uid']['field'] = 'uid';
 $handler->display->display_options['arguments']['uid']['default_action'] = 'default';
 $handler->display->display_options['arguments']['uid']['default_argument_type'] = 'current_user';
@@ -95,13 +89,13 @@ $handler->display->display_options['arguments']['uid']['specify_validation'] = T
 $handler->display->display_options['arguments']['uid']['validate']['type'] = 'user';
 /* Filter criterion: Quiz result: Evaluated */
 $handler->display->display_options['filters']['is_evaluated']['id'] = 'is_evaluated';
-$handler->display->display_options['filters']['is_evaluated']['table'] = 'quiz_node_results';
+$handler->display->display_options['filters']['is_evaluated']['table'] = 'quiz_results';
 $handler->display->display_options['filters']['is_evaluated']['field'] = 'is_evaluated';
 $handler->display->display_options['filters']['is_evaluated']['value'] = '1';
 
 /* Display: Page */
 $handler = $view->new_display('page', 'Page', 'page');
-$handler->display->display_options['path'] = 'node/%/quiz-results';
+$handler->display->display_options['path'] = 'quiz/%/quiz-results';
 $handler->display->display_options['menu']['type'] = 'tab';
 $handler->display->display_options['menu']['title'] = 'My results';
 $handler->display->display_options['menu']['weight'] = '0';
@@ -116,7 +110,7 @@ $handler = $view->new_display('page', 'Page', 'page_1');
 $handler->display->display_options['defaults']['arguments'] = FALSE;
 /* Contextual filter: Quiz result: Uid */
 $handler->display->display_options['arguments']['uid']['id'] = 'uid';
-$handler->display->display_options['arguments']['uid']['table'] = 'quiz_node_results';
+$handler->display->display_options['arguments']['uid']['table'] = 'quiz_results';
 $handler->display->display_options['arguments']['uid']['field'] = 'uid';
 $handler->display->display_options['arguments']['uid']['default_action'] = 'default';
 $handler->display->display_options['arguments']['uid']['default_argument_type'] = 'current_user';
diff --git a/includes/views/handlers/quiz_views_handler_argument_quiz_nid.inc b/includes/views/handlers/quiz_views_handler_argument_quiz_nid.inc
deleted file mode 100644
index 179e4a1..0000000
--- a/includes/views/handlers/quiz_views_handler_argument_quiz_nid.inc
+++ /dev/null
@@ -1,158 +0,0 @@
-<?php
-
-/*
- * @file
- * Handles argument quiz nid.
- */
-
-class quiz_views_handler_argument_quiz_nid extends views_handler_argument_numeric {
-  /**
-   * An array that the subselect will fill with all the corresponding quiz node,
-   * according to the configuration on the argument settings form. These vids
-   * will then be the actual ones used while generating the real query.
-   *
-   * @var array
-   */
-  var $corresponding_vids = array();
-  var $phrase_broken = FALSE;
-
-  function construct() {
-    parent::construct();
-    $this->nid_field = $this->definition['nid field'];
-    $this->vid_field = isset($this->definition['vid field']) ? $this->definition['vid field'] : 'vid';
-  }
-
-  function option_definition() {
-    $options = parent::option_definition();
-
-    $options['which_vid'] = array('default' => 'latest');
-
-    return $options;
-  }
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-    // Set weighting so that our element can sanely ordered
-    $form['title']['#weight'] = -2;
-    //    $form['default_action']['#weight'] = -3;
-    //    $form['wildcard']['#weight'] = -4;
-    //    $form['wildcard_substitution']['#weight'] = -4;
-    $form['which_vid'] = array(
-      '#type' => 'select',
-      '#title' => t('Quiz revision(s) to use'),
-      '#options' => array(
-        'latest' => t('Latest: most recent version of the quiz ONLY.'),
-        'initial' => t('Initial: original version of the quiz ONLY.'),
-        'all' => t('All: ALL versions of the quiz.')
-      ),
-      '#description' => t('The validator will transform the incoming quiz node id(s) into one or more quiz node revisions, depending on your selection.'),
-      '#default_value' => $this->options['which_vid'],
-      '#weight' => -1,
-    );
-  }
-
-  function title_query() {
-    $titles = array();
-
-    $result = db_query("SELECT n.title FROM {node_revision} n WHERE n.vid IN (:vids)", array(':vids' => implode(', ', $this->corresponding_vids)));
-    while ($term = $result->fetch()) {
-      $titles[] = check_plain($term->title);
-    }
-    return $titles;
-  }
-
-  /**
-   * Override the default behavior of query() to introduce the medial step of
-   * retrieving vids from the provided nids.
-   */
-  function query($group_by = FALSE) {
-    $this->ensure_my_table();
-    $this->break_phrase();
-
-    // Do subselect to get relevant vids, then add where clause
-    $this->subselect_vids();
-    $this->query->add_where(0, "$this->table_alias.$this->nid_field", $this->corresponding_vids);
-  }
-
-  /**
-   * Set up the argument with the vids extracted from nids.
-   *
-   * Needs to be done here, because this is the earliest stage at which we can
-   * guarantee the contents of $this->argument to be available.
-   *
-   * @param string $arg
-   *   The argument, as delivered in the URL.
-   */
-  function set_argument($arg) {
-    $this->argument = $arg;
-    if ($this->validate_arg($arg)) {
-      $this->break_phrase();
-      $this->subselect_vids();
-      return TRUE;
-    }
-    return FALSE;
-  }
-
-  function break_phrase() {
-    if (!$this->phrase_broken) {
-      // Handle multiple argument inputs
-      if (!empty($this->options['break_phrase'])) {
-        views_break_phrase($this->argument, $this);
-      }
-      else {
-        $this->value = array($this->argument);
-      }
-      $this->phrase_broken = TRUE;
-    }
-  }
-
-  /**
-   * Helper method to retrieve the vid(s) the final view query should actually
-   * be run against.
-   *
-   * Would be done in pre_query(), but $this->argument is not yet available at
-   * that time. So, called from set_argument().
-   */
-  function subselect_vids() {
-    $query = db_select('quiz_node_properties', 'qnp');
-    if (count($this->value) > 1) {
-      // Guaranteed to produce multiple values; therefore may need the group by
-      $use_group_by = TRUE;
-      $operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
-      $query->condition('nid', $this->value, $operator);
-    }
-    else {
-      // Multiple values only possible with a NOT; only then do we need group by
-      $use_group_by = !empty($this->options['not']);
-      $operator = empty($this->options['not']) ? '=' : '!=';
-      $query->condition('nid', reset($this->value), $operator);
-    }
-
-    switch ($this->options['which_vid']) {
-      case 'initial':
-        // SQL operation for getting the initial vids based on view config settings
-        $query->addExpression('MIN(vid)', 'vid');
-        break;
-      case 'latest':
-        // SQL operation for getting the latest vids based on view config settings
-        $query->addExpression('MAX(vid)', 'vid');
-        break;
-      default:
-        // Get them all
-        $query->addField('qnp', 'vid');
-        break;
-    }
-    if ($use_group_by) {
-      $query->groupBy('nid');
-    }
-    $result = $query->execute();
-
-    $this->nid_field = $this->vid_field;
-
-    $this->corresponding_vids = array();
-    foreach ($result as $item) {
-      $this->corresponding_vids[] = $item->vid;
-    }
-    return (count($this->corresponding_vids) > 1);
-  }
-}
\ No newline at end of file
diff --git a/includes/views/handlers/quiz_views_handler_argument_quiz_qid.inc b/includes/views/handlers/quiz_views_handler_argument_quiz_qid.inc
new file mode 100644
index 0000000..b5a3d98
--- /dev/null
+++ b/includes/views/handlers/quiz_views_handler_argument_quiz_qid.inc
@@ -0,0 +1,160 @@
+<?php
+
+/*
+ * @file
+ * Handles argument quiz nid.
+ */
+
+class quiz_views_handler_argument_quiz_qid extends views_handler_argument_numeric {
+
+  /**
+   * An array that the subselect will fill with all the corresponding quiz entity,
+   * according to the configuration on the argument settings form. These vids
+   * will then be the actual ones used while generating the real query.
+   *
+   * @var array
+   */
+  var $corresponding_vids = array();
+  var $phrase_broken = FALSE;
+
+  function construct() {
+    parent::construct();
+    $this->nid_field = $this->definition['nid field'];
+    $this->vid_field = isset($this->definition['vid field']) ? $this->definition['vid field'] : 'vid';
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['which_vid'] = array('default' => 'latest');
+
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    // Set weighting so that our element can sanely ordered
+    $form['title']['#weight'] = -2;
+    //    $form['default_action']['#weight'] = -3;
+    //    $form['wildcard']['#weight'] = -4;
+    //    $form['wildcard_substitution']['#weight'] = -4;
+    $form['which_vid'] = array(
+        '#type'          => 'select',
+        '#title'         => t('Quiz revision(s) to use'),
+        '#options'       => array(
+            'latest'  => t('Latest: most recent version of the quiz ONLY.'),
+            'initial' => t('Initial: original version of the quiz ONLY.'),
+            'all'     => t('All: ALL versions of the quiz.')
+        ),
+        '#description'   => t('The validator will transform the incoming quiz entity id(s) into one or more quiz entity revisions, depending on your selection.'),
+        '#default_value' => $this->options['which_vid'],
+        '#weight'        => -1,
+    );
+  }
+
+  function title_query() {
+    $titles = array();
+
+    $result = db_query("SELECT n.title FROM {node_revision} n WHERE n.vid IN (:vids)", array(':vids' => implode(', ', $this->corresponding_vids)));
+    while ($term = $result->fetch()) {
+      $titles[] = check_plain($term->title);
+    }
+    return $titles;
+  }
+
+  /**
+   * Override the default behavior of query() to introduce the medial step of
+   * retrieving vids from the provided nids.
+   */
+  function query($group_by = FALSE) {
+    $this->ensure_my_table();
+    $this->break_phrase();
+
+    // Do subselect to get relevant vids, then add where clause
+    $this->subselect_vids();
+    $this->query->add_where(0, "$this->table_alias.$this->nid_field", $this->corresponding_vids);
+  }
+
+  /**
+   * Set up the argument with the vids extracted from nids.
+   *
+   * Needs to be done here, because this is the earliest stage at which we can
+   * guarantee the contents of $this->argument to be available.
+   *
+   * @param string $arg
+   *   The argument, as delivered in the URL.
+   */
+  function set_argument($arg) {
+    $this->argument = $arg;
+    if ($this->validate_arg($arg)) {
+      $this->break_phrase();
+      $this->subselect_vids();
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  function break_phrase() {
+    if (!$this->phrase_broken) {
+      // Handle multiple argument inputs
+      if (!empty($this->options['break_phrase'])) {
+        views_break_phrase($this->argument, $this);
+      }
+      else {
+        $this->value = array($this->argument);
+      }
+      $this->phrase_broken = TRUE;
+    }
+  }
+
+  /**
+   * Helper method to retrieve the vid(s) the final view query should actually
+   * be run against.
+   *
+   * Would be done in pre_query(), but $this->argument is not yet available at
+   * that time. So, called from set_argument().
+   */
+  function subselect_vids() {
+    $query = db_select('quiz_entity_revision', 'qnp');
+    if (count($this->value) > 1) {
+      // Guaranteed to produce multiple values; therefore may need the group by
+      $use_group_by = TRUE;
+      $operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
+      $query->condition('quiz_qid', $this->value, $operator);
+    }
+    else {
+      // Multiple values only possible with a NOT; only then do we need group by
+      $use_group_by = !empty($this->options['not']);
+      $operator = empty($this->options['not']) ? '=' : '!=';
+      $query->condition('quiz_qid', reset($this->value), $operator);
+    }
+
+    switch ($this->options['which_vid']) {
+      case 'initial':
+        // SQL operation for getting the initial vids based on view config settings
+        $query->addExpression('MIN(vid)', 'vid');
+        break;
+      case 'latest':
+        // SQL operation for getting the latest vids based on view config settings
+        $query->addExpression('MAX(vid)', 'vid');
+        break;
+      default:
+        // Get them all
+        $query->addField('qnp', 'vid');
+        break;
+    }
+    if ($use_group_by) {
+      $query->groupBy('quiz_qid');
+    }
+    $result = $query->execute();
+
+    $this->nid_field = $this->vid_field;
+
+    $this->corresponding_vids = array();
+    foreach ($result as $item) {
+      $this->corresponding_vids[] = $item->vid;
+    }
+    return (count($this->corresponding_vids) > 1);
+  }
+
+}
diff --git a/includes/views/handlers/quiz_views_handler_field_node_link_take.inc b/includes/views/handlers/quiz_views_handler_field_node_link_take.inc
index e095e77..54630fe 100644
--- a/includes/views/handlers/quiz_views_handler_field_node_link_take.inc
+++ b/includes/views/handlers/quiz_views_handler_field_node_link_take.inc
@@ -6,7 +6,7 @@
  */
 
 /**
- * Field handler to present a link to take a quiz node.
+ * Field handler to present a link to take a quiz entity.
  *
  * @ingroup views_field_handlers
  */
diff --git a/includes/views/handlers/quiz_views_handler_field_number_questions.inc b/includes/views/handlers/quiz_views_handler_field_number_questions.inc
index c57ccc4..98b7c89 100644
--- a/includes/views/handlers/quiz_views_handler_field_number_questions.inc
+++ b/includes/views/handlers/quiz_views_handler_field_number_questions.inc
@@ -10,29 +10,29 @@ class quiz_views_handler_field_number_questions extends views_handler_field {
   function option_definition() {
     $options = parent::option_definition();
     $options['include_random'] = array(
-      'default' => TRUE,
+        'default' => TRUE,
     );
   }
 
   function options_form(&$form, &$form_state) {
     parent::options_form($form, $form_state);
     $form['include_random'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Include random questions?'),
-      '#description' => t('Whether or not questions set to "random" should be included in the count.'),
-      '#default_value' => $this->options['include_random'],
+        '#type'          => 'checkbox',
+        '#title'         => t('Include random questions?'),
+        '#description'   => t('Whether or not questions set to "random" should be included in the count.'),
+        '#default_value' => $this->options['include_random'],
     );
   }
 
   function query() {
     $this->ensure_my_table();
     $this->field_alias = 'question_count';
-    $formula = "COUNT($this->table_alias.child_vid)";
+    $formula = "COUNT($this->table_alias.question_vid)";
     if ($this->options['include_random']) {
-      $properties_alias = $this->query->ensure_table('quiz_node_properties');
-
-      $formula .= " + (SELECT number_of_random_questions FROM {quiz_node_properties} WHERE vid = $this->table_alias.parent_vid)";
+      $properties_alias = $this->query->ensure_table('quiz_entity_revision');
+      $formula .= " + (SELECT number_of_random_questions FROM {quiz_entity_revision} WHERE vid = $this->table_alias.quiz_vid)";
     }
     $this->query->add_field(NULL, "$formula", $this->field_alias);
   }
+
 }
diff --git a/includes/views/handlers/quiz_views_handler_field_time.inc b/includes/views/handlers/quiz_views_handler_field_time.inc
index 2e36528..ddf7340 100644
--- a/includes/views/handlers/quiz_views_handler_field_time.inc
+++ b/includes/views/handlers/quiz_views_handler_field_time.inc
@@ -1,12 +1,14 @@
 <?php
+
 /**
  * @file A handler to provide proper displays for dates.
  *
  * @ingroup views_field_handlers
  */
 class quiz_views_handler_field_time extends views_handler_field {
+
   function render($values) {
-    $value = $values->{$this->field_alias};
-    return _quiz_format_duration($value);
+    return quiz()->formatDuration($values->{$this->field_alias});
   }
+
 }
diff --git a/includes/views/handlers/quiz_views_handler_filter_quiz_nid.inc b/includes/views/handlers/quiz_views_handler_filter_quiz_nid.inc
deleted file mode 100644
index a1a426c..0000000
--- a/includes/views/handlers/quiz_views_handler_filter_quiz_nid.inc
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-/*
- * @file
- * // options are either a) show only the most recent version, b) show the original version.
- */
-class quiz_views_handler_filter_quiz_nid extends views_handler_filter {
-
-  /**
-   * An array that the subselect will fill with all the corresponding quiz node,
-   * according to the configuration on the argument settings form. These vids
-   * will then be the actual ones used while generating the real query.
-   *
-   * @var array
-   */
-  var $corresponding_vids = array();
-  var $group_by;
-  var $group_by_table;
-  var $secondary_group_by;
-  var $secondary_table;
-  var $secondary_table_alias;
-  var $secondary_tables = array(
-    'none' => array(
-      'secondary_group_by' => 'nid',
-      'secondary_table' => 'quiz_node_results',
-      'secondary_vid' => 'vid',
-      'secondary_nid' => 'nid',
-    ),
-    'results' => array(
-      'secondary_group_by' => 'uid',
-      'secondary_table' => 'quiz_node_results',
-      'secondary_vid' => 'vid',
-      'secondary_nid' => 'nid',
-    ),
-    'questions' => array(
-      'secondary_group_by' => 'child_nid',
-      'secondary_table' => 'quiz_node_relationship',
-      'secondary_vid' => 'parent_vid',
-      'secondary_nid' => 'parent_nid',
-    ),
-  );
-
-  function construct() {
-    parent::construct();
-    $this->vid_field = !empty($this->definition['vid field']) ? $this->definition['vid field'] : 'vid';
-    if (!empty($this->definition['group by'])) {
-      $this->group_by = $this->definition['group by'];
-    }
-    if (!empty($this->definition['secondary group by'])) {
-      $this->secondary_group_by = $this->definition['secondary group by'];
-    }
-  }
-
-  function can_expose() {
-    return FALSE;
-  }
-
-  function option_definition() {
-    $options = parent::option_definition();
-
-    $options['which_vid'] = array('default' => 'latest');
-    $options['secondary'] = array('default' => 'none');
-
-    return $options;
-  }
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-    $form['which_vid'] = array(
-      '#type' => 'select',
-      '#title' => t('Quiz revision(s) to use'),
-      '#options' => array(
-        'latest' => t('Latest: most recent version of the quiz ONLY.'),
-        'initial' => t('Initial: original version of the quiz ONLY.'),
-        // 'all' => t('All: ALL versions of the quiz.')
-      ),
-      '#description' => t('The filter will limit score results for a particular quiz node id(s) into one or more quiz node revision ids, depending on your selection.'),
-      '#default_value' => $this->options['which_vid'],
-    );
-
-    $tables = array('none' => t('None'));
-    $tables['results'] = t('Use Quiz Results');
-    $tables['questions'] = t('Use Quiz Questions');
-    $form['secondary'] = array(
-      '#type' => 'radios',
-      '#title' => t('Secondary Grouping'),
-      '#options' => $tables,
-      '#default_value' => $this->options['secondary'],
-      '#description' => t('If you are looking to generate a list of either quiz questions or quiz results, you must select the appropriate option from this list.'),
-    );
-  }
-
-  function value_submit($form, &$form_state) {
-    $form_state['values']['options']['secondaries'] = $this->secondary_tables[$form_state['values']['options']['secondary']];
-  }
-
-  /**
-   * Override the default behavior of query() to introduce the medial step of
-   * retrieving vids from the provided nids.
-   */
-  function query() {
-    $this->ensure_my_table();
-
-    foreach ($this->options['secondaries'] as $key => $value) {
-      if (!is_null($value)) {
-        $this->$key = $value;
-      }
-    }
-
-    $this->group_by_table = !empty($this->definition['group by table']) ?
-      $this->query->ensure_table($this->definition['group by table']) :
-      $this->table_alias;
-    //    $this->query->add_groupby("$this->group_by_table.$this->group_by");
-    $this->query->add_groupby("$this->table_alias.$this->field");
-    $this->query->add_groupby("$this->table_alias.$this->vid_field");
-
-    //    if (!empty($this->secondary_group_by) || !empty($this->options['secondary_group_by'])) {
-    if (!empty($this->secondary_group_by)) {
-      $this->secondary_table_alias = $this->query->ensure_table($this->secondary_table);
-      $this->query->add_groupby("$this->secondary_table_alias.$this->secondary_group_by");
-    }
-    $this->query->add_where(0, "$this->secondary_table_alias.$this->secondary_vid", $this->subselect());
-  }
-
-  /**
-   * Helper method to retrieve the vid(s) the final view query should actually
-   * be run against.
-   *
-   * Would be done in pre_query(), but $this->argument is not yet available at
-   * that time. So, called from set_argument().
-   */
-  function subselect() {
-    $operation = ($this->options['which_vid'] == 'initial' ? 'MIN' : 'MAX');
-    $subalias = $this->secondary_table_alias . '__subselect';
-    $subselect = "SELECT $operation($subalias.$this->secondary_vid) FROM ";
-    $subselect .= '{' . $this->secondary_table . "} $subalias WHERE ";
-    $subselect .= "$subalias.$this->secondary_group_by = $this->secondary_table_alias.$this->secondary_group_by AND ";
-    $subselect .= "$subalias.$this->secondary_nid = $this->secondary_table_alias.$this->secondary_nid";
-    return $subselect;
-
-  }
-}
diff --git a/includes/views/handlers/quiz_views_handler_filter_quiz_qid.inc b/includes/views/handlers/quiz_views_handler_filter_quiz_qid.inc
new file mode 100644
index 0000000..96da482
--- /dev/null
+++ b/includes/views/handlers/quiz_views_handler_filter_quiz_qid.inc
@@ -0,0 +1,143 @@
+<?php
+
+/*
+ * @file
+ * // options are either a) show only the most recent version, b) show the original version.
+ */
+
+class quiz_views_handler_filter_quiz_qid extends views_handler_filter {
+
+  /**
+   * An array that the subselect will fill with all the corresponding quiz entity,
+   * according to the configuration on the argument settings form. These vids
+   * will then be the actual ones used while generating the real query.
+   *
+   * @var array
+   */
+  var $corresponding_vids = array();
+  var $group_by;
+  var $group_by_table;
+  var $secondary_group_by;
+  var $secondary_table;
+  var $secondary_table_alias;
+  var $secondary_tables = array(
+      'none'      => array(
+          'secondary_group_by' => 'nid',
+          'secondary_table'    => 'quiz_results',
+          'secondary_vid'      => 'vid',
+          'secondary_nid'      => 'nid',
+      ),
+      'results'   => array(
+          'secondary_group_by' => 'uid',
+          'secondary_table'    => 'quiz_results',
+          'secondary_vid'      => 'vid',
+          'secondary_nid'      => 'nid',
+      ),
+      'questions' => array(
+          'secondary_group_by' => 'question_nid',
+          'secondary_table'    => 'quiz_relationship',
+          'secondary_vid'      => 'quiz_vid',
+          'secondary_nid'      => 'quiz_qid',
+      ),
+  );
+
+  function construct() {
+    parent::construct();
+    $this->vid_field = !empty($this->definition['vid field']) ? $this->definition['vid field'] : 'vid';
+    if (!empty($this->definition['group by'])) {
+      $this->group_by = $this->definition['group by'];
+    }
+    if (!empty($this->definition['secondary group by'])) {
+      $this->secondary_group_by = $this->definition['secondary group by'];
+    }
+  }
+
+  function can_expose() {
+    return FALSE;
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['which_vid'] = array('default' => 'latest');
+    $options['secondary'] = array('default' => 'none');
+
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $form['which_vid'] = array(
+        '#type'          => 'select',
+        '#title'         => t('Quiz revision(s) to use'),
+        '#options'       => array(
+            'latest'  => t('Latest: most recent version of the quiz ONLY.'),
+            'initial' => t('Initial: original version of the quiz ONLY.'),
+        // 'all' => t('All: ALL versions of the quiz.')
+        ),
+        '#description'   => t('The filter will limit score results for a particular quiz entity id(s) into one or more quiz entity revision ids, depending on your selection.'),
+        '#default_value' => $this->options['which_vid'],
+    );
+
+    $tables = array('none' => t('None'));
+    $tables['results'] = t('Use Quiz Results');
+    $tables['questions'] = t('Use Quiz Questions');
+    $form['secondary'] = array(
+        '#type'          => 'radios',
+        '#title'         => t('Secondary Grouping'),
+        '#options'       => $tables,
+        '#default_value' => $this->options['secondary'],
+        '#description'   => t('If you are looking to generate a list of either quiz questions or quiz results, you must select the appropriate option from this list.'),
+    );
+  }
+
+  function value_submit($form, &$form_state) {
+    $form_state['values']['options']['secondaries'] = $this->secondary_tables[$form_state['values']['options']['secondary']];
+  }
+
+  /**
+   * Override the default behavior of query() to introduce the medial step of
+   * retrieving vids from the provided nids.
+   */
+  function query() {
+    $this->ensure_my_table();
+
+    foreach ($this->options['secondaries'] as $key => $value) {
+      if (!is_null($value)) {
+        $this->$key = $value;
+      }
+    }
+
+    $this->group_by_table = !empty($this->definition['group by table']) ?
+      $this->query->ensure_table($this->definition['group by table']) :
+      $this->table_alias;
+    //    $this->query->add_groupby("$this->group_by_table.$this->group_by");
+    $this->query->add_groupby("$this->table_alias.$this->field");
+    $this->query->add_groupby("$this->table_alias.$this->vid_field");
+
+    //    if (!empty($this->secondary_group_by) || !empty($this->options['secondary_group_by'])) {
+    if (!empty($this->secondary_group_by)) {
+      $this->secondary_table_alias = $this->query->ensure_table($this->secondary_table);
+      $this->query->add_groupby("$this->secondary_table_alias.$this->secondary_group_by");
+    }
+    $this->query->add_where(0, "$this->secondary_table_alias.$this->secondary_vid", $this->subselect());
+  }
+
+  /**
+   * Helper method to retrieve the vid(s) the final view query should actually
+   * be run against.
+   *
+   * Would be done in pre_query(), but $this->argument is not yet available at
+   * that time. So, called from set_argument().
+   */
+  function subselect() {
+    $operation = ($this->options['which_vid'] == 'initial' ? 'MIN' : 'MAX');
+    $subalias = $this->secondary_table_alias . '__subselect';
+    $subselect = "SELECT $operation($subalias.$this->secondary_vid) FROM ";
+    $subselect .= '{' . $this->secondary_table . "} $subalias WHERE ";
+    $subselect .= "$subalias.$this->secondary_group_by = $this->secondary_table_alias.$this->secondary_group_by AND ";
+    $subselect .= "$subalias.$this->secondary_nid = $this->secondary_table_alias.$this->secondary_nid";
+    return $subselect;
+  }
+
+}
diff --git a/includes/views/handlers/quiz_views_handler_relationship_quiz_nid.inc b/includes/views/handlers/quiz_views_handler_relationship_quiz_nid.inc
deleted file mode 100644
index 7ed06b6..0000000
--- a/includes/views/handlers/quiz_views_handler_relationship_quiz_nid.inc
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-/*
- * @file
- * Handles field quiz node relationship.
- */
-
-class quiz_views_handler_relationship_quiz_nid extends views_handler_relationship {
-
-}
diff --git a/includes/views/handlers/quiz_views_handler_relationship_quiz_qid.inc b/includes/views/handlers/quiz_views_handler_relationship_quiz_qid.inc
new file mode 100644
index 0000000..8fd8394
--- /dev/null
+++ b/includes/views/handlers/quiz_views_handler_relationship_quiz_qid.inc
@@ -0,0 +1,10 @@
+<?php
+
+/*
+ * @file
+ * Handles field quiz entity relationship.
+ */
+
+class quiz_views_handler_relationship_quiz_qid extends views_handler_relationship {
+
+}
diff --git a/includes/views/quiz.views.inc b/includes/views/quiz.views.inc
index 7562f56..535a37c 100644
--- a/includes/views/quiz.views.inc
+++ b/includes/views/quiz.views.inc
@@ -14,64 +14,62 @@
  */
 function quiz_views_handlers() {
   return array(
-    'info' => array(
-      'path' => QUIZ_VIEWS_DIR . '/handlers',
-    ),
-    'handlers' => array(
-      // Fields
-      'quiz_views_handler_field_takes' => array(
-        'parent' => 'views_handler_field',
+      'info'     => array('path' => QUIZ_VIEWS_DIR . '/handlers'),
+      'handlers' => array(
+          // Fields
+          'quiz_views_handler_field_takes'                => array(
+              'parent' => 'views_handler_field',
+          ),
+          'quiz_views_handler_field_node_link_take'       => array(
+              'parent' => 'views_handler_field_node_link',
+          ),
+          'quiz_views_handler_field_time'                 => array(
+              'parent' => 'views_handler_field',
+          ),
+          'quiz_views_handler_field_question_status'      => array(
+              'parent' => 'views_handler_field',
+          ),
+          'quiz_views_handler_field_user_quiz_state'      => array(
+              'parent' => 'views_handler_field',
+          ),
+          'quiz_views_handler_field_score_aggregate'      => array(
+              'parent' => 'views_handler_field_numeric',
+          ),
+          'quiz_views_handler_field_number_questions'     => array(
+              'parent' => 'views_handler_field',
+          ),
+          // Filters
+          'quiz_views_handler_filter_question_status'     => array(
+              'parent' => 'views_handler_filter_in_operator',
+          ),
+          'quiz_views_handler_filter_quiz_qid'            => array(
+              'parent' => 'views_handler_filter',
+          ),
+          'quiz_views_handler_filter_user_quiz_state'     => array(
+              'parent' => 'views_handler_filter',
+          ),
+          'quiz_views_handler_filter_user_nullable'       => array(
+              'parent' => 'views_handler_filter',
+          ),
+          // Relationships
+          'quiz_views_handler_relationship_vid_from_nid'  => array(
+              'parent' => 'views_handler_relationship',
+          ),
+          // Arguments
+          'quiz_views_handler_argument_quiz_qid'          => array(
+              'parent' => 'views_handler_argument_numeric',
+          ),
+          'quiz_views_handler_argument_user_uid_nullable' => array(
+              'parent' => 'views_handler_argument_user_uid',
+          ),
+          // Filters
+          'quiz_views_handler_filter_quiz_question_type'  => array(
+              'parent' => 'views_handler_filter_node_type',
+          ),
+          'quiz_views_handler_filter_quiz_question'       => array(
+              'parent' => 'views_handler_filter_boolean_operator',
+          ),
       ),
-      'quiz_views_handler_field_node_link_take' => array(
-        'parent' => 'views_handler_field_node_link',
-      ),
-      'quiz_views_handler_field_time' => array(
-        'parent' => 'views_handler_field',
-      ),
-      'quiz_views_handler_field_question_status' => array(
-        'parent' => 'views_handler_field',
-      ),
-      'quiz_views_handler_field_user_quiz_state' => array(
-        'parent' => 'views_handler_field',
-      ),
-      'quiz_views_handler_field_score_aggregate' => array(
-        'parent' => 'views_handler_field_numeric',
-      ),
-      'quiz_views_handler_field_number_questions' => array(
-        'parent' => 'views_handler_field',
-      ),
-      // Filters
-      'quiz_views_handler_filter_question_status' => array(
-        'parent' => 'views_handler_filter_in_operator',
-      ),
-      'quiz_views_handler_filter_quiz_nid' => array(
-        'parent' => 'views_handler_filter',
-      ),
-      'quiz_views_handler_filter_user_quiz_state' => array(
-        'parent' => 'views_handler_filter',
-      ),
-      'quiz_views_handler_filter_user_nullable' => array(
-        'parent' => 'views_handler_filter',
-      ),
-      // Relationships
-      'quiz_views_handler_relationship_vid_from_nid' => array(
-        'parent' => 'views_handler_relationship',
-      ),
-      // Arguments
-      'quiz_views_handler_argument_quiz_nid' => array(
-        'parent' => 'views_handler_argument_numeric',
-      ),
-      'quiz_views_handler_argument_user_uid_nullable' => array(
-        'parent' => 'views_handler_argument_user_uid',
-      ),
-      // Filters
-      'quiz_views_handler_filter_quiz_question_type' => array(
-        'parent' => 'views_handler_filter_node_type',
-      ),
-      'quiz_views_handler_filter_quiz_question' => array(
-        'parent' => 'views_handler_filter_boolean_operator',
-      ),
-    ),
   );
 }
 
@@ -79,81 +77,53 @@ function quiz_views_handlers() {
  * Implements hook_views_data_alter().
  */
 function quiz_views_data_alter(&$data) {
-  // Join the quiz properties table to Quiz nodes.
-  $data['quiz_node_properties']['table']['join'] = array(
-    'node' => array(
-      'left_field' => 'vid',
-      'field' => 'vid',
-      'type' => 'INNER',
-    ),
-    'node_revision' => array(
-      'left_field' => 'vid',
-      'field' => 'vid',
-      'type' => 'INNER',
-    ),
+  // Join the quiz properties table to quiz entities.
+  $data['quiz_entity_revision']['table']['join'] = array(
+      'quiz_entity' => array('left_field' => 'vid', 'field' => 'vid', 'type' => 'INNER'),
   );
 
-  $data['quiz_node_results']['table']['join'] = array(
-    // This is vid because vid is always more specific.
-    'node' => array(
-      'left_field' => 'vid',
-      'field' => 'vid',
-    ),
-    'node_revisions' => array(
-      'left_field' => 'vid',
-      'field' => 'vid',
-    ),
-    'quiz_node_properties' => array(
-      'left_field' => 'vid',
-      'field' => 'vid',
-    ),
+  $data['quiz_results']['table']['join'] = array(
+      // This is vid because vid is always more specific.
+      'node'                 => array('left_field' => 'vid', 'field' => 'vid'),
+      'node_revisions'       => array('left_field' => 'vid', 'field' => 'vid'),
+      'quiz_entity_revision' => array('left_field' => 'vid', 'field' => 'vid'),
   );
 
-  $data['quiz_node_relationship']['table']['join'] = array(
-    // This is vid because vid is always more specific.
-    'node' => array(
-      'left_field' => 'vid',
-      'field' => 'parent_vid',
-    ),
-    'node_revisions' => array(
-      'left_field' => 'vid',
-      'field' => 'parent_vid',
-    ),
-    'quiz_node_properties' => array(
-      'left_field' => 'vid',
-      'field' => 'parent_vid',
-      'type' => 'INNER',
-    ),
+  $data['quiz_relationship']['table']['join'] = array(
+      // This is vid because vid is always more specific.
+      'node'                 => array('left_field' => 'vid', 'field' => 'quiz_vid'),
+      'node_revisions'       => array('left_field' => 'vid', 'field' => 'quiz_vid'),
+      'quiz_entity_revision' => array('left_field' => 'vid', 'field' => 'quiz_vid', 'type' => 'INNER'),
   );
 
-  $data['quiz_node_results_answers']['table']['join'] = array(
-    'quiz_node_properties' => array(
-      'left_table' => 'quiz_node_relationship',
-      'left_field' => 'child_nid',
-      'field' => 'question_nid',
-      'extra' => 'quiz_node_results_answers.result_id = quiz_node_results.result_id',
-    ),
+  $data['quiz_results_answers']['table']['join'] = array(
+      'quiz_entity_revision' => array(
+          'left_table' => 'quiz_relationship',
+          'left_field' => 'question_nid',
+          'field'      => 'question_nid',
+          'extra'      => 'quiz_results_answers.result_id = quiz_results.result_id',
+      ),
   );
 
   // Filter for the node being a quiz question.
   $data['node']['quiz_question_type'] = array(
-    'title' => t('Question type'),
-    'help' => t('Filter by quiz question type.'),
-    'real field' => 'type',
-    'filter' => array(
-      'label' => 'Quiz question type',
-      'handler' => 'quiz_views_handler_filter_quiz_question_type',
-    ),
+      'title'      => t('Question type'),
+      'help'       => t('Filter by quiz question type.'),
+      'real field' => 'type',
+      'filter'     => array(
+          'label'   => 'Quiz question type',
+          'handler' => 'quiz_views_handler_filter_quiz_question_type',
+      ),
   );
 
   // Filter for the node being a quiz question.
   $data['node']['quiz_question'] = array(
-    'title' => t('Quiz question'),
-    'help' => t('Filter by quiz question.'),
-    'real field' => 'type',
-    'filter' => array(
-      'label' => 'Quiz question',
-      'handler' => 'quiz_views_handler_filter_quiz_question',
-    ),
+      'title'      => t('Quiz question'),
+      'help'       => t('Filter by quiz question.'),
+      'real field' => 'type',
+      'filter'     => array(
+          'label'   => 'Quiz question',
+          'handler' => 'quiz_views_handler_filter_quiz_question',
+      ),
   );
 }
diff --git a/js/quiz.auto-title.js b/js/quiz.auto-title.js
deleted file mode 100644
index bc1df73..0000000
--- a/js/quiz.auto-title.js
+++ /dev/null
@@ -1,31 +0,0 @@
-(function ($, Drupal) {
-  var maxLength = Drupal.settings.quiz_max_length;
-
-  function quizStripTags(str) {
-    return str.replace(/<\/?[^>]+>/gi, '');
-  }
-
-  function quizUpdateTitle() {
-    var body = $("#edit-body textarea:eq(1)").val();
-    if (quizStripTags(body).length > maxLength) {
-      $("#edit-title").val(quizStripTags(body).substring(0, maxLength - 3) + "...");
-    }
-    else {
-      $("#edit-title").val(quizStripTags(body).substring(0, maxLength));
-    }
-  }
-
-  $(document).ready(function () {
-    $("#edit-body textarea").keyup(quizUpdateTitle);
-
-    // Do not use auto title if a title already has been set
-    if ($("#edit-title").val().length > 0) {
-      $("#edit-body textarea:eq(1)").unbind("keyup", quizUpdateTitle);
-    }
-
-    $("#edit-title").keyup(function () {
-      $("#edit-body textarea:eq(1)").unbind("keyup", quizUpdateTitle);
-    });
-  });
-
-})(jQuery, Drupal);
diff --git a/js/quiz.jumper.js b/js/quiz.jumper.js
deleted file mode 100644
index b6c461d..0000000
--- a/js/quiz.jumper.js
+++ /dev/null
@@ -1,10 +0,0 @@
-(function ($) {
-  Drupal.behaviors.quizJumper = {
-    attach: function(context, settings) {
-      $("#quiz-jumper-form:not(.quizJumper-processed)", context).show().addClass("quizJumper-processed").change(function(){
-        $("#quiz-jumper-form #edit-submit").trigger("click");
-      });
-      $("#quiz-jumper-form-no-js:not(.quizJumper-processed)").hide().addClass("quizJumper-processed");
-    }
-  };
-})(jQuery);
diff --git a/misc/images/arrow-right_808080_64.png b/misc/images/arrow-right_808080_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..f410b1d29a3abe2c7ac1c57f3025b747710637b2
GIT binary patch
literal 906
zcmV;519kj~P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0009~Nkl<Zc%1Fp
zO^B6M9LMpGR=CO*sZ(Q-lx0qDL~WE7Woki-406$`U4gi0TWtnf1Z`Tlh$skLNwg6`
z3d*8|uW6R16(u1qMw_O|HbTj?INrtNoO|cq^LAzY!LzyNh2QV{{O>)_IrmvKK0a<C
zYjJA>t#uthYXhxS8^BiV$2893eT-K2FiSN7yo-;q4!7V=yn<tRq@s^mss&&NUW>Gi
z;B!1&$;&L&0I&nwd-Pd`FYr)#JF`_9z*~BU?`RqJsSKecfP3(IpJS?5LP-G6;)Vgn
zEf*j>nAgtSDhXg|!r0})*WrOoUZN@q;HSj#RUniEuou52&apy(FqZ5lqLKhE;RWHT
zFGVZxr9u!&FN4Ri8Gj|t)2W2DsfPZq8V^E8ut_?E5up;YLa6m9d_<l~$PS_AX6Ued
z5cc8zWJl_6hY#Z!TuhwjHlY&Mq#9~*AaY2kgrR`Y>O|;}Je6>}0O7ttCun&jcF+(&
zXniJlP}ns6ojA{^0U@*o00)Fh$OYlveg<^|FoKUTiJ8LJe%#!T@rXw8l|CTc82NlD
zzLxOg(omgBcoM(#>Kv<pH(Z<HJ6b7J!s=cfV*p;v>86oZ3YBnow@xttcV)NJMt5Kj
zZi;n?0f_xJT#FjR^RdQZSO#n8@mRw`0T9(f0q{+%AqL>zLPlEXG<L@tV*t*U@lstA
z*n)pz9by1JDdDEFCh!z~?$#*=;BEYx+fhZG#|He^t78nnG@igtOl7lGN9Tn*e|kQh
zyR8Bfcp0~1QQ>R7^zATQ5bpfE!ZRGp;5vLJeLH+Xxbvs_8Ppm;&hQ)b8J@NPa)tk0
z2>-(%gIfU@itw}nkURXzlu2p>$Q6DPPfH9>T>!bmpGbD39)R58zfX2_MKu8A3jc@9
z;VBQ`F+&`lvH+F{yUJYQze{#Bhe`r?4yzL9n8HSd!c!JN>dOnA@W<1w#8nbN%BwJc
z3gN%a<Rz+-06xX#0mfAePgwwG@J^p&I^mDyw==g&FN1I5gL(F?9-i_5X7H-s5T=Ef
zDXWI3&XZsV!h4bSNg@2v3SO>Kt-Zhuw&5k=m6(e-hY#_nF8|z=R_%X*cU`%LMy<~T
go9jA&)&^SZKUP$-sHDN@fdBvi07*qoM6N<$g2G{|NdN!<

literal 0
HcmV?d00001

diff --git a/misc/images/check_008000_64.png b/misc/images/check_008000_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..408fd2b7805513c83a7e50428b47143d302dedd0
GIT binary patch
literal 745
zcmV<F0v7#=P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00088Nkl<Zc%1Fp
zJFg8<7>DuSL2^VyqtK9O6hfk>Kon>+f<mS60nVjxIqtW@<pYRHP^d^GqL3g_AtF?q
zM1$^BDv3g|&qOk2?>(0_vu2$6mut<;dY@+(YxcWl!g)(N%VHN0JH3F|1;kD-Aa((<
z(+h}QK<xAaViyoQ|BHZqc!95Yj4gR@{2v6&;wg4wBM#uLft0c7x`0_+&lq2cM{-E$
zvVdXmNhalx&{YBB;FFA%&_w}x;gcj1_GV3XO~8y|@JS}|5F64HT@o;Z8x`(fgX8JJ
zt_Y|KpJaV{pbG**!NdFXK-&d`g#U!w>A^M&2nqk$U>Eq6o@lFpX<^}a<44wH5dk~!
z8b9$7M=O_>ooU<*^sJHa<QH%RFR>k~u@(1ltzv=cGzw2X0f%uP%QNOq;A-L0veP&`
z;{}X@PjV7ha+j8!DDaFHaL53DMULm3!sR>*W+Wv17X{!MF5n;@$OoU~G%gjm9;Sqa
z-=zRN*#b7<u43>>=H!<!g<B=o$Vw@AvIQK&stU!-;o_KUS^-bCfOS=>S<D$+7&6xy
zc(Mh&Df_Tg&hpRK2d#o9TfiORB%JeCLhImRV#pUFYw;Z0sxGjYXNI(-N+j9pzK3|&
zux)Swd+;H|B3cU1=og{T5~?ORJmX)5+ajSBc=G)jiY=iwc=G=pjwPX1c#1VdnwBuH
zW_XIX#F~;YZ?MnT3QxJ7VBHcLgQt8`v@Qva!c(m)99lx-@KkS$hmsHlo-pSERV74&
zC)~kESqY77b)ypUWT>cw1pZsV8VV=$(O6yyt$?SVv%zr^S_4mghogyvSE=!LS_DtI
z)_2}47{KnwW`ke;34YhOX5kELi7lD5_4|Ov(hG=PK<xAaViyoQy@1#S#7-|Db^)=o
b<OTcz&tRem>ZYiz00000NkvXXu0mjf`)gKX

literal 0
HcmV?d00001

diff --git a/misc/images/check_808080_64.png b/misc/images/check_808080_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..aee7c23a4c048ace00c49a33a519db9ed26ff57c
GIT binary patch
literal 750
zcmV<K0ulX*P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0008DNkl<Zc%1Fp
zJ&z4h9LMpGhh#-Wt0B=SgjYa;DA-0LC{zkBU>^z(%U;ighZi6!L7^fclt@&F2o;-X
z&~2rXh$!w(B$L^D=i$tp89Tq^Hs{Qo`G3E+ICKB!t{MymR&rLyE+BS#0kI2+onAof
z0%E5Z5W9fb=>^0tAa?#20SEB{U-1|_^4|DA2$;n)?7>zX!dpWrW7Bm3v$&oyz7CJ%
zkkDlT%fTm^kV8UO1&o7FGFCzt1>}WKl1Mm^HQ6-*Gm60{nZQGANl$c1zzl9wxPK!~
zrU$zspelTl&FO(I2nYoa@6!Wq7Z4Ku6K<ym+bkd?{Aa^m;8%L0tpcWnh2MuCS(8Ns
z?8a;S#77*jTv~RfaWl|bBjL#};22(F7dBug?%`U+0@G;}o_qq1;y%`7%$>s3!lh-W
zad^fH7zLl?G_K?>Ejv-*87<(5A^h4L>zu*mJP*uBNcb-bz_Yx7!+0Pce3G-cRN#7;
z5*B{10`O!D*oM1`!6%uMU&0h_m3T&0O2LyY-~`rJC}s{9$6V72c(Mg-s#48T&f&r`
zbFG0VTfm#Li=}cNgVb27;K>$nM>q-d{z+&ZJlO(%V-g>#F20m`JQo(e&{j7p%WZ>;
z*e{%f%zj*nB=Fw@tvB*TD71vC2@cQrSK+ous0E&UKZasUs12U{KZj#Us1=@K4UwiL
zET|ct;w`bJBrF*2^R>cLt|wTxgvQ`0-xRG&LZk3h>k5aK&^SER+v1@lM1d#Fxj<D3
z(clSpFj7`RBU|05gghB4Dj|XY7O;lG34Jt{S3)b`spo8PoP^fEQ{UleBH>kP{GArT
zQ?B)$H;aa_lh|(f%|9{hIJG8q3V=U&y7Xyf+WP;1#?lLjT|n&g0%8{sJH3F|1;kD-
gAa((<v*HE(1s!juQT{3=>i_@%07*qoM6N<$f^rH@)c^nh

literal 0
HcmV?d00001

diff --git a/misc/images/check_ffff00_64.png b/misc/images/check_ffff00_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc17f6f6b788297ab847947bf6c97d4e235761ad
GIT binary patch
literal 658
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV2bo~aSW-L^LEzWEYUy_xA3Nf
z^vUPuUvko^X)x((-6)ovsCy&p6T`n_$HG4-vT#c~vPe6Ithv(AJ55Y!QNn}G9Lz?~
z?o^+fyElFQ<TLEMC1+SxudRO0_x(-eB#R4?eR&F2lN(;JEi&fpKxO`RJs|Z#<_C`r
zn_v2TP1E?<La(mscW#~YdGYM;4ewu-_qAj&=>N#!yeKg5L!{6m$2|}KPc2Ba=eQBU
zuy6HyA@yI^b6y(mV6>4b_$T-v>VJYZQ`@0(%X{`0ni#Yz8qEL8u6(fX+pg!ozs5Z(
z@V0N7_Ca2y@7-2Elay6?eT?y1389ac9^rpmH+S7OzIdevT}RHzaoc1_+JzseV%oQQ
zZP>$NmNnjgPDXsF>;CYWCFWeq`&V(SALP9sq;u{F;kbYGuG0_ci61l>)m7{GGE~By
zENZ+COmlQDk81GetVp?X<L#}7!A$F<%_>%>{g~3=EVN!mC4_yxP{Wa~BhiQ3=1U!x
z-`p@g!PDzsc^u<B#`K<l-O*2k3;2Fyl^%?gR=VHt`GEE6qin}y<et6fxT<uJRqoTa
zBa2jb9CZFSFJTHxc!kDo5!vWNX6HTx?9;ZH){^@`=iG-4H@5BA-g5Cl;>Vp0(^~=`
zCf-Om5_Ras2ZI`;6jenQxg57-XVsR2&3AJO0y!7kyh=M-WD%vcu=#(|4d=o&%Mb4q
zTGhZl|KkrE&Z`HS@Be0;+akXA@%oh<a%uS;5h;>8IOZgMpIp!MX2-e+iG0SZ%Ui=!
kVld(r9NWbYSpTr{{5>mm$>Z-GU|M7FboFyt=akR{0P1KiA^-pY

literal 0
HcmV?d00001

diff --git a/misc/images/circle-o_808080_64.png b/misc/images/circle-o_808080_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..c659d2e2edfb649a53937fb0ca06ce9f3e220429
GIT binary patch
literal 1419
zcmV;61$6p}P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF000G2Nkl<Zc%1E<
zU2GLa6vuzpw0%&N+gIBn&jMOnDj$B4DB4yZjWr=LJZb#G#1|D6izY;PF`_(Z6w$<o
z5>1SWZLQH5`~am~Q>(s$wEEgBG??5r=);-nO`P3xc6WBSN%<uk%FgV}IsZE|J9B2X
zGCVxYO6H2P4Jez-0+ek)*|Zc;M<>whu8r>hRd=ld=7GAqW`UZ!CV}7cHZCs#)&lnf
zLqLy`x9To{Zs6Xgw9~){a18h@pnd-mx(jRuMu9WH0d(gzdVzzkyib9?jOG20Py%cO
z#(|T-K%g?4sQ`n(4~P&qhAKF#0Ik5wh+%hUH^;;vGCu5c^=BfB0PCG0&r{4`+Dr?u
z7ZHA4mV(j>umkuGxFJjB0qAyi+>y3`Gy?2G`r*}SD-Fywz!>mgnun7%-Ce-DDe9>s
zZ-+_b)i&eqT|k7a0$s?c--q<`)d|XP1>SRizn{dgBm(RJ-bzw9FaaD!-Vlw5Jag_k
z0~~kv+JJswH_4j!+sNeU3+n^cF0c;x5c{b6HLwkNGkij{^HDV1b9Vx_ApP7zEASC;
zy|p5X0Ik3WNMF;q2s{bg4xF$Wq3|tmEASNXmm&XZ#9q^nWf9;7Y!i=JU<+^v_}fx>
z0xkis0$Z`I54w@pvx+7_H}I^XoL>;<r!AFcV9M3=t0C_m3_qMEz<%_Bau(_9xv$)s
z>gxyoQ1Z1P_Qw(@z)k4$`isabt0?D}m~r)LW5OWH0xC{`7Y&9yL;lS^iYeeZL%!j-
z#3%u-L)44oIPiwSfIN8({S`TY_~Ru?fFVQWPZNH$Ps$}{M;sL-IO8QsfCrR9KSS33
z%YkpuXMU0X_yp)g0vvI?uA~>uAtk*B=<p@@1lXh$HiyjdmlNZ_pGqFDUp@h}INm6*
zU@)pk8psko4zFKn1UO}}s01dJbh7}RN<lS~ImL5YN%#8Y6QHUTbk1T;3Ct+zRo~4t
z0@QU|N};Z#SA92q0<2OBnl~6z1{aicuU|d^GzT?-%0dg$dj0YVP*)178jLA}s*+y!
z-Ap6E8r_ys=u*<_zMDP)W|e}nZg}QJuafTd%O}8(N<qPQXak{5;k|zO1kffFg9O-=
z3``q({KD&(PXKL7*^X?IEGPQW2SKl2QDcDC4IbBRDwM~R^coRy%cy|Jh*IDH5;R>7
zYy<96@<jTRRsfJAFcm|-JgGQ4G>%0jL<w*f2{Od79eBi0d7kV>_73A1L;Ud)7Z%@V
zFyJ5=PiSOd1BQ(c6hGnwIEe&5<G2b@_*#P@A?ZLOQ&%ea#)0qSGH6?4dw@kHPZuIo
zQ6hA>`n15!B4U3mngBIygUotlJ3R01umNeAmU>=6f1R7Q$6vy>{j~;ZZjTcF?N9-p
zK%Ccpd4Qh~yHzX#G=QDx0SAyV;V|$e;pPcjfUl6)zt-mg&Lj4kBG=X<GSk3)=;Of>
z+mY5VO1iZ}8*mRYh@Aj#Gd{O~*!i=yq9li;CV+>5cM?1^;I27Dj8n+Cat>La&m$40
zD$<2|`0tchdxCnFcnJ7B$pcA`$sKY3zM1gZcBEPEOjw_l1;qKINgqseQ1S@UJS}5F
z5u8WtIhy9-v`1wpfSW1raKsW*$YLl_vu`HLVd-fk3bP;k;24TU<dk|JazHCJS&z#%
zkYg&F(WlL!7(;~LOMp$<P#;Xxkf_X7^i^b9MiHS0k)|Jn(C7S@z8G1H9J3ihKWq_4
z4T(*RkkdCqd41?`7Fk0aK&GynNcYv47NXBg5np=Dy6fWt>PpOt|8VlM0A(9cHY;9$
Ze*mx#^MUl7b*TUV002ovPDHLkV1is@lgj`A

literal 0
HcmV?d00001

diff --git a/misc/images/circle_808080_64.png b/misc/images/circle_808080_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..576483f27f299ca6414b3c2fd9a50af7babc94b1
GIT binary patch
literal 937
zcmV;a16KTrP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF000AUNkl<Zc%1E<
z&r1|x7{@<`d$ZbmLY^~f{Z&-QkaFu783bVj^*3Z?Yk_2cLWEh+K`H2xn%%=fH_AF^
zV0ST3>Y>9k#X|4Qyfg2wb$37Tg3iqQzTfA&^S(3l&NFS5N`+=-UuXfLX&De&Kxo=M
z^wA0QMWrYDzZjJcU<=rY$~sVu$};fFwK!J>bOD!vVW2nV*kV)wO2EaMeJj8WFbn*&
zsP7-Phrmf-4)_5~k{)~-eZb?Wy|=)CVSE2!%YYtW0ayZtEVWrn8&C#5Ax0dx)v&1n
z1>hkf?3}qAEy~FJFdp@9goy!1BO!P3Gnv-X4vZnjcbhbn*MLFb18~fwa|@Ir9cS}4
zkjH=#WE}3#+o@#^0Q11LJe9Max+B1|JRLcv06dTWevzeOmNQ@wc#@^@06Zn*aL_fN
z8+e6<S=TWI;5BeWw}OrV1>hwz*7gDi5xrVJmW~1Uuz2I)i4t;oP9@EN5^&p4OejXt
z_~FtFn85!{jxE{|{fQ(QZ~`~?^*$++@&J`&z<s+~f>BA@8E3#@#Jxsf2#H5YoB_jb
zv;?6IS)5T4XTTLVT7nT9kIH~fWPziR=mid`cBl;KcdI2B%D7Ypn6Bg1E6TW91}L_5
zIu;TNWn3x)if(lTqo~^0GC;Ad!>x{BDC1HYu;o}tD3o!j4A^k1BN!X1jV%Kd+t%Ic
z2!=8)l>wg}3kiiXE|mc*j)jCm8JEg{WyeB7p^PhT4rsJtP$eeaCT>Aw#+{ZR#KzMs
z2F%9oh%;afNqK1m=8<@mBrS`NTbB}s#5j@+SOVVJ)#9B6;A7GOX|Kjcfn95I{IZMa
zPbAHNDw6NF7kPxn<y`0W_yb_oSdL%5BDz!2F<=|G3j8)v<dIE8?~cyFx+yZNz$NN`
zKG;EYe$%axB}r-#xRq6rXKn)TvsB2MCpZP%p#IK;9pE}JowZ_~M9C@O0`)T|Y$AH5
z^Hk28D!T}rA^g?h3vil@r+qCZ$<nLH3&#ok4~bnQOML+NZqkrxy8Je92l-IK*KPAW
zV*D7Im%1cueK1i4&I6}#_Y^sY7+pqAeJj{bf&2T#NEec3GmKfXMHN|_n8DFEL#}cF
z*N|t3N#w0;Kl#2IbG}6HBMEXnDsMdWm6@jhaI$4UXaS*VdISCd*At6#GU2`#00000
LNkvXXu0mjf-6^U2

literal 0
HcmV?d00001

diff --git a/misc/images/times_808080_64.png b/misc/images/times_808080_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..59fb9e9b6837cb0fa6f435e421d4dc9159809b09
GIT binary patch
literal 869
zcmV-r1DgDaP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF0009lNkl<Zc%1Fp
zzl+>O7{~FK@dsk3h@=o3L1Mrn|H6@gO;EHD4Qec68?caoq9aPW)Itb|r#7~t5X9Kn
z2sUDoU}sAI00{&Vi|-BW<#L<vGxO}u!R;HC<!<LY`+VN)E;G+}9xA0c%p4hAz-TrC
zMi(%ejeyYwjAkQXbOEE;2pC<!XdX$L=M>K3Dg1^@xRo^bB5&e#Jcg_I5_ftg%n<Mi
zK3n+vWxRot_%~sGrcde>ytnYb(|851;=i7Wdnk5?ue^j`@l5CZOf`OGclgRF{D3EV
zCU2$S(<QGT*Ds-le`m><EgceC1r*D}S9&DO314ZJ&?2Ci7rxRWArijQBq1u`ZL#o`
zh=i8#l`UM3To4uTMR5Egb0qYHubjb)a|cHRJdWqW<?LpegpBZ&7v>I(2)Kot@mlt9
zKM9%P;qSQvqhEoKgX8yOQ9^e3?{IzY;OI*58$7S=hwxsigg;G7+zhVa{m2C^OX0Hf
zoF(CS!aPjjPYW)Kdlo|XTLO=wDLnm4;e#NdIXtV|46Tc<@Z>Z^iZK$p!;{w$D%L9@
zB|Nz;v0^<E(!!J96D(FMAvHX!X^IxBk&qsq)pdnG$6tx_-Nre|;aQD<hw-(kFPO!n
z_z_1F=S04M8vac3yl~94cSx0xDPWc1E9nxl1gs-`B~?O(fOUqiq)F%%Fd}><MM95&
zk>M-d5?Tcugz%Lv2`vH+Qus=<gs6Zca>HN4#iY5KBt(0HAIc41*Y>yN^catsy22-M
zcg_XTPrwJ!iuU2&jZKlaf!e}}xr3v3hMo?WvzPsLbw!?pXXg%#-Wj?PuVohx+R_&N
z67HDpHOwOV6*#Z`OwQc>e9<T268@PxI4a<0(@~YEglIFPXp^vw3y}+2)&bjM5?VVs
zMNC4pF0`)%=e}8Q8>^Tnp(#9ldw~bNX;u%fST3PCJpH@DZM<vmp~#=)HZTi5vNz;q
z9pTA2DETe^z*~6IbW-$tGgo2(F5(xwX6g}M-FbA8byW6->BQ)&`F-b+#-Tqfy%8|F
vfYEFOj4og_8v&yW7|lk&=mJJ_=mq=_UP!MYYFtXe00000NkvXXu0mjf=u@59

literal 0
HcmV?d00001

diff --git a/misc/images/times_ff0000_64.png b/misc/images/times_ff0000_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..2d7367ec6f94180480b2839e180ecf1c4283002f
GIT binary patch
literal 641
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVDj*EaSW-L^Y+$8ub4oA_K)X%
zd6K4!B&jdyTHEWNkgX8LrflctA((TC<C>zcgFyEeO`aP+SsGPJqv9PnUo45xXNkOS
zoBnTeasD^HIp5yY*o&3#eKW`Kw(*HLh3cw?H>{hMv!gNPy5HSo_|}kqz_NfNrt{su
z@@<W}4>$|BYFP4O%xC@IW4^)q`R;?09=v<T!(-Q9F*l#Bd*1H7-EaFNR$1D+a4<J7
zHe6r)%(0^@T+v44y<11mcO@UG_f8#s>s>W^*Sl%-)(3A4(R9kKDehBdtg{xI&2mus
z{+z2E4Xg)~9t(tgO#85D?fZt`0#iOFzGrxUAIQ4$abZ34hF$)Rg&gXKejl=DRT8-L
z$d)13dB!jEgJz0L{_0;~V+r5GrNDhqc0J1svnG`|UISg0$Ql+0lO_{8E(I<}IeE?x
zC)&=lhJLK)ndkmSO0-_)1K*F-4R^{KR{St%(L36%P@)^xcj0J-rc?OiRF2rb=?W#b
z_j*6rAK5M7^_cz9594P3nH)<Q@{fKzTmHND<KE&x264uZX9JbixH9NS++jJoTF`9q
zJw|)x$Dth8*|Y;0q_^t}h^}|A(8$zcJ&>@SY2mX(H{L@NdF@v^F?tEw_!oHzbUn6Z
zn5?8Bm+adVAo`xQg2VAAPri&(=^iGAX4&S`*50+E$IPGK<#^*iuj27H>B$Lk4C@=0
z^VDnpTFNMP-s%I_jPxJg7lYroYBiiYcjkY@F7&tr$8h>H#(zvUug_>}^>=LtCM^a}
LS3j3^P6<r_p&|{J

literal 0
HcmV?d00001

diff --git a/misc/js/quiz.admin.categorized-form.js b/misc/js/quiz.admin.categorized-form.js
new file mode 100644
index 0000000..39f65c6
--- /dev/null
+++ b/misc/js/quiz.admin.categorized-form.js
@@ -0,0 +1,20 @@
+(function ($, Drupal) {
+
+  Drupal.behaviors.quiz_categorized = {
+    attach: function (context) {
+      $('#browse-for-term:not(.quiz-processed)', context)
+        .addClass('quiz-processed')
+        .click(function (event) {
+          event.preventDefault();
+          $('#edit-term').focus().val('*').trigger('keyup');
+        });
+
+      $('#edit-term', context)
+        .click(function () {
+          if ('*' === $(this).val()) {
+            $(this).val('');
+          }
+        });
+    }};
+
+}(jQuery, Drupal));
diff --git a/misc/js/quiz.answering.confirm.js b/misc/js/quiz.answering.confirm.js
new file mode 100644
index 0000000..5bcb191
--- /dev/null
+++ b/misc/js/quiz.answering.confirm.js
@@ -0,0 +1,16 @@
+(function ($, Drupal) {
+
+  Drupal.behaviors.quizAnswerConfirm = {
+    attach: function (context) {
+      $('form.quiz-answer-confirm #edit-submit', context).once(function () {
+        var message = $(this).data('confirm-message');
+
+        // Return false to avoid submitting if user aborts
+        $(this).submit(function () {
+          return confirm(message);
+        });
+      });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/misc/js/quiz.auto-title.js b/misc/js/quiz.auto-title.js
new file mode 100644
index 0000000..bc1df73
--- /dev/null
+++ b/misc/js/quiz.auto-title.js
@@ -0,0 +1,31 @@
+(function ($, Drupal) {
+  var maxLength = Drupal.settings.quiz_max_length;
+
+  function quizStripTags(str) {
+    return str.replace(/<\/?[^>]+>/gi, '');
+  }
+
+  function quizUpdateTitle() {
+    var body = $("#edit-body textarea:eq(1)").val();
+    if (quizStripTags(body).length > maxLength) {
+      $("#edit-title").val(quizStripTags(body).substring(0, maxLength - 3) + "...");
+    }
+    else {
+      $("#edit-title").val(quizStripTags(body).substring(0, maxLength));
+    }
+  }
+
+  $(document).ready(function () {
+    $("#edit-body textarea").keyup(quizUpdateTitle);
+
+    // Do not use auto title if a title already has been set
+    if ($("#edit-title").val().length > 0) {
+      $("#edit-body textarea:eq(1)").unbind("keyup", quizUpdateTitle);
+    }
+
+    $("#edit-title").keyup(function () {
+      $("#edit-body textarea:eq(1)").unbind("keyup", quizUpdateTitle);
+    });
+  });
+
+})(jQuery, Drupal);
diff --git a/misc/js/quiz.entity-form.js b/misc/js/quiz.entity-form.js
new file mode 100644
index 0000000..418a3e5
--- /dev/null
+++ b/misc/js/quiz.entity-form.js
@@ -0,0 +1,32 @@
+(function ($, Drupal) {
+
+  /**
+   * JS enabling one filter fieldset to control filter formats for all textareas
+   * in alternatives.
+   */
+  Drupal.behaviors.quizFormBehavior = {
+    attach: function (context) {
+      $('.quiz-filter:first :radio', context).click(function () {
+        $('.quiz-filter:not(:first) :radio[value=' + $(this).val() + ']').click();
+        $('.quiz-filter:not(:first) :radio[value=' + $(this).val() + ']').change();
+      });
+
+      var defaultValue = $('.quiz-filter:first :radio[checked=1]').val();
+      $('.quiz-filter:not(:first):not(.quizFormBehavior-processed) :radio[value=' + defaultValue + ']', context)
+              .click()
+              .change()
+              .addClass('quizFormBehavior-processed');
+
+      $('.quiz-filter:not(:first)')
+              .hide()
+              .addClass('quizStayHidden');
+
+      var oldToggle = Drupal.toggleFieldset;
+      Drupal.toggleFieldset = function (context) {
+        oldToggle(context);
+        $('.quizStayHidden').hide();
+      };
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/misc/js/quiz.jumper.js b/misc/js/quiz.jumper.js
new file mode 100644
index 0000000..1fc74e0
--- /dev/null
+++ b/misc/js/quiz.jumper.js
@@ -0,0 +1,17 @@
+(function ($, Drupal) {
+
+  Drupal.behaviors.quizJumper = {
+    attach: function (context) {
+      $("#edit-question-number:not(.quizJumper-processed)", context)
+        .addClass("quizJumper-processed")
+        .change(function () {
+          $(this).parents('form:eq(0)').trigger('submit');
+        });
+
+      $("#quiz-jumper-no-js:not(.quizJumper-processed)")
+        .hide()
+        .addClass("quizJumper-processed");
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/misc/js/quiz.take.count-down.js b/misc/js/quiz.take.count-down.js
new file mode 100644
index 0000000..26a9e51
--- /dev/null
+++ b/misc/js/quiz.take.count-down.js
@@ -0,0 +1,26 @@
+var quiz_take_finished;
+
+(function (window, $, Drupal) {
+  var button_1 = Drupal.settings.quiz_button_1;
+  var button_2 = Drupal.settings.quiz_button_2;
+
+  quiz_take_finished = function () {
+    // Find all buttons with a name of 'op'.
+    var buttons = $('input[type=submit][name=op], button[type=submit][name=op]');
+
+    // Filter out the ones that don't have the right op value.
+    buttons = buttons.filter(function () {
+      return (this.value === button_1) || (this.value === button_2);
+    });
+
+    if (buttons.length === 1) {
+      // Since only one button was found, this must be it.
+      buttons.click();
+    }
+    else {
+      // Zero, or more than one buttons were found; fall back on a page refresh.
+      window.location = window.location.href;
+    }
+  };
+
+})(window, jQuery, Drupal);
diff --git a/misc/js/quiz_take.js b/misc/js/quiz_take.js
new file mode 100644
index 0000000..3548745
--- /dev/null
+++ b/misc/js/quiz_take.js
@@ -0,0 +1,19 @@
+(function ($) {
+  Drupal.behaviors.quizQuestionBrowserBehavior = {
+    attach: function(context, settings) {
+      //  $('div.checkbox div.form-item').hide();
+      var $cell = $('div.mark-doubtful');
+      var $checkbox = $(':checkbox', $cell);
+      var $switch = $('.toggle', $cell);
+      if ($checkbox.is(':checked')) {
+	$switch.toggleClass('off');
+      }
+      $switch.click(function() {
+	$checkbox.click();
+      });
+      $checkbox.click(function() {
+	$switch.toggleClass('off');
+      });
+    }
+  };
+})(jQuery);
diff --git a/misc/quiz.css b/misc/quiz.css
new file mode 100644
index 0000000..06ccc3d
--- /dev/null
+++ b/misc/quiz.css
@@ -0,0 +1,356 @@
+/**
+ * Definitions that apply while viewing questions
+ */
+.question-type-name {
+  font-style: italic;
+}
+
+/**
+ * Definitions that apply while taking the quiz.
+ */
+#quiz-progress {
+  font-style: italic;
+  font-size: 80%;
+}
+
+#quiz-score-possible, #quiz-score-percent {
+  font-weight: bold;
+}
+
+td.quiz-summary-qcell {
+  vertical-align: top;
+  padding: 1em 1em 0em 0em;
+}
+
+td.quiz-summary-qcell table tr {
+  background: transparent;
+}
+
+td.quiz-summary-qcell table td {
+  vertical-align: top;
+  padding: .5em;
+}
+
+.quiz-answer-feedback {
+  font-style: italic;
+}
+
+.quiz-summary-text {
+}
+
+.feedback-icon {
+  display: inline;
+}
+
+.add-questions {
+  background: transparent url(images/add.png) no-repeat scroll 0% 10%;
+  padding: 0 0 1em 2em;
+}
+
+.hidden-question {
+  display: none;
+}
+
+.quiz-question-browser-row.odd {
+  background-color: #e7e7e7;
+}
+
+.quiz-question-browser-row.even {
+  background-color: #fbfbfb;
+}
+
+#quiz-question-browser-filters {
+  /*background-color: black;*/
+  /* Black bg in seven theme looks horrible. */
+  background: none repeat scroll 0 0 #E1E2DC;
+  border-color: #BEBFB9;
+  border-style: solid;
+  border-width: 1px;
+  font-weight: normal;
+  padding: 3px 10px;
+  text-transform: uppercase;
+}
+
+#quiz-question-browser-filters td.active {
+  background-color: #666;
+}
+
+.quiz-question-browser-row.odd td.active {
+  background-color: #e2e2f9;
+}
+
+.quiz-question-browser-row.even td.active {
+  background-color: #f8f8ff;
+}
+
+tr.selected.quiz-question-browser-row td, tr.selected.quiz-question-browser-row td.active {
+  background: none repeat scroll 0 0 #505050;
+  color: #FFF;
+}
+
+.browser-table th {
+  border-bottom: 0px solid #666666;
+}
+
+.q-staying {
+  display: inline;
+  float: left;
+  margin-right: 8px;
+}
+
+
+/*
+ * Definitions that apply on the quiz report pages.
+ */
+
+div.quiz-report {
+  margin: 0;
+  padding: 0;
+}
+
+div.quiz-report .dt {
+  background: #eee;
+  border-top: solid 1px gray;
+  padding-left: 5px;
+}
+
+div.quiz-report .dt div.quiz-report-score-container {
+  border-right: solid 1px gray;
+  border-left: solid 1px gray;
+  border-bottom: solid 1px gray;
+  float: right;
+  padding: 4px 6px;
+}
+
+div.quiz-report .dt div.quiz-report-score-container span div.form-item {
+  display: inline;
+}
+
+div.quiz-score-submit {
+  float: right;
+}
+
+.quiz-report-skipped {
+  float: right;
+}
+
+.q-correct {
+  background-color: #DDFFDD;
+}
+
+.q-wrong {
+  background-color: #FFCCCC;
+}
+
+.q-waiting {
+  background-color: #FFFFDD;
+}
+
+input#edit-submit-hidden {
+  height: 0;
+  width: 0;
+  overflow: hidden;
+  padding: 0;
+  border: none;
+}
+
+.quiz-passed {
+  color: #00dd00;
+}
+
+.quiz-failed {
+  color: #dd0000;
+}
+
+#no-questions {
+  font-style: italic;
+}
+#quiz-view-table {
+  width: auto;
+  border-collapse: separate;
+  border: thin solid #555555;
+  padding: 0px;
+  margin: 0px;
+  margin-bottom: 20px;
+}
+#quiz-start-quiz-button-form div {
+  padding: 0px;
+  margin: 0px;
+}
+
+#quiz-start-quiz-button-form input {
+  margin: 0px;
+}
+
+#quiz-view-table td {
+  padding: 0px 3px 0px 3px;
+  margin: 0px;
+  line-height: 110%;
+}
+.quiz-view-table-title {
+
+}
+.quiz-view-table-data {
+
+}
+
+#browser-pager .pager-last {
+  display: none;
+}
+#browser-pager .pager-first {
+  display: none;
+}
+
+.matching-tbl {
+  width: auto;
+  border-collapse: separate;
+  padding: 0px;
+  margin: 0px;
+  border: 0px;
+  line-height: 100%;
+}
+
+.matching-tbl p, .matching-tbl select, .matching-tbl div {
+  padding: 0px;
+  margin: 0px;
+  line-height: 100%;
+}
+
+.matching-tbl td {
+  padding: 2px;
+  margin: 2px;
+  line-height: 100%;
+}
+
+.quiz-solution {
+  border-left: solid 5px #00ff00;
+  padding-left: 3px;
+}
+
+.quiz-answer-hidden {
+  font-style: italic;
+}
+
+#quiz-jumper-form, #quiz-jumper-form div, #quiz-jumper-form select {
+  display: inline;
+}
+
+#quiz-progress .item-list .pager {
+  text-align: left;
+}
+
+#quiz-categorized-form #autocomplete {
+  overflow: auto;
+  max-height: 300px;
+}
+
+.mark-doubtful .toggle {
+  margin: auto;
+  position: relative;
+  width: 100px;
+  overflow: hidden;
+  height: 18px;
+  line-height: 18px;
+  font-size: 11px;
+  text-align: center;
+  cursor: pointer;
+  border: 1px solid #ccc;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  -khtml-border-radius: 3px;
+  border-radius: 3px;
+  -moz-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
+  -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
+  box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
+}
+
+.mark-doubtful .toggle.off {
+  background-clip: padding-box;
+  background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, red), color-stop(50%, white), color-stop(100%, white));
+  background-image: -moz-linear-gradient(left, red 50%, white 50%, white 100%);
+  background-image: linear-gradient(left, red 50%, white 50%, white 100%);
+}
+
+.mark-doubtful .toggle {
+  background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, white), color-stop(50%, green), color-stop(100%, green));
+  background-image: -moz-linear-gradient(left, white 50%, green 50%, green 100%);
+  background-image: linear-gradient(left, white 50%, green 50%, green 100%);
+}
+
+.mark-doubtful .toggle div {
+  position: relative;
+  color: #777;
+  width: 52px;
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  -khtml-border-radius: 2px;
+  border-radius: 2px;
+  background: white;
+  text-shadow: 1px 1px 0 white;
+  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EAEAEA));
+  background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%);
+  background-image: linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%);
+  -webkit-transition: left 0.2s;
+  -mox-transition: left 0.2s;
+  -o-transition: left 0.2s;
+  transition: left 0.2s;
+}
+
+
+.mark-doubtful .toggle div:after,
+.mark-doubtful .toggle div:before {
+  color: white;
+  text-shadow: none;
+  width: 50px;
+  position: absolute;
+  top: 0;
+  font-size: 9px;
+  font-weight: bold;
+}
+
+.mark-doubtful .toggle div:before {
+  content: "Doubtful";
+  left: -48px;
+}
+
+.mark-doubtful .toggle div:after {
+  content: "Clear";
+  right: -48px;
+}
+.mark-doubtful .toggle.off div {
+  left: 48px;
+}
+
+/*
+  Icons
+*/
+.quiz-score-icon {
+  display: inline-block;
+  width: 32px;
+  height: 32px;
+  background-size: cover
+}
+.quiz-score-icon.correct {
+  background-image: url('images/check_008000_64.png');
+}
+.quiz-score-icon.incorrect {
+  background-image: url('images/times_ff0000_64.png');
+}
+.quiz-score-icon.unknown {
+  background-image: url('images/question_808080_64.png');
+}
+.quiz-score-icon.should {
+  background-image: url('images/check_808080_64.png');
+}
+.quiz-score-icon.should-not {
+  background-image: url('images/times_808080_64.png');
+}
+.quiz-score-icon.almost {
+  background-image: url('images/check_ffff00_64.png');
+}
+.quiz-score-icon.selected {
+  background-image: url('images/arrow-right_808080_64.png');
+}
+.quiz-score-icon.unselected {
+  background-image: url('images/circle-o_808080_64.png');
+}
diff --git a/modules/ajax_quiz/ajax_quiz.admin.inc b/modules/ajax_quiz/ajax_quiz.admin.inc
deleted file mode 100644
index 48095f8..0000000
--- a/modules/ajax_quiz/ajax_quiz.admin.inc
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-
-/* @file
- * ajax quiz admin page
- */
-
-/*
- * @function
- * callback for ajax quiz menu
- * @return
- * HTML quiz description
- */
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function ajax_quiz_instruction($node) {
-  // If this is an AJAX request, return the AJAX result.
-  if (isset($_REQUEST['ajax_load_example']) && $_REQUEST['ajax_load_example']) {
-    _ajax_quiz_get_questions($node);
-  }
-  else {
-    drupal_add_js(drupal_get_path('module', 'ajax_quiz') . '/ajax_quiz.js');
-    $output = '<div class="ajax-quiz-wrapper">';
-    $output .= '<div class="ajax-quiz-description">' . $node->body . '</div>';
-    $output .= '<div class="ajax-quiz-start">' . l(t('Start'), $_GET['q'], array('attributes' => array('class' => 'ajax-load-example'))) . '</div>';
-    $output .= '</div>';
-    return $output;
-  }
-}
-
-function _ajax_quiz_get_questions($node) {
-  $output = '';
-  foreach (drupal_get_messages() as $type) {
-    foreach ($type as $message) {
-      $output .= '<script type="text/javascript"> alert("' . "$type : $message" . '"); </script>';
-    }
-  }
-  $output .= node_view($node, FALSE, TRUE);
-  $result = array(
-    'content' => $output,
-    // Put the Javascript callback you will use here.
-    // You can if you wish leave out this line and instead
-    // call your callback directly in your Javascript. See
-    // comments in ajax_load_example.js.
-    '__callbacks' => array('Drupal.AjaxLoadExample.formCallback'),
-  );
-  // Call drupal_alter('ajax_data', ...). This is what allows ajax_load to
-  // add its data and register its Javascript callback.
-  // The second argument is the data to be altered.
-  // The third argument is a unique identifier for this AJAX data operation.
-  // The fourth and optional argument is the original data that was the subject
-  // of the ajax operation--in this case, a form ID.
-  drupal_alter('ajax_data', $result, 'ajax_load_example', 'node_view');
-  drupal_json_output($result);
-}
-
diff --git a/modules/ajax_quiz/ajax_quiz.info b/modules/ajax_quiz/ajax_quiz.info
deleted file mode 100644
index 6a102a7..0000000
--- a/modules/ajax_quiz/ajax_quiz.info
+++ /dev/null
@@ -1,8 +0,0 @@
-name = AJAX Quiz
-description = This module doesn't degrade gracefully, and we don't recommend using it until it does. This module attempts to provide an AJAX version of quiz. Successive quiz questions will be loaded in the same page without page reload, but it doesn't work flawlessly at the moment.
-dependencies[] = quiz
-core = 7.x
-package = Quiz Addon
-
-files[] = ajax_quiz.admin.inc
-files[] = ajax_quiz.module
diff --git a/modules/ajax_quiz/ajax_quiz.js b/modules/ajax_quiz/ajax_quiz.js
deleted file mode 100644
index c7e23c6..0000000
--- a/modules/ajax_quiz/ajax_quiz.js
+++ /dev/null
@@ -1,113 +0,0 @@
-
-Drupal.AjaxLoadExample = Drupal.AjaxLoadExample || {};
-
-/**
- * Ajax load example behavior.
- */
-Drupal.behaviors.AjaxLoadExample = function (context) {
-  // to proceed with the quiz
-  $('.form-submit:not(.form-submit-clicked)', context)
-    .each(function () {
-      // The target should not be e.g. a node that will itself be
-      // replaced, as this would mean no node is available for
-      // ajax_load to attach behaviors to when all scripts are loaded.
-      var target = this.parentNode;
-      $(this)
-        .addClass('form-submit-clicked')
-        .click(function () {
-          var tries = '';
-          $('[name^="tries"]:not(.form-submit-clicked)').each(function (i) {
-            switch(this.type) {
-            case 'radio':
-            case 'checkbox':
-            	if (this.checked) {
-            		tries += '&' + this.name + '=' + this.value;
-            	}
-            	break;
-            default:
-            	tries += '&' + this.name + '=' + this.value;
-            	break;
-            }
-            $(this).addClass('form-submit-clicked');
-          });
-          $(this).attr('disabled', true);
-          $.ajax({
-            // Either GET or POST will work.
-            type: 'POST',
-            data: 'ajax_load_example=1&op=Next'+tries,
-            // Need to specify JSON data.
-            dataType: 'json',
-            url: $(this).attr('href'),
-            success: function(response) {
-              $(this).prepend('answer saved');
-              // Call all callbacks.
-              //alert('sucess');
-              if (response.__callbacks) {
-                $.each(response.__callbacks, function(i, callback) {
-                  // The first argument is a target element, the second
-                  // the returned JSON data.
-                  //alert(response);
-                  eval(callback)(target, response);
-                });
-                // If you don't want to return this module's own callback,
-                // you could of course just call it directly here.
-                // Drupal.AjaxLoadExample.formCallback(target, response);
-              }
-            },
-            error: function(xhr, opt, err) {
-              alert(xhr.statusText);
-              alert(opt);
-              alert(err);
-            },
-          });
-          return false;
-        });
-    });
-  // to start the quiz
-  $('a.ajax-load-example:not(.ajax-load-example-processed)', context)
-    .each(function () {
-      // The target should not be e.g. a node that will itself be
-      // replaced, as this would mean no node is available for
-      // ajax_load to attach behaviors to when all scripts are loaded.
-      var target = this.parentNode;
-      $(this)
-        .addClass('ajax-load-example-processed')
-        .click(function () {
-          $.ajax({
-            // Either GET or POST will work.
-            type: 'POST',
-            data: 'ajax_load_example=1',
-            // Need to specify JSON data.
-            dataType: 'json',
-            url: $(this).attr('href'),
-            success: function(response){
-              // Call all callbacks.
-              if (response.__callbacks) {
-                $.each(response.__callbacks, function(i, callback) {
-                  // The first argument is a target element, the second
-                  // the returned JSON data.
-                  eval(callback)(target, response);
-                });
-                // If you don't want to return this module's own callback,
-                // you could of course just call it directly here.
-                // Drupal.AjaxLoadExample.formCallback(target, response);
-              }
-            },
-            error: function(){
-              alert('An error has occurred. Please try again.');
-            },
-          });
-          return false;
-        });
-    });
-};
-
-/**
- * Ajax load example callback.
- */
-Drupal.AjaxLoadExample.formCallback = function (target, response) {
-  target = $(target).append(response.content);
-  Drupal.attachBehaviors(target);
-};
-
-
diff --git a/modules/ajax_quiz/ajax_quiz.module b/modules/ajax_quiz/ajax_quiz.module
deleted file mode 100644
index 3a5e8b3..0000000
--- a/modules/ajax_quiz/ajax_quiz.module
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-
-/*
- * @file
- * module file for ajax_quiz quiz module
- */
-
-/**
- * Implementation of hook_help().
- */
-
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function ajax_quiz_help($path, $arg) {
-  if ($path == 'admin/help#ajax_quiz') {
-    return '<p>' . t('AJAX version of quiz. Successive quiz questions will be loaded in the same page without page reload.') . '</p>';
-  }
-}
-
-/**
- * Implementation of hook_perm()
- * Valid permissions for this module
- * @return array An array of valid permissions.
- */
-
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function ajax_quiz_permission() {
-  return array(
-    'access ajax quiz' => array(
-      'title' => t('access ajax quiz'),
-      'description' => t('TODO Add a description for \'access ajax quiz\''),
-    ),
-  );
-}
-
-/**
- * Implements hook_menu().
- *
- */
-function ajax_quiz_menu() {
-  $items = array();
-  $items['node/%quiz_type_access/ajax-quiz'] = array(
-    'title' => 'Take by AJAX',
-    'file' => 'ajax_quiz.admin.inc',
-    'page callback' => 'ajax_quiz_instruction',
-    'page arguments' => array(1),
-    'access arguments' => array('access ajax quiz'),
-    'type' => MENU_LOCAL_TASK,
-  );
-  return $items;
-}
-
-/*
- * @function
- * Implementation of hook_init
- * includes the necessary js file(s).
- */
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function ajax_quiz_init() {
-  //drupal_add_js(drupal_get_path('module', 'ajax_quiz') . '/ajax_quiz.js');
-}
-
diff --git a/modules/question_types/long_answer/long_answer.admin.inc b/modules/question_types/long_answer/long_answer.admin.inc
new file mode 100644
index 0000000..d9f100a
--- /dev/null
+++ b/modules/question_types/long_answer/long_answer.admin.inc
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * Administration pages for the long answer questions module.
+ * @file
+ */
+
+/**
+ * Generate a view of all unscored answer questions.
+ *
+ * @see theme_long_answer_view_unscored()
+ */
+function long_answer_view_unscored() {
+  $unscored = LongAnswerResponse::fetchAllUnscoredAnswers();
+  return theme('long_answer_view_unscored', array('unscored' => $unscored));
+}
+
+/**
+ * Page handler for displaying a scoring form.
+ * This function is called directly from the menu router. It generates a form for
+ * scoring a quiz.
+ *
+ * @param $vid
+ *  The VID of the question and answer to load.
+ * @param $result_id
+ *  The result ID of the answer to load.
+ * @return
+ *  Text to display.
+ */
+function long_answer_edit_score($vid, $result_id) {
+
+  // We have to do the vid -> nid lookup ourselves because node_load uses only node.vid,
+  // and we need to be able to access old nodes in node_revision.vid.
+  $nid = db_query('SELECT nid FROM {node_revision} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
+  if (!$nid) {
+    drupal_not_found();
+    return;
+  }
+
+  $node = node_load($nid, $vid);
+  if (!$node || $node->type != 'long_answer') {
+    drupal_not_found();
+    return;
+  }
+
+  $answer = (object) long_answer_get_answer($node->nid, $node->vid, $result_id);
+  if (!$answer) {
+    drupal_not_found();
+    return;
+  }
+  if ($node->max_score > 0) {
+    $answer->rel_score = round($answer->score * $answer->rel_max_score / $node->max_score);
+  }
+  else {
+    $answer->rel_score = 0;
+  }
+  drupal_set_title(t('Score answer to "@title"', array('@title' => $node->title)), PASS_THROUGH);
+  return drupal_get_form('long_answer_score_form', $node, $answer);
+}
+
+/**
+ * Build a form for scoring long-answer questions.
+ *
+ * @param $node
+ *  The question node.
+ * @param $answer
+ *  An object containing an answer to the question. This form is for scoring that answer.
+ * @return
+ *  The form (as a FAPI array).
+ */
+function long_answer_score_form($form, $form_state, $node, $answer) {
+  if (!$node || $node->type != 'long_answer' || !$answer) {
+    drupal_not_found();
+    return;
+  }
+  // Set up the form
+  $form['question'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Question'),
+      '#markup' => check_markup($node->body['und'][0]['value'], $node->body['und'][0]['format']),
+  );
+  $form['rubric'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Rubric'),
+      '#markup' => check_markup($node->rubric),
+  );
+  $form['show_max_score'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Maximum Score'),
+      '#markup' => (int) $answer->rel_max_score,
+  );
+  $form['score_answer'] = array(
+      '#type'  => 'fieldset',
+      '#title' => t('Score answer'),
+  );
+  $form['score_answer']['answer'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Answer'),
+      '#markup' => check_markup($answer->answer),
+  );
+  $form['score_answer']['score'] = array(
+      '#type'          => 'textfield',
+      '#title'         => t('Score'),
+      '#description'   => t('The score for this essay, between 0 and @max', array('@max' => $answer->rel_max_score)),
+      '#size'          => 3,
+      '#maxlength'     => 3,
+      '#default_value' => (int) $answer->rel_score,
+      '#required'      => TRUE,
+  );
+  $form['score_answer']['answer_feedback'] = array(
+      '#title'         => t('Feedback'),
+      '#type'          => 'text_format',
+      '#description'   => t('The text entered here would be shown to attendees'),
+      '#default_value' => $answer->answer_feedback,
+      '#format'        => isset($answer->answer_feedback_format) ? $answer->answer_feedback_format : NULL
+  );
+
+  $form['score_answer']['submit'] = array(
+      '#type'  => 'submit',
+      '#value' => t('Save this score'),
+  );
+
+  // Save some work by keeping these.
+  $form['max_score'] = array(
+      '#type'  => 'value',
+      '#value' => $node->max_score,
+  );
+  $form['rel_max_score'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->rel_max_score,
+  );
+  $form['question_nid'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->question_nid,
+  );
+  $form['question_vid'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->question_vid,
+  );
+  $form['result_id'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->result_id,
+  );
+
+  return $form;
+}
+
+/**
+ * Validates the long answer score form
+ */
+function long_answer_score_form_validate($form, $form_state) {
+  // Check to make sure that entered score is not higher than max allowed score.
+  $max = (int) $form_state['values']['rel_max_score'];
+  $given = $form_state['values']['score'];
+  if (!_quiz_is_int($given, 0, $max)) {
+    $args = array('@score' => $given, '@max' => $max);
+    form_set_error('score', t('The given score (@score) must be an integer between 0 and @max', $args));
+  }
+}
+
+/**
+ * Submit handler for the long answer score form
+ */
+function long_answer_score_form_submit($form, &$form_state) {
+  $result = db_query('SELECT quiz_qid, quiz_vid FROM {quiz_results} WHERE result_id = :result_id', array(
+      ':result_id' => $form_state['values']['result_id']
+    ))->fetch();
+
+  $result = long_answer_score_an_answer(array(
+      'quiz'            => node_load($result->quiz_qid, $result->quiz_vid),
+      'nid'             => $form_state['values']['question_nid'],
+      'vid'             => $form_state['values']['question_vid'],
+      'result_id'       => $form_state['values']['result_id'],
+      'score'           => $form_state['values']['score'],
+      'max_score'       => $form_state['values']['rel_max_score'],
+      'answer_feedback' => $form_state['values']['answer_feedback']
+  ));
+
+  if ($result == 1) {
+    drupal_set_message(t('The score has been saved.'));
+    $form_state['redirect'] = 'admin/quiz/reports/score-long-answer';
+  }
+  else {
+    drupal_set_message(t('Error saving the score. The selected answer was not scored.'), 'error');
+  }
+}
diff --git a/modules/question_types/long_answer/long_answer.classes.inc b/modules/question_types/long_answer/long_answer.classes.inc
new file mode 100644
index 0000000..8381a93
--- /dev/null
+++ b/modules/question_types/long_answer/long_answer.classes.inc
@@ -0,0 +1,394 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * Long answer classes.
+ *
+ * @file
+ *  Classes modelling the long answer question and the long answer question response
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class LongAnswerQuestion extends QuizQuestion {
+
+  /**
+   * Implementation of saveNodeProperties
+   * @see QuizQuestion#saveNodeProperties($is_new)
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    if (!isset($this->node->feedback)) {
+      $this->node->feedback = '';
+    }
+
+    if ($is_new || $this->node->revision == 1) {
+      db_insert('quiz_long_answer_node_properties')
+        ->fields(array(
+            'nid'    => $this->node->nid,
+            'vid'    => $this->node->vid,
+            'rubric' => $this->node->rubric,
+        ))
+        ->execute();
+    }
+    else {
+      db_update('quiz_long_answer_node_properties')
+        ->fields(array('rubric' => isset($this->node->rubric) ? $this->node->rubric : ''))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+  }
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validateNode($form)
+   */
+  public function validateNode(array &$form) {
+
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete($only_this_version)
+   */
+  public function delete($only_this_version = FALSE) {
+    if ($only_this_version) {
+      db_delete('quiz_long_answer_user_answers')
+        ->condition('question_nid', $this->node->nid)
+        ->condition('question_vid', $this->node->vid)
+        ->execute();
+      db_delete('quiz_long_answer_node_properties')
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+    else {
+      db_delete('quiz_long_answer_node_properties')
+        ->condition('nid', $this->node->nid)
+        ->execute();
+      db_delete('quiz_long_answer_user_answers')
+        ->condition('question_nid', $this->node->nid)
+        ->execute();
+    }
+    parent::delete($only_this_version);
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+
+    $res_a = db_query('SELECT rubric FROM {quiz_long_answer_node_properties}
+      WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
+
+    if (is_array($res_a)) {
+      $props = array_merge($props, $res_a);
+    }
+
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Implementation of getNodeView
+   *
+   * @see QuizQuestion#getNodeView()
+   */
+  public function getNodeView() {
+    $content = parent::getNodeView();
+
+    if ($this->viewCanRevealCorrect()) {
+      if (!empty($this->node->rubric)) {
+        $content['answers'] = array(
+            '#type'   => 'item',
+            '#title'  => t('Rubric'),
+            '#prefix' => '<div class="quiz-solution">',
+            '#suffix' => '</div>',
+            '#markup' => check_markup($this->node->rubric, $this->getFormat()),
+            '#weight' => 1,
+        );
+      }
+    }
+    else {
+      $content['answers'] = array(
+          '#markup' => '<div class="quiz-answer-hidden">Answer hidden</div>',
+          '#weight' => 1,
+      );
+    }
+    return $content;
+  }
+
+  /**
+   * Implementation of getAnweringForm
+   *
+   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $element = parent::getAnsweringForm($form_state, $result_id);
+
+    $element += array(
+        '#type'        => 'textarea',
+        '#title'       => t('Answer'),
+        '#description' => t('Enter your answer here. If you need more space, click on the grey bar at the bottom of this area and drag it down.'),
+        '#rows'        => 15,
+        '#cols'        => 60,
+      //'#theme' =>'long_answer_answering_form',
+    );
+    if (isset($result_id)) {
+      $response = new LongAnswerResponse($result_id, $this->node);
+      $element['#default_value'] = $response->getResponse();
+    }
+    return $element;
+  }
+
+  /**
+   * Question response validator.
+   */
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+    if ($form_state['values']['question'][$this->node->nid] == '') {
+      form_set_error('', t('You must provide an answer.'));
+    }
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm($form_state)
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    $form['rubric'] = array(
+        '#type'          => 'textarea',
+        '#title'         => t('Rubric'),
+        '#description'   => t('Specify the criteria for grading the response.'),
+        '#default_value' => isset($this->node->rubric) ? $this->node->rubric : '',
+        '#size'          => 60,
+        '#maxlength'     => 2048,
+        '#required'      => FALSE,
+    );
+    return $form;
+  }
+
+  /**
+   * Implementation of getMaximumScore
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    return variable_get('long_answer_default_max_score', 10);
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class LongAnswerResponse extends QuizQuestionResponse {
+
+  /**
+   * Get all scores that have not yet been evaluated.
+   *
+   * @param $count
+   *  Number of items to return (default: 50).
+   * @param $offset
+   *  Where in the results we should start (default: 0).
+   *
+   * @return
+   *  Array of objects describing unanswered questions. Each object will have result_id, question_nid, and question_vid.
+   */
+  public static function fetchAllUnscoredAnswers($count = 50, $offset = 0) {
+    global $user;
+
+    $query = db_select('quiz_long_answer_user_answers', 'answer');
+    $query->fields('answer', array('result_id', 'question_nid', 'question_vid', 'answer_feedback', 'answer_feedback_format'));
+    $query->fields('question_revision', array('title'));
+    $query->fields('qr', array('time_end', 'time_start', 'uid'));
+    $query->join('node_revision', 'question_revision', 'answer.question_vid = question_revision.vid');
+    $query->join('quiz_results', 'qr', 'answer.result_id = qr.result_id');
+    $query->join('quiz_entity', 'quiz', 'qr.quiz_qid = quiz.qid');
+    $query->condition('answer.is_evaluated', 0);
+
+    if (user_access('score own quiz') && user_access('score taken quiz answer')) {
+      $query->condition(db_or()->condition('quiz.uid', $user->uid)->condition('qr.uid', $user->uid));
+    }
+    elseif (user_access('score own quiz')) {
+      $query->condition('quiz.uid', $user->uid);
+    }
+    elseif (user_access('score taken quiz answer')) {
+      $query->condition('qr.uid', $user->uid);
+    }
+
+    $unscored = array();
+    foreach ($query->execute() as $row) {
+      $unscored[] = $row;
+    }
+    return $unscored;
+  }
+
+  /**
+   * Given a quiz, return a list of all of the unscored answers.
+   *
+   * @param $nid
+   *  Node ID for the quiz to check.
+   * @param $vid
+   *  Version ID for the quiz to check.
+   * @param $count
+   *  Number of items to return (default: 50).
+   * @param $offset
+   *  Where in the results we should start (default: 0).
+   *
+   * @return
+   *  Indexed array of result IDs that need to be scored.
+   */
+  public static function fetchUnscoredAnswersByQuestion($nid, $vid, $count = 50, $offset = 0) {
+    $results = db_query('SELECT result_id FROM {quiz_long_answer_user_answers}
+      WHERE is_evaluated = :is_evaluated
+      AND question_nid = :question_nid
+      AND question_vid = :question_vid', array(':is_evaluated' => 0, ':question_nid' => $nid, ':question_vid' => $vid));
+    $unscored = array();
+    foreach ($results as $row) {
+      $unscored[] = $row->result_id;
+    }
+    return $unscored;
+  }
+
+  /**
+   * ID of the answer.
+   */
+  protected $answer_id = 0;
+
+  /**
+   * Constructor
+   */
+  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
+    parent::__construct($result_id, $question_node, $answer);
+
+    if (!isset($answer)) {
+      // Question has been answered allready. We fetch the answer data from the database.
+      $r = db_query('SELECT answer_id, answer, is_evaluated, score, question_vid, question_nid, result_id, answer_feedback, answer_feedback_format
+        FROM {quiz_long_answer_user_answers}
+        WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(':qnid' => $question_node->nid, ':qvid' => $question_node->vid, ':rid' => $result_id))->fetch();
+
+      if (!empty($r)) {
+        $this->answer = $r->answer;
+        $this->score = $r->score;
+        $this->evaluated = $r->is_evaluated;
+        $this->answer_id = $r->answer_id;
+        $this->answer_feedback = $r->answer_feedback;
+        $this->answer_feedback_format = $r->answer_feedback_format;
+      }
+    }
+    else {
+      $this->answer = $answer;
+      $this->evaluated = FALSE;
+    }
+  }
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+    $this->answer_id = db_insert('quiz_long_answer_user_answers')
+      ->fields(array(
+          'answer'       => $this->answer,
+          'question_nid' => $this->question->nid,
+          'question_vid' => $this->question->vid,
+          'result_id'    => $this->result_id,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+    db_delete('quiz_long_answer_user_answers')
+      ->condition('question_nid', $this->question->nid)
+      ->condition('question_vid', $this->question->vid)
+      ->condition('result_id', $this->result_id)
+      ->execute();
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    return (int) db_query('SELECT score FROM {quiz_long_answer_user_answers}
+      WHERE result_id = :result_id AND question_vid = :question_vid', array(':result_id' => $this->result_id, ':question_vid' => $this->question->vid))->fetchField();
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer;
+  }
+
+  /**
+   * Implementation of getReportFormResponse
+   */
+  public function getReportFormResponse() {
+    $data = array();
+
+    $data[] = array(
+        'choice'            => '',
+        'attempt'           => $this->answer,
+        'correct'           => !$this->evaluated ? t('This answer has not yet been scored.') : '',
+        'score'             => $this->getScore(),
+        'answer_feedback'   => check_markup($this->answer_feedback, $this->answer_feedback_format),
+        'question_feedback' => '',
+        'solution'          => $this->question->rubric,
+    );
+
+    return $data;
+  }
+
+  public function getReportFormAnswerFeedback() {
+    return array(
+        '#title'         => t('Enter feedback'),
+        '#type'          => 'text_format',
+        '#default_value' => isset($this->answer_feedback) ? check_markup($this->answer_feedback, $this->answer_feedback_format) : '',
+        '#format'        => isset($this->answer_feedback_format) ? $this->answer_feedback_format : filter_default_format(),
+        '#attributes'    => array('class' => array('quiz-report-score')),
+    );
+  }
+
+  /**
+   * Implementation of getReportFormSubmit
+   *
+   * @see QuizQuestionResponse#getReportFormSubmit()
+   */
+  public function getReportFormSubmit() {
+    return 'long_answer_report_submit';
+  }
+
+  /**
+   * Implements QuizQuestionResponse::getReportFormValidate().
+   */
+  public function getReportFormValidate(&$element, &$form_state) {
+    $max = $this->question->max_score;
+    // Check to make sure that entered score is not higher than max allowed score.
+    if ($element['score']['#value'] > $max) {
+      form_error($element['score'], t('The score needs to be a number between @min and @max', array('@min' => 0, '@max' => $max)));
+    }
+  }
+
+}
diff --git a/modules/question_types/long_answer/long_answer.info b/modules/question_types/long_answer/long_answer.info
new file mode 100644
index 0000000..629e1ce
--- /dev/null
+++ b/modules/question_types/long_answer/long_answer.info
@@ -0,0 +1,9 @@
+name = Long Answer
+package = Quiz Question
+description = This provides long answer (essay, multi-paragraph) question types for use by the Quiz module.
+dependencies[] = quiz
+dependencies[] = quiz_question
+core = 7.x
+
+files[] = long_answer.classes.inc
+files[] = tests/LongAnswerUnitTest.test
diff --git a/modules/question_types/long_answer/long_answer.install b/modules/question_types/long_answer/long_answer.install
new file mode 100644
index 0000000..4d8a89f
--- /dev/null
+++ b/modules/question_types/long_answer/long_answer.install
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * Long answer questions.
+ * @file
+ */
+
+/**
+ * Implements hook_install().
+ */
+function long_answer_install() {
+  // Add body field to long answer node
+  quiz_question_add_body_field('long_answer');
+
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_install_schema('long_answer')
+  variable_set('node_options_long_answer', array('status'));
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function long_answer_uninstall() {
+
+  // Delete tables
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_uninstall_schema('long_answer')
+
+  // Delete data from other tables
+
+  // Clear the cache.
+  cache_clear_all('variables', 'cache');
+  drupal_set_message(t('The Long Answer module has been uninstalled and related data has been deleted.'));
+
+}
+
+/**
+ * Implements hook_schema().
+ */
+function long_answer_schema() {
+  // Properties for a question nodes go in here:
+  $schema['quiz_long_answer_node_properties'] = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'rubric' => array(
+        'type' => 'text',
+      ),
+    ),
+    'primary key' => array('nid', 'vid'),
+  );
+
+  // User answers go in here.
+  $schema['quiz_long_answer_user_answers'] = array(
+    'fields' => array(
+      'answer_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'score' => array(
+        'type' => 'float',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'is_evaluated' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'size' => 'tiny',
+        'default' => 0,
+        'not null' => TRUE,
+      ),
+      'answer' => array(
+        'type' => 'text'
+      ),
+      'answer_feedback' => array(
+        'type' => 'text'
+      ),
+      'answer_feedback_format' => array(
+        'type' => 'varchar',
+        'length' => 255,
+      ),
+    ),
+    'primary key' => array('answer_id'),
+    'unique keys' => array(
+      'ids' => array('result_id', 'question_nid', 'question_vid'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
+ * Adding feedback field to table {quiz_long_answer_user_answers}
+ */
+function long_answer_update_7401() {
+  $spec = array(
+    'type' => 'text',
+    'not null' => FALSE,
+  );
+  db_add_field('quiz_long_answer_user_answers', 'answer_feedback', $spec);
+}
+
+/**
+ * Adding feedback format field to table {quiz_long_answer_user_answers}
+ */
+function long_answer_update_7402() {
+  if (!db_field_exists('quiz_long_answer_user_answers', 'answer_feedback_format')) {
+    $spec = array(
+        'type' => 'varchar',
+        'length' => 255,
+    );
+    db_add_field('quiz_long_answer_user_answers', 'answer_feedback_format', $spec);
+  }
+}
\ No newline at end of file
diff --git a/modules/question_types/long_answer/long_answer.module b/modules/question_types/long_answer/long_answer.module
new file mode 100644
index 0000000..1babdfa
--- /dev/null
+++ b/modules/question_types/long_answer/long_answer.module
@@ -0,0 +1,309 @@
+<?php
+
+/**
+ * This module defines a long answer question type for quizzes.
+ *
+ * Long answer questions make use of the quiz question framework (quiz_question.module).
+ * The functions in this file are largely used for grading long answer questions. Most
+ * of the real logic is in long_answer.classes.inc.
+ *
+ * @file
+ */
+
+/**
+ * Implements hook_help().
+ */
+function long_answer_help($path, $arg) {
+  if ($path == 'admin/help#long_answer') {
+    return '<p>' . t('This module provides long-answer (essay, multi-paragraph) questions to the quiz module.') .
+      '</p><p>' . t('A long-answer question is designed to provide the quiz taker a lengthy area to expand on ideas.
+      Common forms of long-answer questions include essays, single paragraph responses, hypothesis design problems,
+      outlines and summaries, and lengthier math problems
+      where the focus is on showing work rather than simply getting the correct answer.') . '</p>';
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function long_answer_menu() {
+  $items['admin/quiz/reports/score-long-answer'] = array(
+      'title'            => 'Score long answer questions',
+      'description'      => 'Score the answers from quizzes that use long answer questions.',
+      'page callback'    => 'long_answer_view_unscored',
+      'access arguments' => array('score any quiz', 'score own quiz', 'score taken quiz answer'),
+      'access callback'  => 'quiz_access_multi_or',
+      'type'             => MENU_NORMAL_ITEM,
+      'file'             => 'long_answer.admin.inc',
+  );
+  // Pass vid and rid to this path.
+  $items['admin/quiz/reports/score-long-answer/%/%'] = array(
+      'title'            => 'Score long answer response',
+      'description'      => 'Score a response to a long answer question.',
+      'page callback'    => 'long_answer_edit_score',
+      'page arguments'   => array(4, 5),
+      'type'             => MENU_NORMAL_ITEM,
+      'access arguments' => array(4, 5),
+      'access callback'  => 'quiz_question_access_to_score',
+      'file'             => 'long_answer.admin.inc'
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function long_answer_quiz_question_info() {
+  return array(
+      'long_answer' => array(
+          'name'              => t('Long answer question'),
+          'description'       => t('Quiz questions that allow a user to enter multiple paragraphs of text.'),
+          'question provider' => 'LongAnswerQuestion',
+          'response provider' => 'LongAnswerResponse',
+          'module'            => 'quiz_question', // All wrapper functions are in that module.
+      ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function long_answer_quiz_question_config() {
+  $form['long_answer_default_max_score'] = array(
+      '#type'          => 'textfield',
+      '#title'         => t('Default max score'),
+      '#description'   => t('Choose the default maximum score for a long answer question.'),
+      '#default_value' => variable_get('long_answer_default_max_score', 10),
+  );
+  $form['#validate'][] = 'long_answer_config_validate';
+  return $form;
+}
+
+/**
+ * Validate the long_answer config form values
+ */
+function long_answer_config_validate($form, $form_state) {
+  if ($form_state['values']['long_answer_default_max_score'] <= 0) {
+    form_set_error('long_answer_default_max_score', t('The default max score must be greater than 0'));
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function long_answer_theme() {
+  return array(
+      'long_answer_view_unscored'  => array(
+          'variables' => array('unscored' => array()),
+          'path'      => drupal_get_path('module', 'long_answer') . '/theme',
+          'file'      => 'long_answer.theme.inc',
+      ),
+      'long_answer_response_form'  => array(
+          'render element' => 'form',
+          'path'           => drupal_get_path('module', 'long_answer') . '/theme',
+          'file'           => 'long_answer.theme.inc',
+      ),
+      'long_answer_answering_form' => array(
+          'render element' => 'form',
+          'path'           => drupal_get_path('module', 'long_answer') . '/theme',
+          'template'       => 'long-answer-answering-form',
+      ),
+  );
+}
+
+/**
+ * Set a score for a long answer question.
+ *
+ * This stores a score for a long answer question and marks that question as having been evaluated.
+ * The function updates all of the necessary data sources so that the individual answer results should be
+ * reflected in the total scoring table.
+ *
+ * @param $quiz
+ *  Quiz entity.
+ * @param $nid
+ *  Node ID of question.
+ * @param $vid
+ *  Version ID of question.
+ * @param $result_id
+ *  Result ID for the quiz results.
+ * @param $score
+ *  The numeric score to assign the result.
+ * @param $update_total
+ *  Shall the total score be updated?
+ *
+ * @return int
+ *  Number of scores adjusted. If a change was made, this should be 1.
+ */
+function long_answer_score_an_answer($values, $update_total = TRUE) {
+  extract($values);
+  // Quiz scoring information is spread out across three tables:
+  // 1. The module should retain its own scoring information in any case where scoring is non-trivial.
+  // 2. The Quiz moduleretains a limited amount of scoring information.
+  // 3. The Quiz module retains an overall score for a quiz. This is the percentage score for the combination of all
+  //    questions on the quiz.
+  //
+  // We update all three.
+  // First, we update the long answer table
+  $question_max_score = db_query('SELECT max_score FROM {quiz_question_properties} WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->FetchField();
+
+  $quiz_max_score = db_query('SELECT max_score FROM {quiz_relationship} WHERE quiz_vid = :pvid AND question_vid = :cvid', array(':pvid' => $quiz->vid, ':cvid' => $vid))->fetchField();
+
+  $changed = db_update('quiz_long_answer_user_answers')
+    ->fields(array(
+        'score'                  => $score * $question_max_score / $quiz_max_score,
+        'is_evaluated'           => 1,
+        'answer_feedback'        => isset($answer_feedback['value']) ? $answer_feedback['value'] : '',
+        'answer_feedback_format' => empty($answer_feedback['format']) ? '' : $answer_feedback['format'],
+    ))
+    ->condition('question_nid', $nid)
+    ->condition('question_vid', $vid)
+    ->condition('result_id', $result_id)
+    ->execute();
+  if ($changed > 0) {
+    // Second, we update the main quiz answers table
+    // What do we do about the quiz_results_answers table? It assumes strict
+    // bivalence (is_correct). I guess we consider any essay with over 50% to be correct?
+    $max = db_query('SELECT max_score FROM {quiz_question_properties} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
+    if ($max <= 0) {
+      $is_correct = 0;
+      $points_awarded = 0;
+    }
+    else {
+      $is_correct = $score == $max ? 1 : 0;
+      $points_awarded = $score;
+    }
+
+    db_update('quiz_results_answers')
+      ->fields(array(
+          'points_awarded' => $points_awarded,
+          'is_correct'     => $is_correct,
+      ))
+      ->condition('question_vid', $vid)
+      ->condition('result_id', $result_id)
+      ->execute();
+
+    // Third, we update the main quiz results table
+    if ($update_total) {
+      quiz()->getQuizHelper()->getResultHelper()->updateTotalScore($quiz, $result_id);
+    }
+  }
+
+  return $changed;
+}
+
+/**
+ * Get the answer for a question.
+ *
+ * This stores a score for a long answer question and marks that question as having been evaluated.
+ * @param $nid
+ *  Node ID of question.
+ * @param $vid
+ *  Version ID of question.
+ * @param $result_id
+ *  Result ID for the quiz results.
+ *
+ * @return Assoc array
+ *  An array if successful, or FALSE if no result could be found. The array contains the following properties:
+ *  <code>
+ *  answer_id; // The answer ID
+ *  answer; // The full text of the answer
+ *  is_evaluated; // 0 if the question has not been evaluated, 1 if it has
+ *  score; // The score the evaluator gave the user; this should be 0 if is_evaluated is 0.
+ *  question_vid
+ *  question_nid
+ *  result_id
+ *  </code>
+ */
+function long_answer_get_answer($question_nid, $question_vid, $result_id) {
+  $result = db_query('
+    SELECT answer_id, answer, la.is_evaluated, la.score, question_vid, question_nid, la.result_id, la.answer_feedback, la.answer_feedback_format, rel.max_score AS rel_max_score
+    FROM {quiz_long_answer_user_answers} la
+    JOIN {quiz_results} qnr ON (la.result_id = qnr.result_id)
+    JOIN {quiz_relationship} rel ON (qnr.quiz_vid = rel.quiz_vid AND rel.question_vid = la.question_vid)
+    WHERE question_nid = :qnid AND question_vid = :qvid AND la.result_id = :rid', array(':qnid' => $question_nid, ':qvid' => $question_vid, ':rid' => $result_id))->fetchAssoc();
+  return $result ? $result : FALSE;
+}
+
+/**
+ * Given a quiz, return a list of all the unscored answers.
+ *
+ * @param $nid
+ *  Node ID for the quiz to check.
+ * @param $vid
+ *  Version ID for the quiz to check.
+ * @param $count
+ *  Number of items to return (default: 50).
+ * @param $offset
+ *  Where in the results we should start (default: 0).
+ *
+ * @return
+ *  Indexed array of result IDs that need to be scored.
+ */
+function long_answer_get_unscored_answers_by_question($nid, $vid, $count = 50, $offset = 0) {
+  $results = db_query('SELECT result_id FROM {quiz_long_answer_user_answers}
+    WHERE is_evaluated = :is_evaluated AND question_nid = :question_nid AND question_vid = :question_vid', array(':is_evaluated' => 0, ':question_nid' => $nid, ':question_vid' => $vid));
+
+  $unscored = array();
+  foreach ($results as $result) {
+    $unscored[] = $result->result_id;
+  }
+  return $unscored;
+}
+
+/**
+ * Get all quiz scores that have not yet been evaluated.
+ *
+ * @param $count
+ *  Number of items to return (default: 50).
+ * @param $offset
+ *  Where in the results we should start (default: 0).
+ *
+ * @return
+ *  Array of objects describing unanswered questions. Each object will have result_id, question_nid, and question_vid.
+ */
+function long_answer_get_all_unscored_answers($count = 50, $offset = 0) {
+  $unscored = LongAnswerResponse::fetchAllUnscoredAnswers();
+  return $unscored;
+}
+
+/**
+ * Generate a list of long answer questions
+ *
+ * @return
+ *  array containing nid as key and title as value
+ */
+function long_answer_questions_list() {
+  $questions = array();
+  $query = db_query('SELECT nid, title FROM {node} WHERE type = :type', array(':type' => 'long_answer'));
+  foreach ($query as $question) {
+    $questions[$question->nid] = check_plain($question->title);
+  }
+  return $questions;
+}
+
+/**
+ * Submit function for the report form
+ *
+ * @param $values
+ *   The FAPI $form_state['values']
+ */
+function long_answer_report_submit($values) {
+  long_answer_score_an_answer($values, FALSE);
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function long_answer_field_extra_fields() {
+  $extra['node']['long_answer'] = array(
+      'form' => array(
+          'rubric' => array(
+              'label'       => t('Rubric'),
+              'description' => t('Specify the criteria for grading the response'),
+              'weight'      => -4,
+          ),
+      ),
+  );
+  return $extra;
+}
diff --git a/modules/question_types/long_answer/tests/LongAnswerUnitTest.test b/modules/question_types/long_answer/tests/LongAnswerUnitTest.test
new file mode 100644
index 0000000..95e0929
--- /dev/null
+++ b/modules/question_types/long_answer/tests/LongAnswerUnitTest.test
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Test class for long answer.
+ */
+class LongAnswerTestCase extends QuizQuestionTestCase {
+
+  var $question_node_type = 'long_answer';
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Long answer'),
+        'description' => t('Unit test for Long answers question type.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  public function setUp() {
+    parent::setUp(array('long_answer'), array('score any quiz'));
+  }
+
+  /**
+   * Test manually graded questions. Also test feedback here instead of its own
+   * test case.
+   *
+   * Note: we use two questions here to make sure the grading form is handled
+   * correctly.
+   */
+  public function testGradeAnswerManualFeedback() {
+    $this->drupalLogin($this->admin);
+
+    $question_node1 = $this->testCreateQuizQuestion();
+    $quiz = $this->linkQuestionToQuiz($question_node1);
+
+    $question_node2 = $this->testCreateQuizQuestion();
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+
+    // Test correct.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 'the answer is the zero one infinity rule'), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 'the number two really is ridiculous'), t('Finish'));
+    $this->assertText('Your score: 0%');
+
+    // Strange behavior - extra spacing in the HTML.
+    // $this->assertText('Score ? of 10');
+    $this->assertText('This answer has not yet been scored.');
+    $this->assertNoFieldByName('0[score]');
+    $this->assertNoFieldByName('1[score]');
+    $url_of_result = $this->getUrl();
+
+    // Test grading the question.
+    $this->drupalLogin($this->admin);
+    $this->drupalGet($url_of_result);
+    $this->drupalPost(NULL, array(
+        "0[score]"                   => 3,
+        "1[score]"                   => 7,
+        "0[answer_feedback][value]"  => 'Feedback for answer 1.',
+        "1[answer_feedback][value]"  => 'Feedback for answer 2.',
+        "0[answer_feedback][format]" => 'filtered_html',
+        "1[answer_feedback][format]" => 'filtered_html'), t('Save score'));
+    $this->assertText('The scoring data you provided has been saved.');
+
+    // Test the score and feedback are visible to the user.
+    $this->drupalLogin($this->user);
+    $this->drupalGet($url_of_result);
+    $this->assertText('You got 10 of 20 possible points.');
+    $this->assertText('Your score: 50%');
+
+    // Strange behavior - extra spacing in the HTML.
+    // $this->assertText('Score 3 of 10');
+    // $this->assertText('Score 7 of 10');
+    $this->assertText('Feedback for answer 1.');
+    $this->assertText('Feedback for answer 2.');
+  }
+
+  /**
+   * Test adding and taking a truefalse question.
+   */
+  function testCreateQuizQuestion($settings = NULL) {
+    if (!$settings) {
+      $settings = array('rubric' => 'LA 1 rubric.');
+    }
+
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    $question_node = $this->drupalCreateNode(array(
+        'type'  => $this->question_node_type,
+        'title' => 'LA 1 title',
+        'body'  => array(LANGUAGE_NONE => array(array('value' => 'LA 1 body text.'))),
+      ) + $settings);
+
+    return $question_node;
+  }
+
+}
diff --git a/modules/question_types/long_answer/theme/long-answer-answering-form.tpl.php b/modules/question_types/long_answer/theme/long-answer-answering-form.tpl.php
new file mode 100644
index 0000000..b487a9c
--- /dev/null
+++ b/modules/question_types/long_answer/theme/long-answer-answering-form.tpl.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Handles the layout of the long_answer answering form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render_children($form);
+
+?>
diff --git a/modules/question_types/long_answer/theme/long_answer.theme.inc b/modules/question_types/long_answer/theme/long_answer.theme.inc
new file mode 100644
index 0000000..f245a76
--- /dev/null
+++ b/modules/question_types/long_answer/theme/long_answer.theme.inc
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Theme functions for long_answer.
+ */
+
+
+/**
+ * Theme the list of unscored long answer questions.
+ *
+ * @param $unscored
+ *  An array of objects, each with the question_nid, question_vid, and result_id of an unscored question.
+ */
+function theme_long_answer_view_unscored($variables) {
+  $unscored = $variables['unscored'];
+  $output = '';
+  $header = array(
+    t('Question'),
+    t('Finished'),
+    t('Action')
+  );
+  $rows = array();
+
+  foreach ($unscored as $item) {
+    if ($item->time_end > 0) {
+      $rows[] = array(
+        check_plain($item->title),
+        date('Y-m-d H:i', $item->time_end),
+        l(t('Score this response'), 'admin/quiz/reports/score-long-answer/' . $item->question_vid . '/' . $item->result_id),
+      );
+    }
+  }
+  $output .= !empty($rows) ? theme('table', array('header' => $header, 'rows' => $rows)) : t('There are no unscored essays.');
+  return $output;
+}
+
+/**
+ * Theme the long_answer response form
+ *
+ * @param $form
+ *  The response form
+ */
+function theme_long_answer_response_form($variables) {
+  $form = $variables['form'];
+  return drupal_render_children($form);
+}
diff --git a/modules/question_types/matching/matching.classes.inc b/modules/question_types/matching/matching.classes.inc
new file mode 100644
index 0000000..a51061d
--- /dev/null
+++ b/modules/question_types/matching/matching.classes.inc
@@ -0,0 +1,560 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * matching.classes
+ *
+ * This module uses the question interface to define the Matching question type.
+ *
+ * A Matching node defines a series of questions and answers and requires the user
+ * to associate each answer with a question.
+ *
+ * @file
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class MatchingQuestion extends QuizQuestion {
+
+  /**
+   * Constructor
+   *
+   * @param $node
+   *  matching node
+   */
+  public function __construct(stdClass $node) {
+    parent::__construct($node);
+    if (empty($this->node->match)) {
+      $this->node->match = array();
+    }
+  }
+
+  /**
+   * Implementation of saveNodeProperties
+   *
+   * @see QuizQuestion#saveNodeProperties($is_new)
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    // Update or insert the question properties
+    if ($is_new) {
+      db_insert('quiz_matching_properties')
+        ->fields(array(
+            'nid'            => $this->node->nid,
+            'vid'            => $this->node->vid,
+            'choice_penalty' => $this->node->choice_penalty,
+        ))
+        ->execute();
+    }
+    else {
+      db_update('quiz_matching_properties')
+        ->fields(array(
+            'choice_penalty' => $this->node->choice_penalty,
+        ))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+
+    // Loop through each question-answer combination
+    foreach ($this->node->match as $match) {
+      $match['feedback'] = (isset($match['feedback'])) ? $match['feedback'] : '';
+      // match_id is not so it is a new question.
+      if ($is_new || empty($match['match_id']) || $this->node->revision || isset($this->node->node_export_drupal_version)) {
+        if (!empty($match['question']) && !empty($match['answer'])) {
+          $id = db_insert('quiz_matching_node')
+            ->fields(array(
+                'nid'      => $this->node->nid,
+                'vid'      => $this->node->vid,
+                'question' => $match['question'],
+                'answer'   => $match['answer'],
+                'feedback' => $match['feedback'],
+            ))
+            ->execute();
+        }
+      }
+      // match_id is set, user may remove or update existing question.
+      else {
+        if (empty($match['question']) && empty($match['answer'])) {
+          // remove sub question.
+          db_delete('quiz_matching_node')
+            ->condition('match_id', $match['match_id'])
+            ->execute();
+        }
+        else {
+          // update sub question.
+          db_update('quiz_matching_node')
+            ->fields(array(
+                'question' => $match['question'],
+                'answer'   => $match['answer'],
+                'feedback' => $match['feedback'],
+            ))
+            ->condition('match_id', $match['match_id'])
+            ->execute();
+        }
+      }
+    }
+  }
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validateNode($form)
+   */
+  public function validateNode(array &$form) {
+    // No validation is required
+    // The first two pairs are required in the form, if there are other errors we forgive them
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete($only_this_version)
+   */
+  public function delete($only_this_version = FALSE) {
+    parent::delete($only_this_version);
+
+    $delete_properties = db_delete('quiz_matching_properties')->condition('nid', $this->node->nid);
+
+    if ($only_this_version) {
+      $delete_properties->condition('vid', $this->node->vid);
+
+      $match_id = db_query('SELECT match_id FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchCol();
+      db_delete('quiz_matching_user_answers')
+        ->condition('match_id', is_array($match_id) ? $match_id : array(0), 'IN')
+        ->execute();
+
+      db_delete('quiz_matching_node')
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+    // Delete all versions of this question.
+    else {
+      $match_id = db_query('SELECT match_id FROM {quiz_matching_node} WHERE nid = :nid', array(':nid' => $this->node->nid))->fetchCol();
+      if (!empty($match_id)) {
+        db_delete('quiz_matching_user_answers')
+          ->condition('match_id', $match_id, 'IN')
+          ->execute();
+      }
+
+      db_delete('quiz_matching_node')
+        ->condition('nid', $this->node->nid)
+        ->execute();
+    }
+
+    $delete_properties->execute();
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+
+    $res_a = db_query('SELECT choice_penalty FROM {quiz_matching_properties}
+            WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
+
+    if (is_array($res_a)) {
+      $props = array_merge($props, $res_a);
+    }
+
+    //$sql = "SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = %d AND vid = %d";
+    $query = db_query('SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+    while ($result = $query->fetch()) {
+      $props['match'][] = array(
+          'match_id' => $result->match_id,
+          'question' => $result->question,
+          'answer'   => $result->answer,
+          'feedback' => $result->feedback,
+      );
+    }
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Implementation of getNodeView
+   *
+   * @see QuizQuestion#getNodeView()
+   */
+  public function getNodeView() {
+    $content = parent::getNodeView();
+
+    list($matches, $select_option) = $this->getSubquestions();
+    $subquestions = array();
+    if ($this->viewCanRevealCorrect()) {
+      foreach ($matches as $match) {
+        $subquestions[] = array(
+            'question' => $match['question'],
+            'correct'  => $match['answer'],
+            'feedback' => $match['feedback']
+        );
+      }
+    }
+    else {
+      // shuffle the answer column
+      foreach ($matches as $match) {
+        $sub_qs[] = $match['question'];
+        $sub_as[] = $match['answer'];
+      }
+      shuffle($sub_as);
+      foreach ($sub_qs as $i => $sub_q) {
+        $subquestions[] = array(
+            'question' => $sub_q,
+            'random'   => $sub_as[$i],
+        );
+      }
+    }
+    $content['answers'] = array(
+        '#markup' => theme('matching_match_node_view', array('subquestions' => $subquestions)),
+        '#weight' => 2,
+    );
+    return $content;
+  }
+
+  /**
+   * Implementation of getAnsweringForm
+   *
+   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $form = parent::getAnsweringForm($form_state, $result_id);
+
+    if (isset($result_id)) {
+      // The question has already been answered. We load the answers
+      $response = new MatchingResponse($result_id, $this->node);
+      $responses = $response->getResponse();
+    }
+    list($matches, $select_option) = $this->getSubquestions();
+    //$form['#theme'] = 'matching_subquestion_form';
+    foreach ($matches as $match) {
+      $form[$match['match_id']] = array(
+          '#title'   => $match['question'],
+          '#type'    => 'select',
+          '#options' => $this->customShuffle($select_option),
+      );
+      if ($responses) {
+        // If this question already has been answered
+        $form[$match['match_id']]['#default_value'] = $responses[$match['match_id']];
+      }
+    }
+    if (variable_get('quiz_matching_shuffle_options', TRUE)) {
+      $form = $this->customShuffle($form);
+    }
+    $form['scoring_info'] = array(
+        '#markup' => '<p><em>' . t('You lose points by selecting incorrect options. You may leave an option blank to avoid losing points.') . '</em></p>',
+    );
+    return $form;
+  }
+
+  /**
+   * Question response validator.
+   */
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+    $question_answer = $form_state['values']['question'][$this->node->nid];
+    foreach ($question_answer as $value) {
+      if ($value != 'def') {
+        return TRUE;
+      }
+    }
+    form_set_error('', t('You need to match at least one of the items.'));
+  }
+
+  /**
+   * Shuffles an array, but keeps the keys, and makes sure the first element is the default element
+   *
+   * @param $array
+   *  Array to be shuffled
+   * @return
+   *  A shuffled version of the array with $array['def'] = '' as the first element
+   */
+  private function customShuffle(array $array = array()) {
+    $new_array = array();
+    $new_array['def'] = '';
+    while (count($array)) {
+      $element = array_rand($array);
+      $new_array[$element] = $array[$element];
+      unset($array[$element]);
+    }
+    return $new_array;
+  }
+
+  /**
+   * Helper function to fetch subquestions
+   *
+   * @return
+   *  Array with two arrays, matches and selected options
+   */
+  public function getSubquestions() {
+    $matches = $select_option = array();
+    //$sql = "SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = %d AND vid = %d";
+    $query = db_query('SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+    while ($result = $query->fetch()) {
+      $matches[] = array(
+          'match_id' => $result->match_id,
+          'question' => $result->question,
+          'answer'   => $result->answer,
+          'feedback' => $result->feedback,
+      );
+      $select_option[$result->match_id] = $result->answer;
+    }
+    return array($matches, $select_option);
+  }
+
+  /**
+   * Implementation of getBodyFieldTitle
+   *
+   * @see QuizQuestion#getBodyFieldTitle()
+   */
+  public function getBodyFieldTitle() {
+    return t('Instruction');
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm($form_state)
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    // Get the nodes settings, users settings or default settings
+    $default_settings = $this->getDefaultAltSettings();
+
+    $form['settings'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Settings'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => FALSE,
+    );
+    $form['settings']['choice_penalty'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Penalty for guessing'),
+        '#description'   => t('Subtract 1 point from the users score for each incorrect match. Scores cannot go below 0.'),
+        '#default_value' => $default_settings['choice_penalty'],
+        '#parents'       => array('choice_penalty'),
+    );
+
+    $form['match'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Answer'),
+        '#weight'      => -4,
+        '#tree'        => TRUE,
+        '#theme'       => 'matching_question_form',
+        '#description' => t('Write your pairs in the question and answer columns. For the user the question will be fixed and the answers will be shown as alternatives in a dropdown box.'),
+    );
+    for ($i = 1; $i <= variable_get('quiz_matching_form_size', 5); ++$i) {
+      $form['match'][$i] = array(
+          '#type'  => 'fieldset',
+          '#title' => t('Question ' . $i),
+      );
+      $form['match'][$i]['match_id'] = array(
+          '#type'          => 'value',
+          '#default_value' => isset($this->node->match[$i - 1]['match_id']) ? $this->node->match[$i - 1]['match_id'] : ''
+      );
+      $form['match'][$i]['question'] = array(
+          '#type'          => 'textarea',
+          '#rows'          => 2,
+          '#default_value' => isset($this->node->match[$i - 1]['question']) ? $this->node->match[$i - 1]['question'] : '',
+          '#required'      => $i < 3,
+      );
+      $form['match'][$i]['answer'] = array(
+          '#type'          => 'textarea',
+          '#rows'          => 2,
+          '#default_value' => isset($this->node->match[$i - 1]['answer']) ? $this->node->match[$i - 1]['answer'] : '',
+          '#required'      => $i < 3,
+      );
+
+      $form['match'][$i]['feedback'] = array(
+          '#type'          => 'textarea',
+          '#rows'          => 2,
+          '#default_value' => isset($this->node->match[$i - 1]['feedback']) ? $this->node->match[$i - 1]['feedback'] : ''
+      );
+    }
+    return $form;
+  }
+
+  /**
+   * Helper function provding the default settings for the creation form.
+   *
+   * @return
+   *  Array with the default settings
+   */
+  private function getDefaultAltSettings() {
+    // If the node is being updated the default settings are those stored in the node
+    if (isset($this->node->nid)) {
+      $settings['choice_penalty'] = $this->node->choice_penalty;
+    }
+    // The user is creating his first matching node
+    else {
+      $settings['choice_penalty'] = 0;
+    }
+    return $settings;
+  }
+
+  /**
+   * Implementation of getMaximumScore
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    $to_return = 0;
+    foreach ($this->node->match as $match) {
+      if (empty($match['question']) || empty($match['answer'])) {
+        continue;
+      }
+      $to_return++;
+    }
+    // The maximum score = the number of sub-questions
+    return $to_return;
+  }
+
+  /**
+   * Get the correct answers for this question
+   *
+   * @return
+   *  Array of correct answers
+   */
+  public function getCorrectAnswer() {
+    $correct_answers = array();
+    $query = db_query('SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+    while ($result = $query->fetch()) {
+      $correct_answers[$result->match_id] = array(
+          'match_id' => $result->match_id,
+          'question' => $result->question,
+          'answer'   => $result->answer,
+          'feedback' => $result->feedback,
+      );
+    }
+    return $correct_answers;
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class MatchingResponse extends QuizQuestionResponse {
+
+  /**
+   * Constructor
+   */
+  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
+    parent::__construct($result_id, $question_node, $answer);
+    if (!isset($answer)) {
+      $res = db_query('SELECT ua.answer, score, ua.match_id FROM {quiz_matching_user_answers} ua
+              JOIN {quiz_matching_node} n ON n.match_id = ua.match_id
+              WHERE n.nid = :nid AND n.vid = :vid AND ua.result_id = :result_id', array(':nid' => $question_node->nid, ':vid' => $question_node->vid, ':result_id' => $result_id));
+      $this->answer = array();
+      while ($obj = $res->fetch()) {
+        $this->answer[$obj->match_id] = $obj->answer;
+      }
+    }
+    $this->is_correct = $this->isCorrect();
+  }
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+    if (!isset($this->answer) || !is_array($this->answer)) {
+      return;
+    }
+    $insert = db_insert('quiz_matching_user_answers')->fields(array('match_id', 'result_id', 'answer', 'score'));
+    foreach ($this->answer as $key => $value) {
+      $insert->values(array(
+          'match_id'  => $key,
+          'result_id' => $this->result_id,
+          'answer'    => (int) $value,
+          'score'     => ($key == $value) ? 1 : 0,
+      ));
+    }
+    $insert->execute();
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+    $match_id = db_query('SELECT match_id FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->question->nid, ':vid' => $this->question->vid))->fetchCol();
+    db_delete('quiz_matching_user_answers')
+      ->condition('match_id', is_array($match_id) ? $match_id : array(0), 'IN')
+      ->condition('result_id', $this->result_id)
+      ->execute();
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    $wrong_answer = 0;
+    $correct_answer = 0;
+    $user_answers = isset($this->answer['answer']) ? $this->answer['answer'] : $this->answer;
+    $MatchingQuestion = new MatchingQuestion($this->question);
+    $correct_answers = $MatchingQuestion->getCorrectAnswer();
+    foreach ((array) $user_answers as $key => $value) {
+      if ($value != 0 && $correct_answers[$key]['answer'] == $correct_answers[$value]['answer']) {
+        $correct_answer++;
+      }
+      elseif ($value == 0 || $value == 'def') {
+
+      }
+      else {
+        $wrong_answer++;
+      }
+    }
+
+    $score = $correct_answer;
+    if ($this->question->choice_penalty) {
+      $score -= $wrong_answer;
+    }
+
+    return $score < 0 ? 0 : $score;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer;
+  }
+
+  /**
+   * Implementation of getReportFormResponse
+   */
+  public function getReportFormResponse() {
+    $data = array();
+    $answers = $this->question->answers[0]['answer'];
+    $solution = $this->quizQuestion->getSubquestions();
+    foreach ($this->question->match as $match) {
+
+      $data[] = array(
+          'choice'            => $match['question'],
+          'attempt'           => !empty($answers[$match['match_id']]) ? $solution[1][$answers[$match['match_id']]] : '',
+          'correct'           => $answers[$match['match_id']] == $match['match_id'] ? theme('quiz_answer_result', array('type' => 'correct')) : theme('quiz_answer_result', array('type' => 'incorrect')),
+          'score'             => $answers[$match['match_id']] == $match['match_id'] ? 1 : 0,
+          'answer_feedback'   => $match['feedback'],
+          'question_feedback' => 'Question feedback',
+          'solution'          => $solution[1][$match['match_id']],
+          'quiz_feedback'     => t('@quiz feedback', array('@quiz' => QUIZ_NAME)),
+      );
+    }
+
+    return $data;
+  }
+
+}
diff --git a/modules/question_types/matching/matching.info b/modules/question_types/matching/matching.info
new file mode 100644
index 0000000..a8dd1c8
--- /dev/null
+++ b/modules/question_types/matching/matching.info
@@ -0,0 +1,9 @@
+name = Matching question
+package = Quiz Question
+description = Provide a way to create matching type of questions.
+core = 7.x
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = matching.classes.inc
+files[] = tests/MatchingUnitTest.test
diff --git a/modules/question_types/matching/matching.install b/modules/question_types/matching/matching.install
new file mode 100644
index 0000000..9273b5c
--- /dev/null
+++ b/modules/question_types/matching/matching.install
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * @file
+ * matching install file.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function matching_install() {
+  // Add body field to matching node
+  quiz_question_add_body_field('matching');
+
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_install_schema('matching')
+  variable_set('node_options_matching', array('status'));
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function matching_uninstall() {
+
+  // Delete tables
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_uninstall_schema('matching')
+
+  // Delete data from other tables
+
+  // Clear the cache.
+  cache_clear_all('variables', 'cache');
+  drupal_set_message(t('The Matching module has been uninstalled and related data has been deleted.'));
+}
+
+/**
+ * Implements hook_schema().
+ */
+function matching_schema() {
+  // Properties for a question nodes go in here:
+  $schema['quiz_matching_properties'] = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'choice_penalty' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array(
+      'nid', 'vid'
+    ),
+  );
+
+  $schema['quiz_matching_node'] = array(
+    'fields' => array(
+      'match_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+      ),
+      'answer' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+      ),
+      'feedback' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('match_id'),
+    'indexes' => array(
+      'question_id' => array('nid', 'vid'),
+    ),
+  );
+
+  // User answers go in here.
+  $schema['quiz_matching_user_answers'] = array(
+    'fields' => array(
+      'answer_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+
+      'match_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'score' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'answer' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('answer_id'),
+  );
+
+  return $schema;
+}
+
+/**
+ * Implements hook_update_N()
+ */
+
+/**
+ * Add a table to store properties for the matching questions
+ */
+function matching_update_7400() {
+  $schema = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'choice_penalty' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array(
+      'nid', 'vid'
+    ),
+  );
+
+  db_create_table('quiz_matching_properties', $schema);
+
+  // Insert default properties for all of the existing matching questions
+  $result = db_query("SELECT nid, vid FROM {node} WHERE type='matching'");
+
+  foreach ($result as $question) {
+    db_insert('quiz_matching_properties')
+          ->fields(array(
+            'nid' => $question->nid,
+            'vid' => $question->vid,
+            'choice_penalty' => 0,
+          ))
+          ->execute();
+  }
+}
diff --git a/modules/question_types/matching/matching.module b/modules/question_types/matching/matching.module
new file mode 100644
index 0000000..55593bc
--- /dev/null
+++ b/modules/question_types/matching/matching.module
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Matching question type for quiz module
+ *
+ * Allows the creation of matching questions, which associate one term
+ * with another
+ */
+
+/**
+ * Implements hook_help().
+ */
+function matching_help($path, $args) {
+  switch ($path) {
+    case 'admin/modules#description':
+      return t('Matching question type for quiz module.');
+    case 'node/add#matching':
+    case 'admin/help#matching':
+      return t('A question type for the quiz module: allows you to create matching type questions, which connect terms with one another.');
+    default:
+      break;
+  }
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function matching_quiz_question_info() {
+  return array(
+    'matching' => array(
+      'name' => t('Matching'),
+      'description' => t('Matching question type for quiz module. A question type for the quiz module: allows you to create matching type questions, which connect terms with one another.'),
+      'question provider' => 'MatchingQuestion',
+      'response provider' => 'MatchingResponse',
+      'module' => 'quiz_question', // All wrapper functions are in that module.
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ *
+ * @return FAPI array
+ */
+function matching_quiz_question_config() {
+  $form['quiz_matching_form_size'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Match Question Size'),
+    '#description' => t('Number of questions allowed to wrap under a matching type question.'),
+    '#default_value' => variable_get('quiz_matching_form_size', 5),
+  );
+  $form['quiz_matching_shuffle_options'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Shuffle Matching Questions'),
+    '#default_value' => variable_get('quiz_matching_shuffle_options', TRUE),
+    '#description' => t('If checked matching questions will be shuffled'),
+  );
+  $form['#validate'][] = 'matching_config_validate';
+  return $form;
+}
+
+/**
+ * Validate the matching config form values
+ */
+function matching_config_validate($form, $form_state) {
+  if (!_quiz_is_int($form_state['values']['quiz_matching_form_size'], 2, 50)) {
+    form_set_error('quiz_matching_form_size', t('The number of questions must be between 2 and 50'));
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function matching_theme() {
+  return array(
+    'matching_question_form' => array(
+      'render element' => 'form',
+      'path' => drupal_get_path('module', 'matching') . '/theme',
+      'file' => 'matching.theme.inc',
+    ),
+    'matching_response' => array(
+      'variables' => array('metadata' => NULL, 'data' => NULL),
+      'path' => drupal_get_path('module', 'matching') . '/theme',
+      'file' => 'matching.theme.inc',
+    ),
+    'matching_subquestion_form' => array(
+      'render element' => 'form',
+      'path' => drupal_get_path('module', 'matching') . '/theme',
+      'file' => 'matching.theme.inc',
+    ),
+    'matching_match_node_view' => array(
+      'variables' => array('subquestions' => NULL),
+      'path' => drupal_get_path('module', 'matching') . '/theme',
+      'file' => 'matching.theme.inc',
+    ),
+    'matching_answering_form' => array(
+      'render element' => 'form',
+      'path' => drupal_get_path('module', 'matching') . '/theme',
+      'template' => 'matching-answering-form',
+    ),
+
+  );
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function matching_field_extra_fields() {
+  $extra['node']['matching'] = array(
+    'form' => array(
+      'settings' => array(
+        'label' => t('Settings'),
+        'description' => t('The settings for the matching question.'),
+        'weight' => -5,
+      ),
+      'match' => array(
+        'label' => t('Answer'),
+        'description' => t('The sets of matches for this question.'),
+        'weight' => -4,
+      ),
+    ),
+  );
+  return $extra;
+}
diff --git a/modules/question_types/matching/tests/MatchingUnitTest.test b/modules/question_types/matching/tests/MatchingUnitTest.test
new file mode 100644
index 0000000..4c5c86d
--- /dev/null
+++ b/modules/question_types/matching/tests/MatchingUnitTest.test
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Test class for matching questions.
+ */
+class MatchingTestCase extends QuizQuestionTestCase {
+
+  // initializing attributes
+  var $question_node_type = 'matching';
+  var $title = '';
+  var $body = '';
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Matching'),
+        'description' => t('Unit test for matching question type.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  public function setUp() {
+    parent::setUp(array('matching'));
+  }
+
+  /**
+   * function to create true false type question node for testing.
+   */
+  public function testCreateQuizQuestion() {
+    // matching question node attributes
+    $this->title = $this->randomName();
+    $this->body = $this->randomName();
+
+    // array of node attributes to create a test node
+    $settings = array(
+        'type'           => $this->question_node_type,
+        'title'          => $this->title,
+        'body'           => array(LANGUAGE_NONE => array(array('value' => $this->body))),
+        'revisions'      => TRUE,
+        'choice_penalty' => 0,
+    );
+
+    $this->match = array();
+    $form_size = variable_get('quiz_matching_form_size', 5);
+    // generate rand match question, answer and feedback
+    for ($i = 1; $i < $form_size; $i++) {
+      $settings['match'][$i]['question'] = $this->match[$i]['question'] = $this->randomName();
+      $setting['match'][$i]['answer'] = $this->match[$i]['answer'] = $this->randomName();
+      $settings['match'][$i]['feedback'] = $this->match[$i]['feedback'] = $this->randomName();
+    }
+
+    // create drupal node
+    $node = $this->drupalCreateNode($settings);
+    $load = node_load($node->nid);
+
+    // @todo verify save
+
+    return $load;
+  }
+
+}
diff --git a/modules/question_types/matching/theme/matching-answering-form.tpl.php b/modules/question_types/matching/theme/matching-answering-form.tpl.php
new file mode 100644
index 0000000..5410a07
--- /dev/null
+++ b/modules/question_types/matching/theme/matching-answering-form.tpl.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Handles the layout of the matching answering form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render($form);
+
+?>
\ No newline at end of file
diff --git a/modules/question_types/matching/theme/matching.theme.inc b/modules/question_types/matching/theme/matching.theme.inc
new file mode 100644
index 0000000..24c329e
--- /dev/null
+++ b/modules/question_types/matching/theme/matching.theme.inc
@@ -0,0 +1,99 @@
+<?php
+
+
+/**
+ * @file
+ *   Themes for the matching module.
+ */
+
+/**
+ * Theme the matching question form.
+ *
+ * @param $form
+ *   drupal form array
+ */
+function theme_matching_question_form($variables) {
+  $form = $variables['form'];
+  $rows = array();
+  $header = array(
+    'question' => t('Question'),
+    'answer' => t('Correct answer'),
+    'feedback' => t('Feedback'),
+  );
+  foreach (element_children($form) as $key) {
+    $rows[] = array(
+      'question' => drupal_render($form[$key]['question']),
+      'answer' => drupal_render($form[$key]['answer']),
+      'feedback' => drupal_render($form[$key]['feedback']),
+    );
+  }
+  // Theme output and display to screen.
+  return theme('table', array('header' => $header, 'rows' => $rows));
+}
+
+/**
+ * Theme the answering form
+ */
+function theme_matching_subquestion_form($variables) {
+  $form = $variables['form'];
+  $out = '<table class = "matching-tbl">';
+  foreach ($form as $key => $value) {
+    if (is_int($key)) {
+      $out .= '<tr><td class = "matching-question">' . $value['#question'];
+      $out .= '</td><td class = "matching-select">' . drupal_render_children($value) . '</td></tr>';
+    }
+  }
+  $out .= '</table>';
+  return $out;
+}
+
+/**
+ * Theme the answer part of the node view
+ *
+ * @param $subquestions
+ *  Array with all the data in the subquestions.
+ */
+function theme_matching_match_node_view($variables) {
+  $subquestions = $variables['subquestions'];
+  // We know the correct answer for each subquestion
+  if (isset($subquestions[0]['correct'])) {
+    $header = array(t('Subquestion'), t('Correct match'), t('Feedback'));
+  }
+  // We do not know the correct answer for each subquestion
+  else {
+    $header = array(t('Subquestion'), t('Possible match'));
+  }
+  return theme('table', array('header' => $header, 'rows' => $subquestions));
+}
+
+/**
+ * Theme the contents of the matching response form
+ *
+ * @param $metadata
+ *  Can be used as a table header
+ * @param $data
+ *  Can be used as table rows
+ */
+function theme_matching_response($variables) {
+  $metadata = $variables['metadata'];
+  $data = $variables['data'];
+  if (isset($data[0]['is_correct'])) {
+    foreach ($data as $id => $match_data) {
+      if ($match_data['is_correct'] == 0) {
+        $theme = 'quiz_score_incorrect';
+      }
+      if ($match_data['is_correct'] == 1) {
+        $theme = 'quiz_score_correct';
+      }
+      if ($match_data['is_correct'] == 2) {
+        $theme = 'quiz_score_unknown';
+      }
+      // TODO Please change this theme call to use an associative array for the $variables parameter.
+      $data[$id]['is_correct'] = array(
+        'data' => theme($theme),
+        'class' => 'quiz-summary-qcell',
+      );
+    }
+  }
+  return theme('table', array('header' => $metadata, 'rows' => $data));
+}
diff --git a/modules/question_types/multichoice/README.txt b/modules/question_types/multichoice/README.txt
new file mode 100644
index 0000000..256dd47
--- /dev/null
+++ b/modules/question_types/multichoice/README.txt
@@ -0,0 +1,45 @@
+
+/**
+ * @file
+ * README file for Quiz Multichoice.
+ */
+
+Quiz Multichoice
+Allows quiz creators to make a multiple choice question type.
+
+Sponsored by: Norwegian Centre for Telemedicine
+Code: falcon
+
+
+CONTENTS
+--------
+
+1.  Introduction
+2.  Installation
+3.  Configuration
+
+----
+1. Introduction
+This module is an attempt to make it easy to multiple choice questions using the quiz module.
+
+The multichoice module lets the user create single answer questions and multiple answer questions.
+Advanced feedback and scoring options are also available.
+
+The multichoice module is based on the OO framework of the quiz project.
+
+----
+2. Installation
+
+To install, unpack the module to your modules folder, and simply enable the module at Admin > Build > Modules.
+
+Several database tables prefixed with quiz_multichoice are installed to have a separate storage for this module.
+
+----
+3.  Configuration
+Settings are to be found here: admin/quiz/questions_settings
+
+The only available setting is the number of default alternatives. This decides how many alternatives
+will be shown on the node-form by default. The question creator can add more by pushing the
+add more alternatives button, and he can always leave some alternatives blank if not all the shown
+alternatives are needed.
+
diff --git a/modules/question_types/multichoice/js/multichoice.alternative.js b/modules/question_types/multichoice/js/multichoice.alternative.js
new file mode 100644
index 0000000..56768f7
--- /dev/null
+++ b/modules/question_types/multichoice/js/multichoice.alternative.js
@@ -0,0 +1,25 @@
+(function ($, Drupal) {
+
+  Drupal.behaviors.multichoiceAlternativeBehavior = {
+    attach: function (context, settings) {
+      $('.multichoice_row', context).once()
+              .filter(':has(:checkbox:checked)')
+              .addClass('selected').end()
+              .click(function (event) {
+                $(this).toggleClass('selected');
+                if (event.target.type !== 'checkbox') {
+                  $(':checkbox', this).attr('checked', function () {
+                    return !this.checked;
+                  });
+
+                  $(':radio', this).attr('checked', true);
+                  if ($(':radio', this).html() != null) {
+                    $('.multichoice_row').removeClass('selected');
+                    $(this).addClass('selected');
+                  }
+                }
+              });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/modules/question_types/multichoice/js/multichoice.js b/modules/question_types/multichoice/js/multichoice.js
new file mode 100644
index 0000000..90a4499
--- /dev/null
+++ b/modules/question_types/multichoice/js/multichoice.js
@@ -0,0 +1,91 @@
+var Multichoice = Multichoice || {};
+
+(function ($) {
+
+  Multichoice.refreshScores = function (checkbox, scoring) {
+    var prefix = '#' + Multichoice.getCorrectIdPrefix(checkbox.id);
+    if (checkbox.checked) {
+      $(prefix + 'score-if-chosen').val('1');
+      $(prefix + 'score-if-not-chosen').val('0');
+    }
+    else {
+      if (scoring == 0) {
+        $(prefix + 'score-if-not-chosen').val('0');
+        if ($('#edit-choice-multi').attr('checked')) {
+          $(prefix + 'score-if-chosen').val('-1');
+        }
+        else {
+          $(prefix + 'score-if-chosen').val('0');
+        }
+      }
+      else if (scoring == 1) {
+        $(prefix + 'score-if-chosen').val('0');
+        if ($('#edit-choice-multi').attr('checked')) {
+          $(prefix + 'score-if-not-chosen').val('1');
+        }
+        else {
+          $(prefix + 'score-if-not-chosen').val('0');
+        }
+      }
+    }
+  };
+
+  /**
+   * Updates correct checkboxes according to changes of the score values for an alternative
+   *
+   * @param textfield
+   *  The textfield(score) that is being updated
+   */
+  Multichoice.refreshCorrect = function (textfield) {
+    var prefix = '#' + Multichoice.getCorrectIdPrefix(textfield.id);
+    var chosenScore;
+    var notChosenScore;
+
+    // Fetch the score if chosen and score if not chosen values for the active alternative
+    if (Multichoice.isChosen(textfield.id)) {
+      chosenScore = new Number(textfield.value);
+      notChosenScore = new Number($(prefix + 'score-if-not-chosen').val());
+    }
+    else {
+      chosenScore = new Number($(prefix + 'score-if-chosen').val());
+      notChosenScore = new Number(textfield.value);
+    }
+
+    // Set the checked status for the checkbox in the active alternative
+    if (notChosenScore < chosenScore) {
+      $(prefix + 'correct').attr('checked', true);
+    }
+    else {
+      $(prefix + 'correct').attr('checked', false);
+    }
+  };
+
+  /**
+   * Helper function fetching the id prefix for a html id attribute
+   *
+   * @param string
+   *  Html id attribute
+   * @return
+   *  The common prefix for all the alternatives in this alternative fieldset
+   */
+  Multichoice.getCorrectIdPrefix = function (string) {
+    // TODO: Will the regExp below always work?
+    var pattern = new RegExp("^(edit-alternatives-[0-9]{1,2}-)(?:correct|score-if-(?:not-|)chosen)$");
+    pattern.exec(string);
+    return RegExp.lastParen;
+  };
+
+  /**
+   * Checks if the id belongs to the score if chosen textfield
+   *
+   * @param string
+   *  html id attribute of one of the score text fields
+   * @return
+   *  True if the string ends with "score-if-chosen", false otherwise.
+   */
+  Multichoice.isChosen = function (string) {
+    var pattern = new RegExp("score-if-chosen$");
+    return pattern.test(string);
+  };
+
+})(jQuery);
\ No newline at end of file
diff --git a/modules/question_types/multichoice/multichoice.classes.inc b/modules/question_types/multichoice/multichoice.classes.inc
new file mode 100644
index 0000000..989c77f
--- /dev/null
+++ b/modules/question_types/multichoice/multichoice.classes.inc
@@ -0,0 +1,1024 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * The main classes for the multichoice question type.
+ *
+ * These inherit or implement code found in quiz_question.classes.inc.
+ *
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * Based on:
+ * Other question types in the quiz framework.
+ *
+ *
+ *
+ * @file
+ * Question type, enabling the creation of multiple choice and multiple answer questions.
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class MultichoiceQuestion extends QuizQuestion {
+
+  /**
+   * Forgive some possible logical flaws in the user input.
+   */
+  private function forgive() {
+    if ($this->node->choice_multi == 1) {
+      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+        $short = &$this->node->alternatives[$i];
+        // If the scoring data doesn't make sense, use the data from the "correct" checkbox to set the score data
+        if ($short['score_if_chosen'] == $short['score_if_not_chosen'] || !is_numeric($short['score_if_chosen']) || !is_numeric($short['score_if_not_chosen'])) {
+          if (!empty($short['correct'])) {
+            $short['score_if_chosen'] = 1;
+            $short['score_if_not_chosen'] = 0;
+          }
+          else {
+            if (variable_get('multichoice_def_scoring', 0) == 0) {
+              $short['score_if_chosen'] = -1;
+              $short['score_if_not_chosen'] = 0;
+            }
+            elseif (variable_get('multichoice_def_scoring', 0) == 1) {
+              $short['score_if_chosen'] = 0;
+              $short['score_if_not_chosen'] = 1;
+            }
+          }
+        }
+      }
+    }
+    else {
+      // For questions with one, and only one, correct answer, there will be no points awarded for alternatives
+      // not chosen.
+      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+        $short = &$this->node->alternatives[$i];
+        $short['score_if_not_chosen'] = 0;
+        if (isset($short['correct']) && $short['correct'] == 1 && !_quiz_is_int($short['score_if_chosen'], 1)) {
+          $short['score_if_chosen'] = 1;
+        }
+      }
+    }
+  }
+
+  /**
+   * Warn the user about possible user errors
+   */
+  private function warn() {
+    // Count the number of correct answers
+    $num_corrects = 0;
+    for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+      $alt = &$this->node->alternatives[$i];
+      if ($alt['score_if_chosen'] > $alt['score_if_not_chosen']) {
+        $num_corrects++;
+      }
+    }
+    if ($num_corrects == 1 && $this->node->choice_multi == 1 || $num_corrects > 1 && $this->node->choice_multi == 0) {
+      $link_options = array();
+      if (isset($_GET['destination'])) {
+        $link_options['query'] = array('destination' => $_GET['destination']);
+      }
+      $go_back = l(t('go back'), 'node/' . $this->node->nid . '/edit', $link_options);
+      if ($num_corrects == 1) {
+        drupal_set_message(
+          t('Your question allows multiple answers. Only one of the alternatives have been marked as correct. If this wasn\'t intended please !go_back and correct it.', array('!go_back' => $go_back)), 'warning');
+      }
+      else {
+        drupal_set_message(
+          t('Your question doesn\'t allow multiple answers. More than one of the alternatives have been marked as correct. If this wasn\'t intended please !go_back and correct it.', array('!go_back' => $go_back)), 'warning');
+      }
+    }
+  }
+
+  /**
+   * Run check_markup() on the field of the specified choice alternative
+   * @param $alternativeIndex
+   *  The index of the alternative in the alternatives array.
+   * @param $field
+   *  The name of the field we want to check markup on
+   * @param $check_user_access
+   *  Whether or not to check for user access to the filter we're trying to apply
+   * @return HTML markup
+   */
+  private function checkMarkup($alternativeIndex, $field, $check_user_access = FALSE) {
+    $alternative = $this->node->alternatives[$alternativeIndex];
+    return check_markup($alternative[$field]['value'], $alternative[$field]['format']);
+  }
+
+  /**
+   * Implementation of save
+   *
+   * Stores the question in the database.
+   *
+   * @param is_new if - if the node is a new node...
+   * (non-PHPdoc)
+   * @see sites/all/modules/quiz-HEAD/question_types/quiz_question/QuizQuestion#save()
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    $is_new = $is_new || $this->node->revision == 1;
+
+    // Before we save we forgive some possible user errors
+    $this->forgive();
+
+    // We also add warnings on other possible user errors
+    $this->warn();
+
+    if ($is_new) {
+      $id = db_insert('quiz_multichoice_properties')
+        ->fields(array(
+            'nid'            => $this->node->nid,
+            'vid'            => $this->node->vid,
+            'choice_multi'   => $this->node->choice_multi,
+            'choice_random'  => $this->node->choice_random,
+            'choice_boolean' => $this->node->choice_boolean,
+        ))
+        ->execute();
+
+      // TODO: utilize the benefit of multiple insert of DBTNG
+      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+        if (drupal_strlen($this->node->alternatives[$i]['answer']['value']) > 0) {
+          $this->insertAlternative($i);
+        }
+      }
+    }
+    else {
+      db_update('quiz_multichoice_properties')
+        ->fields(array(
+            'choice_multi'   => $this->node->choice_multi,
+            'choice_random'  => $this->node->choice_random,
+            'choice_boolean' => $this->node->choice_boolean,
+        ))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+
+      // We fetch ids for the existing answers belonging to this question
+      // We need to figure out if an existing alternative has been changed or deleted.
+      $res = db_query('SELECT id FROM {quiz_multichoice_answers}
+              WHERE question_nid = :nid AND question_vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+
+      // We start by assuming that all existing alternatives needs to be deleted
+      $ids_to_delete = array();
+      while ($res_o = $res->fetch()) {
+        $ids_to_delete[] = $res_o->id;
+      }
+
+      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+        $short = $this->node->alternatives[$i];
+        if (drupal_strlen($this->node->alternatives[$i]['answer']['value']) > 0) {
+          // If new alternative
+          if (!is_numeric($short['id'])) {
+            $this->insertAlternative($i);
+          }
+          // If existing alternative
+          else {
+            $this->updateAlternative($i);
+            // Make sure this alternative isn't deleted
+            $key = array_search($short['id'], $ids_to_delete);
+            $ids_to_delete[$key] = FALSE;
+          }
+        }
+      }
+      foreach ($ids_to_delete as $id_to_delete) {
+        if ($id_to_delete) {
+          db_delete('quiz_multichoice_answers')
+            ->condition('id', $id_to_delete)
+            ->execute();
+        }
+      }
+    }
+    $this->saveUserSettings();
+  }
+
+  function _normalizeAlternative($alternatives) {
+    $copy = $alternatives;
+    // answer
+    if (is_array($alternatives['answer']) && isset($alternatives['answer']['value'])) {
+      $copy['answer'] = $alternatives['answer']['value'];
+    }
+    // answer format
+    if (is_array($alternatives['answer']) && isset($alternatives['answer']['format'])) {
+      $copy['answer_format'] = $alternatives['answer']['format'];
+    }
+    // feedback if choosen
+    if (is_array($alternatives['feedback_if_chosen']) && isset($alternatives['feedback_if_chosen']['value'])) {
+      $copy['feedback_if_chosen'] = $alternatives['feedback_if_chosen']['value'];
+    }
+    // feedback if choosen foramt
+    if (is_array($alternatives['feedback_if_chosen']) && isset($alternatives['feedback_if_chosen']['format'])) {
+      $copy['feedback_if_chosen_format'] = $alternatives['feedback_if_chosen']['format'];
+    }
+    // feedback if not chosen
+    if (is_array($alternatives['feedback_if_not_chosen']) && isset($alternatives['feedback_if_not_chosen']['value'])) {
+      $copy['feedback_if_not_chosen'] = $alternatives['feedback_if_not_chosen']['value'];
+    }
+    // feedback if not chosen foramt
+    if (is_array($alternatives['feedback_if_not_chosen']) && isset($alternatives['feedback_if_not_chosen']['format'])) {
+      $copy['feedback_if_not_chosen_format'] = $alternatives['feedback_if_not_chosen']['format'];
+    }
+    return $copy;
+  }
+
+  /**
+   * Helper function. Saves new alternatives
+   *
+   * @param $i
+   *  The alternative index
+   */
+  private function insertAlternative($i) {
+    $alternatives = $this->_normalizeAlternative($this->node->alternatives[$i]);
+    db_insert('quiz_multichoice_answers')
+      ->fields(array(
+          'answer'                        => $alternatives['answer'],
+          'answer_format'                 => $alternatives['answer_format'],
+          'feedback_if_chosen'            => $alternatives['feedback_if_chosen'],
+          'feedback_if_chosen_format'     => $alternatives['feedback_if_chosen_format'],
+          'feedback_if_not_chosen'        => $alternatives['feedback_if_not_chosen'],
+          'feedback_if_not_chosen_format' => $alternatives['feedback_if_not_chosen_format'],
+          'score_if_chosen'               => $alternatives['score_if_chosen'],
+          'score_if_not_chosen'           => $alternatives['score_if_not_chosen'],
+          'question_nid'                  => $this->node->nid,
+          'question_vid'                  => $this->node->vid
+      ))
+      ->execute();
+  }
+
+  /**
+   * Helper function. Updates existing alternatives
+   *
+   * @param $i
+   *  The alternative index
+   */
+  private function updateAlternative($i) {
+    $alternatives = $this->_normalizeAlternative($this->node->alternatives[$i]);
+    db_update('quiz_multichoice_answers')
+      ->fields(array(
+          'answer'                        => $alternatives['answer'],
+          'answer_format'                 => $alternatives['answer_format'],
+          'feedback_if_chosen'            => $alternatives['feedback_if_chosen'],
+          'feedback_if_chosen_format'     => $alternatives['feedback_if_chosen_format'],
+          'feedback_if_not_chosen'        => $alternatives['feedback_if_not_chosen'],
+          'feedback_if_not_chosen_format' => $alternatives['feedback_if_not_chosen_format'],
+          'score_if_chosen'               => $alternatives['score_if_chosen'],
+          'score_if_not_chosen'           => $alternatives['score_if_not_chosen'],
+      ))
+      ->condition('id', $alternatives['id'])
+      ->condition('question_nid', $this->node->nid)
+      ->condition('question_vid', $this->node->vid)
+      ->execute();
+  }
+
+  /**
+   * Implementation of validate
+   *
+   * QuizQuestion#validate()
+   */
+  public function validateNode(array &$form) {
+    if ($this->node->choice_multi == 0) {
+      $found_one_correct = FALSE;
+      for ($i = 0; (isset($this->node->alternatives[$i]) && is_array($this->node->alternatives[$i])); $i++) {
+        $short = $this->node->alternatives[$i];
+        if (drupal_strlen($this->checkMarkup($i, 'answer')) < 1) {
+          continue;
+        }
+        if ($short['correct'] == 1) {
+          if ($found_one_correct) {
+            // We don't display an error message here since we allow alternatives to be partially correct
+          }
+          else {
+            $found_one_correct = TRUE;
+          }
+        }
+      }
+      if (!$found_one_correct) {
+        form_set_error('choice_multi', t('You have not marked any alternatives as correct. If there are no correct alternatives you should allow multiple answers.'));
+      }
+    }
+    else {
+      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+        $short = $this->node->alternatives[$i];
+        if (strlen($this->checkMarkup($i, 'answer')) < 1) {
+          continue;
+        }
+        if ($short['score_if_chosen'] < $short['score_if_not_chosen'] && $short['correct']) {
+          form_set_error("alternatives][$i][score_if_not_chosen", t('The alternative is marked as correct, but gives more points if you don\'t select it.'));
+        }
+        elseif ($short['score_if_chosen'] > $short['score_if_not_chosen'] && !$short['correct']) {
+          form_set_error("alternatives][$i][score_if_chosen", t('The alternative is not marked as correct, but gives more points if you select it.'));
+        }
+      }
+    }
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete()
+   */
+  public function delete($only_this_version = FALSE) {
+    $delete_properties = db_delete('quiz_multichoice_properties')->condition('nid', $this->node->nid);
+    $delete_answers = db_delete('quiz_multichoice_answers')->condition('question_nid', $this->node->nid);
+    $delete_results = db_delete('quiz_multichoice_user_answers')->condition('question_nid', $this->node->nid);
+
+    if ($only_this_version) {
+      $delete_properties->condition('vid', $this->node->vid);
+      $delete_answers->condition('question_vid', $this->node->vid);
+      $delete_results->condition('question_vid', $this->node->vid);
+    }
+
+    // Delete from table quiz_multichoice_user_answer_multi
+    if ($only_this_version) {
+      $user_answer_id = db_query('SELECT id FROM {quiz_multichoice_user_answers} WHERE question_nid = :nid AND question_vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchCol();
+    }
+    else {
+      $user_answer_id = db_query('SELECT id FROM {quiz_multichoice_user_answers} WHERE question_nid = :nid', array(':nid' => $this->node->nid))->fetchCol();
+    }
+
+    if (!empty($user_answer_id)) {
+      db_delete('quiz_multichoice_user_answer_multi')
+        ->condition('user_answer_id', $user_answer_id, 'IN')
+        ->execute();
+    }
+    $delete_properties->execute();
+    $delete_answers->execute();
+    $delete_results->execute();
+    parent::delete($only_this_version);
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties) && !empty($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+
+    $res_a = db_query('SELECT choice_multi, choice_random, choice_boolean FROM {quiz_multichoice_properties}
+            WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
+
+    if (is_array($res_a)) {
+      $props = array_merge($props, $res_a);
+    }
+
+    // Load the answers
+    $res = db_query('SELECT id, answer, answer_format, feedback_if_chosen, feedback_if_chosen_format,
+            feedback_if_not_chosen, feedback_if_not_chosen_format, score_if_chosen, score_if_not_chosen
+            FROM {quiz_multichoice_answers}
+            WHERE question_nid = :question_nid AND question_vid = :question_vid
+            ORDER BY id', array(':question_nid' => $this->node->nid, ':question_vid' => $this->node->vid));
+    $props['alternatives'] = array(); // init array so it can be iterated even if empty
+    while ($res_arr = $res->fetchAssoc()) {
+      $props['alternatives'][] = array(
+          'id'                     => $res_arr['id'],
+          'answer'                 => array(
+              'value'  => $res_arr['answer'],
+              'format' => $res_arr['answer_format'],
+          ),
+          'feedback_if_chosen'     => array(
+              'value'  => $res_arr['feedback_if_chosen'],
+              'format' => $res_arr['feedback_if_chosen_format'],
+          ),
+          'feedback_if_not_chosen' => array(
+              'value'  => $res_arr['feedback_if_not_chosen'],
+              'format' => $res_arr['feedback_if_not_chosen_format'],
+          ),
+          'score_if_chosen'        => $res_arr['score_if_chosen'],
+          'score_if_not_chosen'    => $res_arr['score_if_not_chosen'],
+      );
+    }
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Implementation of getNodeView
+   *
+   * @see QuizQuestion#getNodeView()
+   */
+  public function getNodeView() {
+    $content = parent::getNodeView();
+    if ($this->node->choice_random) {
+      $this->shuffle($this->node->alternatives);
+    }
+    $content['answers'] = array(
+        '#markup' => theme('multichoice_answer_node_view', array('alternatives' => $this->node->alternatives, 'show_correct' => $this->viewCanRevealCorrect())),
+        '#weight' => 2,
+    );
+
+    return $content;
+  }
+
+  /**
+   * Generates the question element.
+   *
+   * This is called whenever a question is rendered, either
+   * to an administrator or to a quiz taker.
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $element = parent::getAnsweringForm($form_state, $result_id);
+
+    /* We use an array looking key to be able to store multiple answers in tries.
+     * At the moment all user answers have to be stored in tries. This is something we plan
+     * to fix in quiz 5.x.
+     */
+    $element['#theme'] = 'multichoice_alternative';
+    if (isset($result_id)) {
+      // This question has already been answered. We load the answer.
+      $response = new MultichoiceResponse($result_id, $this->node);
+    }
+    for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+      $short = $this->node->alternatives[$i];
+      $answer_markup = check_markup($short['answer']['value'], $short['answer']['format']);
+      if (drupal_strlen($answer_markup) > 0) {
+        $element['user_answer']['#options'][$short['id']] = $answer_markup;
+      }
+    }
+    if ($this->node->choice_random) {
+      // We save the choice order so that the order will be the same in the answer report
+      $element['choice_order'] = array(
+          '#type'  => 'hidden',
+          '#value' => implode(',', $this->shuffle($element['user_answer']['#options'])),
+      );
+    }
+    if ($this->node->choice_multi) {
+      $element['user_answer']['#type'] = 'checkboxes';
+      $element['user_answer']['#title'] = t('Choose all that apply');
+      if (isset($response)) {
+        if (is_array($response->getResponse())) {
+          $element['#default_value'] = $response->getResponse();
+        }
+      }
+    }
+    else {
+      $element['user_answer']['#type'] = 'radios';
+      $element['user_answer']['#title'] = t('Choose one');
+      if (isset($response)) {
+        $selection = $response->getResponse();
+        if (is_array($selection)) {
+          $element['user_answer']['#default_value'] = array_pop($selection);
+        }
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Custom shuffle function. It keeps the array key - value relationship intact
+   *
+   * @param array $array
+   * @return unknown_type
+   */
+  private function shuffle(array &$array) {
+    $newArray = array();
+    $toReturn = array_keys($array);
+    shuffle($toReturn);
+    foreach ($toReturn as $key) {
+      $newArray[$key] = $array[$key];
+    }
+    $array = $newArray;
+    return $toReturn;
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm()
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    $form = array();
+    $type = node_type_get_type($this->node);
+    // We add #action to the form because of the use of ajax
+    $options = array();
+    $get = $_GET;
+    unset($get['q']);
+    if (!empty($get)) {
+      $options['query'] = $get;
+    }
+
+    // TODO The second parameter to this function call should be an array.
+    $action = url('node/add/' . $type->type, $options);
+    if (isset($this->node->nid)) {
+      // TODO The second parameter to this function call should be an array.
+      $action = url('node/' . $this->node->nid . '/edit', $options);
+    }
+    $form['#action'] = $action;
+
+    $form['alternatives'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Answer'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => FALSE,
+        '#weight'      => -4,
+        '#tree'        => TRUE,
+    );
+
+    // Get the nodes settings, users settings or default settings
+    $default_settings = $this->getDefaultAltSettings();
+
+    $form['alternatives']['settings'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Settings'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => FALSE,
+        '#description' => t('Your settings will be remembered.'),
+    );
+    $form['alternatives']['settings']['choice_multi'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Multiple answers'),
+        '#description'   => t('Allow any number of answers(checkboxes are used). If this box is not checked, one, and only one answer is allowed(radiobuttons are used).'),
+        '#default_value' => $default_settings['choice_multi'],
+        '#parents'       => array('choice_multi'),
+    );
+    $form['alternatives']['settings']['choice_random'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Random order'),
+        '#description'   => t('Present alternatives in random order when @quiz is being taken.', array('@quiz' => QUIZ_NAME)),
+        '#default_value' => $default_settings['choice_random'],
+        '#parents'       => array('choice_random'),
+    );
+    $form['alternatives']['settings']['choice_boolean'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Simple scoring'),
+        '#description'   => t('Give max score if everything is correct. Zero points otherwise.'),
+        '#default_value' => $default_settings['choice_boolean'],
+        '#parents'       => array('choice_boolean'),
+    );
+
+    // Add helper tag where we will place the input selector for all the textareas.
+    $form['alternatives']['input_format_all'] = array(
+        '#markup' => '<DIV id="input-all-ph"></DIV>',
+    );
+
+    $form['alternatives']['#theme'][] = 'multichoice_creation_form';
+    $i = 0;
+
+    // choice_count might be stored in the form_state after an ajax callback
+    if (isset($form_state['choice_count'])) {
+      $choice_count = $form_state['choice_count'];
+    }
+    else {
+      $choice_count = max(variable_get('multichoice_def_num_of_alts', 2), isset($this->node->alternatives) ? count($this->node->alternatives) : 0);
+    }
+
+    for (; $i < $choice_count; $i++) {
+      $short = isset($this->node->alternatives[$i]) ? $this->node->alternatives[$i] : NULL;
+      $form['alternatives'][$i] = array(
+          '#type'        => 'fieldset',
+          '#title'       => t('Alternative !i', array('!i' => ($i + 1))),
+          '#collapsible' => TRUE,
+          '#collapsed'   => FALSE,
+        // - The two first alternatives won't be collapsed.
+        // - Populated alternatives won't be collapsed
+      );
+      $form['alternatives'][$i]['#theme'][] = 'multichoice_alternative_creation';
+
+      if (is_array($short)) {
+        if ($short['score_if_chosen'] == $short['score_if_not_chosen']) {
+          $correct_default = isset($short['correct']) ? $short['correct'] : FALSE;
+        }
+        else {
+          $correct_default = $short['score_if_chosen'] > $short['score_if_not_chosen'];
+        }
+      }
+      else {
+        $correct_default = FALSE;
+      }
+      $form['alternatives'][$i]['correct'] = array(
+          '#type'          => 'checkbox',
+          '#title'         => t('Correct'),
+          '#default_value' => $correct_default,
+          '#attributes'    => array('onchange' => 'Multichoice.refreshScores(this, ' . variable_get('multichoice_def_scoring', 0) . ')'),
+      );
+      // We add id to be able to update the correct alternatives if the node is updated, without destroying
+      // existing answer reports
+      $form['alternatives'][$i]['id'] = array(
+          '#type'  => 'value',
+          '#value' => $short['id'],
+      );
+
+      $form['alternatives'][$i]['answer'] = array(
+          '#type'          => 'text_format',
+          '#base_type'     => 'textarea',
+          '#title'         => t('Alternative !i', array('!i' => ($i + 1))),
+          '#default_value' => $short['answer']['value'],
+          '#required'      => $i < 2,
+          '#format'        => isset($short['answer']['format']) ? $short['answer']['format'] : NULL,
+          '#rows'          => 3,
+      );
+
+      $form['alternatives'][$i]['advanced'] = array(
+          '#type'        => 'fieldset',
+          '#title'       => t('Advanced options'),
+          '#collapsible' => TRUE,
+          '#collapsed'   => TRUE,
+      );
+      $form['alternatives'][$i]['advanced']['feedback_if_chosen'] = array(
+          '#type'          => 'text_format',
+          '#base_type'     => 'textarea',
+          '#title'         => t('Feedback if chosen'),
+          '#description'   => t('This feedback is given to users who chooses this alternative.'),
+          '#parents'       => array('alternatives', $i, 'feedback_if_chosen'),
+          '#default_value' => $short['feedback_if_chosen']['value'],
+          '#format'        => isset($short['feedback_if_chosen']['format']) ? $short['feedback_if_chosen']['format'] : NULL,
+          '#rows'          => 3,
+      );
+      // We add 'helper' to trick the current version of the wysiwyg module to add an editor to several
+      // textareas in the same fieldset
+      $form['alternatives'][$i]['advanced']['helper']['feedback_if_not_chosen'] = array(
+          '#type'          => 'text_format',
+          '#base_type'     => 'textarea',
+          '#title'         => t('Feedback if not chosen'),
+          '#description'   => t('This feedback is given to users who doesn\'t choose this alternative.'),
+          '#parents'       => array('alternatives', $i, 'feedback_if_not_chosen'),
+          '#default_value' => $short['feedback_if_not_chosen']['value'],
+          '#format'        => isset($short['feedback_if_not_chosen']['format']) ? $short['feedback_if_not_chosen']['format'] : NULL,
+          '#rows'          => 3,
+      );
+      $default_value = isset($this->node->alternatives[$i]['score_if_chosen']) ? $this->node->alternatives[$i]['score_if_chosen'] : 0;
+      $form['alternatives'][$i]['advanced']['score_if_chosen'] = array(
+          '#type'          => 'textfield',
+          '#title'         => t('Score if chosen'),
+          '#size'          => 4,
+          '#maxlength'     => 4,
+          '#default_value' => $default_value,
+          '#description'   => t("This score is added to the user's total score if the user chooses this alternative."),
+          '#attributes'    => array(
+              'onkeypress' => 'Multichoice.refreshCorrect(this)',
+              'onkeyup'    => 'Multichoice.refreshCorrect(this)',
+              'onchange'   => 'Multichoice.refreshCorrect(this)'
+          ),
+          '#parents'       => array('alternatives', $i, 'score_if_chosen')
+      );
+
+      $default_value = $short['score_if_not_chosen'];
+      if (!isset($default_value)) {
+        $default_value = '0';
+      }
+      $form['alternatives'][$i]['advanced']['score_if_not_chosen'] = array(
+          '#type'          => 'textfield',
+          '#title'         => t('Score if not chosen'),
+          '#size'          => 4,
+          '#maxlength'     => 4,
+          '#default_value' => $default_value,
+          '#description'   => t("This score is added to the user's total score if the user doesn't choose this alternative. Only used if multiple answers are allowed."),
+          '#attributes'    => array(
+              'onkeypress' => 'Multichoice.refreshCorrect(this)',
+              'onkeyup'    => 'Multichoice.refreshCorrect(this)',
+              'onchange'   => 'Multichoice.refreshCorrect(this)'
+          ),
+          '#parents'       => array('alternatives', $i, 'score_if_not_chosen')
+      );
+    }
+    // ahah helper tag. New questions will be inserted before this tag
+    $form['alternatives']["placeholder"] = array(
+        '#markup' => '<div id="placeholder"></div>',
+    );
+
+    // We can't send the get values to the ahah callback the normal way, so we do it like this.
+    $form['get'] = array(
+        '#type'  => 'value',
+        '#value' => $get,
+    );
+
+    $form['alternatives']['multichoice_add_alternative'] = array(
+        '#type'                    => 'submit',
+        '#value'                   => t('Add more alternatives'),
+        '#submit'                  => array('multichoice_more_choices_submit'), // If no javascript action.
+        '#limit_validation_errors' => array(),
+        '#ajax'                    => array(
+            'callback' => 'multichoice_add_alternative_ajax_callback',
+            'wrapper'  => 'placeholder',
+            'effect'   => 'slide',
+            'method'   => 'before',
+        ),
+    );
+    $form['#attached']['js'] = array(
+        drupal_get_path('module', 'multichoice') . '/js/multichoice.js',
+    );
+
+    return $form;
+  }
+
+  /**
+   * Helper function provding the default settings for the creation form.
+   *
+   * @return
+   *  Array with the default settings
+   */
+  private function getDefaultAltSettings() {
+    // If the node is being updated the default settings are those stored in the node
+    if (isset($this->node->nid)) {
+      $settings['choice_multi'] = $this->node->choice_multi;
+      $settings['choice_random'] = $this->node->choice_random;
+      $settings['choice_boolean'] = $this->node->choice_boolean;
+    }
+    // We try to fetch the users settings
+    elseif ($settings = $this->getUserSettings()) {
+
+    }
+    // The user is creating his first multichoice node
+    else {
+      $settings['choice_multi'] = 0;
+      $settings['choice_random'] = 0;
+      $settings['choice_boolean'] = 0;
+    }
+    return $settings;
+  }
+
+  /**
+   * Fetches the users default settings for the creation form
+   *
+   * @return
+   *  The users default node settings
+   */
+  private function getUserSettings() {
+    global $user;
+    $res = db_query('SELECT choice_multi, choice_boolean, choice_random
+            FROM {quiz_multichoice_user_settings}
+            WHERE uid = :uid', array(':uid' => $user->uid))->fetchAssoc();
+    if ($res) {
+      return $res;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Fetches the users default settings from the creation form
+   */
+  private function saveUserSettings() {
+    global $user;
+    db_merge('quiz_multichoice_user_settings')
+      ->key(array('uid' => $user->uid))
+      ->fields(array(
+          'choice_random'  => $this->node->choice_random,
+          'choice_multi'   => $this->node->choice_multi,
+          'choice_boolean' => $this->node->choice_boolean,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Implementation of getMaximumScore.
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    if ($this->node->choice_boolean) {
+      return 1;
+    }
+
+    $max = 0;
+    for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
+      $short = $this->node->alternatives[$i];
+      if ($this->node->choice_multi) {
+        $max += max($short['score_if_chosen'], $short['score_if_not_chosen']);
+      }
+      else {
+        $max = max($max, $short['score_if_chosen'], $short['score_if_not_chosen']);
+      }
+    }
+    return max($max, 1);
+  }
+
+  /**
+   * Question response validator.
+   */
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+    if (!$this->node->choice_multi && is_null($form_state['values']['question'][$this->node->nid]['user_answer'])) {
+      form_set_error('', t('You must provide an answer.'));
+    }
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class MultichoiceResponse extends QuizQuestionResponse {
+
+  /**
+   * ID of the answers.
+   */
+  protected $user_answer_ids;
+  protected $choice_order;
+
+  /**
+   * Constructor.
+   *
+   * @param type $result_id
+   * @param stdClass $question_node
+   * @param type $values
+   *   Form state values.
+   */
+  public function __construct($result_id, stdClass $question_node, $values = NULL) {
+    parent::__construct($result_id, $question_node, $values['user_answer']);
+    $this->user_answer_ids = array();
+    // tries is the tries part of the post data
+    if (isset($values['user_answer'])) {
+      if (!is_array($values['user_answer'])) {
+        // Account for single-select
+        $values['user_answer'] = array($values['user_answer']);
+      }
+      if (isset($values['choice_order'])) {
+        $this->choice_order = $values['choice_order'];
+      }
+      unset($values['choice_order']);
+      if (isset($values['user_answer']) && is_array($values['user_answer'])) {
+        foreach ($values['user_answer'] as $answer_id) {
+          $this->user_answer_ids[] = $answer_id;
+          $this->answer = $this->user_answer_ids; //@todo: Stop using user_answer_ids and only use answer instead...
+        }
+      }
+      elseif (isset($values['user_answer'])) {
+        $this->user_answer_ids[] = $values['user_answer'];
+      }
+    }
+    // We load the answer from the database
+    else {
+      $res = db_query('SELECT answer_id FROM {quiz_multichoice_user_answers} ua
+              LEFT OUTER JOIN {quiz_multichoice_user_answer_multi} uam ON(uam.user_answer_id = ua.id)
+              WHERE ua.result_id = :result_id AND ua.question_nid = :question_nid AND ua.question_vid = :question_vid', array(':result_id' => $result_id, ':question_nid' => $this->question->nid, ':question_vid' => $this->question->vid));
+      while ($res_o = $res->fetch()) {
+        $this->user_answer_ids[] = $res_o->answer_id;
+      }
+    }
+  }
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+    $user_answer_id = db_insert('quiz_multichoice_user_answers')
+      ->fields(array(
+          'result_id'    => $this->result_id,
+          'question_vid' => $this->question->vid,
+          'question_nid' => $this->question->nid,
+          'choice_order' => $this->choice_order
+      ))
+      ->execute();
+
+    $query = db_insert('quiz_multichoice_user_answer_multi')
+      ->fields(array('user_answer_id', 'answer_id'));
+    for ($i = 0; $i < count($this->user_answer_ids); $i++) {
+      if ($this->user_answer_ids[$i]) {
+        $query->values(array($user_answer_id, $this->user_answer_ids[$i]));
+      }
+    }
+    $query->execute();
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+    $user_answer_id = array();
+    $query = db_query('SELECT id FROM {quiz_multichoice_user_answers} WHERE question_nid = :nid AND question_vid = :vid AND result_id = :result_id', array(':nid' => $this->question->nid, ':vid' => $this->question->vid, ':result_id' => $this->result_id));
+    while ($user_answer = $query->fetch()) {
+      $user_answer_id[] = $user_answer->id;
+    }
+
+    if (!empty($user_answer_id)) {
+      db_delete('quiz_multichoice_user_answer_multi')
+        ->condition('user_answer_id', $user_answer_id, 'IN')
+        ->execute();
+    }
+
+    db_delete('quiz_multichoice_user_answers')
+      ->condition('result_id', $this->result_id)
+      ->condition('question_nid', $this->question->nid)
+      ->condition('question_vid', $this->question->vid)
+      ->execute();
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @return uint
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    if ($this->question->choice_boolean || $this->isAllWrong()) {
+      $score = $this->getMaxScore();
+      foreach ($this->question->alternatives as $key => $alt) {
+        if (in_array($alt['id'], $this->user_answer_ids)) {
+          if ($alt['score_if_chosen'] <= $alt['score_if_not_chosen']) {
+            $score = 0;
+          }
+        }
+        else {
+          if ($alt['score_if_chosen'] > $alt['score_if_not_chosen']) {
+            $score = 0;
+          }
+        }
+      }
+    }
+    else {
+      $score = 0;
+      foreach ($this->question->alternatives as $key => $alt) {
+        if (in_array($alt['id'], $this->user_answer_ids)) {
+          $score += $alt['score_if_chosen'];
+        }
+        else {
+          $score += $alt['score_if_not_chosen'];
+        }
+      }
+    }
+    return $score;
+  }
+
+  /**
+   * If all answers in a question is wrong
+   *
+   * @return boolean
+   *  TRUE if all answers are wrong. False otherwise.
+   */
+  public function isAllWrong() {
+    foreach ($this->question->alternatives as $key => $alt) {
+      if ($alt['score_if_chosen'] > 0 || $alt['score_if_not_chosen'] > 0) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @return answer
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->user_answer_ids;
+  }
+
+  /**
+   * Implementation of getReportFormResponse
+   */
+  public function getReportFormResponse() {
+    $this->orderAlternatives($this->question->alternatives);
+    $simple_scoring = $this->question->choice_boolean;
+
+    $data = array();
+    foreach ($this->question->alternatives as $alternative) {
+      $chosen = in_array($alternative['id'], $this->user_answer_ids);
+      $not = $chosen ? '' : 'not_';
+
+      $data[] = array(
+          'choice'            => check_markup($alternative['answer']['value'], $alternative['answer']['format']),
+          'attempt'           => $chosen ? quiz_icon('selected') : '',
+          'correct'           => $chosen ? $alternative['score_if_chosen'] > 0 ? quiz_icon('correct') : quiz_icon('incorrect') : '',
+          'score'             => $alternative["score_if_{$not}chosen"],
+          'answer_feedback'   => check_markup($alternative["feedback_if_{$not}chosen"]['value'], $alternative["feedback_if_{$not}chosen"]['format'], FALSE),
+          'question_feedback' => 'Question feedback',
+          'solution'          => $alternative['score_if_chosen'] > 0 ? quiz_icon('should') : ($simple_scoring ? quiz_icon('should-not') : ''),
+          'quiz_feedback'     => t('@quiz feedback', array('@quiz' => QUIZ_NAME)),
+      );
+    }
+
+    return $data;
+  }
+
+  /**
+   * Order the alternatives according to the choice order stored in the database
+   *
+   * @param array $alternatives
+   *  The alternatives to be ordered
+   */
+  protected function orderAlternatives(array &$alternatives) {
+    if (!$this->question->choice_random) {
+      return;
+    }
+    $result = db_query('SELECT choice_order FROM {quiz_multichoice_user_answers}
+            WHERE result_id = :result_id AND question_nid = :question_nid AND question_vid = :question_vid', array(':result_id' => $this->result_id, ':question_nid' => $this->question->nid, ':question_vid' => $this->question->vid))->fetchField();
+    if (!$result) {
+      return;
+    }
+    $order = explode(',', $result);
+    $newAlternatives = array();
+    foreach ($order as $value) {
+      foreach ($alternatives as $alternative) {
+        if ($alternative['id'] == $value) {
+          $newAlternatives[] = $alternative;
+          break;
+        }
+      }
+    }
+    $alternatives = $newAlternatives;
+  }
+
+}
diff --git a/modules/question_types/multichoice/multichoice.info b/modules/question_types/multichoice/multichoice.info
new file mode 100644
index 0000000..9507e10
--- /dev/null
+++ b/modules/question_types/multichoice/multichoice.info
@@ -0,0 +1,9 @@
+name = Quiz Multichoice
+package = Quiz Question
+description = Multiple choice question type for quiz.
+core = 7.x
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = multichoice.classes.inc
+files[] = tests/MultichoiceTestCase.test
diff --git a/modules/question_types/multichoice/multichoice.install b/modules/question_types/multichoice/multichoice.install
new file mode 100644
index 0000000..b491afd
--- /dev/null
+++ b/modules/question_types/multichoice/multichoice.install
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * @file
+ * multichoice Install (a quiz question type)
+ */
+
+/**
+ * Implements hook_install().
+ */
+function multichoice_install() {
+  // Add body field to multichoice node
+  quiz_question_add_body_field('multichoice');
+
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_install_schema('multichoice')
+  variable_set('node_options_multichoice', array('status'));
+}
+
+/**
+ * Implements hook_schema().
+ */
+function multichoice_schema() {
+  // Stores the users answers to a question.
+  $schema['quiz_multichoice_user_answers'] = array(
+    'fields'      => array(
+      'id'           => array(
+        'type'     => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id'    => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_nid' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'choice_order' => array(
+        'type' => 'text',
+      ),
+    ),
+    'primary key' => array(
+      'id'
+    ),
+    'indexes'     => array(
+      'answer_id' => array('result_id', 'question_nid', 'question_vid'),
+    ),
+  );
+
+  $schema['quiz_multichoice_user_answer_multi'] = array(
+    'fields'      => array(
+      'user_answer_id' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer_id'      => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'user_answer_id', 'answer_id'
+    ),
+    'indexes'     => array(
+      'answer_id' => array('user_answer_id'),
+    ),
+  );
+
+  $schema['quiz_multichoice_properties'] = array(
+    'fields'      => array(
+      'nid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'choice_multi'   => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'choice_random'  => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'choice_boolean' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+    ),
+    'primary key' => array(
+      'nid', 'vid'
+    ),
+  );
+
+  /*
+   * Holds each answer in the answer_collections.
+   */
+  $schema['quiz_multichoice_answers'] = array(
+    'fields'      => array(
+      'id'                            => array(
+        'type'     => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer'                        => array(
+        'type' => 'text',
+      ),
+      'answer_format'                 => array(
+        'type'     => 'varchar',
+        'length'   => 255,
+        'not null' => TRUE,
+      ),
+      'feedback_if_chosen'            => array(
+        'type' => 'text',
+      ),
+      'feedback_if_chosen_format'     => array(
+        'type'     => 'varchar',
+        'length'   => 255,
+        'not null' => TRUE,
+      ),
+      'feedback_if_not_chosen'        => array(
+        'type' => 'text',
+      ),
+      'feedback_if_not_chosen_format' => array(
+        'type'     => 'varchar',
+        'length'   => 255,
+        'not null' => TRUE,
+      ),
+      'score_if_chosen'               => array(
+        'type'     => 'int',
+        'unsigned' => FALSE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'score_if_not_chosen'           => array(
+        'type'     => 'int',
+        'unsigned' => FALSE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'question_nid'                  => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid'                  => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'id',
+    ),
+    'indexes'     => array(
+      'quiz_id' => array('question_nid', 'question_vid'),
+    )
+  );
+
+  $schema['quiz_multichoice_user_settings'] = array(
+    'fields'      => array(
+      'uid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'choice_multi'   => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'choice_random'  => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'choice_boolean' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+    ),
+    'primary key' => array(
+      'uid'
+    ),
+  );
+  return $schema;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function multichoice_uninstall() {
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_uninstall_schema('multichoice')
+  $var = array(
+    'multichoice_def_scoring',
+    'multichoice_def_num_of_alts',
+  );
+  foreach ($var as $v) {
+    variable_del($v);
+  }
+  drupal_set_message(t("The multichoice module has been uninstalled. multichoice nodes may still exist, but they will not function properly."));
+}
diff --git a/modules/question_types/multichoice/multichoice.module b/modules/question_types/multichoice/multichoice.module
new file mode 100644
index 0000000..c99925d
--- /dev/null
+++ b/modules/question_types/multichoice/multichoice.module
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * The main file for multichoice.
+ *
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * @file
+ * Multiplechoice question type for the Quiz module.
+ *
+ */
+
+/**
+ * Implements hook_help().
+ */
+function multichoice_help($path, $args) {
+  if ($path == 'admin/help#multichoice') {
+    return t("
+      <p>This module provides a multiple choice question type for Quiz.</p>
+
+      <p>The module has three settings.
+      <em>Multiple answers</em> allows the quiz taker to select more than one alternative
+      (it also allows for the possibility that none of the alternatives are correct).
+      Alternatives are selected using checkboxes instead of radio buttons.
+      <em>Random order</em> displays the alternatives in random order when quiz is beeing taken.
+      <em>Simple scoring</em> gives max score if everything is correct. Zero points otherwise.</p>
+
+      <p>The scoring system in multichoice is a bit complex. With multiple answers each alternative adds a given number of points to
+      the total score if it is chosen, and another number of points is added if the alternative isn't chosen. Both <em>score if chosen</em> and
+      <em>score if not chosen</em> may be edited for each alternative by the question creator.
+      If multiple answers isn't allowed the score will be set to the <em>score if chosen</em> of the alternative that has been chosen.
+      The question is considered correct if the quiz taker gets the maximum amount of points possible for the question.</p>
+    ");
+  }
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function multichoice_quiz_question_info() {
+  return array(
+    'multichoice' => array(
+      'name'              => t('Multiple choice question'),
+      'description'       => t('This provides multiple choice questions for use by the Quiz module.'),
+      'question provider' => 'MultichoiceQuestion',
+      'response provider' => 'MultichoiceResponse',
+      'module'            => 'quiz_question', // All wrapper functions are in that module.
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function multichoice_quiz_question_config() {
+  $form['multichoice_def_num_of_alts'] = array(
+    '#type'          => 'textfield',
+    '#title'         => t('Default number of alternatives'),
+    '#default_value' => variable_get('multichoice_def_num_of_alts', 2),
+  );
+  $form['multichoice_def_scoring'] = array(
+    '#type'          => 'radios',
+    '#title'         => t('Default scoring method'),
+    '#description'   => t('Choose the default scoring method for questions with multiple correct answers.'),
+    '#options'       => array(
+      0 => t('Give minus one point for incorrect answers'),
+      1 => t("Give one point for each incorrect option that haven't been chosen"),
+    ),
+    '#default_value' => variable_get('multichoice_def_scoring', 0),
+  );
+  $form['#validate'][] = 'multichoice_config_validate';
+  return $form;
+}
+
+/**
+ * Validate the multichoice config form values
+ */
+function multichoice_config_validate($form, $form_state) {
+  if (!_quiz_is_int($form_state['values']['multichoice_def_num_of_alts'], 2, 50)) {
+    form_set_error('multichoice_def_num_of_alts', t('The default number of alternatives must be between 2 and 50'));
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function multichoice_theme($existing, $type, $theme, $path) {
+  $path = drupal_get_path('module', 'multichoice') . '/theme';
+  return array(
+    'multichoice_creation_form'        => array(
+      'render element' => 'form',
+      'path'           => $path,
+      'file'           => 'multichoice.theme.inc',
+    ),
+    'multichoice_answer_node_view'     => array(
+      'variables' => array(
+        'alternatives' => NULL,
+        'show_correct' => NULL,
+      ),
+      'path'      => $path,
+      'file'      => 'multichoice.theme.inc',
+    ),
+    'multichoice_response'             => array(
+      'variables' => array(
+        'data' => array(),
+      ),
+      'path'      => $path,
+      'file'      => 'multichoice.theme.inc',
+    ),
+    'multichoice_alternative_creation' => array(
+      'render element' => 'form',
+      'path'           => $path,
+      'template'       => 'multichoice-alternative-creation',
+    ),
+    'multichoice_answering_form'       => array(
+      'render element' => 'form',
+      'path'           => $path,
+      'template'       => 'multichoice-answering-form',
+    ),
+    'multichoice_alternative'          => array(
+      'render element' => 'form',
+      'path'           => $path,
+      'template'       => 'multichoice-alternative',
+    ),
+  );
+}
+
+/**
+ * ajax callback function used when adding alternatives to the node-form
+ */
+function multichoice_add_alternative_ajax_callback($form, &$form_state) {
+  $i = 0;
+  while (isset($form['alternatives'][$i])) {
+    $i++;
+  }
+  return $form['alternatives'][$i - 1];
+}
+
+/**
+ * Submit handler used when adding more alternatives to the node-form
+ */
+function multichoice_more_choices_submit($form, &$form_state) {
+  // Set the form to rebuild and run submit handlers.
+  if (!empty($form['node']->nid)) {
+    node_form_submit_build_node($form, $form_state);
+  }
+
+  // Count the existing alternatives
+  $exists = 0;
+  while (isset($form['alternatives'][$exists])) {
+    $exists++;
+  }
+
+  // Make the changes we want to the form state.
+  if ($form_state['values']['alternatives']['multichoice_add_alternative']) {
+    // We add 3 if js is disabled. 1 if the adding is done using ahah
+    $n = $_GET['q'] == 'system/ajax' ? 1 : 3;
+    $form_state['choice_count'] = $exists + $n;
+  }
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Implements hook_user_cancel().
+ */
+function multichoice_user_cancel($edit, $account, $method) {
+  db_delete('quiz_multichoice_user_settings')
+    ->condition('uid', $account->uid)
+    ->execute();
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function multichoice_field_extra_fields() {
+  $extra['node']['multichoice'] = array(
+    'form' => array(
+      'alternatives' => array(
+        'label'       => t('Alternatives'),
+        'description' => t('Alternatives for multichoice'),
+        'weight'      => -4,
+      ),
+    ),
+  );
+  return $extra;
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function multichoice_views_api() {
+  return array(
+    'api'  => 3,
+    'path' => drupal_get_path('module', 'multichoice'),
+  );
+}
diff --git a/modules/question_types/multichoice/multichoice.views.inc b/modules/question_types/multichoice/multichoice.views.inc
new file mode 100644
index 0000000..0b626c9
--- /dev/null
+++ b/modules/question_types/multichoice/multichoice.views.inc
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * Implementation of hook_views_data().
+ */
+function multichoice_views_data() {
+  $data = array();
+
+  $data['quiz_multichoice_answers'] = array(
+    // Table Definition
+    'table'        => array(
+      'group' => 'Quiz Multichoice Answers',
+      'join'  => array(
+        'quiz_entity_revision' => array(
+          'left_table' => 'quiz_relationship',
+          'left_field' => 'question_nid',
+          'field'      => 'question_nid',
+        ),
+      ),
+    ),
+    // Field descriptions:
+    'id'           => array(
+      'title'    => t('Answer ID'),
+      'help'     => t('The ID identifying an individual answer.'),
+      'field'    => array(
+        'handler'        => 'views_handler_field_numeric',
+        'click sortable' => TRUE,
+      ),
+      'argument' => array(
+        'handler'       => 'views_handler_argument_numeric',
+        'name_field'    => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'result_id',
+      ),
+      'filter'   => array(
+        'handler' => 'views_handler_filter_numeric',
+      ),
+      'sort'     => array('handler' => 'views_handler_sort'),
+    ),
+    'question_nid' => array(
+      'title'        => t('Answers Question Node NID'),
+      'help'         => t('The ID of the Question Node.'),
+      'argument'     => array(
+        'handler'       => 'views_handler_argument_numeric',
+        //'name_field' => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'nid',
+      ),
+      'filter'       => array(
+        'handler' => 'quiz_views_handler_filter_numeric',
+        'help'    => t('Filter results to a particular subset of question versions.'),
+      ),
+      'sort'         => array('handler' => 'views_handler_sort'),
+      'relationship' => array(
+        'handler' => 'views_handler_relationship',
+        'base'    => 'node',
+        'field'   => 'nid',
+        'label'   => t('node'),
+      ),
+    ),
+    'question_vid' => array(
+      'title'        => t('Answers Question Node VID'),
+      'help'         => t('The Version ID of the Question Node.'),
+      'field'        => array(
+        'handler'        => 'views_handler_field',
+        'click sortable' => TRUE,
+      ),
+      'argument'     => array(
+        'handler'       => 'views_handler_argument_numeric',
+        //'name_field' => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'vid',
+      ),
+      // Related to NODE
+      'relationship' => array(
+        'handler' => 'views_handler_relationship',
+        'base'    => 'node_revision',
+        'field'   => 'vid',
+        'label'   => t('node revision'),
+      ),
+      'sort'         => array('handler' => 'views_handler_sort'),
+    ),
+    'answer'       => array(
+      'title' => t('Answer'),
+      'help'  => t('Answer text'),
+      'field' => array(
+        'handler'        => 'views_handler_field',
+        'click sortable' => TRUE,
+      ),
+      'sort'  => array('handler' => 'views_handler_sort'),
+    ),
+  ); // End quiz_multichoice_answers
+
+
+  $data['quiz_multichoice_user_answers'] = array(
+    // Table Definition
+    'table'        => array(
+      'group' => 'Quiz Multichoice Answers',
+      'join'  => array(
+        'quiz_entity_revision' => array(
+          'left_table' => 'quiz_relationship',
+          'left_field' => 'question_nid',
+          'field'      => 'question_nid',
+        ),
+      ),
+    ),
+    // Field descriptions:
+    'id'           => array(
+      'title'    => t('User answer set ID'),
+      'help'     => t('The ID identifying an answer set for a user.'),
+      'field'    => array(
+        'handler'        => 'views_handler_field_numeric',
+        'click sortable' => TRUE,
+      ),
+      'argument' => array(
+        'handler'       => 'views_handler_argument_numeric',
+        'name_field'    => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'result_id',
+      ),
+      'filter'   => array(
+        'handler' => 'views_handler_filter_numeric',
+      ),
+      'sort'     => array('handler' => 'views_handler_sort'),
+    ),
+    'result_id'    => array(
+      'title'        => t('Result ID'),
+      'help'         => t('The ID identifying a particular set of quiz results. This ID is unique with respect to the quiz nid and vid of the quiz, uid of the quizee, and the take of the quiz.'),
+      'field'        => array(
+        'handler'        => 'views_handler_field_numeric',
+        'click sortable' => TRUE,
+      ),
+      'argument'     => array(
+        'handler'       => 'views_handler_argument_numeric',
+        'name_field'    => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'result_id',
+      ),
+      'filter'       => array(
+        'handler' => 'views_handler_filter_numeric',
+        'help'    => t('Filter results to a particular result set'),
+      ),
+      'sort'         => array('handler' => 'views_handler_sort'),
+      'relationship' => array(
+        'handler'    => 'views_handler_relationship',
+        'base'       => 'quiz_results',
+        'field'      => 'result_id',
+        'base field' => 'result_id',
+        'label'      => t('Result'),
+      ),
+    ),
+    'question_nid' => array(
+      'title'        => t('Result Question Node NID'),
+      'help'         => t('The ID of the Question Node.'),
+      'argument'     => array(
+        'handler'       => 'views_handler_argument_numeric',
+        //'name_field' => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'nid',
+      ),
+      'filter'       => array(
+        'handler' => 'quiz_views_handler_filter_numeric',
+        'help'    => t('Filter results to a particular subset of question versions.'),
+      ),
+      'sort'         => array('handler' => 'views_handler_sort'),
+      'relationship' => array(
+        'handler' => 'views_handler_relationship',
+        'base'    => 'node',
+        'field'   => 'nid',
+        'label'   => t('node'),
+      ),
+    ),
+    'question_vid' => array(
+      'title'        => t('Result Question Node VID'),
+      'help'         => t('The Version ID of the Question Node.'),
+      'field'        => array(
+        'handler'        => 'views_handler_field',
+        'click sortable' => TRUE,
+      ),
+      'argument'     => array(
+        'handler'       => 'views_handler_argument_numeric',
+        //'name_field' => 'title',
+        'numeric'       => TRUE,
+        'validate type' => 'vid',
+      ),
+      // Related to NODE
+      'relationship' => array(
+        'handler' => 'views_handler_relationship',
+        'base'    => 'node_revision',
+        'field'   => 'vid',
+        'label'   => t('node revision'),
+      ),
+      'sort'         => array('handler' => 'views_handler_sort'),
+    ),
+  ); // End quiz_multichoice_user_answers
+
+  $data['quiz_multichoice_user_answer_multi'] = array(
+    'table'          => array(
+      'group' => t('Quiz Multichoice Answers'),
+      'join'  => array(
+        'quiz_entity_revision' => array(
+          'left_table' => 'quiz_multichoice_user_answers',
+          'left_field' => 'id',
+          'field'      => 'user_answer_id',
+        ),
+      ),
+    ),
+    // Field descriptions:
+    'user_answer_id' => array(
+      'title'    => t('User answer set id'),
+      'help'     => t("The ID of a user's answer set."),
+      'argument' => array(
+        'handler' => 'views_handler_argument_numeric',
+        'numeric' => TRUE,
+      ),
+      'filter'   => array(
+        'handler' => 'views_handler_filter_numeric',
+        'help'    => t("Filter results to a particular subset of a user's answer set."),
+      ),
+      'sort'     => array('handler' => 'views_handler_sort'),
+    ),
+    'answer_id'      => array(
+      'title'        => t('Answer id'),
+      'help'         => t("The ID of a user's answer."),
+      'argument'     => array(
+        'handler' => 'views_handler_argument_numeric',
+        'numeric' => TRUE,
+      ),
+      'filter'       => array(
+        'handler' => 'views_handler_filter_numeric',
+      ),
+      'sort'         => array('handler' => 'views_handler_sort'),
+      'relationship' => array(
+        'handler'    => 'views_handler_relationship',
+        'base'       => 'quiz_multichoice_answers',
+        'field'      => 'answer_id',
+        'base field' => 'id',
+        'label'      => t('answer'),
+      ),
+    ),
+  ); // End quiz_multichoice_user_answer_multi
+
+  return $data;
+}
diff --git a/modules/question_types/multichoice/multichoice_update_6400.inc b/modules/question_types/multichoice/multichoice_update_6400.inc
new file mode 100644
index 0000000..1857d8a
--- /dev/null
+++ b/modules/question_types/multichoice/multichoice_update_6400.inc
@@ -0,0 +1,402 @@
+<?php
+
+/*
+ * @file
+ * Update for choice Questions type module.
+ *
+ */
+
+/**
+ * Helper function for multichoice_update_6400
+ *
+ * Converts the database from the old to the new multichoice module
+ *
+ * @param $sandbox
+ *  Array to store progress in
+ * @return
+ *  Array with progress data
+ */
+function _multichoice_update_6400(&$sandbox = NULL) {
+  $to_return = array();
+  $sandbox['current_step'] = isset($sandbox['current_step']) ? $sandbox['current_step'] : 0;
+
+  // Create datastructure for organizing the update process
+  $steps = array();
+  $steps[] = array('alter_user_answers1', 'next_step');
+  $steps[] = array('move_old_answers');
+  $steps[] = array('alter_user_answers2', 'create_properties', 'alter_answers', 'next_step');
+  $steps[] = array('check_answers');
+  $steps[] = array('create_user_settings', 'next_step');
+
+  // Do all the updates in the cureent step
+  foreach ($steps[$sandbox['current_step']] as $value) {
+    $function = '_multichoice_' . $value;
+    $function($to_return, $sandbox);
+  }
+
+  // Update progress so that the batch_api can update the status bar, and knows when the update is finished
+  $to_return['#finished'] = $sandbox['current_step'] / count($steps);
+
+  return $to_return;
+}
+
+/**
+ * Alter and create tables to store user answers
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_alter_user_answers1(&$to_return, &$sandbox = NULL) {
+  db_drop_primary_key('quiz_multichoice_user_answers');
+  db_add_field('quiz_multichoice_user_answers', 'id', array(
+    'type'     => 'serial',
+    'unsignet' => TRUE,
+    'not_null' => TRUE,
+    ), array('primary key' => array('id')));
+  db_add_field('quiz_multichoice_user_answers', 'choice_order', array('type' => 'text'));
+  db_create_table('quiz_multichoice_user_answer_multi', array(
+    'fields'      => array(
+      'user_answer_id' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer_id'      => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'user_answer_id', 'answer_id'
+    ),
+  ));
+}
+
+/**
+ * Move/convert user answer data from the old data model to the new
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_move_old_answers(&$to_return, &$sandbox = NULL) {
+  // Store persistent data in the sandbox variable
+  if (!isset($sandbox['step_progress'])) {
+    $sandbox['step_progress'] = 0;
+    $sandbox['last_rid'] = -1;
+    $sandbox['last_nid'] = -1;
+    $sandbox['last_vid'] = -1;
+    $sandbox['last_id'] = -1;
+    $sandbox['step_max'] = db_query('SELECT COUNT(DISTINCT id) FROM {quiz_multichoice_user_answers}')->fetchField();
+  }
+  // Fetch old user ans
+  $sql = 'SELECT id, answer_id, question_nid, question_vid, result_id
+          FROM {quiz_multichoice_user_answers}
+          WHERE result_id >= %d
+          ORDER BY result_id, question_nid, question_vid, answer_id';
+  $res = db_query_range('SELECT id, answer_id, question_nid, question_vid, result_id
+          FROM {quiz_multichoice_user_answers}
+          WHERE result_id >= :result_id
+          ORDER BY result_id, question_nid, question_vid, answer_id', array(':result_id' => $sandbox['last_rid']));
+  $progress_to_add = 0;
+  while ($res_o = db_fetch_object($res)) {
+    if ($res_o->question_nid == $sandbox['last_nid'] && $res_o->question_vid == $sandbox['last_vid'] && $res_o->result_id == $sandbox['last_rid']) {
+      // We only keep one row for each question result in quiz_multichoice_user_answers
+      if ($res_o->id > $sandbox['last_id']) {
+        $sql2 = 'DELETE FROM {quiz_multichoice_user_answers}
+                 WHERE id = %d';
+        // TODO Please review the conversion of this statement to the D7 database API syntax.
+        /* db_query($sql2, $res_o->id) */
+        db_delete('quiz_multichoice_user_answers')
+          ->condition('id', $res_o->id)
+          ->execute();
+        if ($progress_to_add + $sandbox['step_progress'] + 1 >= $sandbox['step_max']) {
+          $sandbox['step_progress'] = $sandbox['step_max'];
+        }
+      }
+    }
+    else {
+      // This is the first row for this question result
+      $sandbox['last_nid'] = $res_o->question_nid;
+      $sandbox['last_vid'] = $res_o->question_vid;
+      $sandbox['last_rid'] = $res_o->result_id;
+      $sandbox['last_id'] = $res_o->id;
+      $sandbox['step_progress'] += $progress_to_add;
+      $progress_to_add = 0;
+    }
+    // We insert ther answers in quiz_multichoice_user_answer_multi
+    $sql = 'INSERT IGNORE INTO {quiz_multichoice_user_answer_multi}
+            (user_answer_id, answer_id)
+            VALUES(%d, %d)';
+    // TODO Please convert this statement to the D7 database API syntax.
+    /* db_query($sql, $sandbox['last_id'], $res_o->answer_id) */
+    NULL;
+    $progress_to_add++;
+  }
+  // We move on if all user answers have been moved
+  if ($sandbox['step_progress'] >= $sandbox['step_max']) {
+    _multichoice_next_step($to_return, $sandbox);
+  }
+}
+
+/**
+ * We delete fields not needed after the user_answer data was moved from the old data model
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_alter_user_answers2(&$to_return, &$sandbox = NULL) {
+  db_drop_field('quiz_multichoice_user_answers', 'answer_id');
+}
+
+/**
+ * We create a new table to store multichoice properties in
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_create_properties(&$to_return, &$sandbox = NULL) {
+  db_create_table('quiz_multichoice_properties', array(
+    'fields'      => array(
+      'nid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'choice_multi'   => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'choice_random'  => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+      'choice_boolean' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+    ),
+    'primary key' => array(
+      'nid', 'vid'
+    ),
+  ));
+}
+
+/**
+ * We alter the quiz_multichoice_answers table to fit the new data model
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_alter_answers(&$to_return, &$sandbox = NULL) {
+  //db_drop_primary_key($to_return, 'quiz_multichoice_answers');
+  db_change_field('quiz_multichoice_answers', 'answer_id', 'id', array(
+    'type'     => 'serial',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+  ));
+  db_change_field('quiz_multichoice_answers', 'nid', 'question_nid', array(
+    'type'     => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+  ));
+  db_change_field('quiz_multichoice_answers', 'vid', 'question_vid', array(
+    'type'     => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+  ));
+  db_change_field('quiz_multichoice_answers', 'feedback', 'feedback_if_chosen', array(
+    'type' => 'text',
+  ));
+  db_add_field('quiz_multichoice_answers', 'feedback_if_chosen_format', array(
+    'type' => 'int',
+  ));
+  db_add_field('quiz_multichoice_answers', 'feedback_if_not_chosen', array(
+    'type' => 'text',
+  ));
+  db_add_field('quiz_multichoice_answers', 'feedback_if_not_chosen_format', array(
+    'type' => 'int',
+  ));
+  db_add_field('quiz_multichoice_answers', 'answer_format', array(
+    'type' => 'int',
+  ));
+  db_change_field('quiz_multichoice_answers', 'is_correct', 'score_if_chosen', array(
+    'type'     => 'int',
+    'unsigned' => TRUE,
+    'not_null' => TRUE,
+    'default'  => 0,
+  ));
+  db_add_field('quiz_multichoice_answers', 'score_if_not_chosen', array(
+    'type'     => 'int',
+    'unsigned' => TRUE,
+    'not_null' => TRUE,
+    'default'  => 0,
+  ));
+  db_drop_field('quiz_multichoice_answers', 'result_option');
+}
+
+/**
+ * We go through the alternatives and update question properties and field formats
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_check_answers(&$to_return, &$sandbox) {
+  // Init persistent variables
+  if (!isset($sandbox['multichoice_check_answers'])) {
+    $sandbox['step_progress'] = 0;
+    $sandbox['multichoice_check_answers'] = TRUE;
+    $sandbox['last_nid'] = 0;
+    $sandbox['last_vid'] = 0;
+    $sandbox['step_max'] = db_query('SELECT COUNT(DISTINCT id) FROM {quiz_multichoice_answers}')->fetchField();
+  }
+  $sandbox['num_corrects'] = 0;
+  $sql = 'SELECT id, question_nid, question_vid, score_if_chosen, score_if_not_chosen
+          FROM {quiz_multichoice_answers}
+          WHERE question_nid >= %d AND question_vid >= %d
+          ORDER BY question_nid, question_vid';
+  $res = db_query_range('SELECT id, question_nid, question_vid, score_if_chosen, score_if_not_chosen
+          FROM {quiz_multichoice_answers}
+          WHERE question_nid >= :question_nid AND question_vid >= :question_vid
+          ORDER BY question_nid, question_vid', array(':question_nid' => $sandbox['last_nid'], ':question_vid' => $sandbox['last_vid']));
+  $progress_to_add = 0;
+  while ($res_o = db_fetch_object($res)) {
+    if ($res_o->question_nid == $sandbox['last_nid'] && $res_o->question_vid == $sandbox['last_vid']) {
+      // This is the same node as we processed in the last loop
+      if ($progress_to_add + $sandbox['step_progress'] + 1 >= $sandbox['step_max']) {
+        $sandbox['step_progress'] = $sandbox['step_max'];
+      }
+    }
+    else {
+      // New node
+      $sandbox['step_progress'] += $progress_to_add;
+      $progress_to_add = 0;
+      $sandbox['last_nid'] = $res_o->question_nid;
+      $sandbox['last_vid'] = $res_o->question_vid;
+      $sandbox['num_corrects'] = 0;
+
+      // Store the format for this node
+      $sql = 'SELECT format
+              FROM {node_revision}
+              WHERE vid = %d';
+      $res2 = db_query('SELECT format
+              FROM {node_revision}
+              WHERE vid = :vid', array(':vid' => $res_o->question_vid));
+      $sandbox['last_format'] = $res2->fetchField();
+      $sql = 'INSERT INTO {quiz_multichoice_properties}
+              (nid, vid, choice_boolean)
+              VALUES(%d, %d, 1)';
+      // TODO Please convert this statement to the D7 database API syntax.
+      /* db_query($sql, $res_o->question_nid, $res_o->question_vid) */
+      NULL;
+    }
+
+    if ($res_o->score_if_chosen == 1) {
+      $sandbox['num_corrects'] ++;
+      if ($sandbox['num_corrects'] == 2) {
+        // If more than one answer is correct we set the choice_multi property to one.
+        $sql = 'UPDATE {quiz_multichoice_properties}
+                SET choice_multi = 1
+                WHERE nid = %d AND vid = %d';
+        // TODO Please review the conversion of this statement to the D7 database API syntax.
+        /* db_query($sql, $res_o->question_nid, $res_o->question_vid) */
+        db_update('quiz_multichoice_properties')
+          ->fields(array(
+            'choice_multi' => 1,
+          ))
+          ->condition('nid', $res_o->question_nid)
+          ->condition('vid', $res_o->question_vid)
+          ->execute();
+      }
+    }
+    // Update all the formats
+    $sql = 'UPDATE {quiz_multichoice_answers}
+            SET answer_format = %d, feedback_if_chosen_format = %d, feedback_if_not_chosen_format = %d
+            WHERE question_vid = %d'; // This table haven't been indexed yet, making this query very slow... :/
+    $lf = $sandbox['last_format'];
+    // TODO Please review the conversion of this statement to the D7 database API syntax.
+    /* db_query($sql, $lf, $lf, $lf, $res_o->question_vid) */
+    db_update('quiz_multichoice_answers')
+      ->fields(array(
+        'answer_format'                 => $lf,
+        'feedback_if_chosen_format'     => $lf,
+        'feedback_if_not_chosen_format' => $lf,
+      ))
+      ->condition('question_vid', $res_o->question_vid)
+      ->execute();
+    $progress_to_add++;
+  }
+  $sandbox['step_progress'] += $progress_to_add;
+  // Check if we are finished, and jump to the next step if we are
+  if ($sandbox['step_progress'] >= $sandbox['step_max']) {
+    _multichoice_next_step($to_return, $sandbox);
+  }
+}
+
+/**
+ * We create the user settings table
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_create_user_settings(&$to_return, &$sandbox = NULL) {
+  db_create_table('quiz_multichoice_user_settings', array(
+    'fields'      => array(
+      'uid'      => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'last_nid' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'last_vid' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'uid'
+    ),
+  ));
+}
+
+/**
+ * Helper function to switch steps in the update process
+ *
+ * @param $to_return
+ *  Array where progress can be reported
+ * @param $sandbox
+ *  Array where persistent data can be stored, and progress can be found
+ */
+function _multichoice_next_step(&$to_return, &$sandbox) {
+  $sandbox['current_step'] ++;
+}
diff --git a/modules/question_types/multichoice/tests/MultichoiceTestCase.test b/modules/question_types/multichoice/tests/MultichoiceTestCase.test
new file mode 100644
index 0000000..bb3fc32
--- /dev/null
+++ b/modules/question_types/multichoice/tests/MultichoiceTestCase.test
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * Test class for multichoice questions.
+ */
+class MultichoiceTestCase extends QuizQuestionTestCase {
+
+  var $question_node_type = 'multichoice';
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Multichoice'),
+        'description' => t('Unit test for the multichoice question type.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  public function setUp() {
+    parent::setUp(array('multichoice'));
+  }
+
+  /**
+   * Create a default MCQ with default settings.
+   */
+  public function testCreateQuizQuestion($settings = array()) {
+    $settings += array(
+        'title'          => 'MCQ 1 Title',
+        'type'           => 'multichoice',
+        'choice_multi'   => 0,
+        'choice_random'  => 0,
+        'choice_boolean' => 0,
+        'body'           => array(LANGUAGE_NONE => array(array('value' => 'MCQ 1 body text'))),
+    );
+
+    // Set up some alternatives.
+    $settings['alternatives'][0]['answer']['value'] = 'A';
+    $settings['alternatives'][0]['answer']['format'] = 'filtered_html';
+    $settings['alternatives'][0]['feedback_if_chosen']['value'] = 'You chose A';
+    $settings['alternatives'][0]['feedback_if_chosen']['format'] = 'filtered_html';
+    $settings['alternatives'][0]['feedback_if_not_chosen']['value'] = 'You did not choose A';
+    $settings['alternatives'][0]['feedback_if_not_chosen']['format'] = 'filtered_html';
+    $settings['alternatives'][0]['score_if_chosen'] = 1;
+    $settings['alternatives'][0]['score_if_not_chosen'] = 0;
+
+    $settings['alternatives'][1]['answer']['value'] = 'B';
+    $settings['alternatives'][1]['answer']['format'] = 'filtered_html';
+    $settings['alternatives'][1]['feedback_if_chosen']['value'] = 'You chose B';
+    $settings['alternatives'][1]['feedback_if_chosen']['format'] = 'filtered_html';
+    $settings['alternatives'][1]['feedback_if_not_chosen']['value'] = 'You did not choose B';
+    $settings['alternatives'][1]['feedback_if_not_chosen']['format'] = 'filtered_html';
+    $settings['alternatives'][1]['score_if_chosen'] = 0;
+    $settings['alternatives'][1]['score_if_not_chosen'] = 0;
+
+    $settings['alternatives'][2]['answer']['value'] = 'C';
+    $settings['alternatives'][2]['answer']['format'] = 'filtered_html';
+    $settings['alternatives'][2]['feedback_if_chosen']['value'] = 'You chose C';
+    $settings['alternatives'][2]['feedback_if_chosen']['format'] = 'filtered_html';
+    $settings['alternatives'][2]['feedback_if_not_chosen']['value'] = 'You did not choose C';
+    $settings['alternatives'][2]['feedback_if_not_chosen']['format'] = 'filtered_html';
+    $settings['alternatives'][2]['score_if_chosen'] = 0;
+    $settings['alternatives'][2]['score_if_not_chosen'] = 0;
+
+    $question = $this->drupalCreateNode($settings);
+    $question = node_load($question->nid);
+
+    $this->assertNotNull($question->alternatives[0]['id'], 'Alternative A was created as part of question save.');
+    $this->assertNotNull($question->alternatives[1]['id'], 'Alternative B was created as part of question save.');
+    $this->assertNotNull($question->alternatives[2]['id'], 'Alternative C was created as part of question save.');
+
+    $this->assertEqual($question->alternatives[0]['answer']['value'], 'A', 'Alternative text A was saved and loaded correctly.');
+    $this->assertEqual($question->alternatives[1]['answer']['value'], 'B', 'Alternative text B was saved and loaded correctly.');
+    $this->assertEqual($question->alternatives[2]['answer']['value'], 'C', 'Alternative text C was saved and loaded correctly.');
+
+    $this->assertEqual($question->alternatives[0]['answer']['format'], 'filtered_html', 'Alternative format A was saved and loaded correctly.');
+    $this->assertEqual($question->alternatives[1]['answer']['format'], 'filtered_html', 'Alternative format B was saved and loaded correctly.');
+    $this->assertEqual($question->alternatives[1]['answer']['format'], 'filtered_html', 'Alternative format C was saved and loaded correctly.');
+
+    return $question;
+  }
+
+  public function testQuestionFeedback() {
+    $this->drupalLogin($this->admin);
+
+    $question = $this->testCreateQuizQuestion();
+    $quiz = $this->linkQuestionToQuiz($question);
+
+    // Login as non-admin.
+    $this->drupalLogin($this->user);
+
+    // Test incorrect question. Feedback, answer.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array(
+        "question[$question->nid][user_answer]" => $question->alternatives[1]['id'],
+      ), t('Finish'));
+    $this->assertPattern('/quiz-score-icon selected/', 'Found selected answer.');
+    $this->assertPattern('/quiz-score-icon should/', 'Found should answer.');
+    $this->assertPattern('/quiz-score-icon incorrect/', 'Found incorrect answer.');
+    $this->assertText('You did not choose A');
+    $this->assertText('You chose B');
+    $this->assertText('You did not choose C');
+  }
+
+  /**
+   * Test multiple answers.
+   */
+  function testMultipleAnswers() {
+    $this->drupalLogin($this->admin);
+    $question = $this->testCreateQuizQuestion(array('choice_multi' => 1));
+    $quiz = $this->linkQuestionToQuiz($question);
+
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array(
+        "question[$question->nid][user_answer][1]" => 1,
+        "question[$question->nid][user_answer][3]" => 3,
+      ), t('Finish'));
+    // 0 of 1, because user picked a correct answer and an incorrect answer.
+    $this->assertText('You got 0 of 1 possible points.');
+    $this->assertText('Your score: 0%');
+
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question->nid][user_answer][1]" => 1), t('Finish'));
+    // 1 of 1, because user picked a correct answer and not an incorrect answer.
+    $this->assertText('You got 1 of 1 possible points.');
+    $this->assertText('Your score: 100%');
+  }
+
+  /**
+   * Test random order of choices.
+   * @todo I don't know how we would test random questions.
+   */
+  function testRandomOrder() {
+    $this->drupalLogin($this->admin);
+    $question = $this->testCreateQuizQuestion(array('choice_random' => 1));
+    $quiz = $this->linkQuestionToQuiz($question);
+
+    $this->drupalLogin($this->user);
+
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array(
+        "question[$question->nid][user_answer]" => $question->alternatives[0]['id'],
+      ), t('Finish'));
+  }
+
+  /**
+   * Test simple scoring.
+   */
+  function testSimpleScoring() {
+    $this->drupalLogin($this->admin);
+    $settings = array();
+    $settings['alternatives'][1]['score_if_chosen'] = 0;
+    $settings['alternatives'][1]['score_if_not_chosen'] = 0;
+    $settings['choice_multi'] = 1;
+    $settings['choice_boolean'] = 1;
+
+    $question = $this->testCreateQuizQuestion($settings);
+    $quiz = $this->linkQuestionToQuiz($question);
+
+    $this->drupalLogin($this->user);
+
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array(
+        "question[$question->nid][user_answer][1]" => 1,
+        "question[$question->nid][user_answer][3]" => 3,
+      ), t('Finish'));
+    $this->assertText('You got 0 of 1 possible points.');
+    $this->assertText('Your score: 0%');
+
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question->nid][user_answer][1]" => 1), t('Finish'));
+    $this->assertText('You got 1 of 1 possible points.');
+    $this->assertText('Your score: 100%');
+  }
+
+}
diff --git a/modules/question_types/multichoice/theme/multichoice-alternative-creation.js b/modules/question_types/multichoice/theme/multichoice-alternative-creation.js
new file mode 100644
index 0000000..ce1b9c9
--- /dev/null
+++ b/modules/question_types/multichoice/theme/multichoice-alternative-creation.js
@@ -0,0 +1,34 @@
+(function($) {
+/**
+ * @file
+ * JS enabling one filter fieldset to controll filter formats for all textareas in alternatives.
+ */
+Drupal.behaviors.multichoiceBehavior = {
+  attach: function (context, settings) {
+    // When the top input filter selector is clicked change the rest of the selectors to the same value
+    $('.quiz-filter:first :radio').click(function(){
+      var myValue = $(this).val();
+      $('.quiz-filter:not(:first) :radio[value='+myValue+']').click();
+      $('.quiz-filter:not(:first) :radio[value='+myValue+']').change();
+    });
+
+    // Change all format selectors to have the same value as the first
+    var defaultValue = $('.quiz-filter:first :radio[checked=1]').val();
+    $('.quiz-filter:not(:first):not(.multichoiceBehavior-processed) :radio[value='+defaultValue+']').click().change().addClass('multichoiceBehavior-processed');
+
+    // Hide all format selectors except the first
+    $('.quiz-filter:not(:first)').hide().addClass('multichoiceStayHidden');
+
+    // Move the first input selector to the input-all-ph helper tag
+    $('.quiz-filter:first').insertAfter('#input-all-ph');
+
+    // Make sure the format selectors stay hidden when a fieldset is unfolded
+    var oldToggle = Drupal.toggleFieldset;
+    Drupal.toggleFieldset = function(context) {
+      oldToggle(context);
+      $('.multichoiceStayHidden').hide();
+    };
+  }
+};
+
+})(jQuery);
diff --git a/modules/question_types/multichoice/theme/multichoice-alternative-creation.tpl.php b/modules/question_types/multichoice/theme/multichoice-alternative-creation.tpl.php
new file mode 100644
index 0000000..2a48c48
--- /dev/null
+++ b/modules/question_types/multichoice/theme/multichoice-alternative-creation.tpl.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Handles the layout of the choice creation form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+$p = drupal_get_path('module', 'multichoice');
+drupal_add_js($p . '/theme/multichoice-alternative-creation.js', 'module');
+
+// Get the title from the checkbox, and then unset it. We will place it as a table header
+$title_correct = check_plain($form['correct']['#title']);
+unset($form['correct']['#title']);
+
+// We have to add the required symbol manually
+$suf = $form['answer']['#required'] ? '<SPAN CLASS="form-required"> *</SPAN>' : '';
+
+// We store the title for the answer section as well
+$title_answer = check_plain($form['answer']['#title']) . $suf;
+$form['answer']['#title'] = '';
+
+// Now we can render the table
+$row[] = drupal_render($form['correct']);
+$row[] = drupal_render($form['answer']);
+$rows[] = $row;
+$header[] = array('data' => $title_correct);
+$header[] = array('data' => $title_answer);
+print theme('table', array('header' => $header, 'rows' => $rows));
+
+// These lines make things look alot beter if user only has one input format available:
+quiz()->formatMod($form['format']);
+quiz()->formatMod($form['advanced']['format']);
+quiz()->formatMod($form['advanced']['helper']['format']);
+
+print drupal_render($form['format']);
+print drupal_render($form['advanced']);
diff --git a/modules/question_types/multichoice/theme/multichoice-alternative.tpl.php b/modules/question_types/multichoice/theme/multichoice-alternative.tpl.php
new file mode 100644
index 0000000..d72a412
--- /dev/null
+++ b/modules/question_types/multichoice/theme/multichoice-alternative.tpl.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Handles the layout of the multichoice answering form
+ *
+ * Variables available:
+ * - $form
+ */
+// Add script for using the entire alternative row as a button
+drupal_add_js(drupal_get_path('module', 'multichoice') . '/js/multichoice.alternative.js');
+
+// We want to have the checkbox in one table cell, and the title in the next.
+// We store the checkbox and the titles
+$options = $form['user_answer']['#options'];
+$fullOptions = $titles = array();
+foreach ($options as $key => $value) {
+  $fullOptions[$key] = $form['user_answer'][$key];
+  $titles[$key] = $form['user_answer'][$key]['#title'];
+  $fullOptions[$key]['#title'] = '';
+  unset($form['user_answer'][$key]);
+}
+unset($form['user_answer']['#options']);
+print drupal_render_children($form);
+
+// We use the stored checkboxes and titles to generate a table for the
+// alternatives
+foreach ($titles as $key => $value) {
+  $rows[] = array(
+    'class' => array('multichoice_row'),
+    'data'  => array(
+      array('data' => drupal_render($fullOptions[$key]), 'width' => 35, 'class' => 'selector-td'),
+      $value
+  ));
+}
+print theme('table', array('header' => array(), 'rows' => $rows));
diff --git a/modules/question_types/multichoice/theme/multichoice-answering-form.tpl.php b/modules/question_types/multichoice/theme/multichoice-answering-form.tpl.php
new file mode 100644
index 0000000..8607b31
--- /dev/null
+++ b/modules/question_types/multichoice/theme/multichoice-answering-form.tpl.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Handles the layout of the multichoice answering form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render($form);
diff --git a/modules/question_types/multichoice/theme/multichoice.theme.inc b/modules/question_types/multichoice/theme/multichoice.theme.inc
new file mode 100644
index 0000000..74cd311
--- /dev/null
+++ b/modules/question_types/multichoice/theme/multichoice.theme.inc
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * The theme file for multichoice.
+ *
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * @file
+ * Theming functions for the multichoice question type.
+ */
+
+/**
+ * @todo Please document this function.
+ * @see http://drupal.org/node/1354
+ */
+function theme_multichoice_creation_form($variables) {
+  $form = $variables['form'];
+  $path = drupal_get_path('module', 'multichoice') . '/js/multichoice.js';
+  drupal_add_js($path);
+
+  // We open the settings fieldset if there is errors involving the choice_multi setting
+  $errors = form_get_errors();
+  if (isset($errors['choice_multi'])) {
+    $form['settings']['#collapsed'] = FALSE;
+  }
+
+  // We open the alternative fieldsets if errors have been reported
+  if ($errors) {
+    for ($i = 0; isset($form[$i]) && is_array($form[$i]); $i++) {
+      if (drupal_strlen(strip_tags($_POST['alternatives'][$i]['answer']['value'])) > 0) {
+        $form[$i]['#collapsed'] = FALSE;
+      }
+    }
+  }
+  return drupal_render_children($form);
+}
+
+/**
+ * Theme the answer part of the node view
+ *
+ * @param $alternatives
+ *  Array of alternatives. Each alternative is also an array with all the
+ *  data for each alternative.
+ * @param $show_correct
+ *  True if the user is allowed to view the solution
+ */
+function theme_multichoice_answer_node_view($variables) {
+  $alternatives = $variables['alternatives'];
+  $show_correct = $variables['show_correct'];
+  $header = array('', '');
+
+  $p = drupal_get_path('module', 'multichoice');
+  drupal_add_css($p . '/theme/multichoice.css');
+
+  foreach ($alternatives as $i => $short) {
+    $answer_markup = check_markup($short['answer']['value'], $short['answer']['format']);
+    // Find the is_correct status
+    $is_correct = ($short['score_if_chosen'] > $short['score_if_not_chosen']);
+    $image = $is_correct ? 'correct' : 'wrong';
+    if (!$show_correct) {
+      $image = 'unknown';
+    }
+
+    $rows[] = array(
+      array(
+        'data'  => array(
+          '#theme'      => 'html_tag',
+          '#tag'        => 'span',
+          '#attributes' => array(
+            'class' => array(
+              'multichoice-icon',
+              $image,
+            ),
+            'title' => $show_correct ?
+              t('Score if chosen: @sc Score if not chosen: @nc', array(
+                '@sc' => $short['score_if_chosen'],
+                '@nc' => $short['score_if_not_chosen'])
+              ) :
+              t('You are not allowed to view the solution for this question'),
+          ),
+        ),
+        'class' => array('multichoice-icon-cell'),
+      ),
+      $answer_markup
+    );
+  }
+  return theme('table', array('header' => $header, 'rows' => $rows));
+}
+
+/**
+ * Theme function for the multichoice report
+ *
+ * @param $data
+ *  Array of data to be presented in the report
+ */
+function theme_multichoice_response($variables) {
+  static $css_added;
+  if (!$css_added) {
+    drupal_add_css(drupal_get_path('module', 'multichoice') . '/theme/multichoice.css');
+    $css_added = TRUE;
+  }
+
+  $rows = array();
+  foreach ($variables['data'] as &$alternative) {
+    if ($alternative['is_chosen']) {
+      switch ($alternative['is_correct']) {
+        case 0:
+          $icon = '<span class="multichoice-icon wrong" title="' . t('This option is wrong.') . '"></span>';
+          break;
+        case 1:
+          $icon = '<span class="multichoice-icon almost" title="' . t('This option is correct, but there is another that gives a higher score.') . '"></span>';
+          break;
+        case 2:
+          $icon = '<span class="multichoice-icon correct" title="' . t('This option is correct.') . '"></span>';
+          break;
+        case 3:
+          // The answer is unknown.
+          $icon = '<span class="multichoice-icon unknown" title="' . t('This option is unknown.') . '"></span>';
+          break;
+      }
+    }
+    else {
+      $icon = $alternative['is_correct'] == 2 ? '<span class="multichoice-icon should" title="' . t("This option is correct, but you didn't select it.") . '"></span>' : '';
+    }
+    $rowspan = $alternative['feedback'] ? 2 : 1;
+    $rows[] = array(
+      array(
+        'data'    => $icon,
+        'rowspan' => $rowspan,
+        'class'   => 'selector-td multichoice-icon-cell',
+      ),
+      $alternative['answer']
+    );
+    if ($rowspan == 2) {
+      $rows[] = array('<div class="multichoice-label">' . t('Feedback') . ':</div><div class="multichoice-feedback">' . $alternative['feedback'] . '</div>');
+    }
+  }
+  return theme('table', array('header' => NULL, 'rows' => $rows));
+}
diff --git a/modules/question_types/quiz_ddlines/js/colorwheel.js b/modules/question_types/quiz_ddlines/js/colorwheel.js
new file mode 100644
index 0000000..6562f53
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/js/colorwheel.js
@@ -0,0 +1,310 @@
+/*
+* Colorwheel
+* Copyright (c) 2010 John Weir (http://famedriver.com)
+* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+*
+* requires jQuery & Raphael
+*   http://jquery.com http://raphaeljs.com
+*
+* see http://jweir.github.com/colorwheel for Usage
+*
+*/
+(function ($) {
+Raphael.colorwheel = function(target, color_wheel_size, no_segments){
+  var canvas,
+      current_color,
+      size,
+      segments = no_segments || 60,
+      bs_square = {},
+      hue_ring = {},
+      tri_size,
+      cursor = {},
+      drag_target,
+      input_target,
+      center,
+      parent,
+      change_callback,
+      drag_callbacks = [function(){}, function(){}],
+      offset,
+      padding = 2,
+      sdim; // holds the dimensions for the saturation square
+
+  function point(x, y){ return {x:x, y:y};}
+  function radians(a){ return a * (Math.PI/180);}
+
+  function angle(x,y){
+    var q = x > 0 ? 0 : 180;
+    return q+Math.atan((0 - y)/(0 - x))*180/(Math.PI);
+  }
+
+  function create(target, color_wheel_size){
+    size     = color_wheel_size;
+    tri_size = size/20;
+    center   = size/2;
+    parent   = $(target);
+    canvas   = Raphael(parent[0],size, size);
+    canvas.safari();
+
+    create_bs_square();
+    create_hue_ring();
+    hue_ring.cursor = cursor_create(tri_size);
+    bs_square.cursor = cursor_create(tri_size*0.5);
+    events_setup();
+    parent.css({height:size+"px", width:size+"px"});
+    disable_select(parent);
+    return public_methods();
+  }
+
+  function disable_select(target){
+    $(target).css({"unselectable": "on","-moz-user-select": "none","-webkit-user-select": "none"});
+  }
+
+  function public_methods(){
+    return {
+      input: input,
+      onchange: onchange,
+      ondrag : ondrag,
+      color : public_set_color
+    };
+  }
+
+  // Sets a textfield for user input of hex color values
+  // TODO don't clear the change callback
+  // TODO allow a null target to unbind the input
+  function input(target){
+    change_callback = null;
+    input_target = target;
+    $(target).keyup(function(){
+      if(this.value.match(/^#([0-9A-F]){3}$|^#([0-9A-F]){6}$/img)){
+        set_color(this.value);
+        update_color(true);
+    run_onchange_event();
+      }
+    });
+    set_color(target.value);
+    update_color(true);
+
+    return public_methods();
+  }
+
+  function onchange(callback){
+    change_callback = callback;
+    update_color(false);
+    return public_methods();
+  }
+
+  function ondrag(start_callback, end_callback){
+    drag_callbacks = [start_callback || function(){}, end_callback || function(){}];
+    return public_methods();
+  }
+
+  function drag(e){
+    var x = e.pageX - (parent.offset().left + center),
+        y = e.pageY - (parent.offset().top + center);
+
+    if(drag_target == hue_ring){
+      set_hue_cursor(x,y);
+      update_color();
+    run_onchange_event();
+      return true;
+    }
+    if(drag_target == bs_square){
+      set_bs_cursor(x,y);
+      update_color();
+    run_onchange_event();
+      return true;
+    }
+  }
+
+  function start_drag(event, target){
+    $(document).mouseup(stop_drag);
+    $(document).mousemove(drag);
+    drag_target = target;
+    drag(event);
+    drag_callbacks[0](current_color);
+  }
+
+  function stop_drag(){
+    $(document).unbind("mouseup",stop_drag);
+    $(document).unbind("mousemove",drag);
+    drag_callbacks[1](current_color);
+  run_onchange_event();
+  }
+
+  function event_drag_stop(event,o){
+    o.mousemove = null;
+    drag_target=null;
+  }
+
+  function events_setup(){
+    $([hue_ring.event.node,hue_ring.cursor[0].node]).mousedown(function(e){start_drag(e,hue_ring);});
+    $([bs_square.b.node, bs_square.cursor[0].node]).mousedown(function(e){start_drag(e,bs_square);});
+  }
+
+  function cursor_create(size){
+    var set = canvas.set().push(
+        canvas.circle(0, 0, size).attr({"stroke-width":4, stroke:"#333"}),
+        canvas.circle(0, 0, size+2).attr({"stroke-width":1, stroke:"#FFF", opacity:0.5})
+    );
+
+    set[0].node.style.cursor = "crosshair";
+
+    return set;
+  }
+
+  function set_bs_cursor(x,y){
+    x = x+center;
+    y = y+center;
+    if(x < sdim.x) x = sdim.x;
+    if(x > sdim.x+sdim.l) x = sdim.x+sdim.l;
+    if(y < sdim.y) y = sdim.y;
+    if(y > sdim.y+sdim.l) y = sdim.y + sdim.l;
+
+    bs_square.cursor.attr({cx:x, cy:y}).transform("t0,0");
+  }
+
+
+  function set_hue(color){
+    var hex = Raphael.getRGB(color).hex;
+    bs_square.h.attr("fill", hex);
+  }
+
+  function hue(){
+    return Raphael.rgb2hsb(bs_square.h.attr("fill")).h;
+  }
+
+  function public_set_color(value){
+    var ret = set_color(value);
+    update_color(false);
+    return ret;
+  }
+
+  function set_color(value){
+    if(value === undefined){ return current_color; }
+
+    var temp = canvas.rect(1,1,1,1).attr({fill:value}),
+        hsb = canvas.raphael.rgb2hsb(temp.attr("fill"));
+
+    set_bs_cursor(
+      (0-sdim.l/2) + (sdim.l*hsb.s),
+      sdim.l/2 - (sdim.l*hsb.b));
+    set_hue_cursor((360*(hsb.h))-90);
+    temp.remove();
+    return public_methods();
+  }
+
+  // Could optimize this method
+  function update_color(dont_replace_input_value){
+    var x = bs_square.cursor.items[0].attr("cx"),
+        y = bs_square.cursor.items[0].attr("cy"),
+        hsb = {
+          b: 1-(y-sdim.y)/sdim.l,
+          s: (x-sdim.x)/sdim.l,
+          h: hue()
+        };
+
+    current_color = Raphael.hsb2rgb(hsb.h, hsb.s,hsb.b);
+
+    if(input_target){
+      var c = current_color.hex;
+      if(dont_replace_input_value != true) { input_target.value = c;}
+       if(hsb.b < 0.5){
+        $(input_target).css("color", "#FFF");
+      } else {
+        $(input_target).css("color", "#000");
+      }
+      input_target.style.background = c;
+    }
+
+  }
+
+  // accepts either x,y or d (degrees)
+  function set_hue_cursor(mixed_args){
+    var d;
+    if(arguments.length == 2){
+      d = angle(arguments[0],arguments[1]);
+    } else {
+      d = arguments[0];
+    }
+
+    var x = Math.cos(radians(d)) * (center-tri_size-padding);
+    var y = Math.sin(radians(d)) * (center-tri_size-padding);
+    hue_ring.cursor.attr({cx:x+center, cy:y+center}).transform("t0,0");
+    set_hue("hsb("+(d+90)/360+",1,1)");
+  }
+
+  function bs_square_dim(){
+    if(sdim){ return sdim;}
+    var s = size - (tri_size * 4);
+    sdim = {
+      x:(s/6)+tri_size*2+padding,
+      y:(s/6)+tri_size*2+padding,
+      l:(s * 2/3)-padding*2
+    };
+    return sdim;
+  }
+
+  function create_bs_square(){
+    bs_square_dim();
+    box = [sdim.x, sdim.y, sdim.l, sdim.l];
+
+    bs_square.h = canvas.rect.apply(canvas, box).attr({
+      stroke:"#EEE", gradient: "0-#FFF-#000", opacity:1});
+    bs_square.s = canvas.rect.apply(canvas, box).attr({
+      stroke:null, gradient: "0-#FFF-#FFF", opacity:0});
+    bs_square.b = canvas.rect.apply(canvas, box).attr({
+      stroke:null, gradient: "90-#000-#FFF", opacity:0});
+    bs_square.b.node.style.cursor = "crosshair";
+  }
+
+  function hue_segement_shape(){
+    var path = "M -@W 0 L @W 0 L @W @H L -@W @H z";
+    return path.replace(/@H/img, tri_size*2).replace(/@W/img,tri_size);
+  }
+
+  function copy_segment(r, d, k){
+    var n = r.clone();
+    var hue = d*(255/k);
+
+    var s = size/2,
+      t = tri_size,
+      p = padding;
+
+    n.transform("t"+s+","+(s-t)+"r"+(360/k)*d+"t0,-"+(s-t-p)+"");
+
+    n.attr({"stroke-width":0, fill:"hsb("+d*(1/k)+", 1, 0.85)"});
+    hue_ring.hues.push(n);
+  }
+
+  function create_hue_ring(){
+    var s = hue_segement_shape(),
+        tri = canvas.path(s).attr({stroke:"rgba(0,0,0,0)"}).transform("t"+(size/2)+","+padding),
+        k = segments; // # of segments to use to generate the hues
+
+    hue_ring.hues = canvas.set();
+
+    for(n=0; n<k; n++){ copy_segment(tri, n, k); }
+
+    // IE needs a slight opacity to assign events
+    hue_ring.event = canvas.circle(
+      center,
+      center,
+      center-tri_size-padding).attr({"stroke-width":tri_size*2, opacity:0.01});
+
+    hue_ring.outline = canvas.circle(
+      center,
+      center,
+      center-tri_size-padding).attr({"stroke":"#000", "stroke-width":(tri_size*2)+3, opacity:0.1});
+    hue_ring.outline.toBack();
+    hue_ring.event.node.style.cursor = "crosshair";
+  }
+
+  function run_onchange_event(){
+  if (change_callback != undefined){
+      change_callback(current_color);
+    }
+  }
+
+  return create(target, color_wheel_size);
+};
+}(jQuery));
\ No newline at end of file
diff --git a/modules/question_types/quiz_ddlines/js/quiz_ddlines.js b/modules/question_types/quiz_ddlines/js/quiz_ddlines.js
new file mode 100644
index 0000000..646eb20
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/js/quiz_ddlines.js
@@ -0,0 +1,1650 @@
+/**
+ * Sponsored by: Senter for IKT i utdanningen
+ * Code: paalj
+ *
+ * @file
+ * Drag and drop with lines quiz question type implemented using the Raphaël 
+ * javascript library (http://raphaeljs.com/)
+ * 
+ */
+
+/**
+ * Implementation of the connection between a hotspot and the label
+ */
+Raphael.fn.connection = function (obj1, obj2, line, bg) {
+    if (obj1.line && obj1.from && obj1.to && obj1.bg) {
+        line = obj1;
+        obj1 = line.from;
+        obj2 = line.to;
+    }
+    
+    var bb1 = obj1.getBBox(),
+        bb2 = obj2.getBBox(),
+        p = [{x: bb1.x + bb1.width / 2, y: bb1.y - 1},
+        {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + 1},
+        {x: bb1.x - 1, y: bb1.y + bb1.height / 2},
+        {x: bb1.x + bb1.width + 1, y: bb1.y + bb1.height / 2},
+        {x: bb2.x + bb2.width / 2, y: bb2.y - 1},
+        {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + 1},
+        {x: bb2.x - 1, y: bb2.y + bb2.height / 2},
+        {x: bb2.x + bb2.width + 1, y: bb2.y + bb2.height / 2}],
+        d = {}, dis = [];
+    for (var i = 0; i < 4; i++) {
+        for (var j = 4; j < 8; j++) {
+            var dx = Math.abs(p[i].x - p[j].x),
+                dy = Math.abs(p[i].y - p[j].y);
+            if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
+                dis.push(dx + dy);
+                d[dis[dis.length - 1]] = [i, j];
+            }
+        }
+    }
+    if (dis.length == 0) {
+        var res = [0, 4];
+    } else {
+        res = d[Math.min.apply(Math, dis)];
+    }
+    var x1 = p[res[0]].x,
+        y1 = p[res[0]].y,
+        x4 = p[res[1]].x,
+        y4 = p[res[1]].y;
+    dx = Math.max(Math.abs(x1 - x4) / 2, 10);
+    dy = Math.max(Math.abs(y1 - y4) / 2, 10);
+    var x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
+        y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
+        x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
+        y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
+    var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
+    if (line && line.line) {
+        line.bg && line.bg.attr({path: path});
+        line.line.attr({path: path});
+    } else {
+        var color = typeof line == "string" ? line : "#000";
+        return {
+            bg: bg && bg.split && this.path(path).attr({stroke: bg.split("|")[0], fill: "none", "stroke-width": bg.split("|")[1] || 3})/*.toBack()*/,
+            line: this.path(path).attr({stroke: color, fill: "none", "stroke-width":3}),
+            from: obj1,
+            to: obj2
+        };
+    }
+};
+
+(function ($) {
+var QuizUtil = {
+    // The minimum width of the label
+    LABEL_MIN_WIDTH: 40,
+    
+    CANVAS_PADDING: 10,
+  
+    // The glow implementation should be moved into a class, 
+    // but will do for now 
+    current_glow: null,
+    
+    current_editor: null,
+    
+    drag_mode: false,
+    
+    imageChanged: false,
+
+    defined: function (a) {
+      return (typeof a !== 'undefined');
+    },
+    
+    bboxUnion: function(bbox1, bbox2) {
+      bbox1.x = bbox1.x < bbox2.x ? bbox1.x : bbox2.x; 
+      bbox1.y = bbox1.y < bbox2.y ? bbox1.y : bbox2.y;
+      bbox1.x2 = bbox1.x2 > bbox2.x2 ? bbox1.x2 : bbox2.x2;
+      bbox1.y2 = bbox1.y2 > bbox2.y2 ? bbox1.y2 : bbox2.y2;
+      
+      return bbox1;
+    }
+};
+/**
+ * A list of quiz ddlines elements.
+ * 
+ * Responsible for: 
+ * - Holding the elements in-memory 
+ * - Creating/Parsing the jsondata 
+ * - Making the form input field being in-sync
+ */
+var QuizElementList = {
+  elements: [],
+  engine: null,
+  add: function(element) {
+    this.elements.push(element);
+    this.updateForm();
+  },
+  remove: function(element) {
+    // Remove from list:
+    for(var i=0; i<this.elements.length; i++) {
+      if(element == this.elements[i]) {
+        this.elements.splice(i,1);
+      }
+    }   
+    // Update form cache:
+    this.updateForm();
+  },
+  clear: function() {
+    this.elements = [];
+    
+    // Update form cache:
+    this.updateForm();
+  },
+  updateForm: function() {
+    if(this.engine.isEditMode()) {
+      // Set the value of the hidden form field (ddlines_elements)
+      $('input[name=ddlines_elements]').val(this.toJson());
+    }
+  },
+  load: function(engine, display_answers, id) {
+    
+    QuizElementList.engine = engine;
+    
+    var obj=null;
+    if(engine.isResultMode()) {
+      obj = display_answers ? engine.conf["answers-"+id] : engine.conf["correct-"+id];
+    }
+    else {
+      json = $('input[name=ddlines_elements]').val();
+      
+      if(!json || json.length == 0) {
+        return;
+      }
+      
+      obj = $.parseJSON(json);
+    }
+    
+    // Set canvas size:
+    engine.resizePaper(obj.canvas.width, obj.canvas.height);
+    
+    // Set image position:
+    if(!QuizUtil.imageChanged) {
+      engine.image.setBounds(obj.image.left, obj.image.top, obj.image.width, obj.image.height);
+    }
+    
+    // Add elements:
+    if(QuizUtil.defined(obj.elements)) {
+      for (var i = 0; i < obj.elements.length; i++) {
+        this.add(QuizElement.fromJson(obj.elements[i], engine));
+      }
+    }
+    
+    // Update connecting lines between each pair if hotspot 
+    // and label: 
+    engine.redrawConnections();
+  },
+  toJson: function() {
+    // Get image location:
+    var elements='';
+    var json = '{' + this.engine.image.toJson() + ',' + this.engine.toJson();
+    for (var i = 0; i < this.elements.length; i++) {      
+      elements += this.elements[i].toJson() + (i<this.elements.length-1 ? ',' : '');
+    }
+    
+    if(this.elements.length>0) {
+      json += ',"elements":['+elements+']';
+    }
+    
+    return json+'}';
+  },
+  size: function() {
+    return this.elements.length;
+  },
+  updateHotspotRadius: function(radius) {
+    radius = parseInt(radius);
+    
+    this.engine.conf.hotspot.radius = radius;
+    
+    // Update radius for all elements:
+    for(var i=0; i<this.elements.length; i++) {
+      this.elements[i].uiHotspot.updateHotspotRadius(radius);
+      this.elements[i].redrawConnection();
+    }   
+  },
+  adjustCanvasSize: function() {
+    
+    var moveLeft = moveUp = 0;
+    var bbox = this.engine.image.getBBox();
+    var maxElementWidth = 0;
+    
+    /* Auto adjust canvas size */
+    for (var i = 0; i < this.elements.length; i++) {
+      bbox2 = this.elements[i].getBBox();
+      maxElementWidth = maxElementWidth < bbox2.width ? bbox2.width : maxElementWidth; 
+      
+      bbox = QuizUtil.bboxUnion(bbox, bbox2);
+    }
+    
+    var horizontalPadding = this.engine.executionModeLines() ? QuizUtil.CANVAS_PADDING : maxElementWidth * 0.6;    
+    horizontalPadding = horizontalPadding > QuizUtil.CANVAS_PADDING ? horizontalPadding : QuizUtil.CANVAS_PADDING; 
+    
+    moveLeft = -bbox.x + horizontalPadding;
+    moveUp = -bbox.y + QuizUtil.CANVAS_PADDING;
+    
+    /* Update all elements */
+    for (var i = 0; i < this.elements.length; i++) {
+      this.elements[i].move(moveLeft, moveUp);      
+    }
+    
+    // Update image:
+    this.engine.image.move(moveLeft, moveUp);
+    
+    // Update canvas-size
+    this.engine.setSize(bbox.x2-bbox.x+(horizontalPadding*2),bbox.y2-bbox.y+(QuizUtil.CANVAS_PADDING*2));
+    
+    this.updateForm();
+  }
+};
+
+/*
+ * A user's answers
+ */
+var Answers = {
+  elements: [],  
+  setAnswer: function(label_id, hotspot_id) {
+    this.elements[label_id] = hotspot_id ;
+    this.updateForm();
+  },
+  removeAnswer: function(label_id) {
+    delete this.elements[label_id];
+    this.updateForm();
+  },
+  toJson: function() {
+    var json = '';
+    var count=0;
+    for (var key in this.elements) {    
+      json += (count>0 ? ',' : '') + '{"label_id":'+key+',"hotspot_id":'+this.elements[key]+'}';
+      count++;
+    }
+    
+    if(count===0) {
+      return '';
+    }
+    else {
+      return '['+json+']';
+    }
+  },
+  updateForm: function() {
+    $('input[name=tries]').val(this.toJson());
+  }
+};
+
+Drupal.behaviors.quiz_ddlines = {
+  initialized: false,
+  attach: function(context) {
+    /* Auto adjust canvas size */
+    $('.node-quiz_ddlines-form #edit-submit:not(.canvas-adjusted)').click(function() {
+      QuizElementList.adjustCanvasSize();
+      $(this).addClass('canvas-adjusted');
+    });
+    
+    // Make hotspot radius only accept numbers:
+    $('input#edit-hotspot-radius').keypress(function(e) {
+      var key_codes = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 8];
+    
+      if (!($.inArray(e.which, key_codes) >= 0)) {
+        e.preventDefault();
+      }
+    });
+    
+    // Initializing the result page; the correct answer part
+    $('.quiz-ddlines-correct-answers', context).each(function() {
+      // Id is the node id of the question node
+      // Will be overwritten in engine.init
+      var id = $(this).attr('id');
+
+      var engine = new Engine();
+      engine.init(this, id);
+        
+      // Load elements if they exists:
+      QuizElementList.load(engine, false, id);
+    });
+    
+    // Initializing the result page; the user answer part
+    $('.quiz-ddlines-user-answers', context).each(function() {
+      // Id is the node id of the question node
+      // Will be overwritten in engine.init
+      var id = $(this).attr('id');
+      
+      var engine = new Engine();
+      engine.init(this, id);
+        
+      // Load elements if they exists:
+      QuizElementList.load(engine, true, id);
+    });
+    
+    $('.image-preview', context).each(function() {
+      if(this.initialized) {
+        return;
+      }
+      var self = this;
+      this.initialized = true;
+      
+      // Initialize
+      var engine = new Engine();
+      
+      // Need to wait for IE to make image beeing displayed
+      // This is not a great solution, but it does work.
+      setTimeout( function() {
+        engine.init(self);
+        
+        // Load elements if they exists:
+        QuizElementList.load(engine);
+        
+        // Show helptext if no alternatives have been added:
+        if(engine.isNew()) {
+          engine.addHelpText();
+        }
+      }, ($.browser.msie ? 1000 : 0));
+    });           
+  }
+};
+
+// generic functions for dragging of sets:
+function set_drag_start(x, y, event, set) {
+  set = QuizUtil.defined(set) ? set : this.set;
+  
+  QuizUtil.drag_mode = false;
+  
+  set.ox = x;
+  set.oy = y;
+}
+function set_drag_move(dx, dy, x, y, event, set) {
+  QuizUtil.drag_mode = true;
+  
+  set = QuizUtil.defined(set) ? set : this.set;
+  
+  if(QuizElementList.engine.isEditMode()) {
+    var inside = true;
+    // Do not allow dragging outside canvas:
+    set.forEach(function(elm){
+      var elm_x,elm_y,elm_width,elm_height;
+      if(elm.type === "circle") {      
+        var radius = elm.attr("r");
+        elm_x = elm.attr("cx")+(x - elm.set.ox)-radius;
+        elm_y = elm.attr("cy")+(y - elm.set.oy)-radius;
+        elm_width = elm_height = radius*2;
+      }
+      else {
+        elm_x = elm.attr("x")+(x - elm.set.ox);
+        elm_y = elm.attr("y")+(y - elm.set.oy);
+        var elm_bbox = elm.getBBox();
+        elm_width = elm_bbox.width;
+        elm_height = elm_bbox.height;
+      }
+      
+      var going_left = (x - elm.set.ox) < 0;
+      var going_up = (y - elm.set.oy) < 0;
+      
+      if((elm_x<-10 && going_left)
+          || (elm_y<-10 && going_up) 
+          || ((elm_x + elm_width)>QuizElementList.engine.getCanvasWidth()+30 && !going_left)
+          || (elm_y + elm_height)>QuizElementList.engine.getCanvasHeight()+10 && !going_up) {
+        inside = false;
+        return false;
+      }
+    });
+    
+    
+    if(!inside) {
+      return false;
+    }
+  }
+  
+  set.forEach(function(elm){
+    if(elm.type === "circle") {
+      elm.attr({cx: elm.attr("cx")+(x - elm.set.ox),cy: elm.attr("cy")+(y - elm.set.oy)});
+    }
+    else {
+      elm.attr({x: elm.attr("x")+(x - elm.set.ox),y: elm.attr("y")+(y - elm.set.oy)});
+    }
+  });
+
+  set.ox = x;
+  set.oy = y;
+
+  if(QuizUtil.defined(set.parent)) {
+    set.parent.redrawConnection();
+  }
+  
+  return true;
+};
+function set_drag_finished(event) {
+  // Update form:
+  QuizElementList.updateForm();
+};
+
+
+/*
+ * The main class. Representing the canvas.
+ * */
+function Engine() {
+  var self = this;
+  this.id = null;
+  this.r = null;    
+  this.conf = null; 
+  this.node_id = null;
+  this.helptext = null;
+  
+  this.getSelector = function() {
+    return "#"+this.id;
+  };
+  
+  this.init = function (container_class, node_id) {
+  
+    if(!QuizUtil.defined(Engine.counter)) {
+      Engine.counter = 0;
+    }
+    Engine.counter++;
+    this.id = "ddlines-container-"+Engine.counter;
+    this.node_id = QuizUtil.defined(node_id) ? node_id : null;
+    
+    // Add a specific ID and class to div, so CSS may be set only
+    // for our case:
+    $(container_class).attr("id",this.id);
+    $(container_class).addClass("ddlines-container");
+    
+    this.conf = Drupal.settings.quiz_ddlines;
+    
+    if(this.isEditMode()) {
+      this.conf.feedback.enabled = $('#edit-feedback-enabled').is(':checked') ? "1" : "0";    
+      $('#edit-feedback-enabled').change(function(){
+        // Set the conf variable:
+        self.conf.feedback.enabled = $('#edit-feedback-enabled').is(':checked') ? "1" : "0";
+        $('#feedback-disabled').css({'display': (self.feedbackEnabled() ? 'none' : 'block')});
+      });
+      
+      // Listen to change events on hotspot radius input:
+      $('input#edit-hotspot-radius').change(function(e) {
+        QuizElementList.updateHotspotRadius($(this).val());
+      });
+      
+      // Add click-handler to add elements when clicking in canvas:
+      $(this.getSelector()).click(function(event) {
+        
+        var targetIsSVG = (event.target.nodeName === 'svg' || event.target.nodeName === 'DIV'); 
+        var targetIsImage = (event.target.nodeName === 'image' || event.target.nodeName === 'shape');
+        
+        if(!targetIsSVG && QuizUtil.drag_mode){
+          return;
+        }
+        
+        // Don't create new element when focus is on inputbox
+        if(QuizUtil.current_editor != null && (targetIsSVG || targetIsImage)) {
+          QuizUtil.current_editor.editingDone();
+          return;
+        }
+        
+        if(targetIsSVG || targetIsImage ||
+          (self.helptext != null && event.target.nodeName === 'tspan')) {
+          
+          // Remove help text:
+          if(self.helptext != null) {
+            self.helptext.remove();
+            self.helptext = null;
+          }
+          
+          // Add element:
+          self.createNewElement(event);
+        }
+      });
+      
+      $(this.getSelector()).resizable({
+         stop: function(event, ui) {
+           // Update size!
+           var height=$(self.getSelector()).height();
+           var width=$(self.getSelector()).width();
+           self.setSize(width,height);
+         }
+         /*minHeight: 300,
+         minWidth: 450*/
+      });
+      
+      // Add delete handler:
+      $(document).keydown(function(event) {
+        // Check if the deletion did not take place within an input field
+        // 46 is the delete button        
+        if(event.target.nodeName != 'INPUT' && event.which === 46) {
+          // The glow represents the current selected object
+          if(QuizUtil.current_glow != null) {
+            QuizUtil.current_glow.set.parent.remove();
+            QuizUtil.current_glow = null;
+            $("#quiz-ddlines-toolpane").fadeOut();
+          }
+        }
+      });
+      
+      // Add clearing of data when image is removed
+      $('form.node-quiz_ddlines-form #edit-field-image-und-0-remove-button').mousedown(function() {
+        QuizElementList.clear();
+        
+        QuizUtil.imageChanged = true;
+      });
+    }
+    
+    // Set size of canvas
+    $(this.getSelector()).height(this.getCanvasHeight()).width(this.getCanvasWidth());    
+    
+    // Create paper
+    this.r = Raphael(this.id, this.getCanvasWidth(), this.getCanvasHeight());
+
+    // Get image attributes:
+    var img_width = $(this.getSelector() + ' > img').attr("width");
+    var img_height = $(this.getSelector() + ' > img').attr("height");   
+    var img_uri=$(this.getSelector() + ' > img').attr("src");   
+    this.image = new QuizImage(this, img_uri, img_width, img_height);
+  };
+  
+  this.addHelpText = function() {
+    if(this.isEditMode()) {
+      // Add help text:
+      // Only if this
+      self.helptext = this.r.text(this.getCanvasWidth()/2,this.getCanvasHeight()/2,Drupal.t("Click anywhere in\nthe canvas to\nadd an alternative"));
+      self.helptext.attr({'font-size': 50, 'fill': '#FFF', stroke:'#000', 'font-weight': 'bold'});
+      
+      self.helptext.mousedown(function(){
+        QuizUtil.drag_mode = false;
+      });
+    }
+  };
+  
+  
+  this.getCanvasHeight = function() {
+    if(this.isResultMode()) {
+      return this.conf["correct-"+this.node_id].canvas.height;
+    }
+    else {
+      return parseInt(this.conf.canvas.height);
+    }
+  };
+  
+  this.getCanvasWidth = function() {
+    if(this.isResultMode()) {
+      return this.conf["correct-"+this.node_id].canvas.width;
+    }
+    else {      
+      return parseInt(this.conf.canvas.width);
+    }
+  };
+  
+  this.resizePaper = function(width, height) {
+    this.setSize(width,height);
+    $(this.getSelector()).height(height).width(width);
+  };
+  
+  this.setSize = function(width,height) {
+    if(this.isEditMode()) {
+      this.conf.canvas.width=width;
+      this.conf.canvas.height=height;
+    }
+    this.r.setSize(width,height);
+  };
+   
+  this.redrawConnections = function() {
+    for (var i = QuizElementList.elements.length; i--;) {
+      QuizElementList.elements[i].redrawConnection();
+    }
+    this.r.safari();
+  };
+    
+  this.createNewElement = function (event) {
+    // Create a new element    
+    var x = this.getRelativeX(event.pageX);
+    var y = this.getRelativeY(event.pageY);    
+    var element = new QuizElement(self, Raphael.getColor(0.9), "", undefined, x > 20 ? x-20 : x, y > 80 ? y-80 : y, undefined, x, y);
+    // Add to list
+    QuizElementList.add(element);
+    
+    element.gotoEditMode();
+  };
+  
+  this.toJson = function() {     
+    return '"canvas":{"width":'+this.conf.canvas.width+',"height":'+this.conf.canvas.height+'}';
+  };
+    
+  this.getPosition = function() {
+    return $(this.getSelector()).offset();
+  };
+    
+  this.getRelativeX = function(x) {
+    return (x-this.getPosition().left);
+  };
+  
+  this.getRelativeY = function(y) {
+    return (y-this.getPosition().top);
+  };
+  
+  this.isNew = function() {
+    return (this.conf.editmode === 'add');
+  };
+  
+  this.isEditMode = function() {
+    return (this.conf.mode === 'edit');
+  };
+  
+  this.executionModeLines = function() {
+    return (this.conf.execution_mode === '0');
+  };
+  
+  this.isResultMode = function() {
+    return (this.conf.mode === 'result');
+  };
+  
+  this.isTakeMode = function() {
+    return (this.conf.mode === 'take');
+  };
+  
+  this.feedbackEnabled = function() {
+    return (this.conf.feedback.enabled === '1');
+  };
+  
+  this.pointInsideCanvas = function(x,y) {
+    x = this.getRelativeX(x);
+    y = this.getRelativeY(y);
+    return (x>5 && y>5 && x<this.conf.canvas.width-5 && y<this.conf.canvas.height-5);
+  };
+  
+  this.showInfoPopup = function(text, border_color) {
+    if(text) {
+      // Show feedback:
+      // Create a div, since shapes in raphael need a value for height, width,
+      // and we don't know how much space we need at this point.
+      
+      // Calculate the displaytime for the message based on the length of the text:
+      var displaytime = 1800 + (text.length*30);
+      
+      var $feedback = $('<div id="quiz-ddlines-feedback">'+text+'</div>');
+      $feedback.css({'border-color': border_color, 'display': 'none'});
+      $(this.getSelector()).append($feedback);
+      $feedback.fadeIn();
+      setTimeout(function() { $feedback.fadeOut(); }, displaytime);
+    }
+  };
+};
+
+/**
+ * The image class
+ */
+function QuizImage(engine, uri, width, height) {
+  
+  /*var self = this;*/
+  
+  this.engine = engine;
+  this.image = this.engine.r.image(uri, 0, 0, width, height);
+  this.image.toFront();
+  
+  if(this.engine.isEditMode()) {
+    
+    // Center image when added:
+    if(this.engine.isNew()) {
+      this.image.attr({"x": (engine.getCanvasWidth()/2)-(width/2) ,"y": (engine.getCanvasHeight()/2)-(height/2)});
+    }
+    
+    /*
+    var bbox = this.image.getBBox();
+    this.image.resizer = this.engine.r.rect(bbox.x2-20,bbox.y2-20,20,20);
+    this.image.resizer.attr({color: "#00bb27", "fill": "#00bb27", "fill-opacity": 0.9, opacity:0.9, cursor: "se-resize"});
+    */
+    
+    this.image.attr({cursor:'move'});
+    /*
+    rstart = function () {
+      this.ox = this.attr("x");
+      this.oy = this.attr("y");
+      
+      self.image.ow = self.image.attr("width");
+      self.image.oh = self.image.attr("height");        
+    },
+    rmove = function (dx, dy) {
+        this.attr({x: this.ox + dx, y: this.oy + dy});
+        self.image.attr({width: self.image.ow + dx, height: self.image.oh + dy});
+    };
+    this.image.resizer.drag(rmove, rstart);*/
+    
+    // Setup image dragging handling
+    function image_drag_move(dx, dy, x, y, event) {
+      if(set_drag_move(dx, dy, x, y, event, this.set)) {
+        // Move all hotspots accordingly:
+        for (var i = QuizElementList.elements.length; i--;) {
+          set_drag_move(dx, dy, x, y, event, QuizElementList.elements[i].uiHotspot.set);
+        }
+      }
+    }
+
+    function image_drag_start(x, y, event) {
+      set_drag_start(x, y, event, this.set);
+      
+      // Make all hotspots readu to be moved:
+      for (var i = QuizElementList.elements.length; i--;) {
+        set_drag_start(x, y, event, QuizElementList.elements[i].uiHotspot.set);
+      }
+    }
+    
+    var set = this.engine.r.set();
+    set.push(this.image);
+    //set.push(this.image.resizer);
+    this.image.set = set;
+    //this.image.resizer.set = set;
+    set.drag(image_drag_move, image_drag_start, set_drag_finished);
+  }
+  
+  this.toJson = function() {    
+    return '"image":{"left":'+this.image.attr("x")+',"top":'+this.image.attr("y")+',"width":'+this.image.attr("width")+',"height":'+this.image.attr("height")+'}';
+  };
+  
+  this.setBounds = function(x,y,width,height) {
+    this.image.attr({"x":x, "y":y, "width":width, "height":height});
+  };
+  
+  this.getBBox = function() {
+    return this.image.getBBox();
+  };
+  
+  this.move = function(deltax, deltay) {
+    this.image.attr({"x":this.image.attr("x")+deltax, "y":this.image.attr("y")+deltay});
+  };
+}
+
+/**
+ * The label UI element class
+ */
+function QuizLabel(parent, color, id, text, x, y) {
+  
+  var self = this;
+  
+  this.parent = parent;
+  this.color = color;
+  this.text = text;
+  
+  this.setText = function(text) {
+    
+    self.label.text.hide();
+    
+    // Set the new text
+    self.label.text.attr({text: text});
+    
+    // Recalculate rectangle size.
+    var bbox = self.label.text.getBBox();
+    self.label.text.attr({
+      x: self.label.attr("x")+bbox.width/2+10,
+      y: self.label.attr("y")+10});
+    
+    // Set new width of parent rectangle:
+    var rect_width = bbox.width+20;
+    self.label.attr({width: rect_width < QuizUtil.LABEL_MIN_WIDTH ? QuizUtil.LABEL_MIN_WIDTH : rect_width});
+    self.label.fg.attr({width: rect_width < QuizUtil.LABEL_MIN_WIDTH ? QuizUtil.LABEL_MIN_WIDTH : rect_width});
+    
+    // Set size of glow if it exists
+    if(QuizUtil.defined(self.glow)) {
+      self.glow.attr({width: rect_width+8 < QuizUtil.LABEL_MIN_WIDTH+8 ? QuizUtil.LABEL_MIN_WIDTH+8 : rect_width+8});
+    }
+    
+    self.label.text.show();
+  };
+  
+  this.updateTextPosition = function() {
+    this.setText(this.text);
+    
+    // Update position of foreground object:
+    var bbox = self.label.getBBox();
+    self.label.fg.attr({x:bbox.x, y:bbox.y, width:bbox.width, height: bbox.height});
+    self.label.fg.show();
+  };
+  
+  // Create Raphael objects:
+  this.label = this.parent.engine.r.rect(x, y, 100, 20);
+  this.label.parent = this;
+  
+  // Use same id as previous if this is set
+  if(QuizUtil.defined(id)) {
+    this.label.id = id;
+  }
+  
+  this.label.text = this.parent.engine.r.text(-200,y+10,""); // initially hide the text;
+  this.label.fg = this.parent.engine.r.rect(x-5, y-5, 110, 30); // Foreground object
+  
+  // Set attributes:
+  this.label.attr({fill: "#fff", stroke: this.color, "fill-opacity": 1, "stroke-width": 3, cursor: (!this.parent.engine.isResultMode() ? "move" : "default")});
+  this.label.text.attr({"font-size": 12, cursor: (!this.parent.engine.isResultMode() ? "move" : "default")});
+  this.label.fg.attr({fill: "#fff", "fill-opacity": 0, "stroke-width": 0, "stroke-opacity":0, cursor: (!this.parent.engine.isResultMode() ? "move" : "default")});
+  this.label.fg.toFront();
+  
+  // Create a set so they can be treated as one:
+  this.set = this.parent.engine.r.set();
+  this.set.parent = parent;
+  this.set.push(this.label, this.label.text, this.label.fg);
+  
+  // Create backreference from elements to the set:
+  this.label.set = this.label.text.set = this.label.fg.set = this.set;
+  
+  // Set text if value set:
+  this.setText(text);
+  
+  // Move it to center of hotspot if resultpage
+  // and execution modes without lines:
+  if(!this.parent.engine.executionModeLines() && this.parent.engine.isResultMode() && QuizUtil.defined(this.parent.uiHotspot)) {
+    // Get hotspot:
+    var center = this.parent.uiHotspot.getCenter();
+    if (center != null) {
+      this.set.forEach(function(element) {
+        element.attr({x: center.x-(element.attr('width')/2), y:center.y-(element.attr('height')/2)});
+      });
+    }
+  }
+  
+  this.toJson = function(){
+    return '"label":{"id":'+this.label.id+',"x":'+this.label.attr("x")+',"y":'+this.label.attr("y")+',"text":"'+this.label.text.attr("text")+'"}';
+  };
+  
+  this.setColor = function(color) {
+    this.color = color;
+    this.label.attr({stroke: this.color});
+  };
+  
+  if(this.parent.engine.isEditMode()) {
+    this.set.drag(set_drag_move,set_drag_start,set_drag_finished);
+  }
+  
+  var edit_mode = function (event) {
+    // Add an inputbox as overlay:
+    // Get dimensions of rect
+    
+    if(QuizUtil.drag_mode) {
+      return false;
+    }
+    
+    if(event !== null) {
+      event.stopPropagation();
+    }
+    
+    if(QuizUtil.current_editor != null) {
+      QuizUtil.current_editor.editingDone();
+    }
+    
+    var me = QuizUtil.current_editor = this;
+    
+    var topleft=self.label.attr("x")-2;
+    var topright=self.label.attr("y")-2;
+    var bbox=self.label.getBBox();
+    
+    var $editButton = $('<input type="text" id="quiz-ddlines-edit" value="'+self.label.text.attr("text")+'"></input>');
+    $editButton.css({
+      'position': 'absolute',
+      'left': topleft+'px',
+      'top': topright+'px',
+      'z-index': 99999,
+      'border': '2px dashed '+self.color,
+      'height': (bbox.height+4)+'px',
+      'width': (bbox.width+4)+'px'
+    });
+    
+    
+    this.editingDone = function() {
+      // Save the text
+      self.parent.setText($editButton.val());
+      // Remove the input field
+      $editButton.remove();
+      self.parent.redrawConnection();
+      
+      QuizUtil.current_editor = null;
+    };
+    
+    // Listen on editing finished:    
+    $editButton.keypress(function(event) {
+      // 13 is enter
+      if(event.which === 13) {
+        me.editingDone();
+      }
+    });
+    $editButton.keyup(function(event) {
+      // Update toolpane:
+      $('#quiz-ddlines-toolpane label > em').html($editButton.val());
+    });
+    
+    $(self.parent.engine.getSelector()).append($editButton);
+    setTimeout( function() {
+      $editButton.focus();
+    }, 100);
+    
+    return false;
+  };
+  
+  var focus_mode = function () {
+    if($('#quiz-ddlines-toolpane').length != 0) {
+      $('#quiz-ddlines-toolpane').remove();
+    }
+    
+    if(QuizUtil.current_glow != null) {
+      if(QuizUtil.current_glow.set) {
+        QuizUtil.current_glow.set.exclude(QuizUtil.current_glow);
+      }
+      QuizUtil.current_glow.remove();
+      QuizUtil.current_glow = null;
+    }
+    
+    QuizUtil.current_glow = self.parent.engine.r.rect(self.label.attr('x')-4, self.label.attr('y')-4, self.label.attr('width')+8, self.label.attr('height')+8, 5);
+    QuizUtil.current_glow.attr({"fill-opacity": 0, "stroke": self.color, "stroke-width": 4, "stroke-opacity": 0.3});
+    QuizUtil.current_glow.parent_id = self.label.id;
+    
+    self.glow = QuizUtil.current_glow;
+    self.set.push(QuizUtil.current_glow);
+    QuizUtil.current_glow.set = self.set;
+    
+    /*var topleft=self.label.attr("x")-2;
+    var topright=self.label.attr("y")-2;
+    var bbox=self.label.getBBox();*/
+    var selected_text=self.label.text.attr("text");
+    
+    var $toolPane = $('<div id="quiz-ddlines-toolpane"></div>');
+    var $buttonPane = $('<div id="quiz-ddlines-buttonpane"></div>');
+    var $feedbackPane = $('<div id="quiz-ddlines-feedback-pane"></div>');
+    var $feedbackCorrect = $('<input type="text" class="form-text feedback correct" value="'+self.parent.feedback_correct+'"></input>');
+    var $feedbackWrong = $('<input type="text" class="form-text feedback wrong" value="'+self.parent.feedback_wrong+'"></input>');
+    var $feedbackLabel = $('<div class="form-item"><label id="settings-for">'+Drupal.t('Settings for')+' <em>'+selected_text+'</em></label></div>');
+    var $deleteButton = $('<input id="ddlines-delete-element" type="button" class="form-submit" tabindex="-1" value="'+Drupal.t('delete')+'">');
+    var $colorButton = $('<input id="ddlines-change-color" class="form-submit" type="button" value="'+Drupal.t('color')+'">');
+    var $closeButton = $('<input id="ddlines-close-panel" class="form-submit" type="button" value="'+Drupal.t('save')+'">');
+    
+    $toolPane.css({
+      'z-index': 10001,
+      'border-color': self.color
+    });
+    
+    $(self.parent.engine.getSelector()).append($toolPane);
+    $toolPane.append($feedbackPane);
+    $toolPane.append($buttonPane);
+    
+    $feedbackPane.append($feedbackLabel);
+    $feedbackPane.append($('<span id="feedback-correct">'+Drupal.t('Feedback when correct')+'</span>'));
+    $feedbackPane.append($('<span id="feedback-wrong">'+Drupal.t('Feedback when wrong')+'</span>'));
+    $feedbackPane.append($feedbackCorrect);
+    $feedbackPane.append($feedbackWrong);
+    
+    var updateFeedback = function(){
+      self.parent.setFeedback($feedbackCorrect.val(), $feedbackWrong.val());
+    };      
+    $feedbackCorrect.keyup(updateFeedback);
+    $feedbackWrong.keyup(updateFeedback);
+    
+    // Add feedback disabled:
+    var $feedbackDisabled = $('<div id="feedback-disabled"><p>'+Drupal.t('Feedback disabled')+'</p></div>');
+    $feedbackDisabled.css({'display': self.parent.engine.feedbackEnabled() ? 'none' : 'block'});
+    $feedbackPane.append($feedbackDisabled);
+    
+    $buttonPane.append($colorButton);
+    $buttonPane.append($closeButton);
+    $buttonPane.append($deleteButton);
+    
+    // Create container:
+    var $colorContainer = $('<div id="colorwheel-container"></div>').css({'width':'150px','height':'150px'});
+    $toolPane.append($colorContainer);
+    var cw = Raphael.colorwheel($('#colorwheel-container'), 150);
+    cw.color(self.color);
+    cw.onchange(function(color) {
+      $toolPane.css({'border-color': color.hex});
+      $colorContainer.css({'border-color': color.hex});
+      self.parent.setColor(color.hex);
+    });
+    
+    // Colorbutton clicked
+    $colorButton.click(function() {
+      
+      // If color container is visible, close it
+      if ( $colorContainer.is(":visible") ) {
+        $colorContainer.hide();
+      }
+      else {
+      
+        /*var position = $toolPane.position();
+        var pos_left = (position.left + 300) + "px";
+        var pos_top = (position.top - 150) + "px";*/
+        
+        // Set position of container:
+        $colorContainer.css({'border-color': self.color}).show();
+      }      
+    });
+        
+    $deleteButton.click(function() {      
+      $("#quiz-ddlines-toolpane").fadeOut();
+      if(QuizUtil.current_editor != null) {
+        QuizUtil.current_editor.editingDone();
+      }
+      self.parent.remove();
+    });    
+  
+    $('#ddlines-close-panel').click(function() {
+      // Hide colorwheel:
+      $colorContainer.hide();
+      
+      // Update form for saving
+      QuizElementList.updateForm();    
+      
+      $("#quiz-ddlines-toolpane").fadeOut();
+    });
+  };
+  
+  if(this.parent.engine.isEditMode()) {
+    this.set.click(edit_mode);
+    this.set.mousedown(focus_mode);
+  }
+  
+  this.gotoEditMode = function() {
+    focus_mode();
+    edit_mode(null);
+  };
+  
+  this.remove = function() {
+    this.set.remove();
+  };
+  
+  this.getBBox = function() {
+    return this.label.getBBox();
+  };
+  
+  this.move = function(deltax,deltay) {
+    this.label.attr({"x": this.label.attr("x") + deltax}); 
+    this.label.attr({"y": this.label.attr("y") + deltay});
+  };
+}
+
+function QuizLabelDrag(parent, label, color) {
+  var self = this;
+  
+  this.initial_x = label.attr('x');
+  this.initial_y = label.attr('y');
+  
+  this.label = label;
+  this.parent = parent;
+  this.color = color;
+  this.ox = this.oy = null;
+  
+  var drag_start = function(x,y) {
+    self.ox = x;
+    self.oy = y;
+    self.label.set.toFront();
+  };
+  
+  var drag_move = function(dx,dy,x,y) {
+    
+    /*if(!self.parent.engine.pointInsideCanvas(x,y)) {
+      return;
+    }*/
+    
+    self.label.set.forEach(function(elm){
+      elm.attr({x: elm.attr("x")+(x - self.ox), y: elm.attr("y")+(y - self.oy)});
+    });
+    
+    self.ox = x;
+    self.oy = y;
+    
+    // Hover on hits:
+    x = self.label.attr('x')+(self.label.attr('width')/2);
+    y = self.label.attr('y')+(self.label.attr('height')/2);
+    var hit_hotspot = false;
+    for (var i = QuizElementList.elements.length; i--;) {
+      // Check if x,y is within BBox:
+      var id = QuizElementList.elements[i].uiHotspot.hotspot.id;
+      if(QuizElementList.elements[i].uiHotspot.hotspot.margin.isPointInside(x,y)) {
+        
+        hit_hotspot = true;
+        
+        if(QuizUtil.current_glow === null || QuizUtil.current_glow.parent_id != id) {
+          if(QuizUtil.current_glow != null) {
+            QuizUtil.current_glow.remove();
+            QuizUtil.current_glow = null;
+          }
+          QuizUtil.current_glow = QuizElementList.elements[i].uiHotspot.hotspot.glow({"color": self.color, width: self.label.attr('width'), fill: true, opacity: 1.0});
+          QuizUtil.current_glow.parent_id = id;
+        }
+        
+        break;
+      }
+    }
+    
+    if(!hit_hotspot && QuizUtil.current_glow != null) {
+      QuizUtil.current_glow.remove();
+      QuizUtil.current_glow = null;
+    }
+  };
+  
+  var drag_finish = function(event) {
+    
+    if(QuizUtil.current_glow != null) {
+      QuizUtil.current_glow.remove();
+    }
+    
+    var x = self.label.attr("x") + (self.label.attr("width")/2);
+    var y = self.label.attr("y") + (self.label.attr("height")/2);
+    
+    // Inside any hotspot at all?
+    var targets = self.parent.engine.r.getElementsByPoint(x, y);
+    var selected_hotspot=null;
+    
+    targets.forEach(function(e){
+      if(e.isHotspotMargin) {
+        selected_hotspot = e.hotspot;
+        // Makes callback stop (like break if this was a for loop)
+        return false; 
+      }
+    });
+    
+    var correct_answer = selected_hotspot === null ? false : (self.parent.uiHotspot.hotspot === selected_hotspot);
+    var feedback_enabled = self.parent.engine.feedbackEnabled();
+    
+    if((!feedback_enabled && selected_hotspot !== null) || (feedback_enabled && correct_answer)) {
+      // Glue it to the center of the selected hotspot,
+      // and make it smaller:
+      self.label.text.hide();
+      self.label.fg.hide();
+      self.label.animate({
+        x: selected_hotspot.attr("cx")-(self.label.attr('width')/2), 
+        y: selected_hotspot.attr("cy")-(self.label.attr('height')/2),
+        "stroke-opacity": 0.3,
+        "fill-opacity": 0.95},
+        500, 
+        "bounce", function() {
+          self.label.parent.updateTextPosition();          
+        });        
+    }
+    
+    if(selected_hotspot === null || (feedback_enabled && !correct_answer)) {
+      // Animate back to source!      
+      // Do the animation of the label:
+      self.label.text.hide();
+      self.label.fg.hide();
+      self.label.animate({
+        "stroke-opacity": 1,
+        "fill-opacity": 1,
+        x: self.initial_x, 
+        y: self.initial_y}, 500, "bounce", function() {
+          self.label.parent.updateTextPosition();          
+        });
+      
+      if(selected_hotspot != null && feedback_enabled && !correct_answer){
+        self.parent.engine.showInfoPopup(self.parent.feedback_wrong, self.color);
+      }
+      
+      Answers.removeAnswer(self.label.id);
+    }
+    else if(feedback_enabled && correct_answer) {
+      self.parent.engine.showInfoPopup(self.parent.feedback_correct, self.color);     
+      // Save answer:
+      Answers.setAnswer(self.label.id, selected_hotspot.id);
+    }
+    else if(!feedback_enabled && selected_hotspot !== null) {
+      // Save answer:     
+      Answers.setAnswer(self.label.id, selected_hotspot.id);
+    }
+  };
+  
+  label.set.drag(drag_move, drag_start, drag_finish);
+}
+
+/**
+ * The pointer class, the UI element dragged while taking
+ * a test.
+ * 
+ */
+function QuizPointer(parent, label, color) {
+  var self = this;
+  
+  this.color = color;
+  this.parent = parent;
+  this.label = label;
+  this.radius = parseInt(parent.engine.conf.pointer.radius);
+  
+  this.connect = function() {
+    return this.ie_fix;
+  };
+  
+  this.createVisual = function(x,y) {
+    this.pointer = this.parent.engine.r.circle(x,y,this.radius);
+    this.pointer.attr({fill: this.color, stroke: this.color, "fill-opacity": 0.9, "stroke-opacity": 1, "stroke-width": 2, cursor: "move"});
+    this.border = this.parent.engine.r.circle(x,y,this.radius+2);
+    this.border.attr({stroke: "#fff", "stroke-width": 2, "stroke-opacity": 1});
+    this.ie_fix = this.parent.engine.r.rect(x-this.radius, y-this.radius, this.radius*2, this.radius*2);    
+    this.ie_fix.attr({opacity:0});
+    
+    this.set = this.parent.engine.r.set();
+    this.set.push(this.pointer, this.border);
+  };
+  
+  this.updateVisual = function(cx, cy, updatePointer) {
+    updatePointer = QuizUtil.defined(updatePointer) ? updatePointer : false;
+    
+    if(updatePointer) {
+      self.pointer.attr({cx: cx, cy: cy, r:self.radius});
+    }
+    else {
+      self.radius = parseInt(self.pointer.attr("r"));
+    }
+    
+    self.ie_fix.attr({x: cx-self.radius, y:cy-self.radius, width: self.radius*2, height: self.radius*2});
+    self.border.attr({cx: cx, cy: cy, r:(self.radius+2)});
+    
+    self.parent.redrawConnection();
+  };
+  
+  // Start dragging pointer 
+  this.pointer_drag_start = function(x,y) {
+    if(!self.pointer) {
+      self.createVisual(self.parent.engine.getRelativeX(x), self.parent.engine.getRelativeY(y));
+      
+      if(!self.parent.connection) {
+        self.parent.createConnection();
+      }
+  
+      self.pointer.drag(self.pointer_move, self.pointer_drag_start, self.pointer_up);
+    }
+  };  
+  
+  // Dragging of pointer
+  this.pointer_move = function(dx,dy,x,y) {
+    /*if(!self.parent.engine.pointInsideCanvas(x,y)) {
+      return;
+    }*/
+    x = self.parent.engine.getRelativeX(x);
+    y = self.parent.engine.getRelativeY(y);
+    
+    self.updateVisual(x,y,true);
+    
+    // Check if hotspot is hit, for hovering:
+    // Tried using onDragOver, but that did not work. See comment here:
+    // http://dashasalo.com/2011/08/15/raphaeljs-2-0-ondragover-limitation/
+    var hit_hotspot = false;
+    for (var i = QuizElementList.elements.length; i--;) {
+      // Check if x,y is within BBox:
+      var id = QuizElementList.elements[i].uiHotspot.hotspot.id;
+      if(QuizElementList.elements[i].uiHotspot.hotspot.isPointInside(x,y)) {
+        
+        hit_hotspot = true;
+        
+        if(QuizUtil.current_glow === null || QuizUtil.current_glow.parent_id !== id) {
+          if(QuizUtil.current_glow !== null) {
+            QuizUtil.current_glow.remove();
+            QuizUtil.current_glow = null;
+          }          
+          QuizUtil.current_glow = QuizElementList.elements[i].uiHotspot.hotspot.glow({"color": self.color, width: 20, fill: true, opacity: 1.0});
+          QuizUtil.current_glow.parent_id = id;
+        }
+        
+        break;
+      }
+    }
+    
+    if(!hit_hotspot && QuizUtil.current_glow !== null) {
+      QuizUtil.current_glow.remove();
+      QuizUtil.current_glow = null;
+    }
+  };
+      
+  this.pointer_up = function(event) {
+    
+    if(QuizUtil.current_glow !== null) {
+      QuizUtil.current_glow.remove();
+    }
+    
+    var x = self.pointer.attr("cx");
+    var y = self.pointer.attr("cy");
+    
+    // Inside any hotspot at all?
+    var targets = self.parent.engine.r.getElementsByPoint(x, y);
+    var selected_hotspot=null;
+    
+    targets.forEach(function(e){
+      if(e !== self.pointer && e.isHotspot) {
+        // Hit one
+        // Check if correct hotspot hit.
+        //correct_answer = (self.parent.uiHotspot.hotspot == e);
+        selected_hotspot = e;
+        
+        // Makes callback stop (like break if this was a for loop)
+        return false; 
+      }
+    });
+    
+    var correct_answer = selected_hotspot===null ? false : (self.parent.uiHotspot.hotspot == selected_hotspot);
+    var feedback_enabled = self.parent.engine.feedbackEnabled();
+    
+    if((!feedback_enabled && selected_hotspot !== null) || (feedback_enabled && correct_answer)) {
+      // Remove connecting line:
+      self.parent.hideConnection();
+      
+      // Glue it to the center of the selected hotspot:
+      self.set.animate({cx: selected_hotspot.attr("cx"), cy: selected_hotspot.attr("cy"), r: selected_hotspot.attr("r")}, 500, "bounce", function() {
+        self.updateVisual(self.pointer.attr("cx"), self.pointer.attr("cy"), false);        
+      });
+    }
+    
+    if(selected_hotspot === null || (feedback_enabled && !correct_answer)) {
+      // Animate back to source!      
+      // Remove connecting line:
+      self.parent.hideConnection();      
+      
+      // Do the animation of the pointer
+      var bbox = self.label.getBBox();
+      self.set.animate({cx: self.label.attr("x")+(bbox.width/2), cy: self.label.attr("y")+50, r: self.parent.engine.conf.pointer.radius}, 500, "bounce", function() {
+        self.updateVisual(self.pointer.attr("cx"), self.pointer.attr("cy"), false);
+      });
+      
+      if(selected_hotspot !== null && feedback_enabled && !correct_answer){
+        self.parent.engine.showInfoPopup(self.parent.feedback_wrong, self.color);
+      }
+      
+      Answers.removeAnswer(self.label.id);
+    }
+    else if(feedback_enabled && correct_answer) {
+      self.parent.engine.showInfoPopup(self.parent.feedback_correct, self.color);     
+      // Save answer:
+      Answers.setAnswer(self.label.id, selected_hotspot.id);
+    }
+    else if(!feedback_enabled && selected_hotspot !== null) {
+      // Save answer:     
+      Answers.setAnswer(self.label.id, selected_hotspot.id);
+    }
+  };
+  
+}
+
+/**
+ * The hotspot UI element class
+ */
+function QuizHotspot(parent, color, id, x, y, radius) {
+  var self = this;  
+  this.parent = parent;
+  this.color = color;
+  
+  this.radius = parseInt(self.parent.engine.conf.hotspot.radius);
+  
+  this.hotspot = self.parent.engine.r.circle(x, y, this.radius);  
+  this.hotspot.attr({fill: "#fff", stroke: "#000", "fill-opacity": 0.4, "stroke-opacity": 1, "stroke-width": 2});
+  this.hotspot.isHotspot=true;
+  
+  this.hotspot.margin = self.parent.engine.r.circle(x, y, this.radius+20);
+  this.hotspot.margin.isHotspotMargin = true;
+  this.hotspot.margin.hotspot = this.hotspot; 
+  this.hotspot.margin.hide();
+  
+  this.border = self.parent.engine.r.circle(x, y, this.radius+2);
+  this.border.attr({stroke: "#fff", "stroke-width": 2, "stroke-opacity": 1});
+  
+  this.ie_fix = self.parent.engine.r.rect(x-this.radius, y-this.radius, this.radius*2, this.radius*2);
+  this.ie_fix.attr({opacity:0});
+  this.ie_fix.toFront();
+  
+  if(self.parent.engine.isEditMode()) {
+    this.hotspot.attr({cursor: "move", stroke: this.color});
+  }
+  
+  if(QuizUtil.defined(id)) {
+    this.hotspot.id = id;
+  }
+  
+  if(self.parent.engine.isEditMode()) { 
+    this.set = self.parent.engine.r.set();
+    this.set.parent = parent;
+    this.set.push(this.hotspot, this.border, this.ie_fix);
+    this.hotspot.set = this.set;
+    this.border.set = this.set;
+    this.ie_fix.set = this.set;
+    
+    this.hotspot.set.drag(set_drag_move,set_drag_start,set_drag_finished);
+  } 
+  
+  this.connect = function() {
+    return this.ie_fix;
+  };
+  
+  this.toJson = function() {
+    return '"hotspot":{"id":'+this.hotspot.id+',"x":'+this.hotspot.attr("cx")+',"y":'+this.hotspot.attr("cy")+'}';
+  };
+  
+  this.remove = function() {    
+    this.hotspot.remove();
+    this.border.remove();
+  };
+  
+  this.setColor = function(color) {
+    this.color = color;
+    this.hotspot.attr({stroke: this.color});
+  };
+  
+  this.updateHotspotRadius = function(radius) {
+    var previous_radius = this.radius;
+    var diff = radius-previous_radius;
+    this.radius = radius;
+    
+    this.hotspot.attr({r: radius});
+    this.border.attr({r: radius+2});
+    this.ie_fix.attr({x: this.ie_fix.attr('x')-diff, y: this.ie_fix.attr('y')-diff, width: radius*2, height: radius*2});
+  };
+  
+  this.getCenter = function() {
+    if(this.hotspot) {
+      return {x: this.hotspot.attr('cx'), y:this.hotspot.attr('cy')};
+    }
+    else {
+      return null;
+    }
+  };
+  
+  this.getBBox = function() {
+    return this.hotspot.getBBox();
+  };
+  
+  this.move = function(deltax,deltay) {
+    this.hotspot.attr({"cx": this.hotspot.attr("cx") + deltax}); 
+    this.hotspot.attr({"cy": this.hotspot.attr("cy") + deltay});
+  };
+}
+
+
+/**
+ * The quiz ddlines element.
+ * 
+ * It contains the metadata needed to draw the label, hotspot, pointer 
+ * and connecting lines
+ * 
+ */
+function QuizElement (engine, color, text, label_id, label_x, label_y, hotspot_id, hotspot_x, hotspot_y, feedback_correct, feedback_wrong, correct_answer) {  
+  
+  var self = this;
+  this.engine = engine;
+  
+  text = QuizUtil.defined(text) ? text : "";
+  label_x = QuizUtil.defined(label_x) ? label_x : 5;
+  label_y = QuizUtil.defined(label_y) ? label_y : 5;
+  hotspot_x = QuizUtil.defined(hotspot_x) ? hotspot_x : 55;
+  hotspot_y = QuizUtil.defined(hotspot_y) ? hotspot_y : 100;  
+  this.feedback_wrong = QuizUtil.defined(feedback_wrong)  ? feedback_wrong : engine.conf.feedback.wrong;
+  this.feedback_correct = QuizUtil.defined(feedback_correct) ? feedback_correct : engine.conf.feedback.correct;
+  correct_answer = QuizUtil.defined(correct_answer) ? correct_answer : null;
+  
+  // Create default hotspot and label
+  this.color = color;
+  
+  if((correct_answer !== null || hotspot_id !== null) && hotspot_x !== null) {
+    this.uiHotspot = new QuizHotspot(this, color, hotspot_id, hotspot_x, hotspot_y);
+  }
+  
+  if(correct_answer !== null || label_id !== null) {
+    this.uiLabel = new QuizLabel(this, color, label_id, text, label_x, label_y);
+  }
+  
+  if(this.engine.isTakeMode()) {
+    if(this.engine.executionModeLines()) {
+      this.pointer = new QuizPointer(this, this.uiLabel.label, color);
+      this.uiLabel.set.drag(this.pointer.pointer_move, this.pointer.pointer_drag_start, this.pointer.pointer_up);
+    }
+    else {
+      // Make label beeing draggable
+      new QuizLabelDrag(this, this.uiLabel.label, color);
+    }
+  }
+  
+  this.createConnection = function() {
+    if(self.engine.isEditMode() || self.engine.executionModeLines()) {
+      this.connection = this.engine.r.connection(this.uiLabel.label, (this.engine.isTakeMode() ? this.pointer.connect() : this.uiHotspot.connect()), this.color, "#FFF|5");
+    }
+  };
+  
+  this.hideConnection = function() {
+    if(self.engine.isEditMode() || self.engine.executionModeLines()) {
+      this.connection.bg.hide();
+      this.connection.line.hide();
+    }
+  };
+  
+  if(this.engine.isEditMode() || (this.engine.isResultMode() && hotspot_id !== null && label_id !== null)) {
+    this.createConnection();
+  }
+  
+  if(this.engine.isResultMode() && (correct_answer != null || (label_id === null || hotspot_id === null))) {
+    
+    //var settings = {color: (correct_answer ? '#00bb27' : '#fe2020'), width: 20, fill: true, opacity: 1};
+    
+    var imagepath = this.engine.conf.quiz_imagepath + (correct_answer ? "icon_ok" : "icon_wrong") + ".gif";
+    
+    // Set glow color:
+    if(QuizUtil.defined(this.uiLabel)) {
+      // Create Image:
+      var bbox = this.uiLabel.getBBox();
+      this.engine.r.image(imagepath, bbox.x-8, bbox.y-8, 20, 20);
+      //this.engine.r.image.attr({'width': 16, 'height': 16});
+      
+      //this.uiLabel.label.glow(settings);
+    }
+    /*if(QuizUtil.defined(this.uiHotspot) && this.engine.executionModeLines()) {
+      //this.uiHotspot.hotspot.glow(settings);
+      var bbox = this.uiHotspot.getBBox();
+      this.engine.r.image(imagepath, bbox.x, bbox.y, 16, 16);
+    }*/
+  }
+  
+  this.redrawConnection = function() {
+    if(this.connection) {
+      this.connection.bg.show();
+      this.connection.line.show();
+      this.engine.r.connection(this.connection);
+    }
+    this.engine.r.safari();
+  };
+  
+  this.gotoEditMode = function() {
+    this.uiLabel.gotoEditMode();
+  };
+      
+  this.toJson = function() {
+    // Generates JSON
+    return '{"feedback_wrong":"'+this.feedback_wrong+'",'+'"feedback_correct":"'+this.feedback_correct+'","color":"'+this.color+'",'+this.uiLabel.toJson()+','+this.uiHotspot.toJson()+'}';
+  };
+  
+  this.setFeedback = function(correct, wrong) {
+    this.feedback_correct = correct;
+    this.feedback_wrong = wrong;
+    
+    // Update list:
+    QuizElementList.updateForm();
+  };
+  
+  this.setText = function(text) {
+    // Set the new text
+    this.uiLabel.setText(text);
+    
+    // Update form element, so it is ready to be saved
+    QuizElementList.updateForm();
+  };
+  
+  this.setColor = function(color) {
+    this.color = color;
+    
+    this.uiLabel.setColor(this.color);
+    this.uiHotspot.setColor(this.color);
+    
+    // Set glow if it exists:
+    if(QuizUtil.current_glow != null) {
+      QuizUtil.current_glow.attr({stroke: this.color});
+    }
+    
+    // Set connection color:
+    this.connection.line.attr({stroke: this.color});
+  };
+  
+  this.remove = function() {    
+    // Remove from list:
+    QuizElementList.remove(this);
+    
+    // Remove from canvas:
+    this.uiHotspot.remove();
+    this.uiLabel.remove();
+    this.connection.bg.remove();
+    this.connection.line.remove();
+  };
+  
+  this.getBBox = function() {
+    var bbox = this.uiLabel.getBBox();
+    
+    if(QuizUtil.defined(this.uiHotspot)) {
+      var bboxHotSpot = this.uiHotspot.getBBox();
+      bbox = QuizUtil.bboxUnion(bbox, bboxHotSpot);
+    }
+    
+    return bbox;
+  };
+  
+  this.move = function(deltax,deltay) {
+    this.uiLabel.move(deltax,deltay);
+    this.uiHotspot.move(deltax,deltay);
+  };
+}
+
+QuizElement.fromJson = function(json, engine) {
+  return new QuizElement(
+       engine, 
+       json.color, 
+       QuizUtil.defined(json.label) ? json.label.text : null, 
+       QuizUtil.defined(json.label) ? json.label.id : null, 
+       QuizUtil.defined(json.label) ? json.label.x : null, 
+       QuizUtil.defined(json.label) ? json.label.y : null, 
+       QuizUtil.defined(json.hotspot) ? json.hotspot.id : null, 
+       QuizUtil.defined(json.hotspot) ? json.hotspot.x : null, 
+       QuizUtil.defined(json.hotspot) ? json.hotspot.y : null, 
+       json.feedback_correct, 
+       json.feedback_wrong, 
+       QuizUtil.defined(json.correct) ? json.correct : null);
+};
+/*
+var Glow = {
+    instance: null,
+    glow: function(elem, color) {
+      this.remove();
+      
+      var settings = {};
+      
+      if(elem.type='circle') {
+        settings = {}
+      }
+      else {
+        
+      }
+      
+      this.instance = elem.glow({"color": color, width: 20, fill: true, opacity: 1.0});
+      
+      Engine.r.rect(self.label.attr('x')-4, self.label.attr('y')-4, self.label.attr('width')+8, self.label.attr('height')+8, 5);
+      
+      
+    },
+    remove: function() {
+      this.instance.remove();
+      this.instance = null;
+    },
+    isGlowing(): function() {
+      return (set != null);
+    }
+};*/
+
+
+
+
+
+}(jQuery));
\ No newline at end of file
diff --git a/modules/question_types/quiz_ddlines/js/raphael-min.js b/modules/question_types/quiz_ddlines/js/raphael-min.js
new file mode 100644
index 0000000..d30dbad
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/js/raphael-min.js
@@ -0,0 +1,10 @@
+// ┌────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël 2.1.0 - JavaScript Vector Library                          │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
+// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
+// └────────────────────────────────────────────────────────────────────┘ \\
+
+(function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;t<u;t++)"zIndex"in f[t]&&(o.push(f[t].zIndex),f[t].zIndex<0&&(p[f[t].zIndex]=f[t]));o.sort(g);while(o[l]<0){n=p[o[l++]],q.push(n.apply(b,e));if(i){i=d;return q}}for(t=0;t<u;t++){n=f[t];if("zIndex"in n)if(n.zIndex==o[l]){q.push(n.apply(b,e));if(i)break;do{l++,n=p[o[l]],n&&q.push(n.apply(b,e));if(i)break}while(n)}else p[n.zIndex]=n;else{q.push(n.apply(b,e));if(i)break}}i=d,h=r;return q.length?q:null};k.listeners=function(a){var b=a.split(d),c=j,f,g,h,i,k,l,m,n,o=[c],p=[];for(i=0,k=b.length;i<k;i++){n=[];for(l=0,m=o.length;l<m;l++){c=o[l].n,g=[c[b[i]],c[e]],h=2;while(h--)f=g[h],f&&(n.push(f),p=p.concat(f.f||[]))}o=n}return p},k.on=function(a,b){var c=a.split(d),e=j;for(var g=0,h=c.length;g<h;g++)e=e.n,!e[c[g]]&&(e[c[g]]={n:{}}),e=e[c[g]];e.f=e.f||[];for(g=0,h=e.f.length;g<h;g++)if(e.f[g]==b)return f;e.f.push(b);return function(a){+a==+a&&(b.zIndex=+a)}},k.stop=function(){i=1},k.nt=function(a){if(a)return(new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)")).test(h);return h},k.off=k.unbind=function(a,b){var f=a.split(d),g,h,i,k,l,m,n,o=[j];for(k=0,l=f.length;k<l;k++)for(m=0;m<o.length;m+=i.length-2){i=[m,1],g=o[m].n;if(f[k]!=e)g[f[k]]&&i.push(g[f[k]]);else for(h in g)g[c](h)&&i.push(g[h]);o.splice.apply(o,i)}for(k=0,l=o.length;k<l;k++){g=o[k];while(g.n){if(b){if(g.f){for(m=0,n=g.f.length;m<n;m++)if(g.f[m]==b){g.f.splice(m,1);break}!g.f.length&&delete g.f}for(h in g.n)if(g.n[c](h)&&g.n[h].f){var p=g.n[h].f;for(m=0,n=p.length;m<n;m++)if(p[m]==b){p.splice(m,1);break}!p.length&&delete g.n[h].f}}else{delete g.f;for(h in g.n)g.n[c](h)&&g.n[h].f&&delete g.n[h].f}g=g.n}}},k.once=function(a,b){var c=function(){var d=b.apply(this,arguments);k.unbind(a,c);return d};return k.on(a,c)},k.version=b,k.toString=function(){return"You are running Eve "+b},typeof module!="undefined"&&module.exports?module.exports=k:typeof define!="undefined"?define("eve",[],function(){return k}):a.eve=k})(this),function(){function cF(a){for(var b=0;b<cy.length;b++)cy[b].el.paper==a&&cy.splice(b--,1)}function cE(b,d,e,f,h,i){e=Q(e);var j,k,l,m=[],o,p,q,t=b.ms,u={},v={},w={};if(f)for(y=0,z=cy.length;y<z;y++){var x=cy[y];if(x.el.id==d.id&&x.anim==b){x.percent!=e?(cy.splice(y,1),l=1):k=x,d.attr(x.totalOrigin);break}}else f=+v;for(var y=0,z=b.percents.length;y<z;y++){if(b.percents[y]==e||b.percents[y]>f*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;y<z;y++){w[A][y]=[0];for(var F=1,G=u[A][y].length;F<G;F++)w[A][y][F]=(E[y][F]-u[A][y][F])/t}break;case"transform":var H=d._,I=ca(H[A],v[A]);if(I){u[A]=I.from,v[A]=I.to,w[A]=[],w[A].real=!0;for(y=0,z=u[A].length;y<z;y++){w[A][y]=[u[A][y][0]];for(F=1,G=u[A][y].length;F<G;F++)w[A][y][F]=(v[A][y][F]-u[A][y][F])/t}}else{var J=d.matrix||new cb,K={_:{transform:H.transform},getBBox:function(){return d.getBBox(1)}};u[A]=[J.a,J.b,J.c,J.d,J.e,J.f],b$(K,v[A]),v[A]=K._.transform,w[A]=[(K.matrix.a-J.a)/t,(K.matrix.b-J.b)/t,(K.matrix.c-J.c)/t,(K.matrix.d-J.d)/t,(K.matrix.e-J.e)/t,(K.matrix.f-J.f)/t]}break;case"csv":var L=r(j[A])[s](c),M=r(u[A])[s](c);if(A=="clip-rect"){u[A]=M,w[A]=[],y=M.length;while(y--)w[A][y]=(L[y]-u[A][y])/t}v[A]=L;break;default:L=[][n](j[A]),M=[][n](u[A]),w[A]=[],y=d.paper.customAttributes[A].length;while(y--)w[A][y]=((L[y]||0)-(M[y]||0))/t}}var O=j.easing,P=a.easing_formulas[O];if(!P){P=r(O).match(N);if(P&&P.length==5){var R=P;P=function(a){return cC(a,+R[1],+R[2],+R[3],+R[4],t)}}else P=bf}q=j.start||b.start||+(new Date),x={anim:b,percent:e,timestamp:q,start:q+(b.del||0),status:0,initstatus:f||0,stop:!1,ms:t,easing:P,from:u,diff:w,to:v,el:d,callback:j.callback,prev:p,next:o,repeat:i||b.times,origin:d.attr(),totalOrigin:h},cy.push(x);if(f&&!k&&!l){x.stop=!0,x.start=new Date-t*f;if(cy.length==1)return cA()}l&&(x.start=new Date-x.ms*f),cy.length==1&&cz(cA)}else k.initstatus=f,k.start=new Date-k.ms*f;eve("raphael.anim.start."+d.id,d,b)}}function cD(a,b){var c=[],d={};this.ms=b,this.times=1;if(a){for(var e in a)a[g](e)&&(d[Q(e)]=a[e],c.push(Q(e)));c.sort(bd)}this.anim=d,this.top=c[c.length-1],this.percents=c}function cC(a,b,c,d,e,f){function o(a,b){var c,d,e,f,j,k;for(e=a,k=0;k<8;k++){f=m(e)-a;if(z(f)<b)return e;j=(3*i*e+2*h)*e+g;if(z(j)<1e-6)break;e=e-f/j}c=0,d=1,e=a;if(e<c)return c;if(e>d)return d;while(c<d){f=m(e);if(z(f-a)<b)return e;a>f?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" × "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p<q;p++){var r=b[p];if(r[0]=="M")e=i=r[1],f=j=r[2];else{r[0]=="C"?(m=[e,f].concat(r.slice(1)),e=m[6],f=m[7]):(m=[e,f,e,f,i,j,i,j],e=i,f=j);for(var s=0,t=c.length;s<t;s++){var u=c[s];if(u[0]=="M")g=k=u[1],h=l=u[2];else{u[0]=="C"?(n=[g,h].concat(u.slice(1)),g=n[6],h=n[7]):(n=[g,h,g,h,k,l,k,l],g=k,h=l);var v=bG(m,n,d);if(d)o+=v;else{for(var w=0,x=v.length;w<x;w++)v[w].segment1=p,v[w].segment2=s,v[w].bez1=m,v[w].bez2=n;o=o.concat(v)}}}}}return o}function bG(b,c,d){var e=a.bezierBBox(b),f=a.bezierBBox(c);if(!a.isBBoxIntersect(e,f))return d?0:[];var g=bB.apply(0,b),h=bB.apply(0,c),i=~~(g/5),j=~~(h/5),k=[],l=[],m={},n=d?0:[];for(var o=0;o<i+1;o++){var p=a.findDotsAtSegment.apply(a,b.concat(o/i));k.push({x:p.x,y:p.y,t:o/i})}for(o=0;o<j+1;o++)p=a.findDotsAtSegment.apply(a,c.concat(o/j)),l.push({x:p.x,y:p.y,t:o/j});for(o=0;o<i;o++)for(var q=0;q<j;q++){var r=k[o],s=k[o+1],t=l[q],u=l[q+1],v=z(s.x-r.x)<.001?"y":"x",w=z(u.x-t.x)<.001?"y":"x",x=bD(r.x,r.y,s.x,s.y,t.x,t.y,u.x,u.y);if(x){if(m[x.x.toFixed(4)]==x.y.toFixed(4))continue;m[x.x.toFixed(4)]=x.y.toFixed(4);var y=r.t+z((x[v]-r[v])/(s[v]-r[v]))*(s.t-r.t),A=t.t+z((x[w]-t[w])/(u[w]-t[w]))*(u.t-t.t);y>=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)<y(e,g)||y(a,c)>x(e,g)||x(b,d)<y(f,h)||y(b,d)>x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)<i)){var j=1,k=j/2,l=j-k,m,n=.01;m=bB(a,b,c,d,e,f,g,h,l);while(z(m-i)>n)k/=2,l+=(m<i?1:-1)*k,m=bB(a,b,c,d,e,f,g,h,l);return l}}function bB(a,b,c,d,e,f,g,h,i){i==null&&(i=1),i=i>1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;o<k;o++){var p=j*l[o]+j,q=bA(p,a,c,e,g),r=bA(p,b,d,f,h),s=q*q+r*r;n+=m[o]*w.sqrt(s)}return j*n}function bA(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function by(a,b){var c=[];for(var d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function bm(a){if(Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[g](c)&&(b[c]=bm(a[c]));return b}function a(c){if(a.is(c,"function"))return b?c():eve.on("raphael.DOMload",c);if(a.is(c,E))return a._engine.create[m](a,c.splice(0,3+a.is(c[0],C))).add(c);var d=Array.prototype.slice.call(arguments,0);if(a.is(d[d.length-1],"function")){var e=d.pop();return b?e.call(a._engine.create[m](a,d)):eve.on("raphael.DOMload",function(){e.call(a._engine.create[m](a,d))})}return a._engine.create[m](a,arguments)}a.version="2.1.0",a.eve=eve;var b,c=/[, ]+/,d={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},e=/\{(\d+)\}/g,f="prototype",g="hasOwnProperty",h={doc:document,win:window},i={was:Object.prototype[g].call(h.win,"Raphael"),is:h.win.Raphael},j=function(){this.ca=this.customAttributes={}},k,l="appendChild",m="apply",n="concat",o="createTouch"in h.doc,p="",q=" ",r=String,s="split",t="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[s](q),u={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},v=r.prototype.toLowerCase,w=Math,x=w.max,y=w.min,z=w.abs,A=w.pow,B=w.PI,C="number",D="string",E="array",F="toString",G="fill",H=Object.prototype.toString,I={},J="push",K=a._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,L=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,M={NaN:1,Infinity:1,"-Infinity":1},N=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,O=w.round,P="setAttribute",Q=parseFloat,R=parseInt,S=r.prototype.toUpperCase,T=a._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},U=a._availableAnimAttrs={blur:C,"clip-rect":"csv",cx:C,cy:C,fill:"colour","fill-opacity":C,"font-size":C,height:C,opacity:C,path:"path",r:C,rx:C,ry:C,stroke:"colour","stroke-opacity":C,"stroke-width":C,transform:"transform",width:C,x:C,y:C},V=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,W=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,X={hs:1,rg:1},Y=/,?([achlmqrstvxz]),?/gi,Z=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,$=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,_=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,ba=a._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,bb={},bc=function(a,b){return a.key-b.key},bd=function(a,b){return Q(a)-Q(b)},be=function(){},bf=function(a){return a},bg=a._rectPath=function(a,b,c,d,e){if(e)return[["M",a+e,b],["l",c-e*2,0],["a",e,e,0,0,1,e,e],["l",0,d-e*2],["a",e,e,0,0,1,-e,e],["l",e*2-c,0],["a",e,e,0,0,1,-e,-e],["l",0,e*2-d],["a",e,e,0,0,1,e,-e],["z"]];return[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},bh=function(a,b,c,d){d==null&&(d=c);return[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},bi=a._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return bh(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return bh(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return bg(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return bg(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return bg(b.x,b.y,b.width,b.height)}},bj=a.mapPath=function(a,b){if(!b)return a;var c,d,e,f,g,h,i;a=bR(a);for(e=0,g=a.length;e<g;e++){i=a[e];for(f=1,h=i.length;f<h;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d}return a};a._g=h,a.type=h.win.SVGAngle||h.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML";if(a.type=="VML"){var bk=h.doc.createElement("div"),bl;bk.innerHTML='<v:shape adj="1"/>',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(f<d)return c-f;if(f>b-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("<body>"),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r<t)&&(z+=180);return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:x,y:y},alpha:z}},a.bezierBBox=function(b,c,d,e,f,g,h,i){a.is(b,"array")||(b=[b,c,d,e,f,g,h,i]);var j=bQ.apply(null,b);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},a.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.x<c.x2&&b.x>c.x||c.x<b.x2&&c.x>b.x)&&(b.y<c.y2&&b.y>c.y||c.y<b.y2&&c.y>b.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h<i;h++){g=a[h];if(g[0]=="M")c=g[1],d=g[2],e.push(c),f.push(d);else{var j=bQ(c,d,g[1],g[2],g[3],g[4],g[5],g[6]);e=e[n](j.min.x,j.max.x),f=f[n](j.min.y,j.max.y),c=g[5],d=g[6]}}var k=y[m](0,e),l=y[m](0,f),o=x[m](0,e),p=x[m](0,f),q={x:k,y:l,x2:o,y2:p,width:o-k,height:p-l};b.bbox=bm(q);return q},bJ=function(b){var c=bm(b);c.toString=a._path2string;return c},bK=a._pathToRelative=function(b){var c=bz(b);if(c.rel)return bJ(c.rel);if(!a.is(b,E)||!a.is(b&&b[0],E))b=a.parsePathString(b);var d=[],e=0,f=0,g=0,h=0,i=0;b[0][0]=="M"&&(e=b[0][1],f=b[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=b.length;j<k;j++){var l=d[j]=[],m=b[j];if(m[0]!=v.call(m[0])){l[0]=v.call(m[0]);switch(l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;n<o;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}}else{l=d[j]=[],m[0]=="m"&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;p<q;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}d.toString=a._path2string,c.rel=bJ(d);return d},bL=a._pathToAbsolute=function(b){var c=bz(b);if(c.abs)return bJ(c.abs);if(!a.is(b,E)||!a.is(b&&b[0],E))b=a.parsePathString(b);if(!b||!b.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,h=0,i=0;b[0][0]=="M"&&(e=+b[0][1],f=+b[0][2],g=e,h=f,i++,d[0]=["M",e,f]);var j=b.length==3&&b[0][0]=="M"&&b[1][0].toUpperCase()=="R"&&b[2][0].toUpperCase()=="Z";for(var k,l,m=i,o=b.length;m<o;m++){d.push(k=[]),l=b[m];if(l[0]!=S.call(l[0])){k[0]=S.call(l[0]);switch(k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":var p=[e,f][n](l.slice(1));for(var q=2,r=p.length;q<r;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[n](by(p,j));break;case"M":g=+l[1]+e,h=+l[2]+f;default:for(q=1,r=l.length;q<r;q++)k[q]=+l[q]+(q%2?e:f)}}else if(l[0]=="R")p=[e,f][n](l.slice(1)),d.pop(),d=d[n](by(p,j)),k=["R"][n](l.slice(-2));else for(var s=0,t=l.length;s<t;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=h;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],h=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}d.toString=a._path2string,c.abs=bJ(d);return d},bM=function(a,b,c,d){return[a,b,c,d,c,d]},bN=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},bO=function(a,b,c,d,e,f,g,h,i,j){var k=B*120/180,l=B/180*(+e||0),m=[],o,p=bv(function(a,b,c){var d=a*w.cos(c)-b*w.sin(c),e=a*w.sin(c)+b*w.cos(c);return{x:d,y:e}});if(!j){o=p(a,b,-l),a=o.x,b=o.y,o=p(h,i,-l),h=o.x,i=o.y;var q=w.cos(B/180*e),r=w.sin(B/180*e),t=(a-h)/2,u=(b-i)/2,v=t*t/(c*c)+u*u/(d*d);v>1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=a<C?B-E:E,F=h<C?B-F:F,E<0&&(E=B*2+E),F<0&&(F=B*2+F),g&&E>F&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W<X;W++)V[W]=W%2?p(m[W-1],m[W],l).y:p(m[W],m[W+1],l).x;return V},bP=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:A(j,3)*a+A(j,2)*3*i*c+j*3*i*i*e+A(i,3)*g,y:A(j,3)*b+A(j,2)*3*i*d+j*3*i*i*f+A(i,3)*h}},bQ=bv(function(a,b,c,d,e,f,g,h){var i=e-2*c+a-(g-2*e+c),j=2*(c-a)-2*(e-c),k=a-c,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,o=[b,h],p=[a,g],q;z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);k<l;k++){d[k]=h(d[k],f),i(d,k),e&&(e[k]=h(e[k],g)),e&&i(e,k),j(d,e,f,g,k),j(e,d,g,f,k);var o=d[k],p=e&&e[k],q=o.length,r=e&&p.length;f.x=o[q-2],f.y=o[q-1],f.bx=Q(o[q-4])||f.x,f.by=Q(o[q-3])||f.y,g.bx=e&&(Q(p[r-4])||g.x),g.by=e&&(Q(p[r-3])||g.y),g.x=e&&p[r-2],g.y=e&&p[r-1]}e||(c.curve=bJ(d));return e?[d,e]:d},null,bJ),bS=a._parseDots=bv(function(b){var c=[];for(var d=0,e=b.length;d<e;d++){var f={},g=b[d].match(/^([^:]*):?([\d\.]*)/);f.color=a.getRGB(g[1]);if(f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),c.push(f)}for(d=1,e=c.length-1;d<e;d++)if(!c[d].offset){var h=Q(c[d-1].offset||0),i=0;for(var j=d+1;j<e;j++)if(c[j].offset){i=c[j].offset;break}i||(i=100,j=e),i=Q(i);var k=(i-h)/(j-d+1);for(;d<j;d++)h+=k,c[d].offset=h+"%"}return c}),bT=a._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)},bU=a._tofront=function(a,b){b.top!==a&&(bT(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},bV=a._toback=function(a,b){b.bottom!==a&&(bT(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},bW=a._insertafter=function(a,b,c){bT(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},bX=a._insertbefore=function(a,b,c){bT(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},bY=a.toMatrix=function(a,b){var c=bI(a),d={_:{transform:p},getBBox:function(){return c}};b$(d,b);return d.matrix},bZ=a.transformPath=function(a,b){return bj(a,bY(a,b))},b$=a._extractTransform=function(b,c){if(c==null)return b._.transform;c=r(c).replace(/\.{3}|\u2026/g,b._.transform||p);var d=a.parseTransformString(c),e=0,f=0,g=0,h=1,i=1,j=b._,k=new cb;j.transform=d||[];if(d)for(var l=0,m=d.length;l<m;l++){var n=d[l],o=n.length,q=r(n[0]).toLowerCase(),s=n[0]!=q,t=s?k.invert():0,u,v,w,x,y;q=="t"&&o==3?s?(u=t.x(0,0),v=t.y(0,0),w=t.x(n[1],n[2]),x=t.y(n[1],n[2]),k.translate(w-u,x-v)):k.translate(n[1],n[2]):q=="r"?o==2?(y=y||b.getBBox(1),k.rotate(n[1],y.x+y.width/2,y.y+y.height/2),e+=n[1]):o==4&&(s?(w=t.x(n[2],n[3]),x=t.y(n[2],n[3]),k.rotate(n[1],w,x)):k.rotate(n[1],n[2],n[3]),e+=n[1]):q=="s"?o==2||o==3?(y=y||b.getBBox(1),k.scale(n[1],n[o-1],y.x+y.width/2,y.y+y.height/2),h*=n[1],i*=n[o-1]):o==5&&(s?(w=t.x(n[3],n[4]),x=t.y(n[3],n[4]),k.scale(n[1],n[2],w,x)):k.scale(n[1],n[2],n[3],n[4]),h*=n[1],i*=n[2]):q=="m"&&o==7&&k.add(n[1],n[2],n[3],n[4],n[5],n[6]),j.dirtyT=1,b.matrix=k}b.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,h==1&&i==1&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1},b_=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return a.length==4?[b,0,a[2],a[3]]:[b,0];case"s":return a.length==5?[b,1,1,a[3],a[4]]:a.length==3?[b,1,1]:[b,1]}},ca=a._equaliseTransform=function(b,c){c=r(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];var d=x(b.length,c.length),e=[],f=[],g=0,h,i,j,k;for(;g<d;g++){j=b[g]||b_(c[g]),k=c[g]||b_(j);if(j[0]!=k[0]||j[0].toLowerCase()=="r"&&(j[2]!=k[2]||j[3]!=k[3])||j[0].toLowerCase()=="s"&&(j[3]!=k[3]||j[4]!=k[4]))return;e[g]=[],f[g]=[];for(h=0,i=x(j.length,k.length);h<i;h++)h in j&&(e[g][h]=j[h]),h in k&&(f[g][h]=k[h])}return{from:e,to:f}};a._getContainer=function(b,c,d,e){var f;f=e==null&&!a.is(b,"object")?h.doc.getElementById(b):b;if(f!=null){if(f.tagName)return c==null?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d};return{container:1,x:b,y:c,width:d,height:e}}},a.pathToRelative=bK,a._engine={},a.path2curve=bR,a.matrix=function(a,b,c,d,e,f){return new cb(a,b,c,d,e,f)},function(b){function d(a){var b=w.sqrt(c(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}function c(a){return a[0]*a[0]+a[1]*a[1]}b.add=function(a,b,c,d,e,f){var g=[[],[],[]],h=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],i=[[a,c,e],[b,d,f],[0,0,1]],j,k,l,m;a&&a instanceof cb&&(i=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]);for(j=0;j<3;j++)for(k=0;k<3;k++){m=0;for(l=0;l<3;l++)m+=h[j][l]*i[l][k];g[j][k]=m}this.a=g[0][0],this.b=g[1][0],this.c=g[0][1],this.d=g[1][1],this.e=g[0][2],this.f=g[1][2]},b.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new cb(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},b.clone=function(){return new cb(this.a,this.b,this.c,this.d,this.e,this.f)},b.translate=function(a,b){this.add(1,0,0,1,a,b)},b.scale=function(a,b,c,d){b==null&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},b.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var e=+w.cos(b).toFixed(9),f=+w.sin(b).toFixed(9);this.add(e,f,-f,e,c,d),this.add(1,0,0,1,-c,-d)},b.x=function(a,b){return a*this.a+b*this.c+this.e},b.y=function(a,b){return a*this.b+b*this.d+this.f},b.get=function(a){return+this[r.fromCharCode(97+a)].toFixed(4)},b.toString=function(){return a.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},b.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},b.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},b.split=function(){var b={};b.dx=this.e,b.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];b.scalex=w.sqrt(c(e[0])),d(e[0]),b.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*b.shear,e[1][1]-e[0][1]*b.shear],b.scaley=w.sqrt(c(e[1])),d(e[1]),b.shear/=b.scaley;var f=-e[0][1],g=e[1][1];g<0?(b.rotate=a.deg(w.acos(g)),f<0&&(b.rotate=360-b.rotate)):b.rotate=a.deg(w.asin(f)),b.isSimple=!+b.shear.toFixed(9)&&(b.scalex.toFixed(9)==b.scaley.toFixed(9)||!b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate;return b},b.toTransformString=function(a){var b=a||this[s]();if(b.isSimple){b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4);return(b.dx||b.dy?"t"+[b.dx,b.dy]:p)+(b.scalex!=1||b.scaley!=1?"s"+[b.scalex,b.scaley,0,0]:p)+(b.rotate?"r"+[b.rotate,0,0]:p)}return"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(cb.prototype);var cc=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);navigator.vendor=="Apple Computer, Inc."&&(cc&&cc[1]<4||navigator.platform.slice(0,2)=="iP")||navigator.vendor=="Google Inc."&&cc&&cc[1]<8?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:k.safari=be;var cd=function(){this.returnValue=!1},ce=function(){return this.originalEvent.preventDefault()},cf=function(){this.cancelBubble=!0},cg=function(){return this.originalEvent.stopPropagation()},ch=function(){if(h.doc.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,f=function(e){var f=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,i=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,j=e.clientX+i,k=e.clientY+f;if(o&&u[g](b))for(var l=0,m=e.targetTouches&&e.targetTouches.length;l<m;l++)if(e.targetTouches[l].target==a){var n=e;e=e.targetTouches[l],e.originalEvent=n,e.preventDefault=ce,e.stopPropagation=cg;break}return c.call(d,e,j,k)};a.addEventListener(e,f,!1);return function(){a.removeEventListener(e,f,!1);return!0}};if(h.doc.attachEvent)return function(a,b,c,d){var e=function(a){a=a||h.win.event;var b=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,e=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;a.preventDefault=a.preventDefault||cd,a.stopPropagation=a.stopPropagation||cf;return c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){a.detachEvent("on"+b,e);return!0};return f}}(),ci=[],cj=function(a){var b=a.clientX,c=a.clientY,d=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,e=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,f,g=ci.length;while(g--){f=ci[g];if(o){var i=a.touches.length,j;while(i--){j=a.touches[i];if(j.identifier==f.el._drag.id){b=j.clientX,c=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}}else a.preventDefault();var k=f.el.node,l,m=k.nextSibling,n=k.parentNode,p=k.style.display;h.win.opera&&n.removeChild(k),k.style.display="none",l=f.el.paper.getElementByPoint(b,c),k.style.display=p,h.win.opera&&(m?n.insertBefore(k,m):n.appendChild(k)),l&&eve("raphael.drag.over."+f.el.id,f.el,l),b+=e,c+=d,eve("raphael.drag.move."+f.el.id,f.move_scope||f.el,b-f.el._drag.x,c-f.el._drag.y,b,c,a)}},ck=function(b){a.unmousemove(cj).unmouseup(ck);var c=ci.length,d;while(c--)d=ci[c],d.el._drag={},eve("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,b);ci=[]},cl=a.el={};for(var cm=t.length;cm--;)(function(b){a[b]=cl[b]=function(c,d){a.is(c,"function")&&(this.events=this.events||[],this.events.push({name:b,f:c,unbind:ch(this.shape||this.node||h.doc,b,c,d||this)}));return this},a["un"+b]=cl["un"+b]=function(a){var c=this.events||[],d=c.length;while(d--)if(c[d].name==b&&c[d].f==a){c[d].unbind(),c.splice(d,1),!c.length&&delete this.events;return this}return this}})(t[cm]);cl.data=function(b,c){var d=bb[this.id]=bb[this.id]||{};if(arguments.length==1){if(a.is(b,"object")){for(var e in b)b[g](e)&&this.data(e,b[e]);return this}eve("raphael.data.get."+this.id,this,d[b],b);return d[b]}d[b]=c,eve("raphael.data.set."+this.id,this,c,b);return this},cl.removeData=function(a){a==null?bb[this.id]={}:bb[this.id]&&delete bb[this.id][a];return this},cl.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},cl.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var cn=[];cl.drag=function(b,c,d,e,f,g){function i(i){(i.originalEvent||i).preventDefault();var j=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,k=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft;this._drag.x=i.clientX+k,this._drag.y=i.clientY+j,this._drag.id=i.identifier,!ci.length&&a.mousemove(cj).mouseup(ck),ci.push({el:this,move_scope:e,start_scope:f,end_scope:g}),c&&eve.on("raphael.drag.start."+this.id,c),b&&eve.on("raphael.drag.move."+this.id,b),d&&eve.on("raphael.drag.end."+this.id,d),eve("raphael.drag.start."+this.id,f||e||this,i.clientX+k,i.clientY+j,i)}this._drag={},cn.push({el:this,start:i}),this.mousedown(i);return this},cl.onDragOver=function(a){a?eve.on("raphael.drag.over."+this.id,a):eve.unbind("raphael.drag.over."+this.id)},cl.undrag=function(){var b=cn.length;while(b--)cn[b].el==this&&(this.unmousedown(cn[b].start),cn.splice(b,1),eve.unbind("raphael.drag.*."+this.id));!cn.length&&a.unmousemove(cj).unmouseup(ck)},k.circle=function(b,c,d){var e=a._engine.circle(this,b||0,c||0,d||0);this.__set__&&this.__set__.push(e);return e},k.rect=function(b,c,d,e,f){var g=a._engine.rect(this,b||0,c||0,d||0,e||0,f||0);this.__set__&&this.__set__.push(g);return g},k.ellipse=function(b,c,d,e){var f=a._engine.ellipse(this,b||0,c||0,d||0,e||0);this.__set__&&this.__set__.push(f);return f},k.path=function(b){b&&!a.is(b,D)&&!a.is(b[0],E)&&(b+=p);var c=a._engine.path(a.format[m](a,arguments),this);this.__set__&&this.__set__.push(c);return c},k.image=function(b,c,d,e,f){var g=a._engine.image(this,b||"about:blank",c||0,d||0,e||0,f||0);this.__set__&&this.__set__.push(g);return g},k.text=function(b,c,d){var e=a._engine.text(this,b||0,c||0,r(d));this.__set__&&this.__set__.push(e);return e},k.set=function(b){!a.is(b,"array")&&(b=Array.prototype.splice.call(arguments,0,arguments.length));var c=new cG(b);this.__set__&&this.__set__.push(c);return c},k.setStart=function(a){this.__set__=a||this.set()},k.setFinish=function(a){var b=this.__set__;delete this.__set__;return b},k.setSize=function(b,c){return a._engine.setSize.call(this,b,c)},k.setViewBox=function(b,c,d,e,f){return a._engine.setViewBox.call(this,b,c,d,e,f)},k.top=k.bottom=null,k.raphael=a;var co=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,i=b.top+(h.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(h.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:i,x:j}};k.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=h.doc.elementFromPoint(a,b);if(h.win.opera&&e.tagName=="svg"){var f=co(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var i=d.getIntersectionList(g,null);i.length&&(e=i[i.length-1])}if(!e)return null;while(e.parentNode&&e!=d.parentNode&&!e.raphael)e=e.parentNode;e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null;return e},k.getById=function(a){var b=this.bottom;while(b){if(b.id==a)return b;b=b.next}return null},k.forEach=function(a,b){var c=this.bottom;while(c){if(a.call(b,c)===!1)return this;c=c.next}return this},k.getElementsByPoint=function(a,b){var c=this.set();this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)});return c},cl.isPointInside=function(b,c){var d=this.realPath=this.realPath||bi[this.type](this);return a.isPointInsidePath(d,b,c)},cl.getBBox=function(a){if(this.removed)return{};var b=this._;if(a){if(b.dirty||!b.bboxwt)this.realPath=bi[this.type](this),b.bboxwt=bI(this.realPath),b.bboxwt.toString=cq,b.dirty=0;return b.bboxwt}if(b.dirty||b.dirtyT||!b.bbox){if(b.dirty||!this.realPath)b.bboxwt=0,this.realPath=bi[this.type](this);b.bbox=bI(bj(this.realPath,this.matrix)),b.bbox.toString=cq,b.dirty=b.dirtyT=0}return b.bbox},cl.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());this.__set__&&this.__set__.push(a);return a},cl.glow=function(a){if(this.type=="text")return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||bi[this.type](this);f=this.matrix?bj(f,this.matrix):f;for(var g=1;g<c+1;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cr={},cs=function(b,c,d,e,f,g,h,i,j){return j==null?bB(b,c,d,e,f,g,h,i):a.findDotsAtSegment(b,c,d,e,f,g,h,i,bC(b,c,d,e,f,g,h,i,j))},ct=function(b,c){return function(d,e,f){d=bR(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;o<p;o++){i=d[o];if(i[0]=="M")g=+i[1],h=+i[2];else{j=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6]);if(n+j>e){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c<cy.length;c++){var d=cy[c];if(d.el.removed||d.paused)continue;var e=b-d.start,f=d.ms,h=d.easing,i=d.from,j=d.diff,k=d.to,l=d.t,m=d.el,o={},p,r={},s;d.initstatus?(e=(d.initstatus*d.anim.top-d.prev)/(d.percent-d.prev)*f,d.status=d.initstatus,delete d.initstatus,d.stop&&cy.splice(c--,1)):d.status=(d.prev+(d.percent-d.prev)*(e/f))/d.anim.top;if(e<0)continue;if(e<f){var t=h(e/f);for(var u in i)if(i[g](u)){switch(U[u]){case C:p=+i[u]+t*f*j[u];break;case"colour":p="rgb("+[cB(O(i[u].r+t*f*j[u].r)),cB(O(i[u].g+t*f*j[u].g)),cB(O(i[u].b+t*f*j[u].b))].join(",")+")";break;case"path":p=[];for(var v=0,w=i[u].length;v<w;v++){p[v]=[i[u][v][0]];for(var x=1,y=i[u][v].length;x<y;x++)p[v][x]=+i[u][v][x]+t*f*j[u][v][x];p[v]=p[v].join(q)}p=p.join(q);break;case"transform":if(j[u].real){p=[];for(v=0,w=i[u].length;v<w;v++){p[v]=[i[u][v][0]];for(x=1,y=i[u][v].length;x<y;x++)p[v][x]=i[u][v][x]+t*f*j[u][v][x]}}else{var z=function(a){return+i[u][a]+t*f*j[u][a]};p=[["m",z(0),z(1),z(2),z(3),z(4),z(5)]]}break;case"csv":if(u=="clip-rect"){p=[],v=4;while(v--)p[v]=+i[u][v]+t*f*j[u][v]}break;default:var A=[][n](i[u]);p=[],v=m.paper.customAttributes[u].length;while(v--)p[v]=+A[v]+t*f*j[u][v]}o[u]=p}m.attr(o),function(a,b,c){setTimeout(function(){eve("raphael.anim.frame."+a,b,c)})}(m.id,m,d.anim)}else{(function(b,c,d){setTimeout(function(){eve("raphael.anim.frame."+c.id,c,d),eve("raphael.anim.finish."+c.id,c,d),a.is(b,"function")&&b.call(c)})})(d.callback,m,d.anim),m.attr(k),cy.splice(c--,1);if(d.repeat>1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l<m;l++)if(cy[l].anim==c&&cy[l].el==b){cy[m-1].start=cy[l].start;break}return h},cl.onAnimation=function(a){a?eve.on("raphael.anim.frame."+this.id,a):eve.unbind("raphael.anim.frame."+this.id);return this},cD.prototype.delay=function(a){var b=new cD(this.anim,this.ms);b.times=this.times,b.del=+a||0;return b},cD.prototype.repeat=function(a){var b=new cD(this.anim,this.ms);b.del=this.del,b.times=w.floor(x(a,0))||1;return b},a.animation=function(b,c,d,e){if(b instanceof cD)return b;if(a.is(d,"function")||!d)e=e||d||null,d=null;b=Object(b),c=+c||0;var f={},h,i;for(i in b)b[g](i)&&Q(i)!=i&&Q(i)+"%"!=i&&(h=!0,f[i]=b[i]);if(!h)return new cD(b,c);d&&(f.easing=d),e&&(f.callback=e);return new cD({100:f},c)},cl.animate=function(b,c,d,e){var f=this;if(f.removed){e&&e.call(f);return f}var g=b instanceof cD?b:a.animation(b,c,d,e);cE(g,f,g.percents[0],null,f.attr());return f},cl.setTime=function(a,b){a&&b!=null&&this.status(a,y(b,a.ms)/a.ms);return this},cl.status=function(a,b){var c=[],d=0,e,f;if(b!=null){cE(a,this,-1,y(b,1));return this}e=cy.length;for(;d<e;d++){f=cy[d];if(f.el.id==this.id&&(!a||f.anim==a)){if(a)return f.status;c.push({anim:f.anim,status:f.status})}}if(a)return 0;return c},cl.pause=function(a){for(var b=0;b<cy.length;b++)cy[b].el.id==this.id&&(!a||cy[b].anim==a)&&eve("raphael.anim.pause."+this.id,this,cy[b].anim)!==!1&&(cy[b].paused=!0);return this},cl.resume=function(a){for(var b=0;b<cy.length;b++)if(cy[b].el.id==this.id&&(!a||cy[b].anim==a)){var c=cy[b];eve("raphael.anim.resume."+this.id,this,c.anim)!==!1&&(delete c.paused,this.status(c.anim,c.status))}return this},cl.stop=function(a){for(var b=0;b<cy.length;b++)cy[b].el.id==this.id&&(!a||cy[b].anim==a)&&eve("raphael.anim.stop."+this.id,this,cy[b].anim)!==!1&&cy.splice(b--,1);return this},eve.on("raphael.remove",cF),eve.on("raphael.clear",cF),cl.toString=function(){return"Raphaël’s object"};var cG=function(a){this.items=[],this.length=0,this.type="set";if(a)for(var b=0,c=a.length;b<c;b++)a[b]&&(a[b].constructor==cl.constructor||a[b].constructor==cG)&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},cH=cG.prototype;cH.push=function(){var a,b;for(var c=0,d=arguments.length;c<d;c++)a=arguments[c],a&&(a.constructor==cl.constructor||a.constructor==cG)&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},cH.pop=function(){this.length&&delete this[this.length--];return this.items.pop()},cH.forEach=function(a,b){for(var c=0,d=this.items.length;c<d;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var cI in cl)cl[g](cI)&&(cH[cI]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][m](c,b)})}}(cI));cH.attr=function(b,c){if(b&&a.is(b,E)&&a.is(b[0],"object"))for(var d=0,e=b.length;d<e;d++)this.items[d].attr(b[d]);else for(var f=0,g=this.items.length;f<g;f++)this.items[f].attr(b,c);return this},cH.clear=function(){while(this.length)this.pop()},cH.splice=function(a,b,c){a=a<0?x(this.length+a,0):a,b=x(0,y(this.length-a,b));var d=[],e=[],f=[],g;for(g=2;g<arguments.length;g++)f.push(arguments[g]);for(g=0;g<b;g++)e.push(this[a+g]);for(;g<this.length-a;g++)d.push(this[a+g]);var h=f.length;for(g=0;g<h+d.length;g++)this.items[a+g]=this[a+g]=g<h?f[g]:d[g-h];g=this.items.length=this.length-=b-h;while(this[g])delete this[g++];return new cG(e)},cH.exclude=function(a){for(var b=0,c=this.length;b<c;b++)if(this[b]==a){this.splice(b,1);return!0}},cH.animate=function(b,c,d,e){(a.is(d,"function")||!d)&&(e=d||null);var f=this.items.length,g=f,h,i=this,j;if(!f)return this;e&&(j=function(){!--f&&e.call(i)}),d=a.is(d,D)?d:j;var k=a.animation(b,c,d,j);h=this.items[--g].animate(k);while(g--)this.items[g]&&!this.items[g].removed&&this.items[g].animateWith(h,k,k);return this},cH.insertAfter=function(a){var b=this.items.length;while(b--)this.items[b].insertAfter(a);return this},cH.getBBox=function(){var a=[],b=[],c=[],d=[];for(var e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}a=y[m](0,a),b=y[m](0,b),c=x[m](0,c),d=x[m](0,d);return{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},cH.clone=function(a){a=new cG;for(var b=0,c=this.items.length;b<c;b++)a.push(this.items[b].clone());return a},cH.toString=function(){return"Raphaël‘s set"},a.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[g](d)&&(b.face[d]=a.face[d]);this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b];if(!a.svg){b.face["units-per-em"]=R(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[g](e)){var f=a.glyphs[e];b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"};if(f.k)for(var h in f.k)f[g](h)&&(b.glyphs[e].k[h]=f.k[h])}}return a},k.getFont=function(b,c,d,e){e=e||"normal",d=d||"normal",c=+c||{normal:400,bold:700,lighter:300,bolder:800}[c]||400;if(!!a.fonts){var f=a.fonts[b];if(!f){var h=new RegExp("(^|\\s)"+b.replace(/[^\w\d\s+!~.:_-]/g,p)+"(\\s|$)","i");for(var i in a.fonts)if(a.fonts[g](i)&&h.test(i)){f=a.fonts[i];break}}var j;if(f)for(var k=0,l=f.length;k<l;k++){j=f[k];if(j.face["font-weight"]==c&&(j.face["font-style"]==d||!j.face["font-style"])&&j.face["font-stretch"]==e)break}return j}},k.print=function(b,d,e,f,g,h,i){h=h||"middle",i=x(y(i||0,1),-1);var j=r(e)[s](p),k=0,l=0,m=p,n;a.is(f,e)&&(f=this.getFont(f));if(f){n=(g||16)/f.face["units-per-em"];var o=f.face.bbox[s](c),q=+o[0],t=o[3]-o[1],u=0,v=+o[1]+(h=="baseline"?t+ +f.face.descent:t/2);for(var w=0,z=j.length;w<z;w++){if(j[w]=="\n")k=0,B=0,l=0,u+=t;else{var A=l&&f.glyphs[j[w-1]]||{},B=f.glyphs[j[w]];k+=l?(A.w||f.w)+(A.k&&A.k[j[w]]||0)+f.w*i:0,l=1}B&&B.d&&(m+=a.transformPath(B.d,["t",k*n,u*n,"s",n,n,q,v,"t",(b-q)/n,(d-v)/n]))}}return this.path(m).attr({fill:"#000",stroke:"none"})},k.add=function(b){if(a.is(b,"array")){var c=this.set(),e=0,f=b.length,h;for(;e<f;e++)h=b[e]||{},d[g](h.type)&&c.push(this[h.type]().attr(h))}return c},a.format=function(b,c){var d=a.is(c,E)?[0][n](c):arguments;b&&a.is(b,D)&&d.length-1&&(b=b.replace(e,function(a,b){return d[++b]==null?p:d[b]}));return b||p},a.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),typeof e=="function"&&f&&(e=e()))}),e=(e==null||e==d?a:e)+"";return e};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),a.ninja=function(){i.was?h.win.Raphael=i.is:delete Raphael;return a},a.st=cH,function(b,c,d){function e(){/in/.test(b.readyState)?setTimeout(e,9):a.eve("raphael.DOMload")}b.readyState==null&&b.addEventListener&&(b.addEventListener(c,d=function(){b.removeEventListener(c,d,!1),b.readyState="complete"},!1),b.readyState="loading"),e()}(document,"DOMContentLoaded"),i.was?h.win.Raphael=a:Raphael=a,eve.on("raphael.DOMload",function(){b=!0})}(),window.Raphael.svg&&function(a){var b="hasOwnProperty",c=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=a.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};a.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){typeof d=="string"&&(d=q(d));for(var f in e)e[b](f)&&(f.substring(0,6)=="xlink:"?d.setAttributeNS(n,f.substring(6),c(e[f])):d.setAttribute(f,c(e[f])))}else d=a._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(b,e){var j="linear",k=b.id+e,m=.5,n=.5,o=b.node,p=b.paper,r=o.style,s=a._g.doc.getElementById(k);if(!s){e=c(e).replace(a._radial_gradient,function(a,b,c){j="radial";if(b&&c){m=d(b),n=d(c);var e=(n>.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x<y;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}q(o,{fill:"url(#"+k+")",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1;return 1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if(d.type=="path"){var g=c(e).toLowerCase().split("-"),h=d.paper,i=f?"end":"start",j=d.node,k=d.attrs,m=k["stroke-width"],n=g.length,r="classic",s,t,u,v,w,x=3,y=3,z=5;while(n--)switch(g[n]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":r=g[n];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}r=="open"?(x+=2,y+=2,z+=2,u=1,v=f?4:1,w={fill:"none",stroke:k.stroke}):(v=u=x/2,w={fill:k.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={};if(r!="none"){var A="raphael-marker-"+r,B="raphael-marker-"+i+r+x+y;a._g.doc.getElementById(A)?p[A]++:(h.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[r],id:A})),p[A]=1);var C=a._g.doc.getElementById(B),D;C?(p[B]++,D=C.getElementsByTagName("use")[0]):(C=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:v,refY:y/2}),D=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),C.appendChild(D),h.defs.appendChild(C),p[B]=1),q(D,w);var F=u*(r!="diamond"&&r!="oval");f?(s=d._.arrows.startdx*m||0,t=a.getTotalLength(k.path)-F*m):(s=F*m,t=a.getTotalLength(k.path)-(d._.arrows.enddx*m||0)),w={},w["marker-"+i]="url(#"+B+")";if(t||s)w.d=Raphael.getSubpath(k.path,s,t);q(j,w),d._.arrows[i+"Path"]=A,d._.arrows[i+"Marker"]=B,d._.arrows[i+"dx"]=F,d._.arrows[i+"Type"]=r,d._.arrows[i+"String"]=e}else f?(s=d._.arrows.startdx*m||0,t=a.getTotalLength(k.path)-s):(s=0,t=a.getTotalLength(k.path)-(d._.arrows.enddx*m||0)),d._.arrows[i+"Path"]&&q(j,{d:Raphael.getSubpath(k.path,s,t)}),delete d._.arrows[i+"Path"],delete d._.arrows[i+"Marker"],delete d._.arrows[i+"dx"],delete d._.arrows[i+"Type"],delete d._.arrows[i+"String"];for(w in p)if(p[b](w)&&!p[w]){var G=a._g.doc.getElementById(w);G&&G.parentNode.removeChild(G)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,b,d){b=u[c(b).toLowerCase()];if(b){var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=b.length;while(h--)g[h]=b[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[b](o)){if(!a._availableAttrs[b](o))continue;var p=f[o];k[o]=p;switch(o){case"blur":d.blur(p);break;case"href":case"title":case"target":var u=i.parentNode;if(u.tagName.toLowerCase()!="a"){var w=q("a");u.insertBefore(w,i),w.appendChild(i),u=w}o=="target"?u.setAttributeNS(n,"show",p=="blank"?"new":p):u.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var x=c(p).split(j);if(x.length==4){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var z=q("clipPath"),A=q("rect");z.id=a.createUUID(),q(A,{x:x[0],y:x[1],width:x[2],height:x[3]}),z.appendChild(A),d.paper.defs.appendChild(z),q(i,{"clip-path":"url(#"+z.id+")"}),d.clip=A}if(!p){var B=i.getAttribute("clip-path");if(B){var C=a._g.doc.getElementById(B.replace(/(^url\(#|\)$)/g,l));C&&C.parentNode.removeChild(C),q(i,{"clip-path":l}),delete d.clip}}break;case"path":d.type=="path"&&(q(i,{d:p?k.path=a._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":i.setAttribute(o,p),d._.dirty=1;if(k.fx)o="x",p=k.x;else break;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if(o=="rx"&&d.type=="rect")break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":i.setAttribute(o,p),d._.dirty=1;if(k.fy)o="y",p=k.y;else break;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if(o=="ry"&&d.type=="rect")break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":d.type=="rect"?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":d.type=="image"&&i.setAttributeNS(n,"href",p);break;case"stroke-width":if(d._.sx!=1||d._.sy!=1)p/=g(h(d._.sx),h(d._.sy))||1;d.paper._vbSize&&(p*=d.paper._vbSize),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var D=c(p).match(a._ISURL);if(D){z=q("pattern");var F=q("image");z.id=a.createUUID(),q(z,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(F,{x:0,y:0,"xlink:href":D[1]}),z.appendChild(F),function(b){a._preload(D[1],function(){var a=this.offsetWidth,c=this.offsetHeight;q(b,{width:a,height:c}),q(F,{width:a,height:c}),d.paper.safari()})}(z),d.paper.defs.appendChild(z),q(i,{fill:"url(#"+z.id+")"}),d.pattern=z,d.pattern&&s(d);break}var G=a.getRGB(p);if(!G.error)delete f.gradient,delete k.gradient,!a.is(k.opacity,"undefined")&&a.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!a.is(k["fill-opacity"],"undefined")&&a.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});else if((d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(H){var I=H.getElementsByTagName("stop");q(I[I.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}G[b]("opacity")&&q(i,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n<o;n++)m=q("tspan"),n&&q(m,{dy:i*x,x:g.x}),m.appendChild(a._g.doc.createTextNode(j[n])),h.appendChild(m),k[n]=m}else{k=h.getElementsByTagName("tspan");for(n=0,o=k.length;n<o;n++)n?q(k[n],{dy:i*x,x:g.x}):q(k[0],{dy:0})}q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&a.is(r,"finite")&&q(k[0],{dy:r})}},z=function(b,c){var d=0,e=0;this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.matrix=a.matrix(),this.realPath=null,this.paper=c,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},A=a.el;z.prototype=A,A.constructor=z,a._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new z(c,b);d.type="path",w(d,{fill:"none",stroke:"#000",path:a});return d},A.rotate=function(a,b,e){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),e==null&&(b=e);if(b==null||e==null){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}this.transform(this._.transform.concat([["r",a,b,e]]));return this},A.scale=function(a,b,e,f){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),b==null&&(b=a),f==null&&(e=f);if(e==null||f==null)var g=this.getBBox(1);e=e==null?g.x+g.width/2:e,f=f==null?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]]));return this},A.translate=function(a,b){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this.transform(this._.transform.concat([["t",a,b]]));return this},A.transform=function(c){var d=this._;if(c==null)return d.transform;a._extractTransform(this,c),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix});if(d.sx!=1||d.sy!=1){var e=this.attrs[b]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},A.hide=function(){!this.removed&&this.paper.safari(this.node.style.display="none");return this},A.show=function(){!this.removed&&this.paper.safari(this.node.style.display="");return this},A.remove=function(){if(!this.removed&&!!this.node.parentNode){var b=this.paper;b.__set__&&b.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&b.defs.removeChild(this.gradient),a._tear(this,b),this.node.parentNode.tagName.toLowerCase()=="a"?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var c in this)this[c]=typeof this[c]=="function"?a._removedFactory(c):null;this.removed=!0}},A._getBBox=function(){if(this.node.style.display=="none"){this.show();var a=!0}var b={};try{b=this.node.getBBox()}catch(c){}finally{b=b||{}}a&&this.hide();return b},A.attr=function(c,d){if(this.removed)return this;if(c==null){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);e.gradient&&e.fill=="none"&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform;return e}if(d==null&&a.is(c,"string")){if(c=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;if(c=="transform")return this._.transform;var g=c.split(j),h={};for(var i=0,l=g.length;i<l;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return l-1?h:h[g[0]]}if(d==null&&a.is(c,"array")){h={};for(i=0,l=c.length;i<l;i++)h[c[i]]=this.attr(c[i]);return h}if(d!=null){var m={};m[c]=d}else c!=null&&a.is(c,"object")&&(m=c);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[b](n)&&m[b](n)&&a.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[b](p)&&(m[p]=o[p])}w(this,m);return this},A.toFront=function(){if(this.removed)return this;this.node.parentNode.tagName.toLowerCase()=="a"?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var b=this.paper;b.top!=this&&a._tofront(this,b);return this},A.toBack=function(){if(this.removed)return this;var b=this.node.parentNode;b.tagName.toLowerCase()=="a"?b.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):b.firstChild!=this.node&&b.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper);var c=this.paper;return this},A.insertAfter=function(b){if(this.removed)return this;var c=b.node||b[b.length-1].node;c.nextSibling?c.parentNode.insertBefore(this.node,c.nextSibling):c.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper);return this},A.insertBefore=function(b){if(this.removed)return this;var c=b.node||b[0].node;c.parentNode.insertBefore(this.node,c),a._insertbefore(this,b,this.paper);return this},A.blur=function(b){var c=this;if(+b!==0){var d=q("filter"),e=q("feGaussianBlur");c.attrs.blur=b,d.id=a.createUUID(),q(e,{stdDeviation:+b||1.5}),d.appendChild(e),c.paper.defs.appendChild(d),c._blur=d,q(c.node,{filter:"url(#"+d.id+")"})}else c._blur&&(c._blur.parentNode.removeChild(c._blur),delete c._blur,delete c.attrs.blur),c.node.removeAttribute("filter")},a._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new z(e,a);f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs);return f},a._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs);return h},a._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs);return g},a._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image";return h},a._engine.text=function(b,c,d,e){var f=q("text");b.canvas&&b.canvas.appendChild(f);var g=new z(f,b);g.attrs={x:c,y:d,"text-anchor":"middle",text:e,font:a._availableAttrs.font,stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs);return g},a._engine.setSize=function(a,b){this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox);return this},a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,g=b.height;if(!c)throw new Error("SVG container not found.");var h=q("svg"),i="overflow:hidden;",j;d=d||0,e=e||0,f=f||512,g=g||342,q(h,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg"}),c==1?(h.style.cssText=i+"position:absolute;left:"+d+"px;top:"+e+"px",a._g.doc.body.appendChild(h),j=1):(h.style.cssText=i+"position:relative",c.firstChild?c.insertBefore(h,c.firstChild):c.appendChild(h)),c=new a._Paper,c.width=f,c.height=g,c.canvas=h,c.clear(),c._left=c._top=0,j&&(c.renderfix=function(){}),c.renderfix();return c},a._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f=g(c/this.width,d/this.height),h=this.top,i=e?"meet":"xMinYMin",j,l;a==null?(this._vbSize&&(f=1),delete this._vbSize,j="0 0 "+this.width+m+this.height):(this._vbSize=f,j=a+m+b+m+c+m+d),q(this.canvas,{viewBox:j,preserveAspectRatio:i});while(f&&h)l="stroke-width"in h.attrs?h.attrs["stroke-width"]:1,h.attr({"stroke-width":l}),h._.dirty=1,h._.dirtyT=1,h=h.prev;this._viewBox=[a,b,c,d,!!e];return this},a.prototype.renderfix=function(){var a=this.canvas,b=a.style,c;try{c=a.getScreenCTM()||a.createSVGMatrix()}catch(d){c=a.createSVGMatrix()}var e=-c.e%1,f=-c.f%1;if(e||f)e&&(this._left=(this._left+e)%1,b.left=this._left+"px"),f&&(this._top=(this._top+f)%1,b.top=this._top+"px")},a.prototype.clear=function(){a.eve("raphael.clear",this);var b=this.canvas;while(b.firstChild)b.removeChild(b.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(a._g.doc.createTextNode("Created with Raphaël "+a.version)),b.appendChild(this.desc),b.appendChild(this.defs=q("defs"))},a.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null};var B=a.st;for(var C in A)A[b](C)&&!B[b](C)&&(B[C]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(C))}(window.Raphael),window.Raphael.vml&&function(a){var b="hasOwnProperty",c=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=a.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(b){var d=/[ahqstv]/ig,e=a._pathToAbsolute;c(b).match(d)&&(e=a._path2curve),d=/[clmz]/g;if(e==a._pathToAbsolute&&!c(b).match(d)){var g=c(b).replace(q,function(a,b,c){var d=[],e=b.toLowerCase()=="m",g=p[b];c.replace(s,function(a){e&&d.length==2&&(g+=d+p[b=="m"?"l":"L"],d=[]),d.push(f(a*u))});return g+d});return g}var h=e(b),i,j;g=[];for(var k=0,l=h.length;k<l;k++){i=h[k],j=h[k][0].toLowerCase(),j=="z"&&(j="x");for(var m=1,r=i.length;m<r;m++)j+=f(i[m]*u)+(m!=r-1?",":o);g.push(j)}return g.join(n)},y=function(b,c,d){var e=a.matrix();e.rotate(-b,.5,.5);return{dx:e.x(c,d),dy:e.y(c,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q,r=u/b,s=u/c;m.visibility="hidden";if(!!b&&!!c){l.coordsize=i(r)+n+i(s),m.rotation=f*(b*c<0?-1:1);if(f){var t=y(f,d,e);d=t.dx,e=t.dy}b<0&&(p+="x"),c<0&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-r+n+e*-s;if(k||g.fillsize){var v=l.getElementsByTagName(j);v=v&&v[0],l.removeChild(v),k&&(t=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),v.position=t.dx*o+n+t.dy*o),g.fillsize&&(v.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(v)}m.visibility="visible"}};a.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,b,d){var e=c(b).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";while(g--)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q,r=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),s=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),t=e;for(var y in i)i[b](y)&&(m[y]=i[y]);r&&(m.path=a._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur);if(i.path&&e.type=="path"||r)l.path=x(~c(m.path).toLowerCase().indexOf("r")?a._pathToAbsolute(m.path):m.path),e.type=="image"&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0));"transform"in i&&e.transform(i.transform);if(s){var B=+m.cx,D=+m.cy,E=+m.rx||+m.r||0,G=+m.ry||+m.r||0;l.path=a.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((B-E)*u),f((D-G)*u),f((B+E)*u),f((D+G)*u),f(B*u))}if("clip-rect"in i){var H=c(i["clip-rect"]).split(k);if(H.length==4){H[2]=+H[2]+ +H[0],H[3]=+H[3]+ +H[1];var I=l.clipRect||a._g.doc.createElement("div"),J=I.style;J.clip=a.format("rect({1}px {2}px {3}px {0}px)",H),l.clipRect||(J.position="absolute",J.top=0,J.left=0,J.width=e.paper.width+"px",J.height=e.paper.height+"px",l.parentNode.insertBefore(I,l),I.appendChild(l),l.clipRect=I)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var K=e.textpath.style;i.font&&(K.font=i.font),i["font-family"]&&(K.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(K.fontSize=i["font-size"]),i["font-weight"]&&(K.fontWeight=i["font-weight"]),i["font-style"]&&(K.fontStyle=i["font-style"])}"arrow-start"in i&&A(t,i["arrow-start"]),"arrow-end"in i&&A(t,i["arrow-end"],1);if(i.opacity!=null||i["stroke-width"]!=null||i.fill!=null||i.src!=null||i.stroke!=null||i["stroke-width"]!=null||i["stroke-opacity"]!=null||i["fill-opacity"]!=null||i["stroke-dasharray"]!=null||i["stroke-miterlimit"]!=null||i["stroke-linejoin"]!=null||i["stroke-linecap"]!=null){var L=l.getElementsByTagName(j),M=!1;L=L&&L[0],!L&&(M=L=F(j)),e.type=="image"&&i.src&&(L.src=i.src),i.fill&&(L.on=!0);if(L.on==null||i.fill=="none"||i.fill===null)L.on=!1;if(L.on&&i.fill){var N=c(i.fill).match(a._ISURL);if(N){L.parentNode==l&&l.removeChild(L),L.rotate=!0,L.src=N[1],L.type="tile";var O=e.getBBox(1);L.position=O.x+n+O.y,e._.fillpos=[O.x,O.y],a._preload(N[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else L.color=a.getRGB(i.fill).hex,L.src=o,L.type="solid",a.getRGB(i.fill).error&&(t.type in{circle:1,ellipse:1}||c(i.fill).charAt()!="r")&&C(t,i.fill,L)&&(m.fill="none",m.gradient=i.fill,L.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var P=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+a.getRGB(i.fill).o+1||2)-1);P=h(g(P,0),1),L.opacity=P,L.src&&(L.color="none")}l.appendChild(L);var Q=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],T=!1;!Q&&(T=Q=F("stroke"));if(i.stroke&&i.stroke!="none"||i["stroke-width"]||i["stroke-opacity"]!=null||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])Q.on=!0;(i.stroke=="none"||i.stroke===null||Q.on==null||i.stroke==0||i["stroke-width"]==0)&&(Q.on=!1);var U=a.getRGB(i.stroke);Q.on&&i.stroke&&(Q.color=U.hex),P=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+U.o+1||2)-1);var V=(d(i["stroke-width"])||1)*.75;P=h(g(P,0),1),i["stroke-width"]==null&&(V=m["stroke-width"]),i["stroke-width"]&&(Q.weight=V),V&&V<1&&(P*=V)&&(Q.weight=1),Q.opacity=P,i["stroke-linejoin"]&&(Q.joinstyle=i["stroke-linejoin"]||"miter"),Q.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(Q.endcap=i["stroke-linecap"]=="butt"?"flat":i["stroke-linecap"]=="square"?"square":"round");if(i["stroke-dasharray"]){var W={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};Q.dashstyle=W[b](i["stroke-dasharray"])?W[i["stroke-dasharray"]]:o}T&&l.appendChild(Q)}if(t.type=="text"){t.paper.canvas.style.display=o;var X=t.paper.span,Y=100,Z=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=X.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),Z=d(m["font-size"]||Z&&Z[0])||10,p.fontSize=Z*Y+"px",t.textpath.string&&(X.innerHTML=c(t.textpath.string).replace(/</g,"&#60;").replace(/&/g,"&#38;").replace(/\n/g,"<br>"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba<bb;ba++)if(_[ba]in i){t._.dirty=1;break}switch(m["text-anchor"]){case"start":t.textpath.style["v-text-align"]="left",t.bbx=t.W/2;break;case"end":t.textpath.style["v-text-align"]="right",t.bbx=-t.W/2;break;default:t.textpath.style["v-text-align"]="center",t.bbx=0}t.textpath.style["v-text-kern"]=!0}},C=function(b,f,g){b.attrs=b.attrs||{};var h=b.attrs,i=Math.pow,j,k,l="linear",m=".5 .5";b.attrs.gradient=f,f=c(f).replace(a._radial_gradient,function(a,b,c){l="radial",b&&c&&(b=d(b),c=d(c),i(b-.5,2)+i(c-.5,2)>.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s<t;s++)q[s].offset&&r.push(q[s].offset+n+q[s].color);g.colors=r.length?r.join():"0% "+g.color,l=="radial"?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=m,g.angle=0):(g.type="gradient",g.angle=(270-p)%360),b.appendChild(g)}return 1},D=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=c,this.matrix=a.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},E=a.el;D.prototype=E,E.constructor=D,E.transform=function(b){if(b==null)return this._.transform;var d=this.paper._viewBoxShift,e=d?"s"+[d.scale,d.scale]+"-1-1t"+[d.dx,d.dy]:o,f;d&&(f=b=c(b).replace(/\.{3}|\u2026/g,this._.transform||o)),a._extractTransform(this,e+b);var g=this.matrix.clone(),h=this.skew,i=this.node,j,k=~c(this.attrs.fill).indexOf("-"),l=!c(this.attrs.fill).indexOf("url(");g.translate(-0.5,-0.5);if(l||k||this.type=="image"){h.matrix="1 0 0 1",h.offset="0 0",j=g.split();if(k&&j.noRotation||!j.isSimple){i.style.filter=g.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;i.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else i.style.filter=o,z(this,j.scalex,j.scaley,j.dx,j.dy,j.rotate)}else i.style.filter=o,h.matrix=c(g),h.offset=g.offset();f&&(this._.transform=f);return this},E.rotate=function(a,b,e){if(this.removed)return this;if(a!=null){a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),e==null&&(b=e);if(b==null||e==null){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,b,e]]));return this}},E.translate=function(a,b){if(this.removed)return this;a=c(a).split(k),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=b),this.transform(this._.transform.concat([["t",a,b]]));return this},E.scale=function(a,b,e,f){if(this.removed)return this;a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),b==null&&(b=a),f==null&&(e=f);if(e==null||f==null)var g=this.getBBox(1);e=e==null?g.x+g.width/2:e,f=f==null?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this._.dirtyT=1;return this},E.hide=function(){!this.removed&&(this.node.style.display="none");return this},E.show=function(){!this.removed&&(this.node.style.display=o);return this},E._getBBox=function(){if(this.removed)return{};return{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&!!this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),a.eve.unbind("raphael.*.*."+this.id),a._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;this.removed=!0}},E.attr=function(c,d){if(this.removed)return this;if(c==null){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);e.gradient&&e.fill=="none"&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform;return e}if(d==null&&a.is(c,"string")){if(c==j&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;var g=c.split(k),h={};for(var i=0,m=g.length;i<m;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return m-1?h:h[g[0]]}if(this.attrs&&d==null&&a.is(c,"array")){h={};for(i=0,m=c.length;i<m;i++)h[c[i]]=this.attr(c[i]);return h}var n;d!=null&&(n={},n[c]=d),d==null&&a.is(c,"object")&&(n=c);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[b](o)&&n[b](o)&&a.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[b](q)&&(n[q]=p[q])}n.text&&this.type=="text"&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&a._tofront(this,this.paper);return this},E.toBack=function(){if(this.removed)return this;this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper));return this},E.insertAfter=function(b){if(this.removed)return this;b.constructor==a.st.constructor&&(b=b[b.length-1]),b.node.nextSibling?b.node.parentNode.insertBefore(this.node,b.node.nextSibling):b.node.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper);return this},E.insertBefore=function(b){if(this.removed)return this;b.constructor==a.st.constructor&&(b=b[0]),b.node.parentNode.insertBefore(this.node,b.node),a._insertbefore(this,b,this.paper);return this},E.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;d=d.replace(r,o),+b!==0?(this.attrs.blur=b,c.filter=d+n+m+".Blur(pixelradius="+(+b||1.5)+")",c.margin=a.format("-{0}px 0 0 -{0}px",f(+b||1.5))):(c.filter=d,c.margin=0,delete this.attrs.blur)},a._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");f.on=!0,c.appendChild(f),d.skew=f,d.transform(o);return d},a._engine.rect=function(b,c,d,e,f,g){var h=a._rectPath(c,d,e,f,g),i=b.path(h),j=i.attrs;i.X=j.x=c,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect";return i},a._engine.ellipse=function(a,b,c,d,e){var f=a.path(),g=f.attrs;f.X=b-d,f.Y=c-e,f.W=d*2,f.H=e*2,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e});return f},a._engine.circle=function(a,b,c,d){var e=a.path(),f=e.attrs;e.X=b-d,e.Y=c-d,e.W=e.H=d*2,e.type="circle",B(e,{cx:b,cy:c,r:d});return e},a._engine.image=function(b,c,d,e,f,g){var h=a._rectPath(d,e,f,g),i=b.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];k.src=c,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=c,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0);return i},a._engine.text=function(b,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=a.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=c(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,b),l={fill:"#000",stroke:"none",font:a._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=c(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),b.canvas.appendChild(h);var m=F("skew");m.on=!0,h.appendChild(m),k.skew=m,k.transform(o);return k},a._engine.setSize=function(b,c){var d=this.canvas.style;this.width=b,this.height=c,b==+b&&(b+="px"),c==+c&&(c+="px"),d.width=b,d.height=c,d.clip="rect(0 "+b+" "+c+" 0)",this._viewBox&&a._engine.setViewBox.apply(this,this._viewBox);return this},a._engine.setViewBox=function(b,c,d,e,f){a.eve("raphael.setViewBox",this,this._viewBox,[b,c,d,e,f]);var h=this.width,i=this.height,j=1/g(d/h,e/i),k,l;f&&(k=i/e,l=h/d,d*k<h&&(b-=(h-d*k)/2/k),e*l<i&&(c-=(i-e*l)/2/l)),this._viewBox=[b,c,d,e,!!f],this._viewBoxShift={dx:-b,dy:-c,scale:j},this.forEach(function(a){a.transform("...")});return this};var F;a._engine.initWin=function(a){var b=a.document;b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael)
\ No newline at end of file
diff --git a/modules/question_types/quiz_ddlines/js/raphael.js b/modules/question_types/quiz_ddlines/js/raphael.js
new file mode 100644
index 0000000..a704866
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/js/raphael.js
@@ -0,0 +1,7955 @@
+// ┌────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël 2.1.0 - JavaScript Vector Library                          │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
+// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
+// └────────────────────────────────────────────────────────────────────┘ \\
+// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
+// 
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// 
+// http://www.apache.org/licenses/LICENSE-2.0
+// 
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ┌────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
+// ├────────────────────────────────────────────────────────────┤ \\
+// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// └────────────────────────────────────────────────────────────┘ \\
+
+(function (glob) {
+    var version = "0.4.2",
+        has = "hasOwnProperty",
+        separator = /[\.\/]/,
+        wildcard = "*",
+        fun = function () {},
+        numsort = function (a, b) {
+            return a - b;
+        },
+        current_event,
+        stop,
+        events = {n: {}},
+    /*\
+     * eve
+     [ method ]
+
+     * Fires event with given `name`, given scope and other parameters.
+
+     > Arguments
+
+     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
+     - scope (object) context for the event handlers
+     - varargs (...) the rest of arguments will be sent to event handlers
+
+     = (object) array of returned values from the listeners
+    \*/
+        eve = function (name, scope) {
+			name = String(name);
+            var e = events,
+                oldstop = stop,
+                args = Array.prototype.slice.call(arguments, 2),
+                listeners = eve.listeners(name),
+                z = 0,
+                f = false,
+                l,
+                indexed = [],
+                queue = {},
+                out = [],
+                ce = current_event,
+                errors = [];
+            current_event = name;
+            stop = 0;
+            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+                indexed.push(listeners[i].zIndex);
+                if (listeners[i].zIndex < 0) {
+                    queue[listeners[i].zIndex] = listeners[i];
+                }
+            }
+            indexed.sort(numsort);
+            while (indexed[z] < 0) {
+                l = queue[indexed[z++]];
+                out.push(l.apply(scope, args));
+                if (stop) {
+                    stop = oldstop;
+                    return out;
+                }
+            }
+            for (i = 0; i < ii; i++) {
+                l = listeners[i];
+                if ("zIndex" in l) {
+                    if (l.zIndex == indexed[z]) {
+                        out.push(l.apply(scope, args));
+                        if (stop) {
+                            break;
+                        }
+                        do {
+                            z++;
+                            l = queue[indexed[z]];
+                            l && out.push(l.apply(scope, args));
+                            if (stop) {
+                                break;
+                            }
+                        } while (l)
+                    } else {
+                        queue[l.zIndex] = l;
+                    }
+                } else {
+                    out.push(l.apply(scope, args));
+                    if (stop) {
+                        break;
+                    }
+                }
+            }
+            stop = oldstop;
+            current_event = ce;
+            return out.length ? out : null;
+        };
+		// Undocumented. Debug only.
+		eve._events = events;
+    /*\
+     * eve.listeners
+     [ method ]
+
+     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
+
+     > Arguments
+
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated
+
+     = (array) array of event handlers
+    \*/
+    eve.listeners = function (name) {
+        var names = name.split(separator),
+            e = events,
+            item,
+            items,
+            k,
+            i,
+            ii,
+            j,
+            jj,
+            nes,
+            es = [e],
+            out = [];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            nes = [];
+            for (j = 0, jj = es.length; j < jj; j++) {
+                e = es[j].n;
+                items = [e[names[i]], e[wildcard]];
+                k = 2;
+                while (k--) {
+                    item = items[k];
+                    if (item) {
+                        nes.push(item);
+                        out = out.concat(item.f || []);
+                    }
+                }
+            }
+            es = nes;
+        }
+        return out;
+    };
+    
+    /*\
+     * eve.on
+     [ method ]
+     **
+     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
+     | eve.on("*.under.*", f);
+     | eve("mouse.under.floor"); // triggers f
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. 
+     > Example:
+     | eve.on("mouse", eatIt)(2);
+     | eve.on("mouse", scream);
+     | eve.on("mouse", catchIt)(1);
+     * This will ensure that `catchIt()` function will be called before `eatIt()`.
+	 *
+     * If you want to put your handler before non-indexed handlers, specify a negative value.
+     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
+    \*/
+    eve.on = function (name, f) {
+		name = String(name);
+		if (typeof f != "function") {
+			return function () {};
+		}
+        var names = name.split(separator),
+            e = events;
+        for (var i = 0, ii = names.length; i < ii; i++) {
+            e = e.n;
+            e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
+        }
+        e.f = e.f || [];
+        for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+            return fun;
+        }
+        e.f.push(f);
+        return function (zIndex) {
+            if (+zIndex == +zIndex) {
+                f.zIndex = +zIndex;
+            }
+        };
+    };
+    /*\
+     * eve.f
+     [ method ]
+     **
+     * Returns function that will fire given event with optional arguments.
+	 * Arguments that will be passed to the result function will be also
+	 * concated to the list of final arguments.
+ 	 | el.onclick = eve.f("click", 1, 2);
+ 	 | eve.on("click", function (a, b, c) {
+ 	 |     console.log(a, b, c); // 1, 2, [event object]
+ 	 | });
+     > Arguments
+	 - event (string) event name
+	 - varargs (…) and any other arguments
+	 = (function) possible event handler function
+    \*/
+	eve.f = function (event) {
+		var attrs = [].slice.call(arguments, 1);
+		return function () {
+			eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
+		};
+	};
+    /*\
+     * eve.stop
+     [ method ]
+     **
+     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
+    \*/
+    eve.stop = function () {
+        stop = 1;
+    };
+    /*\
+     * eve.nt
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     > Arguments
+     **
+     - subname (string) #optional subname of the event
+     **
+     = (string) name of the event, if `subname` is not specified
+     * or
+     = (boolean) `true`, if current event’s name contains `subname`
+    \*/
+    eve.nt = function (subname) {
+        if (subname) {
+            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+        }
+        return current_event;
+    };
+    /*\
+     * eve.nts
+     [ method ]
+     **
+     * Could be used inside event handler to figure out actual name of the event.
+     **
+     **
+     = (array) names of the event
+    \*/
+    eve.nts = function () {
+        return current_event.split(separator);
+    };
+    /*\
+     * eve.off
+     [ method ]
+     **
+     * Removes given function from the list of event listeners assigned to given name.
+	 * If no arguments specified all the events will be cleared.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+    \*/
+    /*\
+     * eve.unbind
+     [ method ]
+     **
+     * See @eve.off
+    \*/
+    eve.off = eve.unbind = function (name, f) {
+		if (!name) {
+		    eve._events = events = {n: {}};
+			return;
+		}
+        var names = name.split(separator),
+            e,
+            key,
+            splice,
+            i, ii, j, jj,
+            cur = [events];
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
+                splice = [j, 1];
+                e = cur[j].n;
+                if (names[i] != wildcard) {
+                    if (e[names[i]]) {
+                        splice.push(e[names[i]]);
+                    }
+                } else {
+                    for (key in e) if (e[has](key)) {
+                        splice.push(e[key]);
+                    }
+                }
+                cur.splice.apply(cur, splice);
+            }
+        }
+        for (i = 0, ii = cur.length; i < ii; i++) {
+            e = cur[i];
+            while (e.n) {
+                if (f) {
+                    if (e.f) {
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
+                            break;
+                        }
+                        !e.f.length && delete e.f;
+                    }
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        var funcs = e.n[key].f;
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
+                            break;
+                        }
+                        !funcs.length && delete e.n[key].f;
+                    }
+                } else {
+                    delete e.f;
+                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+                        delete e.n[key].f;
+                    }
+                }
+                e = e.n;
+            }
+        }
+    };
+    /*\
+     * eve.once
+     [ method ]
+     **
+     * Binds given event handler with a given name to only run once then unbind itself.
+     | eve.once("login", f);
+     | eve("login"); // triggers f
+     | eve("login"); // no listeners
+     * Use @eve to trigger the listener.
+     **
+     > Arguments
+     **
+     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
+     - f (function) event handler function
+     **
+     = (function) same return function as @eve.on
+    \*/
+    eve.once = function (name, f) {
+        var f2 = function () {
+            eve.unbind(name, f2);
+            return f.apply(this, arguments);
+        };
+        return eve.on(name, f2);
+    };
+    /*\
+     * eve.version
+     [ property (string) ]
+     **
+     * Current version of the library.
+    \*/
+    eve.version = version;
+    eve.toString = function () {
+        return "You are running Eve " + version;
+    };
+    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
+})(this);
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ "Raphaël 2.1.0" - JavaScript Vector Library                         │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+(function () {
+    /*\
+     * Raphael
+     [ method ]
+     **
+     * Creates a canvas object on which to draw.
+     * You must do this first, as all future calls to drawing methods
+     * from this instance will be bound to this canvas.
+     > Parameters
+     **
+     - container (HTMLElement|string) DOM element or its ID which is going to be a parent for drawing surface
+     - width (number)
+     - height (number)
+     - callback (function) #optional callback function which is going to be executed in the context of newly created paper
+     * or
+     - x (number)
+     - y (number)
+     - width (number)
+     - height (number)
+     - callback (function) #optional callback function which is going to be executed in the context of newly created paper
+     * or
+     - all (array) (first 3 or 4 elements in the array are equal to [containerID, width, height] or [x, y, width, height]. The rest are element descriptions in format {type: type, <attributes>}). See @Paper.add.
+     - callback (function) #optional callback function which is going to be executed in the context of newly created paper
+     * or
+     - onReadyCallback (function) function that is going to be called on DOM ready event. You can also subscribe to this event via Eve’s “DOMLoad” event. In this case method returns `undefined`.
+     = (object) @Paper
+     > Usage
+     | // Each of the following examples create a canvas
+     | // that is 320px wide by 200px high.
+     | // Canvas is created at the viewport’s 10,50 coordinate.
+     | var paper = Raphael(10, 50, 320, 200);
+     | // Canvas is created at the top left corner of the #notepad element
+     | // (or its top right corner in dir="rtl" elements)
+     | var paper = Raphael(document.getElementById("notepad"), 320, 200);
+     | // Same as above
+     | var paper = Raphael("notepad", 320, 200);
+     | // Image dump
+     | var set = Raphael(["notepad", 320, 200, {
+     |     type: "rect",
+     |     x: 10,
+     |     y: 10,
+     |     width: 25,
+     |     height: 25,
+     |     stroke: "#f00"
+     | }, {
+     |     type: "text",
+     |     x: 30,
+     |     y: 40,
+     |     text: "Dump"
+     | }]);
+    \*/
+    function R(first) {
+        if (R.is(first, "function")) {
+            return loaded ? first() : eve.on("raphael.DOMload", first);
+        } else if (R.is(first, array)) {
+            return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
+        } else {
+            var args = Array.prototype.slice.call(arguments, 0);
+            if (R.is(args[args.length - 1], "function")) {
+                var f = args.pop();
+                return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
+                    f.call(R._engine.create[apply](R, args));
+                });
+            } else {
+                return R._engine.create[apply](R, arguments);
+            }
+        }
+    }
+    R.version = "2.1.0";
+    R.eve = eve;
+    var loaded,
+        separator = /[, ]+/,
+        elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
+        formatrg = /\{(\d+)\}/g,
+        proto = "prototype",
+        has = "hasOwnProperty",
+        g = {
+            doc: document,
+            win: window
+        },
+        oldRaphael = {
+            was: Object.prototype[has].call(g.win, "Raphael"),
+            is: g.win.Raphael
+        },
+        Paper = function () {
+            /*\
+             * Paper.ca
+             [ property (object) ]
+             **
+             * Shortcut for @Paper.customAttributes
+            \*/
+            /*\
+             * Paper.customAttributes
+             [ property (object) ]
+             **
+             * If you have a set of attributes that you would like to represent
+             * as a function of some number you can do it easily with custom attributes:
+             > Usage
+             | paper.customAttributes.hue = function (num) {
+             |     num = num % 1;
+             |     return {fill: "hsb(" + num + ", 0.75, 1)"};
+             | };
+             | // Custom attribute “hue” will change fill
+             | // to be given hue with fixed saturation and brightness.
+             | // Now you can use it like this:
+             | var c = paper.circle(10, 10, 10).attr({hue: .45});
+             | // or even like this:
+             | c.animate({hue: 1}, 1e3);
+             | 
+             | // You could also create custom attribute
+             | // with multiple parameters:
+             | paper.customAttributes.hsb = function (h, s, b) {
+             |     return {fill: "hsb(" + [h, s, b].join(",") + ")"};
+             | };
+             | c.attr({hsb: "0.5 .8 1"});
+             | c.animate({hsb: [1, 0, 0.5]}, 1e3);
+            \*/
+            this.ca = this.customAttributes = {};
+        },
+        paperproto,
+        appendChild = "appendChild",
+        apply = "apply",
+        concat = "concat",
+        supportsTouch = "createTouch" in g.doc,
+        E = "",
+        S = " ",
+        Str = String,
+        split = "split",
+        events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
+        touchMap = {
+            mousedown: "touchstart",
+            mousemove: "touchmove",
+            mouseup: "touchend"
+        },
+        lowerCase = Str.prototype.toLowerCase,
+        math = Math,
+        mmax = math.max,
+        mmin = math.min,
+        abs = math.abs,
+        pow = math.pow,
+        PI = math.PI,
+        nu = "number",
+        string = "string",
+        array = "array",
+        toString = "toString",
+        fillString = "fill",
+        objectToString = Object.prototype.toString,
+        paper = {},
+        push = "push",
+        ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+        colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
+        isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
+        bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+        round = math.round,
+        setAttribute = "setAttribute",
+        toFloat = parseFloat,
+        toInt = parseInt,
+        upperCase = Str.prototype.toUpperCase,
+        availableAttrs = R._availableAttrs = {
+            "arrow-end": "none",
+            "arrow-start": "none",
+            blur: 0,
+            "clip-rect": "0 0 1e9 1e9",
+            cursor: "default",
+            cx: 0,
+            cy: 0,
+            fill: "#fff",
+            "fill-opacity": 1,
+            font: '10px "Arial"',
+            "font-family": '"Arial"',
+            "font-size": "10",
+            "font-style": "normal",
+            "font-weight": 400,
+            gradient: 0,
+            height: 0,
+            href: "http://raphaeljs.com/",
+            "letter-spacing": 0,
+            opacity: 1,
+            path: "M0,0",
+            r: 0,
+            rx: 0,
+            ry: 0,
+            src: "",
+            stroke: "#000",
+            "stroke-dasharray": "",
+            "stroke-linecap": "butt",
+            "stroke-linejoin": "butt",
+            "stroke-miterlimit": 0,
+            "stroke-opacity": 1,
+            "stroke-width": 1,
+            target: "_blank",
+            "text-anchor": "middle",
+            title: "Raphael",
+            transform: "",
+            width: 0,
+            x: 0,
+            y: 0
+        },
+        availableAnimAttrs = R._availableAnimAttrs = {
+            blur: nu,
+            "clip-rect": "csv",
+            cx: nu,
+            cy: nu,
+            fill: "colour",
+            "fill-opacity": nu,
+            "font-size": nu,
+            height: nu,
+            opacity: nu,
+            path: "path",
+            r: nu,
+            rx: nu,
+            ry: nu,
+            stroke: "colour",
+            "stroke-opacity": nu,
+            "stroke-width": nu,
+            transform: "transform",
+            width: nu,
+            x: nu,
+            y: nu
+        },
+        whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
+        commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
+        hsrg = {hs: 1, rg: 1},
+        p2s = /,?([achlmqrstvxz]),?/gi,
+        pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
+        tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
+        pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
+        radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
+        eldata = {},
+        sortByKey = function (a, b) {
+            return a.key - b.key;
+        },
+        sortByNumber = function (a, b) {
+            return toFloat(a) - toFloat(b);
+        },
+        fun = function () {},
+        pipe = function (x) {
+            return x;
+        },
+        rectPath = R._rectPath = function (x, y, w, h, r) {
+            if (r) {
+                return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
+            }
+            return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+        },
+        ellipsePath = function (x, y, rx, ry) {
+            if (ry == null) {
+                ry = rx;
+            }
+            return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
+        },
+        getPath = R._getPath = {
+            path: function (el) {
+                return el.attr("path");
+            },
+            circle: function (el) {
+                var a = el.attrs;
+                return ellipsePath(a.cx, a.cy, a.r);
+            },
+            ellipse: function (el) {
+                var a = el.attrs;
+                return ellipsePath(a.cx, a.cy, a.rx, a.ry);
+            },
+            rect: function (el) {
+                var a = el.attrs;
+                return rectPath(a.x, a.y, a.width, a.height, a.r);
+            },
+            image: function (el) {
+                var a = el.attrs;
+                return rectPath(a.x, a.y, a.width, a.height);
+            },
+            text: function (el) {
+                var bbox = el._getBBox();
+                return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+            },
+            set : function(el) {
+                var bbox = el._getBBox();
+                return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+            }
+        },
+        /*\
+         * Raphael.mapPath
+         [ method ]
+         **
+         * Transform the path string with given matrix.
+         > Parameters
+         - path (string) path string
+         - matrix (object) see @Matrix
+         = (string) transformed path string
+        \*/
+        mapPath = R.mapPath = function (path, matrix) {
+            if (!matrix) {
+                return path;
+            }
+            var x, y, i, j, ii, jj, pathi;
+            path = path2curve(path);
+            for (i = 0, ii = path.length; i < ii; i++) {
+                pathi = path[i];
+                for (j = 1, jj = pathi.length; j < jj; j += 2) {
+                    x = matrix.x(pathi[j], pathi[j + 1]);
+                    y = matrix.y(pathi[j], pathi[j + 1]);
+                    pathi[j] = x;
+                    pathi[j + 1] = y;
+                }
+            }
+            return path;
+        };
+
+    R._g = g;
+    /*\
+     * Raphael.type
+     [ property (string) ]
+     **
+     * Can be “SVG”, “VML” or empty, depending on browser support.
+    \*/
+    R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
+    if (R.type == "VML") {
+        var d = g.doc.createElement("div"),
+            b;
+        d.innerHTML = '<v:shape adj="1"/>';
+        b = d.firstChild;
+        b.style.behavior = "url(#default#VML)";
+        if (!(b && typeof b.adj == "object")) {
+            return (R.type = E);
+        }
+        d = null;
+    }
+    /*\
+     * Raphael.svg
+     [ property (boolean) ]
+     **
+     * `true` if browser supports SVG.
+    \*/
+    /*\
+     * Raphael.vml
+     [ property (boolean) ]
+     **
+     * `true` if browser supports VML.
+    \*/
+    R.svg = !(R.vml = R.type == "VML");
+    R._Paper = Paper;
+    /*\
+     * Raphael.fn
+     [ property (object) ]
+     **
+     * You can add your own method to the canvas. For example if you want to draw a pie chart,
+     * you can create your own pie chart function and ship it as a Raphaël plugin. To do this
+     * you need to extend the `Raphael.fn` object. You should modify the `fn` object before a
+     * Raphaël instance is created, otherwise it will take no effect. Please note that the
+     * ability for namespaced plugins was removed in Raphael 2.0. It is up to the plugin to
+     * ensure any namespacing ensures proper context.
+     > Usage
+     | Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
+     |     return this.path( ... );
+     | };
+     | // or create namespace
+     | Raphael.fn.mystuff = {
+     |     arrow: function () {…},
+     |     star: function () {…},
+     |     // etc…
+     | };
+     | var paper = Raphael(10, 10, 630, 480);
+     | // then use it
+     | paper.arrow(10, 10, 30, 30, 5).attr({fill: "#f00"});
+     | paper.mystuff.arrow();
+     | paper.mystuff.star();
+    \*/
+    R.fn = paperproto = Paper.prototype = R.prototype;
+    R._id = 0;
+    R._oid = 0;
+    /*\
+     * Raphael.is
+     [ method ]
+     **
+     * Handfull replacement for `typeof` operator.
+     > Parameters
+     - o (…) any object or primitive
+     - type (string) name of the type, i.e. “string”, “function”, “number”, etc.
+     = (boolean) is given value is of given type
+    \*/
+    R.is = function (o, type) {
+        type = lowerCase.call(type);
+        if (type == "finite") {
+            return !isnan[has](+o);
+        }
+        if (type == "array") {
+            return o instanceof Array;
+        }
+        return  (type == "null" && o === null) ||
+                (type == typeof o && o !== null) ||
+                (type == "object" && o === Object(o)) ||
+                (type == "array" && Array.isArray && Array.isArray(o)) ||
+                objectToString.call(o).slice(8, -1).toLowerCase() == type;
+    };
+
+    function clone(obj) {
+        if (Object(obj) !== obj) {
+            return obj;
+        }
+        var res = new obj.constructor;
+        for (var key in obj) if (obj[has](key)) {
+            res[key] = clone(obj[key]);
+        }
+        return res;
+    }
+
+    /*\
+     * Raphael.angle
+     [ method ]
+     **
+     * Returns angle between two or three points
+     > Parameters
+     - x1 (number) x coord of first point
+     - y1 (number) y coord of first point
+     - x2 (number) x coord of second point
+     - y2 (number) y coord of second point
+     - x3 (number) #optional x coord of third point
+     - y3 (number) #optional y coord of third point
+     = (number) angle in degrees.
+    \*/
+    R.angle = function (x1, y1, x2, y2, x3, y3) {
+        if (x3 == null) {
+            var x = x1 - x2,
+                y = y1 - y2;
+            if (!x && !y) {
+                return 0;
+            }
+            return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+        } else {
+            return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
+        }
+    };
+    /*\
+     * Raphael.rad
+     [ method ]
+     **
+     * Transform angle to radians
+     > Parameters
+     - deg (number) angle in degrees
+     = (number) angle in radians.
+    \*/
+    R.rad = function (deg) {
+        return deg % 360 * PI / 180;
+    };
+    /*\
+     * Raphael.deg
+     [ method ]
+     **
+     * Transform angle to degrees
+     > Parameters
+     - deg (number) angle in radians
+     = (number) angle in degrees.
+    \*/
+    R.deg = function (rad) {
+        return rad * 180 / PI % 360;
+    };
+    /*\
+     * Raphael.snapTo
+     [ method ]
+     **
+     * Snaps given value to given grid.
+     > Parameters
+     - values (array|number) given array of values or step of the grid
+     - value (number) value to adjust
+     - tolerance (number) #optional tolerance for snapping. Default is `10`.
+     = (number) adjusted value.
+    \*/
+    R.snapTo = function (values, value, tolerance) {
+        tolerance = R.is(tolerance, "finite") ? tolerance : 10;
+        if (R.is(values, array)) {
+            var i = values.length;
+            while (i--) if (abs(values[i] - value) <= tolerance) {
+                return values[i];
+            }
+        } else {
+            values = +values;
+            var rem = value % values;
+            if (rem < tolerance) {
+                return value - rem;
+            }
+            if (rem > values - tolerance) {
+                return value - rem + values;
+            }
+        }
+        return value;
+    };
+    
+    /*\
+     * Raphael.createUUID
+     [ method ]
+     **
+     * Returns RFC4122, version 4 ID
+    \*/
+    var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
+        return function () {
+            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
+        };
+    })(/[xy]/g, function (c) {
+        var r = math.random() * 16 | 0,
+            v = c == "x" ? r : (r & 3 | 8);
+        return v.toString(16);
+    });
+
+    /*\
+     * Raphael.setWindow
+     [ method ]
+     **
+     * Used when you need to draw in `&lt;iframe>`. Switched window to the iframe one.
+     > Parameters
+     - newwin (window) new window object
+    \*/
+    R.setWindow = function (newwin) {
+        eve("raphael.setWindow", R, g.win, newwin);
+        g.win = newwin;
+        g.doc = g.win.document;
+        if (R._engine.initWin) {
+            R._engine.initWin(g.win);
+        }
+    };
+    var toHex = function (color) {
+        if (R.vml) {
+            // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
+            var trim = /^\s+|\s+$/g;
+            var bod;
+            try {
+                var docum = new ActiveXObject("htmlfile");
+                docum.write("<body>");
+                docum.close();
+                bod = docum.body;
+            } catch(e) {
+                bod = createPopup().document.body;
+            }
+            var range = bod.createTextRange();
+            toHex = cacher(function (color) {
+                try {
+                    bod.style.color = Str(color).replace(trim, E);
+                    var value = range.queryCommandValue("ForeColor");
+                    value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
+                    return "#" + ("000000" + value.toString(16)).slice(-6);
+                } catch(e) {
+                    return "none";
+                }
+            });
+        } else {
+            var i = g.doc.createElement("i");
+            i.title = "Rapha\xebl Colour Picker";
+            i.style.display = "none";
+            g.doc.body.appendChild(i);
+            toHex = cacher(function (color) {
+                i.style.color = color;
+                return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+            });
+        }
+        return toHex(color);
+    },
+    hsbtoString = function () {
+        return "hsb(" + [this.h, this.s, this.b] + ")";
+    },
+    hsltoString = function () {
+        return "hsl(" + [this.h, this.s, this.l] + ")";
+    },
+    rgbtoString = function () {
+        return this.hex;
+    },
+    prepareRGB = function (r, g, b) {
+        if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
+            b = r.b;
+            g = r.g;
+            r = r.r;
+        }
+        if (g == null && R.is(r, string)) {
+            var clr = R.getRGB(r);
+            r = clr.r;
+            g = clr.g;
+            b = clr.b;
+        }
+        if (r > 1 || g > 1 || b > 1) {
+            r /= 255;
+            g /= 255;
+            b /= 255;
+        }
+        
+        return [r, g, b];
+    },
+    packageRGB = function (r, g, b, o) {
+        r *= 255;
+        g *= 255;
+        b *= 255;
+        var rgb = {
+            r: r,
+            g: g,
+            b: b,
+            hex: R.rgb(r, g, b),
+            toString: rgbtoString
+        };
+        R.is(o, "finite") && (rgb.opacity = o);
+        return rgb;
+    };
+    
+    /*\
+     * Raphael.color
+     [ method ]
+     **
+     * Parses the color string and returns object with all values for the given color.
+     > Parameters
+     - clr (string) color string in one of the supported formats (see @Raphael.getRGB)
+     = (object) Combined RGB & HSB object in format:
+     o {
+     o     r (number) red,
+     o     g (number) green,
+     o     b (number) blue,
+     o     hex (string) color in HTML/CSS format: #••••••,
+     o     error (boolean) `true` if string can’t be parsed,
+     o     h (number) hue,
+     o     s (number) saturation,
+     o     v (number) value (brightness),
+     o     l (number) lightness
+     o }
+    \*/
+    R.color = function (clr) {
+        var rgb;
+        if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+            rgb = R.hsb2rgb(clr);
+            clr.r = rgb.r;
+            clr.g = rgb.g;
+            clr.b = rgb.b;
+            clr.hex = rgb.hex;
+        } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+            rgb = R.hsl2rgb(clr);
+            clr.r = rgb.r;
+            clr.g = rgb.g;
+            clr.b = rgb.b;
+            clr.hex = rgb.hex;
+        } else {
+            if (R.is(clr, "string")) {
+                clr = R.getRGB(clr);
+            }
+            if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
+                rgb = R.rgb2hsl(clr);
+                clr.h = rgb.h;
+                clr.s = rgb.s;
+                clr.l = rgb.l;
+                rgb = R.rgb2hsb(clr);
+                clr.v = rgb.b;
+            } else {
+                clr = {hex: "none"};
+                clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+            }
+        }
+        clr.toString = rgbtoString;
+        return clr;
+    };
+    /*\
+     * Raphael.hsb2rgb
+     [ method ]
+     **
+     * Converts HSB values to RGB object.
+     > Parameters
+     - h (number) hue
+     - s (number) saturation
+     - v (number) value or brightness
+     = (object) RGB object in format:
+     o {
+     o     r (number) red,
+     o     g (number) green,
+     o     b (number) blue,
+     o     hex (string) color in HTML/CSS format: #••••••
+     o }
+    \*/
+    R.hsb2rgb = function (h, s, v, o) {
+        if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
+            v = h.b;
+            s = h.s;
+            h = h.h;
+            o = h.o;
+        }
+        h *= 360;
+        var R, G, B, X, C;
+        h = (h % 360) / 60;
+        C = v * s;
+        X = C * (1 - abs(h % 2 - 1));
+        R = G = B = v - C;
+
+        h = ~~h;
+        R += [C, X, 0, 0, X, C][h];
+        G += [X, C, C, X, 0, 0][h];
+        B += [0, 0, X, C, C, X][h];
+        return packageRGB(R, G, B, o);
+    };
+    /*\
+     * Raphael.hsl2rgb
+     [ method ]
+     **
+     * Converts HSL values to RGB object.
+     > Parameters
+     - h (number) hue
+     - s (number) saturation
+     - l (number) luminosity
+     = (object) RGB object in format:
+     o {
+     o     r (number) red,
+     o     g (number) green,
+     o     b (number) blue,
+     o     hex (string) color in HTML/CSS format: #••••••
+     o }
+    \*/
+    R.hsl2rgb = function (h, s, l, o) {
+        if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
+            l = h.l;
+            s = h.s;
+            h = h.h;
+        }
+        if (h > 1 || s > 1 || l > 1) {
+            h /= 360;
+            s /= 100;
+            l /= 100;
+        }
+        h *= 360;
+        var R, G, B, X, C;
+        h = (h % 360) / 60;
+        C = 2 * s * (l < .5 ? l : 1 - l);
+        X = C * (1 - abs(h % 2 - 1));
+        R = G = B = l - C / 2;
+
+        h = ~~h;
+        R += [C, X, 0, 0, X, C][h];
+        G += [X, C, C, X, 0, 0][h];
+        B += [0, 0, X, C, C, X][h];
+        return packageRGB(R, G, B, o);
+    };
+    /*\
+     * Raphael.rgb2hsb
+     [ method ]
+     **
+     * Converts RGB values to HSB object.
+     > Parameters
+     - r (number) red
+     - g (number) green
+     - b (number) blue
+     = (object) HSB object in format:
+     o {
+     o     h (number) hue
+     o     s (number) saturation
+     o     b (number) brightness
+     o }
+    \*/
+    R.rgb2hsb = function (r, g, b) {
+        b = prepareRGB(r, g, b);
+        r = b[0];
+        g = b[1];
+        b = b[2];
+
+        var H, S, V, C;
+        V = mmax(r, g, b);
+        C = V - mmin(r, g, b);
+        H = (C == 0 ? null :
+             V == r ? (g - b) / C :
+             V == g ? (b - r) / C + 2 :
+                      (r - g) / C + 4
+            );
+        H = ((H + 360) % 6) * 60 / 360;
+        S = C == 0 ? 0 : C / V;
+        return {h: H, s: S, b: V, toString: hsbtoString};
+    };
+    /*\
+     * Raphael.rgb2hsl
+     [ method ]
+     **
+     * Converts RGB values to HSL object.
+     > Parameters
+     - r (number) red
+     - g (number) green
+     - b (number) blue
+     = (object) HSL object in format:
+     o {
+     o     h (number) hue
+     o     s (number) saturation
+     o     l (number) luminosity
+     o }
+    \*/
+    R.rgb2hsl = function (r, g, b) {
+        b = prepareRGB(r, g, b);
+        r = b[0];
+        g = b[1];
+        b = b[2];
+
+        var H, S, L, M, m, C;
+        M = mmax(r, g, b);
+        m = mmin(r, g, b);
+        C = M - m;
+        H = (C == 0 ? null :
+             M == r ? (g - b) / C :
+             M == g ? (b - r) / C + 2 :
+                      (r - g) / C + 4);
+        H = ((H + 360) % 6) * 60 / 360;
+        L = (M + m) / 2;
+        S = (C == 0 ? 0 :
+             L < .5 ? C / (2 * L) :
+                      C / (2 - 2 * L));
+        return {h: H, s: S, l: L, toString: hsltoString};
+    };
+    R._path2string = function () {
+        return this.join(",").replace(p2s, "$1");
+    };
+    function repush(array, item) {
+        for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+            return array.push(array.splice(i, 1)[0]);
+        }
+    }
+    function cacher(f, scope, postprocessor) {
+        function newf() {
+            var arg = Array.prototype.slice.call(arguments, 0),
+                args = arg.join("\u2400"),
+                cache = newf.cache = newf.cache || {},
+                count = newf.count = newf.count || [];
+            if (cache[has](args)) {
+                repush(count, args);
+                return postprocessor ? postprocessor(cache[args]) : cache[args];
+            }
+            count.length >= 1e3 && delete cache[count.shift()];
+            count.push(args);
+            cache[args] = f[apply](scope, arg);
+            return postprocessor ? postprocessor(cache[args]) : cache[args];
+        }
+        return newf;
+    }
+
+    var preload = R._preload = function (src, f) {
+        var img = g.doc.createElement("img");
+        img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+        img.onload = function () {
+            f.call(this);
+            this.onload = null;
+            g.doc.body.removeChild(this);
+        };
+        img.onerror = function () {
+            g.doc.body.removeChild(this);
+        };
+        g.doc.body.appendChild(img);
+        img.src = src;
+    };
+    
+    function clrToString() {
+        return this.hex;
+    }
+
+    /*\
+     * Raphael.getRGB
+     [ method ]
+     **
+     * Parses colour string as RGB object
+     > Parameters
+     - colour (string) colour string in one of formats:
+     # <ul>
+     #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
+     #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
+     #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
+     #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
+     #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
+     #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
+     #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+     #     <li>hsl(•••, •••, •••) — same as hsb</li>
+     #     <li>hsl(•••%, •••%, •••%) — same as hsb</li>
+     # </ul>
+     = (object) RGB object in format:
+     o {
+     o     r (number) red,
+     o     g (number) green,
+     o     b (number) blue
+     o     hex (string) color in HTML/CSS format: #••••••,
+     o     error (boolean) true if string can’t be parsed
+     o }
+    \*/
+    R.getRGB = cacher(function (colour) {
+        if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+            return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
+        }
+        if (colour == "none") {
+            return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
+        }
+        !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+        var res,
+            red,
+            green,
+            blue,
+            opacity,
+            t,
+            values,
+            rgb = colour.match(colourRegExp);
+        if (rgb) {
+            if (rgb[2]) {
+                blue = toInt(rgb[2].substring(5), 16);
+                green = toInt(rgb[2].substring(3, 5), 16);
+                red = toInt(rgb[2].substring(1, 3), 16);
+            }
+            if (rgb[3]) {
+                blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+                green = toInt((t = rgb[3].charAt(2)) + t, 16);
+                red = toInt((t = rgb[3].charAt(1)) + t, 16);
+            }
+            if (rgb[4]) {
+                values = rgb[4][split](commaSpaces);
+                red = toFloat(values[0]);
+                values[0].slice(-1) == "%" && (red *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+            }
+            if (rgb[5]) {
+                values = rgb[5][split](commaSpaces);
+                red = toFloat(values[0]);
+                values[0].slice(-1) == "%" && (red *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+                rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+                return R.hsb2rgb(red, green, blue, opacity);
+            }
+            if (rgb[6]) {
+                values = rgb[6][split](commaSpaces);
+                red = toFloat(values[0]);
+                values[0].slice(-1) == "%" && (red *= 2.55);
+                green = toFloat(values[1]);
+                values[1].slice(-1) == "%" && (green *= 2.55);
+                blue = toFloat(values[2]);
+                values[2].slice(-1) == "%" && (blue *= 2.55);
+                (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+                rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+                return R.hsl2rgb(red, green, blue, opacity);
+            }
+            rgb = {r: red, g: green, b: blue, toString: clrToString};
+            rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+            R.is(opacity, "finite") && (rgb.opacity = opacity);
+            return rgb;
+        }
+        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
+    }, R);
+    /*\
+     * Raphael.hsb
+     [ method ]
+     **
+     * Converts HSB values to hex representation of the colour.
+     > Parameters
+     - h (number) hue
+     - s (number) saturation
+     - b (number) value or brightness
+     = (string) hex representation of the colour.
+    \*/
+    R.hsb = cacher(function (h, s, b) {
+        return R.hsb2rgb(h, s, b).hex;
+    });
+    /*\
+     * Raphael.hsl
+     [ method ]
+     **
+     * Converts HSL values to hex representation of the colour.
+     > Parameters
+     - h (number) hue
+     - s (number) saturation
+     - l (number) luminosity
+     = (string) hex representation of the colour.
+    \*/
+    R.hsl = cacher(function (h, s, l) {
+        return R.hsl2rgb(h, s, l).hex;
+    });
+    /*\
+     * Raphael.rgb
+     [ method ]
+     **
+     * Converts RGB values to hex representation of the colour.
+     > Parameters
+     - r (number) red
+     - g (number) green
+     - b (number) blue
+     = (string) hex representation of the colour.
+    \*/
+    R.rgb = cacher(function (r, g, b) {
+        return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+    });
+    /*\
+     * Raphael.getColor
+     [ method ]
+     **
+     * On each call returns next colour in the spectrum. To reset it back to red call @Raphael.getColor.reset
+     > Parameters
+     - value (number) #optional brightness, default is `0.75`
+     = (string) hex representation of the colour.
+    \*/
+    R.getColor = function (value) {
+        var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
+            rgb = this.hsb2rgb(start.h, start.s, start.b);
+        start.h += .075;
+        if (start.h > 1) {
+            start.h = 0;
+            start.s -= .2;
+            start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
+        }
+        return rgb.hex;
+    };
+    /*\
+     * Raphael.getColor.reset
+     [ method ]
+     **
+     * Resets spectrum position for @Raphael.getColor back to red.
+    \*/
+    R.getColor.reset = function () {
+        delete this.start;
+    };
+
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp, z) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+            var p = [
+                        {x: +crp[i - 2], y: +crp[i - 1]},
+                        {x: +crp[i],     y: +crp[i + 1]},
+                        {x: +crp[i + 2], y: +crp[i + 3]},
+                        {x: +crp[i + 4], y: +crp[i + 5]}
+                    ];
+            if (z) {
+                if (!i) {
+                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+                } else if (iLen - 4 == i) {
+                    p[3] = {x: +crp[0], y: +crp[1]};
+                } else if (iLen - 2 == i) {
+                    p[2] = {x: +crp[0], y: +crp[1]};
+                    p[3] = {x: +crp[2], y: +crp[3]};
+                }
+            } else {
+                if (iLen - 4 == i) {
+                    p[3] = p[2];
+                } else if (!i) {
+                    p[0] = {x: +crp[i], y: +crp[i + 1]};
+                }
+            }
+            d.push(["C",
+                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                  (p[1].y + 6*p[2].y - p[3].y) / 6,
+                  p[2].x,
+                  p[2].y
+            ]);
+        }
+
+        return d;
+    }
+    /*\
+     * Raphael.parsePathString
+     [ method ]
+     **
+     * Utility method
+     **
+     * Parses given path string into an array of arrays of path segments.
+     > Parameters
+     - pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
+     = (array) array of segments.
+    \*/
+    R.parsePathString = function (pathString) {
+        if (!pathString) {
+            return null;
+        }
+        var pth = paths(pathString);
+        if (pth.arr) {
+            return pathClone(pth.arr);
+        }
+        
+        var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
+            data = [];
+        if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
+            data = pathClone(pathString);
+        }
+        if (!data.length) {
+            Str(pathString).replace(pathCommand, function (a, b, c) {
+                var params = [],
+                    name = b.toLowerCase();
+                c.replace(pathValues, function (a, b) {
+                    b && params.push(+b);
+                });
+                if (name == "m" && params.length > 2) {
+                    data.push([b][concat](params.splice(0, 2)));
+                    name = "l";
+                    b = b == "m" ? "l" : "L";
+                }
+                if (name == "r") {
+                    data.push([b][concat](params));
+                } else while (params.length >= paramCounts[name]) {
+                    data.push([b][concat](params.splice(0, paramCounts[name])));
+                    if (!paramCounts[name]) {
+                        break;
+                    }
+                }
+            });
+        }
+        data.toString = R._path2string;
+        pth.arr = pathClone(data);
+        return data;
+    };
+    /*\
+     * Raphael.parseTransformString
+     [ method ]
+     **
+     * Utility method
+     **
+     * Parses given path string into an array of transformations.
+     > Parameters
+     - TString (string|array) transform string or array of transformations (in the last case it will be returned straight away)
+     = (array) array of transformations.
+    \*/
+    R.parseTransformString = cacher(function (TString) {
+        if (!TString) {
+            return null;
+        }
+        var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+            data = [];
+        if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
+            data = pathClone(TString);
+        }
+        if (!data.length) {
+            Str(TString).replace(tCommand, function (a, b, c) {
+                var params = [],
+                    name = lowerCase.call(b);
+                c.replace(pathValues, function (a, b) {
+                    b && params.push(+b);
+                });
+                data.push([b][concat](params));
+            });
+        }
+        data.toString = R._path2string;
+        return data;
+    });
+    // PATHS
+    var paths = function (ps) {
+        var p = paths.ps = paths.ps || {};
+        if (p[ps]) {
+            p[ps].sleep = 100;
+        } else {
+            p[ps] = {
+                sleep: 100
+            };
+        }
+        setTimeout(function () {
+            for (var key in p) if (p[has](key) && key != ps) {
+                p[key].sleep--;
+                !p[key].sleep && delete p[key];
+            }
+        });
+        return p[ps];
+    };
+    /*\
+     * Raphael.findDotsAtSegment
+     [ method ]
+     **
+     * Utility method
+     **
+     * Find dot coordinates on the given cubic bezier curve at the given t.
+     > Parameters
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     - t (number) position on the curve (0..1)
+     = (object) point information in format:
+     o {
+     o     x: (number) x coordinate of the point
+     o     y: (number) y coordinate of the point
+     o     m: {
+     o         x: (number) x coordinate of the left anchor
+     o         y: (number) y coordinate of the left anchor
+     o     }
+     o     n: {
+     o         x: (number) x coordinate of the right anchor
+     o         y: (number) y coordinate of the right anchor
+     o     }
+     o     start: {
+     o         x: (number) x coordinate of the start of the curve
+     o         y: (number) y coordinate of the start of the curve
+     o     }
+     o     end: {
+     o         x: (number) x coordinate of the end of the curve
+     o         y: (number) y coordinate of the end of the curve
+     o     }
+     o     alpha: (number) angle of the curve derivative at the point
+     o }
+    \*/
+    R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+        var t1 = 1 - t,
+            t13 = pow(t1, 3),
+            t12 = pow(t1, 2),
+            t2 = t * t,
+            t3 = t2 * t,
+            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+            ax = t1 * p1x + t * c1x,
+            ay = t1 * p1y + t * c1y,
+            cx = t1 * c2x + t * p2x,
+            cy = t1 * c2y + t * p2y,
+            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+        (mx > nx || my < ny) && (alpha += 180);
+        return {
+            x: x,
+            y: y,
+            m: {x: mx, y: my},
+            n: {x: nx, y: ny},
+            start: {x: ax, y: ay},
+            end: {x: cx, y: cy},
+            alpha: alpha
+        };
+    };
+    /*\
+     * Raphael.bezierBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Return bounding box of a given cubic bezier curve
+     > Parameters
+     - p1x (number) x of the first point of the curve
+     - p1y (number) y of the first point of the curve
+     - c1x (number) x of the first anchor of the curve
+     - c1y (number) y of the first anchor of the curve
+     - c2x (number) x of the second anchor of the curve
+     - c2y (number) y of the second anchor of the curve
+     - p2x (number) x of the second point of the curve
+     - p2y (number) y of the second point of the curve
+     * or
+     - bez (array) array of six points for bezier curve
+     = (object) point information in format:
+     o {
+     o     min: {
+     o         x: (number) x coordinate of the left point
+     o         y: (number) y coordinate of the top point
+     o     }
+     o     max: {
+     o         x: (number) x coordinate of the right point
+     o         y: (number) y coordinate of the bottom point
+     o     }
+     o }
+    \*/
+    R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        if (!R.is(p1x, "array")) {
+            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+        }
+        var bbox = curveDim.apply(null, p1x);
+        return {
+            x: bbox.min.x,
+            y: bbox.min.y,
+            x2: bbox.max.x,
+            y2: bbox.max.y,
+            width: bbox.max.x - bbox.min.x,
+            height: bbox.max.y - bbox.min.y
+        };
+    };
+    /*\
+     * Raphael.isPointInsideBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside bounding boxes.
+     > Parameters
+     - bbox (string) bounding box
+     - x (string) x coordinate of the point
+     - y (string) y coordinate of the point
+     = (boolean) `true` if point inside
+    \*/
+    R.isPointInsideBBox = function (bbox, x, y) {
+        return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
+    };
+    /*\
+     * Raphael.isBBoxIntersect
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if two bounding boxes intersect
+     > Parameters
+     - bbox1 (string) first bounding box
+     - bbox2 (string) second bounding box
+     = (boolean) `true` if they intersect
+    \*/
+    R.isBBoxIntersect = function (bbox1, bbox2) {
+        var i = R.isPointInsideBBox;
+        return i(bbox2, bbox1.x, bbox1.y)
+            || i(bbox2, bbox1.x2, bbox1.y)
+            || i(bbox2, bbox1.x, bbox1.y2)
+            || i(bbox2, bbox1.x2, bbox1.y2)
+            || i(bbox1, bbox2.x, bbox2.y)
+            || i(bbox1, bbox2.x2, bbox2.y)
+            || i(bbox1, bbox2.x, bbox2.y2)
+            || i(bbox1, bbox2.x2, bbox2.y2)
+            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+    };
+    function base3(t, p1, p2, p3, p4) {
+        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+        return t * t2 - 3 * p1 + 3 * p2;
+    }
+    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+        if (z == null) {
+            z = 1;
+        }
+        z = z > 1 ? 1 : z < 0 ? 0 : z;
+        var z2 = z / 2,
+            n = 12,
+            Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],
+            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
+            sum = 0;
+        for (var i = 0; i < n; i++) {
+            var ct = z2 * Tvalues[i] + z2,
+                xbase = base3(ct, x1, x2, x3, x4),
+                ybase = base3(ct, y1, y2, y3, y4),
+                comb = xbase * xbase + ybase * ybase;
+            sum += Cvalues[i] * math.sqrt(comb);
+        }
+        return z2 * sum;
+    }
+    function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+            return;
+        }
+        var t = 1,
+            step = t / 2,
+            t2 = t - step,
+            l,
+            e = .01;
+        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        while (abs(l - ll) > e) {
+            step /= 2;
+            t2 += (l < ll ? 1 : -1) * step;
+            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+        }
+        return t2;
+    }
+    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+        if (
+            mmax(x1, x2) < mmin(x3, x4) ||
+            mmin(x1, x2) > mmax(x3, x4) ||
+            mmax(y1, y2) < mmin(y3, y4) ||
+            mmin(y1, y2) > mmax(y3, y4)
+        ) {
+            return;
+        }
+        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+        if (!denominator) {
+            return;
+        }
+        var px = nx / denominator,
+            py = ny / denominator,
+            px2 = +px.toFixed(2),
+            py2 = +py.toFixed(2);
+        if (
+            px2 < +mmin(x1, x2).toFixed(2) ||
+            px2 > +mmax(x1, x2).toFixed(2) ||
+            px2 < +mmin(x3, x4).toFixed(2) ||
+            px2 > +mmax(x3, x4).toFixed(2) ||
+            py2 < +mmin(y1, y2).toFixed(2) ||
+            py2 > +mmax(y1, y2).toFixed(2) ||
+            py2 < +mmin(y3, y4).toFixed(2) ||
+            py2 > +mmax(y3, y4).toFixed(2)
+        ) {
+            return;
+        }
+        return {x: px, y: py};
+    }
+    function inter(bez1, bez2) {
+        return interHelper(bez1, bez2);
+    }
+    function interCount(bez1, bez2) {
+        return interHelper(bez1, bez2, 1);
+    }
+    function interHelper(bez1, bez2, justCount) {
+        var bbox1 = R.bezierBBox(bez1),
+            bbox2 = R.bezierBBox(bez2);
+        if (!R.isBBoxIntersect(bbox1, bbox2)) {
+            return justCount ? 0 : [];
+        }
+        var l1 = bezlen.apply(0, bez1),
+            l2 = bezlen.apply(0, bez2),
+            n1 = ~~(l1 / 5),
+            n2 = ~~(l2 / 5),
+            dots1 = [],
+            dots2 = [],
+            xy = {},
+            res = justCount ? 0 : [];
+        for (var i = 0; i < n1 + 1; i++) {
+            var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
+            dots1.push({x: p.x, y: p.y, t: i / n1});
+        }
+        for (i = 0; i < n2 + 1; i++) {
+            p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
+            dots2.push({x: p.x, y: p.y, t: i / n2});
+        }
+        for (i = 0; i < n1; i++) {
+            for (var j = 0; j < n2; j++) {
+                var di = dots1[i],
+                    di1 = dots1[i + 1],
+                    dj = dots2[j],
+                    dj1 = dots2[j + 1],
+                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+                if (is) {
+                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+                        continue;
+                    }
+                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
+                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+                        if (justCount) {
+                            res++;
+                        } else {
+                            res.push({
+                                x: is.x,
+                                y: is.y,
+                                t1: t1,
+                                t2: t2
+                            });
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    /*\
+     * Raphael.pathIntersection
+     [ method ]
+     **
+     * Utility method
+     **
+     * Finds intersections of two paths
+     > Parameters
+     - path1 (string) path string
+     - path2 (string) path string
+     = (array) dots of intersection
+     o [
+     o     {
+     o         x: (number) x coordinate of the point
+     o         y: (number) y coordinate of the point
+     o         t1: (number) t value for segment of path1
+     o         t2: (number) t value for segment of path2
+     o         segment1: (number) order number for segment of path1
+     o         segment2: (number) order number for segment of path2
+     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1
+     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
+     o     }
+     o ]
+    \*/
+    R.pathIntersection = function (path1, path2) {
+        return interPathHelper(path1, path2);
+    };
+    R.pathIntersectionNumber = function (path1, path2) {
+        return interPathHelper(path1, path2, 1);
+    };
+    function interPathHelper(path1, path2, justCount) {
+        path1 = R._path2curve(path1);
+        path2 = R._path2curve(path2);
+        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+            res = justCount ? 0 : [];
+        for (var i = 0, ii = path1.length; i < ii; i++) {
+            var pi = path1[i];
+            if (pi[0] == "M") {
+                x1 = x1m = pi[1];
+                y1 = y1m = pi[2];
+            } else {
+                if (pi[0] == "C") {
+                    bez1 = [x1, y1].concat(pi.slice(1));
+                    x1 = bez1[6];
+                    y1 = bez1[7];
+                } else {
+                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+                    x1 = x1m;
+                    y1 = y1m;
+                }
+                for (var j = 0, jj = path2.length; j < jj; j++) {
+                    var pj = path2[j];
+                    if (pj[0] == "M") {
+                        x2 = x2m = pj[1];
+                        y2 = y2m = pj[2];
+                    } else {
+                        if (pj[0] == "C") {
+                            bez2 = [x2, y2].concat(pj.slice(1));
+                            x2 = bez2[6];
+                            y2 = bez2[7];
+                        } else {
+                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+                            x2 = x2m;
+                            y2 = y2m;
+                        }
+                        var intr = interHelper(bez1, bez2, justCount);
+                        if (justCount) {
+                            res += intr;
+                        } else {
+                            for (var k = 0, kk = intr.length; k < kk; k++) {
+                                intr[k].segment1 = i;
+                                intr[k].segment2 = j;
+                                intr[k].bez1 = bez1;
+                                intr[k].bez2 = bez2;
+                            }
+                            res = res.concat(intr);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+    /*\
+     * Raphael.isPointInsidePath
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns `true` if given point is inside a given closed path.
+     > Parameters
+     - path (string) path string
+     - x (number) x of the point
+     - y (number) y of the point
+     = (boolean) true, if point is inside the path
+    \*/
+    R.isPointInsidePath = function (path, x, y) {
+        var bbox = R.pathBBox(path);
+        return R.isPointInsideBBox(bbox, x, y) &&
+               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+    };
+    R._removedFactory = function (methodname) {
+        return function () {
+            eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
+        };
+    };
+    /*\
+     * Raphael.pathBBox
+     [ method ]
+     **
+     * Utility method
+     **
+     * Return bounding box of a given path
+     > Parameters
+     - path (string) path string
+     = (object) bounding box
+     o {
+     o     x: (number) x coordinate of the left top point of the box
+     o     y: (number) y coordinate of the left top point of the box
+     o     x2: (number) x coordinate of the right bottom point of the box
+     o     y2: (number) y coordinate of the right bottom point of the box
+     o     width: (number) width of the box
+     o     height: (number) height of the box
+     o }
+    \*/
+    var pathDimensions = R.pathBBox = function (path) {
+        var pth = paths(path);
+        if (pth.bbox) {
+            return clone(pth.bbox);
+        }
+        if (!path) {
+            return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
+        }
+        path = path2curve(path);
+        var x = 0, 
+            y = 0,
+            X = [],
+            Y = [],
+            p;
+        for (var i = 0, ii = path.length; i < ii; i++) {
+            p = path[i];
+            if (p[0] == "M") {
+                x = p[1];
+                y = p[2];
+                X.push(x);
+                Y.push(y);
+            } else {
+                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                X = X[concat](dim.min.x, dim.max.x);
+                Y = Y[concat](dim.min.y, dim.max.y);
+                x = p[5];
+                y = p[6];
+            }
+        }
+        var xmin = mmin[apply](0, X),
+            ymin = mmin[apply](0, Y),
+            xmax = mmax[apply](0, X),
+            ymax = mmax[apply](0, Y),
+            bb = {
+                x: xmin,
+                y: ymin,
+                x2: xmax,
+                y2: ymax,
+                width: xmax - xmin,
+                height: ymax - ymin
+            };
+        pth.bbox = clone(bb);
+        return bb;
+    },
+        pathClone = function (pathArray) {
+            var res = clone(pathArray);
+            res.toString = R._path2string;
+            return res;
+        },
+        pathToRelative = R._pathToRelative = function (pathArray) {
+            var pth = paths(pathArray);
+            if (pth.rel) {
+                return pathClone(pth.rel);
+            }
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.parsePathString(pathArray);
+            }
+            var res = [],
+                x = 0,
+                y = 0,
+                mx = 0,
+                my = 0,
+                start = 0;
+            if (pathArray[0][0] == "M") {
+                x = pathArray[0][1];
+                y = pathArray[0][2];
+                mx = x;
+                my = y;
+                start++;
+                res.push(["M", x, y]);
+            }
+            for (var i = start, ii = pathArray.length; i < ii; i++) {
+                var r = res[i] = [],
+                    pa = pathArray[i];
+                if (pa[0] != lowerCase.call(pa[0])) {
+                    r[0] = lowerCase.call(pa[0]);
+                    switch (r[0]) {
+                        case "a":
+                            r[1] = pa[1];
+                            r[2] = pa[2];
+                            r[3] = pa[3];
+                            r[4] = pa[4];
+                            r[5] = pa[5];
+                            r[6] = +(pa[6] - x).toFixed(3);
+                            r[7] = +(pa[7] - y).toFixed(3);
+                            break;
+                        case "v":
+                            r[1] = +(pa[1] - y).toFixed(3);
+                            break;
+                        case "m":
+                            mx = pa[1];
+                            my = pa[2];
+                        default:
+                            for (var j = 1, jj = pa.length; j < jj; j++) {
+                                r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+                            }
+                    }
+                } else {
+                    r = res[i] = [];
+                    if (pa[0] == "m") {
+                        mx = pa[1] + x;
+                        my = pa[2] + y;
+                    }
+                    for (var k = 0, kk = pa.length; k < kk; k++) {
+                        res[i][k] = pa[k];
+                    }
+                }
+                var len = res[i].length;
+                switch (res[i][0]) {
+                    case "z":
+                        x = mx;
+                        y = my;
+                        break;
+                    case "h":
+                        x += +res[i][len - 1];
+                        break;
+                    case "v":
+                        y += +res[i][len - 1];
+                        break;
+                    default:
+                        x += +res[i][len - 2];
+                        y += +res[i][len - 1];
+                }
+            }
+            res.toString = R._path2string;
+            pth.rel = pathClone(res);
+            return res;
+        },
+        pathToAbsolute = R._pathToAbsolute = function (pathArray) {
+            var pth = paths(pathArray);
+            if (pth.abs) {
+                return pathClone(pth.abs);
+            }
+            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+                pathArray = R.parsePathString(pathArray);
+            }
+            if (!pathArray || !pathArray.length) {
+                return [["M", 0, 0]];
+            }
+            var res = [],
+                x = 0,
+                y = 0,
+                mx = 0,
+                my = 0,
+                start = 0;
+            if (pathArray[0][0] == "M") {
+                x = +pathArray[0][1];
+                y = +pathArray[0][2];
+                mx = x;
+                my = y;
+                start++;
+                res[0] = ["M", x, y];
+            }
+            var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
+            for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+                res.push(r = []);
+                pa = pathArray[i];
+                if (pa[0] != upperCase.call(pa[0])) {
+                    r[0] = upperCase.call(pa[0]);
+                    switch (r[0]) {
+                        case "A":
+                            r[1] = pa[1];
+                            r[2] = pa[2];
+                            r[3] = pa[3];
+                            r[4] = pa[4];
+                            r[5] = pa[5];
+                            r[6] = +(pa[6] + x);
+                            r[7] = +(pa[7] + y);
+                            break;
+                        case "V":
+                            r[1] = +pa[1] + y;
+                            break;
+                        case "H":
+                            r[1] = +pa[1] + x;
+                            break;
+                        case "R":
+                            var dots = [x, y][concat](pa.slice(1));
+                            for (var j = 2, jj = dots.length; j < jj; j++) {
+                                dots[j] = +dots[j] + x;
+                                dots[++j] = +dots[j] + y;
+                            }
+                            res.pop();
+                            res = res[concat](catmullRom2bezier(dots, crz));
+                            break;
+                        case "M":
+                            mx = +pa[1] + x;
+                            my = +pa[2] + y;
+                        default:
+                            for (j = 1, jj = pa.length; j < jj; j++) {
+                                r[j] = +pa[j] + ((j % 2) ? x : y);
+                            }
+                    }
+                } else if (pa[0] == "R") {
+                    dots = [x, y][concat](pa.slice(1));
+                    res.pop();
+                    res = res[concat](catmullRom2bezier(dots, crz));
+                    r = ["R"][concat](pa.slice(-2));
+                } else {
+                    for (var k = 0, kk = pa.length; k < kk; k++) {
+                        r[k] = pa[k];
+                    }
+                }
+                switch (r[0]) {
+                    case "Z":
+                        x = mx;
+                        y = my;
+                        break;
+                    case "H":
+                        x = r[1];
+                        break;
+                    case "V":
+                        y = r[1];
+                        break;
+                    case "M":
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
+                    default:
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
+                }
+            }
+            res.toString = R._path2string;
+            pth.abs = pathClone(res);
+            return res;
+        },
+        l2c = function (x1, y1, x2, y2) {
+            return [x1, y1, x2, y2, x2, y2];
+        },
+        q2c = function (x1, y1, ax, ay, x2, y2) {
+            var _13 = 1 / 3,
+                _23 = 2 / 3;
+            return [
+                    _13 * x1 + _23 * ax,
+                    _13 * y1 + _23 * ay,
+                    _13 * x2 + _23 * ax,
+                    _13 * y2 + _23 * ay,
+                    x2,
+                    y2
+                ];
+        },
+        a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+            // for more information of where this math came from visit:
+            // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+            var _120 = PI * 120 / 180,
+                rad = PI / 180 * (+angle || 0),
+                res = [],
+                xy,
+                rotate = cacher(function (x, y, rad) {
+                    var X = x * math.cos(rad) - y * math.sin(rad),
+                        Y = x * math.sin(rad) + y * math.cos(rad);
+                    return {x: X, y: Y};
+                });
+            if (!recursive) {
+                xy = rotate(x1, y1, -rad);
+                x1 = xy.x;
+                y1 = xy.y;
+                xy = rotate(x2, y2, -rad);
+                x2 = xy.x;
+                y2 = xy.y;
+                var cos = math.cos(PI / 180 * angle),
+                    sin = math.sin(PI / 180 * angle),
+                    x = (x1 - x2) / 2,
+                    y = (y1 - y2) / 2;
+                var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+                if (h > 1) {
+                    h = math.sqrt(h);
+                    rx = h * rx;
+                    ry = h * ry;
+                }
+                var rx2 = rx * rx,
+                    ry2 = ry * ry,
+                    k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                        math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                    cx = k * rx * y / ry + (x1 + x2) / 2,
+                    cy = k * -ry * x / rx + (y1 + y2) / 2,
+                    f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+                    f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+                f1 = x1 < cx ? PI - f1 : f1;
+                f2 = x2 < cx ? PI - f2 : f2;
+                f1 < 0 && (f1 = PI * 2 + f1);
+                f2 < 0 && (f2 = PI * 2 + f2);
+                if (sweep_flag && f1 > f2) {
+                    f1 = f1 - PI * 2;
+                }
+                if (!sweep_flag && f2 > f1) {
+                    f2 = f2 - PI * 2;
+                }
+            } else {
+                f1 = recursive[0];
+                f2 = recursive[1];
+                cx = recursive[2];
+                cy = recursive[3];
+            }
+            var df = f2 - f1;
+            if (abs(df) > _120) {
+                var f2old = f2,
+                    x2old = x2,
+                    y2old = y2;
+                f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+                x2 = cx + rx * math.cos(f2);
+                y2 = cy + ry * math.sin(f2);
+                res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+            }
+            df = f2 - f1;
+            var c1 = math.cos(f1),
+                s1 = math.sin(f1),
+                c2 = math.cos(f2),
+                s2 = math.sin(f2),
+                t = math.tan(df / 4),
+                hx = 4 / 3 * rx * t,
+                hy = 4 / 3 * ry * t,
+                m1 = [x1, y1],
+                m2 = [x1 + hx * s1, y1 - hy * c1],
+                m3 = [x2 + hx * s2, y2 - hy * c2],
+                m4 = [x2, y2];
+            m2[0] = 2 * m1[0] - m2[0];
+            m2[1] = 2 * m1[1] - m2[1];
+            if (recursive) {
+                return [m2, m3, m4][concat](res);
+            } else {
+                res = [m2, m3, m4][concat](res).join()[split](",");
+                var newres = [];
+                for (var i = 0, ii = res.length; i < ii; i++) {
+                    newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+                }
+                return newres;
+            }
+        },
+        findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+            var t1 = 1 - t;
+            return {
+                x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+                y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+            };
+        },
+        curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+            var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
+                b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
+                c = p1x - c1x,
+                t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
+                t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
+                y = [p1y, p2y],
+                x = [p1x, p2x],
+                dot;
+            abs(t1) > "1e12" && (t1 = .5);
+            abs(t2) > "1e12" && (t2 = .5);
+            if (t1 > 0 && t1 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            if (t2 > 0 && t2 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
+            b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
+            c = p1y - c1y;
+            t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
+            t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
+            abs(t1) > "1e12" && (t1 = .5);
+            abs(t2) > "1e12" && (t2 = .5);
+            if (t1 > 0 && t1 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            if (t2 > 0 && t2 < 1) {
+                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+                x.push(dot.x);
+                y.push(dot.y);
+            }
+            return {
+                min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
+                max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
+            };
+        }),
+        path2curve = R._path2curve = cacher(function (path, path2) {
+            var pth = !path2 && paths(path);
+            if (!path2 && pth.curve) {
+                return pathClone(pth.curve);
+            }
+            var p = pathToAbsolute(path),
+                p2 = path2 && pathToAbsolute(path2),
+                attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+                attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+                processPath = function (path, d) {
+                    var nx, ny;
+                    if (!path) {
+                        return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+                    }
+                    !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null);
+                    switch (path[0]) {
+                        case "M":
+                            d.X = path[1];
+                            d.Y = path[2];
+                            break;
+                        case "A":
+                            path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
+                            break;
+                        case "S":
+                            nx = d.x + (d.x - (d.bx || d.x));
+                            ny = d.y + (d.y - (d.by || d.y));
+                            path = ["C", nx, ny][concat](path.slice(1));
+                            break;
+                        case "T":
+                            d.qx = d.x + (d.x - (d.qx || d.x));
+                            d.qy = d.y + (d.y - (d.qy || d.y));
+                            path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+                            break;
+                        case "Q":
+                            d.qx = path[1];
+                            d.qy = path[2];
+                            path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                            break;
+                        case "L":
+                            path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
+                            break;
+                        case "H":
+                            path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
+                            break;
+                        case "V":
+                            path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
+                            break;
+                        case "Z":
+                            path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
+                            break;
+                    }
+                    return path;
+                },
+                fixArc = function (pp, i) {
+                    if (pp[i].length > 7) {
+                        pp[i].shift();
+                        var pi = pp[i];
+                        while (pi.length) {
+                            pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
+                        }
+                        pp.splice(i, 1);
+                        ii = mmax(p.length, p2 && p2.length || 0);
+                    }
+                },
+                fixM = function (path1, path2, a1, a2, i) {
+                    if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                        path2.splice(i, 0, ["M", a2.x, a2.y]);
+                        a1.bx = 0;
+                        a1.by = 0;
+                        a1.x = path1[i][1];
+                        a1.y = path1[i][2];
+                        ii = mmax(p.length, p2 && p2.length || 0);
+                    }
+                };
+            for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+                p[i] = processPath(p[i], attrs);
+                fixArc(p, i);
+                p2 && (p2[i] = processPath(p2[i], attrs2));
+                p2 && fixArc(p2, i);
+                fixM(p, p2, attrs, attrs2, i);
+                fixM(p2, p, attrs2, attrs, i);
+                var seg = p[i],
+                    seg2 = p2 && p2[i],
+                    seglen = seg.length,
+                    seg2len = p2 && seg2.length;
+                attrs.x = seg[seglen - 2];
+                attrs.y = seg[seglen - 1];
+                attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+                attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+                attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+                attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+                attrs2.x = p2 && seg2[seg2len - 2];
+                attrs2.y = p2 && seg2[seg2len - 1];
+            }
+            if (!p2) {
+                pth.curve = pathClone(p);
+            }
+            return p2 ? [p, p2] : p;
+        }, null, pathClone),
+        parseDots = R._parseDots = cacher(function (gradient) {
+            var dots = [];
+            for (var i = 0, ii = gradient.length; i < ii; i++) {
+                var dot = {},
+                    par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
+                dot.color = R.getRGB(par[1]);
+                if (dot.color.error) {
+                    return null;
+                }
+                dot.color = dot.color.hex;
+                par[2] && (dot.offset = par[2] + "%");
+                dots.push(dot);
+            }
+            for (i = 1, ii = dots.length - 1; i < ii; i++) {
+                if (!dots[i].offset) {
+                    var start = toFloat(dots[i - 1].offset || 0),
+                        end = 0;
+                    for (var j = i + 1; j < ii; j++) {
+                        if (dots[j].offset) {
+                            end = dots[j].offset;
+                            break;
+                        }
+                    }
+                    if (!end) {
+                        end = 100;
+                        j = ii;
+                    }
+                    end = toFloat(end);
+                    var d = (end - start) / (j - i + 1);
+                    for (; i < j; i++) {
+                        start += d;
+                        dots[i].offset = start + "%";
+                    }
+                }
+            }
+            return dots;
+        }),
+        tear = R._tear = function (el, paper) {
+            el == paper.top && (paper.top = el.prev);
+            el == paper.bottom && (paper.bottom = el.next);
+            el.next && (el.next.prev = el.prev);
+            el.prev && (el.prev.next = el.next);
+        },
+        tofront = R._tofront = function (el, paper) {
+            if (paper.top === el) {
+                return;
+            }
+            tear(el, paper);
+            el.next = null;
+            el.prev = paper.top;
+            paper.top.next = el;
+            paper.top = el;
+        },
+        toback = R._toback = function (el, paper) {
+            if (paper.bottom === el) {
+                return;
+            }
+            tear(el, paper);
+            el.next = paper.bottom;
+            el.prev = null;
+            paper.bottom.prev = el;
+            paper.bottom = el;
+        },
+        insertafter = R._insertafter = function (el, el2, paper) {
+            tear(el, paper);
+            el2 == paper.top && (paper.top = el);
+            el2.next && (el2.next.prev = el);
+            el.next = el2.next;
+            el.prev = el2;
+            el2.next = el;
+        },
+        insertbefore = R._insertbefore = function (el, el2, paper) {
+            tear(el, paper);
+            el2 == paper.bottom && (paper.bottom = el);
+            el2.prev && (el2.prev.next = el);
+            el.prev = el2.prev;
+            el2.prev = el;
+            el.next = el2;
+        },
+        /*\
+         * Raphael.toMatrix
+         [ method ]
+         **
+         * Utility method
+         **
+         * Returns matrix of transformations applied to a given path
+         > Parameters
+         - path (string) path string
+         - transform (string|array) transformation string
+         = (object) @Matrix
+        \*/
+        toMatrix = R.toMatrix = function (path, transform) {
+            var bb = pathDimensions(path),
+                el = {
+                    _: {
+                        transform: E
+                    },
+                    getBBox: function () {
+                        return bb;
+                    }
+                };
+            extractTransform(el, transform);
+            return el.matrix;
+        },
+        /*\
+         * Raphael.transformPath
+         [ method ]
+         **
+         * Utility method
+         **
+         * Returns path transformed by a given transformation
+         > Parameters
+         - path (string) path string
+         - transform (string|array) transformation string
+         = (string) path
+        \*/
+        transformPath = R.transformPath = function (path, transform) {
+            return mapPath(path, toMatrix(path, transform));
+        },
+        extractTransform = R._extractTransform = function (el, tstr) {
+            if (tstr == null) {
+                return el._.transform;
+            }
+            tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
+            var tdata = R.parseTransformString(tstr),
+                deg = 0,
+                dx = 0,
+                dy = 0,
+                sx = 1,
+                sy = 1,
+                _ = el._,
+                m = new Matrix;
+            _.transform = tdata || [];
+            if (tdata) {
+                for (var i = 0, ii = tdata.length; i < ii; i++) {
+                    var t = tdata[i],
+                        tlen = t.length,
+                        command = Str(t[0]).toLowerCase(),
+                        absolute = t[0] != command,
+                        inver = absolute ? m.invert() : 0,
+                        x1,
+                        y1,
+                        x2,
+                        y2,
+                        bb;
+                    if (command == "t" && tlen == 3) {
+                        if (absolute) {
+                            x1 = inver.x(0, 0);
+                            y1 = inver.y(0, 0);
+                            x2 = inver.x(t[1], t[2]);
+                            y2 = inver.y(t[1], t[2]);
+                            m.translate(x2 - x1, y2 - y1);
+                        } else {
+                            m.translate(t[1], t[2]);
+                        }
+                    } else if (command == "r") {
+                        if (tlen == 2) {
+                            bb = bb || el.getBBox(1);
+                            m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                            deg += t[1];
+                        } else if (tlen == 4) {
+                            if (absolute) {
+                                x2 = inver.x(t[2], t[3]);
+                                y2 = inver.y(t[2], t[3]);
+                                m.rotate(t[1], x2, y2);
+                            } else {
+                                m.rotate(t[1], t[2], t[3]);
+                            }
+                            deg += t[1];
+                        }
+                    } else if (command == "s") {
+                        if (tlen == 2 || tlen == 3) {
+                            bb = bb || el.getBBox(1);
+                            m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+                            sx *= t[1];
+                            sy *= t[tlen - 1];
+                        } else if (tlen == 5) {
+                            if (absolute) {
+                                x2 = inver.x(t[3], t[4]);
+                                y2 = inver.y(t[3], t[4]);
+                                m.scale(t[1], t[2], x2, y2);
+                            } else {
+                                m.scale(t[1], t[2], t[3], t[4]);
+                            }
+                            sx *= t[1];
+                            sy *= t[2];
+                        }
+                    } else if (command == "m" && tlen == 7) {
+                        m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+                    }
+                    _.dirtyT = 1;
+                    el.matrix = m;
+                }
+            }
+
+            /*\
+             * Element.matrix
+             [ property (object) ]
+             **
+             * Keeps @Matrix object, which represents element transformation
+            \*/
+            el.matrix = m;
+
+            _.sx = sx;
+            _.sy = sy;
+            _.deg = deg;
+            _.dx = dx = m.e;
+            _.dy = dy = m.f;
+
+            if (sx == 1 && sy == 1 && !deg && _.bbox) {
+                _.bbox.x += +dx;
+                _.bbox.y += +dy;
+            } else {
+                _.dirtyT = 1;
+            }
+        },
+        getEmpty = function (item) {
+            var l = item[0];
+            switch (l.toLowerCase()) {
+                case "t": return [l, 0, 0];
+                case "m": return [l, 1, 0, 0, 1, 0, 0];
+                case "r": if (item.length == 4) {
+                    return [l, 0, item[2], item[3]];
+                } else {
+                    return [l, 0];
+                }
+                case "s": if (item.length == 5) {
+                    return [l, 1, 1, item[3], item[4]];
+                } else if (item.length == 3) {
+                    return [l, 1, 1];
+                } else {
+                    return [l, 1];
+                }
+            }
+        },
+        equaliseTransform = R._equaliseTransform = function (t1, t2) {
+            t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+            t1 = R.parseTransformString(t1) || [];
+            t2 = R.parseTransformString(t2) || [];
+            var maxlength = mmax(t1.length, t2.length),
+                from = [],
+                to = [],
+                i = 0, j, jj,
+                tt1, tt2;
+            for (; i < maxlength; i++) {
+                tt1 = t1[i] || getEmpty(t2[i]);
+                tt2 = t2[i] || getEmpty(tt1);
+                if ((tt1[0] != tt2[0]) ||
+                    (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                    (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                    ) {
+                    return;
+                }
+                from[i] = [];
+                to[i] = [];
+                for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
+                    j in tt1 && (from[i][j] = tt1[j]);
+                    j in tt2 && (to[i][j] = tt2[j]);
+                }
+            }
+            return {
+                from: from,
+                to: to
+            };
+        };
+    R._getContainer = function (x, y, w, h) {
+        var container;
+        container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
+        if (container == null) {
+            return;
+        }
+        if (container.tagName) {
+            if (y == null) {
+                return {
+                    container: container,
+                    width: container.style.pixelWidth || container.offsetWidth,
+                    height: container.style.pixelHeight || container.offsetHeight
+                };
+            } else {
+                return {
+                    container: container,
+                    width: y,
+                    height: w
+                };
+            }
+        }
+        return {
+            container: 1,
+            x: x,
+            y: y,
+            width: w,
+            height: h
+        };
+    };
+    /*\
+     * Raphael.pathToRelative
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to relative form
+     > Parameters
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments.
+    \*/
+    R.pathToRelative = pathToRelative;
+    R._engine = {};
+    /*\
+     * Raphael.path2curve
+     [ method ]
+     **
+     * Utility method
+     **
+     * Converts path to a new path where all segments are cubic bezier curves.
+     > Parameters
+     - pathString (string|array) path string or array of segments
+     = (array) array of segments.
+    \*/
+    R.path2curve = path2curve;
+    /*\
+     * Raphael.matrix
+     [ method ]
+     **
+     * Utility method
+     **
+     * Returns matrix based on given parameters.
+     > Parameters
+     - a (number)
+     - b (number)
+     - c (number)
+     - d (number)
+     - e (number)
+     - f (number)
+     = (object) @Matrix
+    \*/
+    R.matrix = function (a, b, c, d, e, f) {
+        return new Matrix(a, b, c, d, e, f);
+    };
+    function Matrix(a, b, c, d, e, f) {
+        if (a != null) {
+            this.a = +a;
+            this.b = +b;
+            this.c = +c;
+            this.d = +d;
+            this.e = +e;
+            this.f = +f;
+        } else {
+            this.a = 1;
+            this.b = 0;
+            this.c = 0;
+            this.d = 1;
+            this.e = 0;
+            this.f = 0;
+        }
+    }
+    (function (matrixproto) {
+        /*\
+         * Matrix.add
+         [ method ]
+         **
+         * Adds given matrix to existing one.
+         > Parameters
+         - a (number)
+         - b (number)
+         - c (number)
+         - d (number)
+         - e (number)
+         - f (number)
+         or
+         - matrix (object) @Matrix
+        \*/
+        matrixproto.add = function (a, b, c, d, e, f) {
+            var out = [[], [], []],
+                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+                x, y, z, res;
+
+            if (a && a instanceof Matrix) {
+                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+            }
+
+            for (x = 0; x < 3; x++) {
+                for (y = 0; y < 3; y++) {
+                    res = 0;
+                    for (z = 0; z < 3; z++) {
+                        res += m[x][z] * matrix[z][y];
+                    }
+                    out[x][y] = res;
+                }
+            }
+            this.a = out[0][0];
+            this.b = out[1][0];
+            this.c = out[0][1];
+            this.d = out[1][1];
+            this.e = out[0][2];
+            this.f = out[1][2];
+        };
+        /*\
+         * Matrix.invert
+         [ method ]
+         **
+         * Returns inverted version of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.invert = function () {
+            var me = this,
+                x = me.a * me.d - me.b * me.c;
+            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+        };
+        /*\
+         * Matrix.clone
+         [ method ]
+         **
+         * Returns copy of the matrix
+         = (object) @Matrix
+        \*/
+        matrixproto.clone = function () {
+            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+        };
+        /*\
+         * Matrix.translate
+         [ method ]
+         **
+         * Translate the matrix
+         > Parameters
+         - x (number)
+         - y (number)
+        \*/
+        matrixproto.translate = function (x, y) {
+            this.add(1, 0, 0, 1, x, y);
+        };
+        /*\
+         * Matrix.scale
+         [ method ]
+         **
+         * Scales the matrix
+         > Parameters
+         - x (number)
+         - y (number) #optional
+         - cx (number) #optional
+         - cy (number) #optional
+        \*/
+        matrixproto.scale = function (x, y, cx, cy) {
+            y == null && (y = x);
+            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+            this.add(x, 0, 0, y, 0, 0);
+            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+        };
+        /*\
+         * Matrix.rotate
+         [ method ]
+         **
+         * Rotates the matrix
+         > Parameters
+         - a (number)
+         - x (number)
+         - y (number)
+        \*/
+        matrixproto.rotate = function (a, x, y) {
+            a = R.rad(a);
+            x = x || 0;
+            y = y || 0;
+            var cos = +math.cos(a).toFixed(9),
+                sin = +math.sin(a).toFixed(9);
+            this.add(cos, sin, -sin, cos, x, y);
+            this.add(1, 0, 0, 1, -x, -y);
+        };
+        /*\
+         * Matrix.x
+         [ method ]
+         **
+         * Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y
+         > Parameters
+         - x (number)
+         - y (number)
+         = (number) x
+        \*/
+        matrixproto.x = function (x, y) {
+            return x * this.a + y * this.c + this.e;
+        };
+        /*\
+         * Matrix.y
+         [ method ]
+         **
+         * Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x
+         > Parameters
+         - x (number)
+         - y (number)
+         = (number) y
+        \*/
+        matrixproto.y = function (x, y) {
+            return x * this.b + y * this.d + this.f;
+        };
+        matrixproto.get = function (i) {
+            return +this[Str.fromCharCode(97 + i)].toFixed(4);
+        };
+        matrixproto.toString = function () {
+            return R.svg ?
+                "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
+                [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
+        };
+        matrixproto.toFilter = function () {
+            return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
+                ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
+                ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
+        };
+        matrixproto.offset = function () {
+            return [this.e.toFixed(4), this.f.toFixed(4)];
+        };
+        function norm(a) {
+            return a[0] * a[0] + a[1] * a[1];
+        }
+        function normalize(a) {
+            var mag = math.sqrt(norm(a));
+            a[0] && (a[0] /= mag);
+            a[1] && (a[1] /= mag);
+        }
+        /*\
+         * Matrix.split
+         [ method ]
+         **
+         * Splits matrix into primitive transformations
+         = (object) in format:
+         o dx (number) translation by x
+         o dy (number) translation by y
+         o scalex (number) scale by x
+         o scaley (number) scale by y
+         o shear (number) shear
+         o rotate (number) rotation in deg
+         o isSimple (boolean) could it be represented via simple transformations
+        \*/
+        matrixproto.split = function () {
+            var out = {};
+            // translation
+            out.dx = this.e;
+            out.dy = this.f;
+
+            // scale and shear
+            var row = [[this.a, this.c], [this.b, this.d]];
+            out.scalex = math.sqrt(norm(row[0]));
+            normalize(row[0]);
+
+            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+            out.scaley = math.sqrt(norm(row[1]));
+            normalize(row[1]);
+            out.shear /= out.scaley;
+
+            // rotation
+            var sin = -row[0][1],
+                cos = row[1][1];
+            if (cos < 0) {
+                out.rotate = R.deg(math.acos(cos));
+                if (sin < 0) {
+                    out.rotate = 360 - out.rotate;
+                }
+            } else {
+                out.rotate = R.deg(math.asin(sin));
+            }
+
+            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+            return out;
+        };
+        /*\
+         * Matrix.toTransformString
+         [ method ]
+         **
+         * Return transform string that represents given matrix
+         = (string) transform string
+        \*/
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this[split]();
+            if (s.isSimple) {
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) + 
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [s.rotate, 0, 0] : E);
+            } else {
+                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+            }
+        };
+    })(Matrix.prototype);
+
+    // WebKit rendering bug workaround method
+    var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
+    if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
+        (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
+        /*\
+         * Paper.safari
+         [ method ]
+         **
+         * There is an inconvenient rendering bug in Safari (WebKit):
+         * sometimes the rendering should be forced.
+         * This method should help with dealing with this bug.
+        \*/
+        paperproto.safari = function () {
+            var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
+            setTimeout(function () {rect.remove();});
+        };
+    } else {
+        paperproto.safari = fun;
+    }
+ 
+    var preventDefault = function () {
+        this.returnValue = false;
+    },
+    preventTouch = function () {
+        return this.originalEvent.preventDefault();
+    },
+    stopPropagation = function () {
+        this.cancelBubble = true;
+    },
+    stopTouch = function () {
+        return this.originalEvent.stopPropagation();
+    },
+    addEvent = (function () {
+        if (g.doc.addEventListener) {
+            return function (obj, type, fn, element) {
+                var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+                    f = function (e) {
+                        var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+                            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+                            x = e.clientX + scrollX,
+                            y = e.clientY + scrollY;
+                    if (supportsTouch && touchMap[has](type)) {
+                        for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+                            if (e.targetTouches[i].target == obj) {
+                                var olde = e;
+                                e = e.targetTouches[i];
+                                e.originalEvent = olde;
+                                e.preventDefault = preventTouch;
+                                e.stopPropagation = stopTouch;
+                                break;
+                            }
+                        }
+                    }
+                    return fn.call(element, e, x, y);
+                };
+                obj.addEventListener(realName, f, false);
+                return function () {
+                    obj.removeEventListener(realName, f, false);
+                    return true;
+                };
+            };
+        } else if (g.doc.attachEvent) {
+            return function (obj, type, fn, element) {
+                var f = function (e) {
+                    e = e || g.win.event;
+                    var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+                        scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+                        x = e.clientX + scrollX,
+                        y = e.clientY + scrollY;
+                    e.preventDefault = e.preventDefault || preventDefault;
+                    e.stopPropagation = e.stopPropagation || stopPropagation;
+                    return fn.call(element, e, x, y);
+                };
+                obj.attachEvent("on" + type, f);
+                var detacher = function () {
+                    obj.detachEvent("on" + type, f);
+                    return true;
+                };
+                return detacher;
+            };
+        }
+    })(),
+    drag = [],
+    dragMove = function (e) {
+        var x = e.clientX,
+            y = e.clientY,
+            scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+            dragi,
+            j = drag.length;
+        while (j--) {
+            dragi = drag[j];
+            if (supportsTouch) {
+                var i = e.touches.length,
+                    touch;
+                while (i--) {
+                    touch = e.touches[i];
+                    if (touch.identifier == dragi.el._drag.id) {
+                        x = touch.clientX;
+                        y = touch.clientY;
+                        (e.originalEvent ? e.originalEvent : e).preventDefault();
+                        break;
+                    }
+                }
+            } else {
+                e.preventDefault();
+            }
+            var node = dragi.el.node,
+                o,
+                next = node.nextSibling,
+                parent = node.parentNode,
+                display = node.style.display;
+            g.win.opera && parent.removeChild(node);
+            node.style.display = "none";
+            o = dragi.el.paper.getElementByPoint(x, y);
+            node.style.display = display;
+            g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+            o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
+            x += scrollX;
+            y += scrollY;
+            eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+        }
+    },
+    dragUp = function (e) {
+        R.unmousemove(dragMove).unmouseup(dragUp);
+        var i = drag.length,
+            dragi;
+        while (i--) {
+            dragi = drag[i];
+            dragi.el._drag = {};
+            eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+        }
+        drag = [];
+    },
+    /*\
+     * Raphael.el
+     [ property (object) ]
+     **
+     * You can add your own method to elements. This is usefull when you want to hack default functionality or
+     * want to wrap some common transformation or attributes in one method. In difference to canvas methods,
+     * you can redefine element method at any time. Expending element methods wouldn’t affect set.
+     > Usage
+     | Raphael.el.red = function () {
+     |     this.attr({fill: "#f00"});
+     | };
+     | // then use it
+     | paper.circle(100, 100, 20).red();
+    \*/
+    elproto = R.el = {};
+    /*\
+     * Element.click
+     [ method ]
+     **
+     * Adds event handler for click for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unclick
+     [ method ]
+     **
+     * Removes event handler for click for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.dblclick
+     [ method ]
+     **
+     * Adds event handler for double click for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.undblclick
+     [ method ]
+     **
+     * Removes event handler for double click for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.mousedown
+     [ method ]
+     **
+     * Adds event handler for mousedown for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousedown
+     [ method ]
+     **
+     * Removes event handler for mousedown for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.mousemove
+     [ method ]
+     **
+     * Adds event handler for mousemove for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmousemove
+     [ method ]
+     **
+     * Removes event handler for mousemove for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.mouseout
+     [ method ]
+     **
+     * Adds event handler for mouseout for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseout
+     [ method ]
+     **
+     * Removes event handler for mouseout for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.mouseover
+     [ method ]
+     **
+     * Adds event handler for mouseover for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseover
+     [ method ]
+     **
+     * Removes event handler for mouseover for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.mouseup
+     [ method ]
+     **
+     * Adds event handler for mouseup for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.unmouseup
+     [ method ]
+     **
+     * Removes event handler for mouseup for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.touchstart
+     [ method ]
+     **
+     * Adds event handler for touchstart for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchstart
+     [ method ]
+     **
+     * Removes event handler for touchstart for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.touchmove
+     [ method ]
+     **
+     * Adds event handler for touchmove for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchmove
+     [ method ]
+     **
+     * Removes event handler for touchmove for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.touchend
+     [ method ]
+     **
+     * Adds event handler for touchend for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchend
+     [ method ]
+     **
+     * Removes event handler for touchend for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    
+    /*\
+     * Element.touchcancel
+     [ method ]
+     **
+     * Adds event handler for touchcancel for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    /*\
+     * Element.untouchcancel
+     [ method ]
+     **
+     * Removes event handler for touchcancel for the element.
+     > Parameters
+     - handler (function) handler for the event
+     = (object) @Element
+    \*/
+    for (var i = events.length; i--;) {
+        (function (eventName) {
+            R[eventName] = elproto[eventName] = function (fn, scope) {
+                if (R.is(fn, "function")) {
+                    this.events = this.events || [];
+                    this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)});
+                }
+                return this;
+            };
+            R["un" + eventName] = elproto["un" + eventName] = function (fn) {
+                var events = this.events || [],
+                    l = events.length;
+                while (l--) if (events[l].name == eventName && events[l].f == fn) {
+                    events[l].unbind();
+                    events.splice(l, 1);
+                    !events.length && delete this.events;
+                    return this;
+                }
+                return this;
+            };
+        })(events[i]);
+    }
+    
+    /*\
+     * Element.data
+     [ method ]
+     **
+     * Adds or retrieves given value asociated with given key.
+     ** 
+     * See also @Element.removeData
+     > Parameters
+     - key (string) key to store data
+     - value (any) #optional value to store
+     = (object) @Element
+     * or, if value is not specified:
+     = (any) value
+     > Usage
+     | for (var i = 0, i < 5, i++) {
+     |     paper.circle(10 + 15 * i, 10, 10)
+     |          .attr({fill: "#000"})
+     |          .data("i", i)
+     |          .click(function () {
+     |             alert(this.data("i"));
+     |          });
+     | }
+    \*/
+    elproto.data = function (key, value) {
+        var data = eldata[this.id] = eldata[this.id] || {};
+        if (arguments.length == 1) {
+            if (R.is(key, "object")) {
+                for (var i in key) if (key[has](i)) {
+                    this.data(i, key[i]);
+                }
+                return this;
+            }
+            eve("raphael.data.get." + this.id, this, data[key], key);
+            return data[key];
+        }
+        data[key] = value;
+        eve("raphael.data.set." + this.id, this, value, key);
+        return this;
+    };
+    /*\
+     * Element.removeData
+     [ method ]
+     **
+     * Removes value associated with an element by given key.
+     * If key is not provided, removes all the data of the element.
+     > Parameters
+     - key (string) #optional key
+     = (object) @Element
+    \*/
+    elproto.removeData = function (key) {
+        if (key == null) {
+            eldata[this.id] = {};
+        } else {
+            eldata[this.id] && delete eldata[this.id][key];
+        }
+        return this;
+    };
+     /*\
+     * Element.getData
+     [ method ]
+     **
+     * Retrieves the element data
+     = (object) data
+    \*/
+    elproto.getData = function () {
+        return eldata[this.id] || {};
+    };
+    /*\
+     * Element.hover
+     [ method ]
+     **
+     * Adds event handlers for hover for the element.
+     > Parameters
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     - icontext (object) #optional context for hover in handler
+     - ocontext (object) #optional context for hover out handler
+     = (object) @Element
+    \*/
+    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+    };
+    /*\
+     * Element.unhover
+     [ method ]
+     **
+     * Removes event handlers for hover for the element.
+     > Parameters
+     - f_in (function) handler for hover in
+     - f_out (function) handler for hover out
+     = (object) @Element
+    \*/
+    elproto.unhover = function (f_in, f_out) {
+        return this.unmouseover(f_in).unmouseout(f_out);
+    };
+    var draggable = [];
+    /*\
+     * Element.drag
+     [ method ]
+     **
+     * Adds event handlers for drag of the element.
+     > Parameters
+     - onmove (function) handler for moving
+     - onstart (function) handler for drag start
+     - onend (function) handler for drag end
+     - mcontext (object) #optional context for moving handler
+     - scontext (object) #optional context for drag start handler
+     - econtext (object) #optional context for drag end handler
+     * Additionaly following `drag` events will be triggered: `drag.start.<id>` on start, 
+     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element 
+     * `drag.over.<id>` will be fired as well.
+     *
+     * Start event and start handler will be called in specified context or in context of the element with following parameters:
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * Move event and move handler will be called in specified context or in context of the element with following parameters:
+     o dx (number) shift by x from the start point
+     o dy (number) shift by y from the start point
+     o x (number) x position of the mouse
+     o y (number) y position of the mouse
+     o event (object) DOM event object
+     * End event and end handler will be called in specified context or in context of the element with following parameters:
+     o event (object) DOM event object
+     = (object) @Element
+    \*/
+    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+        function start(e) {
+            (e.originalEvent || e).preventDefault();
+            var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
+            this._drag.x = e.clientX + scrollX;
+            this._drag.y = e.clientY + scrollY;
+            this._drag.id = e.identifier;
+            !drag.length && R.mousemove(dragMove).mouseup(dragUp);
+            drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+            onstart && eve.on("raphael.drag.start." + this.id, onstart);
+            onmove && eve.on("raphael.drag.move." + this.id, onmove);
+            onend && eve.on("raphael.drag.end." + this.id, onend);
+            eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
+        }
+        this._drag = {};
+        draggable.push({el: this, start: start});
+        this.mousedown(start);
+        return this;
+    };
+    /*\
+     * Element.onDragOver
+     [ method ]
+     **
+     * Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
+     > Parameters
+     - f (function) handler for event, first argument would be the element you are dragging over
+    \*/
+    elproto.onDragOver = function (f) {
+        f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
+    };
+    /*\
+     * Element.undrag
+     [ method ]
+     **
+     * Removes all drag event handlers from given element.
+    \*/
+    elproto.undrag = function () {
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].start);
+            draggable.splice(i, 1);
+            eve.unbind("raphael.drag.*." + this.id);
+        }
+        !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
+        drag = [];
+    };
+    /*\
+     * Paper.circle
+     [ method ]
+     **
+     * Draws a circle.
+     **
+     > Parameters
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - r (number) radius
+     = (object) Raphaël element object with type “circle”
+     **
+     > Usage
+     | var c = paper.circle(50, 50, 40);
+    \*/
+    paperproto.circle = function (x, y, r) {
+        var out = R._engine.circle(this, x || 0, y || 0, r || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Paper.rect
+     [ method ]
+     *
+     * Draws a rectangle.
+     **
+     > Parameters
+     **
+     - x (number) x coordinate of the top left corner
+     - y (number) y coordinate of the top left corner
+     - width (number) width
+     - height (number) height
+     - r (number) #optional radius for rounded corners, default is 0
+     = (object) Raphaël element object with type “rect”
+     **
+     > Usage
+     | // regular rectangle
+     | var c = paper.rect(10, 10, 50, 50);
+     | // rectangle with rounded corners
+     | var c = paper.rect(40, 40, 50, 50, 10);
+    \*/
+    paperproto.rect = function (x, y, w, h, r) {
+        var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Paper.ellipse
+     [ method ]
+     **
+     * Draws an ellipse.
+     **
+     > Parameters
+     **
+     - x (number) x coordinate of the centre
+     - y (number) y coordinate of the centre
+     - rx (number) horizontal radius
+     - ry (number) vertical radius
+     = (object) Raphaël element object with type “ellipse”
+     **
+     > Usage
+     | var c = paper.ellipse(50, 50, 40, 20);
+    \*/
+    paperproto.ellipse = function (x, y, rx, ry) {
+        var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Paper.path
+     [ method ]
+     **
+     * Creates a path element by given path data string.
+     > Parameters
+     - pathString (string) #optional path string in SVG format.
+     * Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example:
+     | "M10,20L30,40"
+     * Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative.
+     *
+     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a>.</p>
+     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
+     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
+     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
+     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
+     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
+     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
+     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
+     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
+     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
+     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
+     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
+     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
+     * * “Catmull-Rom curveto” is a not standard SVG command and added in 2.0 to make life easier.
+     * Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning.
+     > Usage
+     | var c = paper.path("M10 10L90 90");
+     | // draw a diagonal line:
+     | // move to 10,10, line to 90,90
+     * For example of path strings, check out these icons: http://raphaeljs.com/icons/
+    \*/
+    paperproto.path = function (pathString) {
+        pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
+        var out = R._engine.path(R.format[apply](R, arguments), this);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Paper.image
+     [ method ]
+     **
+     * Embeds an image into the surface.
+     **
+     > Parameters
+     **
+     - src (string) URI of the source image
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - width (number) width of the image
+     - height (number) height of the image
+     = (object) Raphaël element object with type “image”
+     **
+     > Usage
+     | var c = paper.image("apple.png", 10, 10, 80, 80);
+    \*/
+    paperproto.image = function (src, x, y, w, h) {
+        var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Paper.text
+     [ method ]
+     **
+     * Draws a text string. If you need line breaks, put “\n” in the string.
+     **
+     > Parameters
+     **
+     - x (number) x coordinate position
+     - y (number) y coordinate position
+     - text (string) The text string to draw
+     = (object) Raphaël element object with type “text”
+     **
+     > Usage
+     | var t = paper.text(50, 50, "Raphaël\nkicks\nbutt!");
+    \*/
+    paperproto.text = function (x, y, text) {
+        var out = R._engine.text(this, x || 0, y || 0, Str(text));
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Paper.set
+     [ method ]
+     **
+     * Creates array-like object to keep and operate several elements at once.
+     * Warning: it doesn’t create any elements for itself in the page, it just groups existing elements.
+     * Sets act as pseudo elements — all methods available to an element can be used on a set.
+     = (object) array-like object that represents set of elements
+     **
+     > Usage
+     | var st = paper.set();
+     | st.push(
+     |     paper.circle(10, 10, 5),
+     |     paper.circle(30, 10, 5)
+     | );
+     | st.attr({fill: "red"}); // changes the fill of both circles
+    \*/
+    paperproto.set = function (itemsArray) {
+        !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
+        var out = new Set(itemsArray);
+        this.__set__ && this.__set__.push(out);
+        out["paper"] = this;
+        out["type"] = "set";
+        return out;
+    };
+    /*\
+     * Paper.setStart
+     [ method ]
+     **
+     * Creates @Paper.set. All elements that will be created after calling this method and before calling
+     * @Paper.setFinish will be added to the set.
+     **
+     > Usage
+     | paper.setStart();
+     | paper.circle(10, 10, 5),
+     | paper.circle(30, 10, 5)
+     | var st = paper.setFinish();
+     | st.attr({fill: "red"}); // changes the fill of both circles
+    \*/
+    paperproto.setStart = function (set) {
+        this.__set__ = set || this.set();
+    };
+    /*\
+     * Paper.setFinish
+     [ method ]
+     **
+     * See @Paper.setStart. This method finishes catching and returns resulting set.
+     **
+     = (object) set
+    \*/
+    paperproto.setFinish = function (set) {
+        var out = this.__set__;
+        delete this.__set__;
+        return out;
+    };
+    /*\
+     * Paper.setSize
+     [ method ]
+     **
+     * If you need to change dimensions of the canvas call this method
+     **
+     > Parameters
+     **
+     - width (number) new width of the canvas
+     - height (number) new height of the canvas
+    \*/
+    paperproto.setSize = function (width, height) {
+        return R._engine.setSize.call(this, width, height);
+    };
+    /*\
+     * Paper.setViewBox
+     [ method ]
+     **
+     * Sets the view box of the paper. Practically it gives you ability to zoom and pan whole paper surface by 
+     * specifying new boundaries.
+     **
+     > Parameters
+     **
+     - x (number) new x position, default is `0`
+     - y (number) new y position, default is `0`
+     - w (number) new width of the canvas
+     - h (number) new height of the canvas
+     - fit (boolean) `true` if you want graphics to fit into new boundary box
+    \*/
+    paperproto.setViewBox = function (x, y, w, h, fit) {
+        return R._engine.setViewBox.call(this, x, y, w, h, fit);
+    };
+    /*\
+     * Paper.top
+     [ property ]
+     **
+     * Points to the topmost element on the paper
+    \*/
+    /*\
+     * Paper.bottom
+     [ property ]
+     **
+     * Points to the bottom element on the paper
+    \*/
+    paperproto.top = paperproto.bottom = null;
+    /*\
+     * Paper.raphael
+     [ property ]
+     **
+     * Points to the @Raphael object/function
+    \*/
+    paperproto.raphael = R;
+    var getOffset = function (elem) {
+        var box = elem.getBoundingClientRect(),
+            doc = elem.ownerDocument,
+            body = doc.body,
+            docElem = doc.documentElement,
+            clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+            top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
+            left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+        return {
+            y: top,
+            x: left
+        };
+    };
+    /*\
+     * Paper.getElementByPoint
+     [ method ]
+     **
+     * Returns you topmost element under given point.
+     **
+     = (object) Raphaël element object
+     > Parameters
+     **
+     - x (number) x coordinate from the top left corner of the window
+     - y (number) y coordinate from the top left corner of the window
+     > Usage
+     | paper.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
+    \*/
+    paperproto.getElementByPoint = function (x, y) {
+        var paper = this,
+            svg = paper.canvas,
+            target = g.doc.elementFromPoint(x, y);
+        if (g.win.opera && target.tagName == "svg") {
+            var so = getOffset(svg),
+                sr = svg.createSVGRect();
+            sr.x = x - so.x;
+            sr.y = y - so.y;
+            sr.width = sr.height = 1;
+            var hits = svg.getIntersectionList(sr, null);
+            if (hits.length) {
+                target = hits[hits.length - 1];
+            }
+        }
+        if (!target) {
+            return null;
+        }
+        while (target.parentNode && target != svg.parentNode && !target.raphael) {
+            target = target.parentNode;
+        }
+        target == paper.canvas.parentNode && (target = svg);
+        target = target && target.raphael ? paper.getById(target.raphaelid) : null;
+        return target;
+    };
+
+    /*\
+     * Paper.getElementsByBBox
+     [ method ]
+     **
+     * Returns set of elements that have an intersecting bounding box
+     **
+     > Parameters
+     **
+     - bbox (string) bbox to check with
+     = (object) @Set
+     \*/
+    paperproto.getElementsByBBox = function (bbox) {
+        var set = this.set();
+        this.forEach(function (el) {
+            if (Raphael.isBBoxIntersect(el.getBBox(), bbox)) {
+                set.push(el);
+            }
+        });
+        return set;
+    };
+
+    /*\
+     * Paper.getById
+     [ method ]
+     **
+     * Returns you element by its internal ID.
+     **
+     > Parameters
+     **
+     - id (number) id
+     = (object) Raphaël element object
+    \*/
+    paperproto.getById = function (id) {
+        var bot = this.bottom;
+        while (bot) {
+            if (bot.id == id) {
+                return bot;
+            }
+            bot = bot.next;
+        }
+        return null;
+    };
+    /*\
+     * Paper.forEach
+     [ method ]
+     **
+     * Executes given function for each element on the paper
+     *
+     * If callback function returns `false` it will stop loop running.
+     **
+     > Parameters
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Paper object
+     > Usage
+     | paper.forEach(function (el) {
+     |     el.attr({ stroke: "blue" });
+     | });
+    \*/
+    paperproto.forEach = function (callback, thisArg) {
+        var bot = this.bottom;
+        while (bot) {
+            if (callback.call(thisArg, bot) === false) {
+                return this;
+            }
+            bot = bot.next;
+        }
+        return this;
+    };
+    /*\
+     * Paper.getElementsByPoint
+     [ method ]
+     **
+     * Returns set of elements that have common point inside
+     **
+     > Parameters
+     **
+     - x (number) x coordinate of the point
+     - y (number) y coordinate of the point
+     = (object) @Set
+    \*/
+    paperproto.getElementsByPoint = function (x, y) {
+        var set = this.set();
+        this.forEach(function (el) {
+            if (el.isPointInside(x, y)) {
+                set.push(el);
+            }
+        });
+        return set;
+    };
+    function x_y() {
+        return this.x + S + this.y;
+    }
+    function x_y_w_h() {
+        return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+    }
+    /*\
+     * Element.isPointInside
+     [ method ]
+     **
+     * Determine if given point is inside this element’s shape
+     **
+     > Parameters
+     **
+     - x (number) x coordinate of the point
+     - y (number) y coordinate of the point
+     = (boolean) `true` if point inside the shape
+    \*/
+    elproto.isPointInside = function (x, y) {
+        var rp = this.realPath = this.realPath || getPath[this.type](this);
+        return R.isPointInsidePath(rp, x, y);
+    };
+    /*\
+     * Element.getBBox
+     [ method ]
+     **
+     * Return bounding box for a given element
+     **
+     > Parameters
+     **
+     - isWithoutTransform (boolean) flag, `true` if you want to have bounding box before transformations. Default is `false`.
+     = (object) Bounding box object:
+     o {
+     o     x: (number) top left corner x
+     o     y: (number) top left corner y
+     o     x2: (number) bottom right corner x
+     o     y2: (number) bottom right corner y
+     o     width: (number) width
+     o     height: (number) height
+     o }
+    \*/
+    elproto.getBBox = function (isWithoutTransform) {
+        if (this.removed) {
+            return {};
+        }
+        var _ = this._;
+        if (isWithoutTransform) {
+            if (_.dirty || !_.bboxwt) {
+                this.realPath = getPath[this.type](this);
+                _.bboxwt = pathDimensions(this.realPath);
+                _.bboxwt.toString = x_y_w_h;
+                _.dirty = 0;
+            }
+            return _.bboxwt;
+        }
+        if (_.dirty || _.dirtyT || !_.bbox) {
+            if (_.dirty || !this.realPath) {
+                _.bboxwt = 0;
+                this.realPath = getPath[this.type](this);
+            }
+            _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
+            _.bbox.toString = x_y_w_h;
+            _.dirty = _.dirtyT = 0;
+        }
+        return _.bbox;
+    };
+    /*\
+     * Element.clone
+     [ method ]
+     **
+     = (object) clone of a given element
+     **
+    \*/
+    elproto.clone = function () {
+        if (this.removed) {
+            return null;
+        }
+        var out = this.paper[this.type]().attr(this.attr());
+        this.__set__ && this.__set__.push(out);
+        return out;
+    };
+    /*\
+     * Element.glow
+     [ method ]
+     **
+     * Return set of elements that create glow-like effect around given element. See @Paper.set.
+     *
+     * Note: Glow is not connected to the element. If you change element attributes it won’t adjust itself.
+     **
+     > Parameters
+     **
+     - glow (object) #optional parameters object with all properties optional:
+     o {
+     o     width (number) size of the glow, default is `10`
+     o     fill (boolean) will it be filled, default is `false`
+     o     opacity (number) opacity, default is `0.5`
+     o     offsetx (number) horizontal offset, default is `0`
+     o     offsety (number) vertical offset, default is `0`
+     o     color (string) glow colour, default is `black`
+     o }
+     = (object) @Paper.set of elements that represents glow
+    \*/
+    elproto.glow = function (glow) {
+        if (this.type == "text") {
+            return null;
+        }
+        glow = glow || {};
+        var s = {
+            width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
+            fill: glow.fill || false,
+            opacity: glow.opacity || .5,
+            offsetx: glow.offsetx || 0,
+            offsety: glow.offsety || 0,
+            color: glow.color || "#000"
+        },
+            c = s.width / 2,
+            r = this.paper,
+            out = r.set(),
+            path = this.realPath || getPath[this.type](this);
+        path = this.matrix ? mapPath(path, this.matrix) : path;
+        for (var i = 1; i < c + 1; i++) {
+            out.push(r.path(path).attr({
+                stroke: s.color,
+                fill: s.fill ? s.color : "none",
+                "stroke-linejoin": "round",
+                "stroke-linecap": "round",
+                "stroke-width": +(s.width / c * i).toFixed(3),
+                opacity: +(s.opacity / c).toFixed(3)
+            }));
+        }
+        return out.insertBefore(this).translate(s.offsetx, s.offsety);
+    };
+    var curveslengths = {},
+    getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+        if (length == null) {
+            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+        } else {
+            return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+        }
+    },
+    getLengthFactory = function (istotal, subpath) {
+        return function (path, length, onlystart) {
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
+                            if (onlystart) {return sp;}
+                            subpaths.start = sp;
+                            sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+                            return {x: point.x, y: point.y, alpha: point.alpha};
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += p.shift() + p;
+            }
+            subpaths.end = sp;
+            point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+            point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
+            return point;
+        };
+    };
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    /*\
+     * Raphael.getTotalLength
+     [ method ]
+     **
+     * Returns length of the given path in pixels.
+     **
+     > Parameters
+     **
+     - path (string) SVG path string.
+     **
+     = (number) length.
+    \*/
+    R.getTotalLength = getTotalLength;
+    /*\
+     * Raphael.getPointAtLength
+     [ method ]
+     **
+     * Return coordinates of the point located at the given length on the given path.
+     **
+     > Parameters
+     **
+     - path (string) SVG path string
+     - length (number)
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate
+     o     y: (number) y coordinate
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    R.getPointAtLength = getPointAtLength;
+    /*\
+     * Raphael.getSubpath
+     [ method ]
+     **
+     * Return subpath of a given path from given length to given length.
+     **
+     > Parameters
+     **
+     - path (string) SVG path string
+     - from (number) position of the start of the segment
+     - to (number) position of the end of the segment
+     **
+     = (string) pathstring for the segment
+    \*/
+    R.getSubpath = function (path, from, to) {
+        if (this.getTotalLength(path) - to < 1e-6) {
+            return getSubpathsAtLength(path, from).end;
+        }
+        var a = getSubpathsAtLength(path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
+    /*\
+     * Element.getTotalLength
+     [ method ]
+     **
+     * Returns length of the path in pixels. Only works for element of “path” type.
+     = (number) length.
+    \*/
+    elproto.getTotalLength = function () {
+        if (this.type != "path") {return;}
+        if (this.node.getTotalLength) {
+            return this.node.getTotalLength();
+        }
+        return getTotalLength(this.attrs.path);
+    };
+    /*\
+     * Element.getPointAtLength
+     [ method ]
+     **
+     * Return coordinates of the point located at the given length on the given path. Only works for element of “path” type.
+     **
+     > Parameters
+     **
+     - length (number)
+     **
+     = (object) representation of the point:
+     o {
+     o     x: (number) x coordinate
+     o     y: (number) y coordinate
+     o     alpha: (number) angle of derivative
+     o }
+    \*/
+    elproto.getPointAtLength = function (length) {
+        if (this.type != "path") {return;}
+        return getPointAtLength(this.attrs.path, length);
+    };
+    /*\
+     * Element.getSubpath
+     [ method ]
+     **
+     * Return subpath of a given element from given length to given length. Only works for element of “path” type.
+     **
+     > Parameters
+     **
+     - from (number) position of the start of the segment
+     - to (number) position of the end of the segment
+     **
+     = (string) pathstring for the segment
+    \*/
+    elproto.getSubpath = function (from, to) {
+        if (this.type != "path") {return;}
+        return R.getSubpath(this.attrs.path, from, to);
+    };
+    /*\
+     * Raphael.easing_formulas
+     [ property ]
+     **
+     * Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
+     # <ul>
+     #     <li>“linear”</li>
+     #     <li>“&lt;” or “easeIn” or “ease-in”</li>
+     #     <li>“>” or “easeOut” or “ease-out”</li>
+     #     <li>“&lt;>” or “easeInOut” or “ease-in-out”</li>
+     #     <li>“backIn” or “back-in”</li>
+     #     <li>“backOut” or “back-out”</li>
+     #     <li>“elastic”</li>
+     #     <li>“bounce”</li>
+     # </ul>
+     # <p>See also <a href="http://raphaeljs.com/easing.html">Easing demo</a>.</p>
+    \*/
+    var ef = R.easing_formulas = {
+        linear: function (n) {
+            return n;
+        },
+        "<": function (n) {
+            return pow(n, 1.7);
+        },
+        ">": function (n) {
+            return pow(n, .48);
+        },
+        "<>": function (n) {
+            var q = .48 - n / 1.04,
+                Q = math.sqrt(.1734 + q * q),
+                x = Q - q,
+                X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+                y = -Q - q,
+                Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+                t = X + Y + .5;
+            return (1 - t) * 3 * t * t + t * t * t;
+        },
+        backIn: function (n) {
+            var s = 1.70158;
+            return n * n * ((s + 1) * n - s);
+        },
+        backOut: function (n) {
+            n = n - 1;
+            var s = 1.70158;
+            return n * n * ((s + 1) * n + s) + 1;
+        },
+        elastic: function (n) {
+            if (n == !!n) {
+                return n;
+            }
+            return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
+        },
+        bounce: function (n) {
+            var s = 7.5625,
+                p = 2.75,
+                l;
+            if (n < (1 / p)) {
+                l = s * n * n;
+            } else {
+                if (n < (2 / p)) {
+                    n -= (1.5 / p);
+                    l = s * n * n + .75;
+                } else {
+                    if (n < (2.5 / p)) {
+                        n -= (2.25 / p);
+                        l = s * n * n + .9375;
+                    } else {
+                        n -= (2.625 / p);
+                        l = s * n * n + .984375;
+                    }
+                }
+            }
+            return l;
+        }
+    };
+    ef.easeIn = ef["ease-in"] = ef["<"];
+    ef.easeOut = ef["ease-out"] = ef[">"];
+    ef.easeInOut = ef["ease-in-out"] = ef["<>"];
+    ef["back-in"] = ef.backIn;
+    ef["back-out"] = ef.backOut;
+
+    var animationElements = [],
+        requestAnimFrame = window.requestAnimationFrame       ||
+                           window.webkitRequestAnimationFrame ||
+                           window.mozRequestAnimationFrame    ||
+                           window.oRequestAnimationFrame      ||
+                           window.msRequestAnimationFrame     ||
+                           function (callback) {
+                               setTimeout(callback, 16);
+                           },
+        animation = function () {
+            var Now = +new Date,
+                l = 0;
+            for (; l < animationElements.length; l++) {
+                var e = animationElements[l];
+                if (e.el.removed || e.paused) {
+                    continue;
+                }
+                var time = Now - e.start,
+                    ms = e.ms,
+                    easing = e.easing,
+                    from = e.from,
+                    diff = e.diff,
+                    to = e.to,
+                    t = e.t,
+                    that = e.el,
+                    set = {},
+                    now,
+                    init = {},
+                    key;
+                if (e.initstatus) {
+                    time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
+                    e.status = e.initstatus;
+                    delete e.initstatus;
+                    e.stop && animationElements.splice(l--, 1);
+                } else {
+                    e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
+                }
+                if (time < 0) {
+                    continue;
+                }
+                if (time < ms) {
+                    var pos = easing(time / ms);
+                    for (var attr in from) if (from[has](attr)) {
+                        switch (availableAnimAttrs[attr]) {
+                            case nu:
+                                now = +from[attr] + pos * ms * diff[attr];
+                                break;
+                            case "colour":
+                                now = "rgb(" + [
+                                    upto255(round(from[attr].r + pos * ms * diff[attr].r)),
+                                    upto255(round(from[attr].g + pos * ms * diff[attr].g)),
+                                    upto255(round(from[attr].b + pos * ms * diff[attr].b))
+                                ].join(",") + ")";
+                                break;
+                            case "path":
+                                now = [];
+                                for (var i = 0, ii = from[attr].length; i < ii; i++) {
+                                    now[i] = [from[attr][i][0]];
+                                    for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                        now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
+                                    }
+                                    now[i] = now[i].join(S);
+                                }
+                                now = now.join(S);
+                                break;
+                            case "transform":
+                                if (diff[attr].real) {
+                                    now = [];
+                                    for (i = 0, ii = from[attr].length; i < ii; i++) {
+                                        now[i] = [from[attr][i][0]];
+                                        for (j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                            now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
+                                        }
+                                    }
+                                } else {
+                                    var get = function (i) {
+                                        return +from[attr][i] + pos * ms * diff[attr][i];
+                                    };
+                                    // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
+                                    now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
+                                }
+                                break;
+                            case "csv":
+                                if (attr == "clip-rect") {
+                                    now = [];
+                                    i = 4;
+                                    while (i--) {
+                                        now[i] = +from[attr][i] + pos * ms * diff[attr][i];
+                                    }
+                                }
+                                break;
+                            default:
+                                var from2 = [][concat](from[attr]);
+                                now = [];
+                                i = that.paper.customAttributes[attr].length;
+                                while (i--) {
+                                    now[i] = +from2[i] + pos * ms * diff[attr][i];
+                                }
+                                break;
+                        }
+                        set[attr] = now;
+                    }
+                    that.attr(set);
+                    (function (id, that, anim) {
+                        setTimeout(function () {
+                            eve("raphael.anim.frame." + id, that, anim);
+                        });
+                    })(that.id, that, e.anim);
+                } else {
+                    (function(f, el, a) {
+                        setTimeout(function() {
+                            eve("raphael.anim.frame." + el.id, el, a);
+                            eve("raphael.anim.finish." + el.id, el, a);
+                            R.is(f, "function") && f.call(el);
+                        });
+                    })(e.callback, that, e.anim);
+                    that.attr(to);
+                    animationElements.splice(l--, 1);
+                    if (e.repeat > 1 && !e.next) {
+                        for (key in to) if (to[has](key)) {
+                            init[key] = e.totalOrigin[key];
+                        }
+                        e.el.attr(init);
+                        runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
+                    }
+                    if (e.next && !e.stop) {
+                        runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
+                    }
+                }
+            }
+            R.svg && that && that.paper && that.paper.safari();
+            animationElements.length && requestAnimFrame(animation);
+        },
+        upto255 = function (color) {
+            return color > 255 ? 255 : color < 0 ? 0 : color;
+        };
+    /*\
+     * Element.animateWith
+     [ method ]
+     **
+     * Acts similar to @Element.animate, but ensure that given animation runs in sync with another given element.
+     **
+     > Parameters
+     **
+     - el (object) element to sync with
+     - anim (object) animation to sync with
+     - params (object) #optional final attributes for the element, see also @Element.attr
+     - ms (number) #optional number of milliseconds for animation to run
+     - easing (string) #optional easing type. Accept on of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
+     - callback (function) #optional callback function. Will be called at the end of animation.
+     * or
+     - element (object) element to sync with
+     - anim (object) animation to sync with
+     - animation (object) #optional animation object, see @Raphael.animation
+     **
+     = (object) original element
+    \*/
+    elproto.animateWith = function (el, anim, params, ms, easing, callback) {
+        var element = this;
+        if (element.removed) {
+            callback && callback.call(element);
+            return element;
+        }
+        var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
+            x, y;
+        runAnimation(a, element, a.percents[0], null, element.attr());
+        for (var i = 0, ii = animationElements.length; i < ii; i++) {
+            if (animationElements[i].anim == anim && animationElements[i].el == el) {
+                animationElements[ii - 1].start = animationElements[i].start;
+                break;
+            }
+        }
+        return element;
+        // 
+        // 
+        // var a = params ? R.animation(params, ms, easing, callback) : anim,
+        //     status = element.status(anim);
+        // return this.animate(a).status(a, status * anim.ms / a.ms);
+    };
+    function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
+        var cx = 3 * p1x,
+            bx = 3 * (p2x - p1x) - cx,
+            ax = 1 - cx - bx,
+            cy = 3 * p1y,
+            by = 3 * (p2y - p1y) - cy,
+            ay = 1 - cy - by;
+        function sampleCurveX(t) {
+            return ((ax * t + bx) * t + cx) * t;
+        }
+        function solve(x, epsilon) {
+            var t = solveCurveX(x, epsilon);
+            return ((ay * t + by) * t + cy) * t;
+        }
+        function solveCurveX(x, epsilon) {
+            var t0, t1, t2, x2, d2, i;
+            for(t2 = x, i = 0; i < 8; i++) {
+                x2 = sampleCurveX(t2) - x;
+                if (abs(x2) < epsilon) {
+                    return t2;
+                }
+                d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
+                if (abs(d2) < 1e-6) {
+                    break;
+                }
+                t2 = t2 - x2 / d2;
+            }
+            t0 = 0;
+            t1 = 1;
+            t2 = x;
+            if (t2 < t0) {
+                return t0;
+            }
+            if (t2 > t1) {
+                return t1;
+            }
+            while (t0 < t1) {
+                x2 = sampleCurveX(t2);
+                if (abs(x2 - x) < epsilon) {
+                    return t2;
+                }
+                if (x > x2) {
+                    t0 = t2;
+                } else {
+                    t1 = t2;
+                }
+                t2 = (t1 - t0) / 2 + t0;
+            }
+            return t2;
+        }
+        return solve(t, 1 / (200 * duration));
+    }
+    elproto.onAnimation = function (f) {
+        f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
+        return this;
+    };
+    function Animation(anim, ms) {
+        var percents = [],
+            newAnim = {};
+        this.ms = ms;
+        this.times = 1;
+        if (anim) {
+            for (var attr in anim) if (anim[has](attr)) {
+                newAnim[toFloat(attr)] = anim[attr];
+                percents.push(toFloat(attr));
+            }
+            percents.sort(sortByNumber);
+        }
+        this.anim = newAnim;
+        this.top = percents[percents.length - 1];
+        this.percents = percents;
+    }
+    /*\
+     * Animation.delay
+     [ method ]
+     **
+     * Creates a copy of existing animation object with given delay.
+     **
+     > Parameters
+     **
+     - delay (number) number of ms to pass between animation start and actual animation
+     **
+     = (object) new altered Animation object
+     | var anim = Raphael.animation({cx: 10, cy: 20}, 2e3);
+     | circle1.animate(anim); // run the given animation immediately
+     | circle2.animate(anim.delay(500)); // run the given animation after 500 ms
+    \*/
+    Animation.prototype.delay = function (delay) {
+        var a = new Animation(this.anim, this.ms);
+        a.times = this.times;
+        a.del = +delay || 0;
+        return a;
+    };
+    /*\
+     * Animation.repeat
+     [ method ]
+     **
+     * Creates a copy of existing animation object with given repetition.
+     **
+     > Parameters
+     **
+     - repeat (number) number iterations of animation. For infinite animation pass `Infinity`
+     **
+     = (object) new altered Animation object
+    \*/
+    Animation.prototype.repeat = function (times) { 
+        var a = new Animation(this.anim, this.ms);
+        a.del = this.del;
+        a.times = math.floor(mmax(times, 0)) || 1;
+        return a;
+    };
+    function runAnimation(anim, element, percent, status, totalOrigin, times) {
+        percent = toFloat(percent);
+        var params,
+            isInAnim,
+            isInAnimSet,
+            percents = [],
+            next,
+            prev,
+            timestamp,
+            ms = anim.ms,
+            from = {},
+            to = {},
+            diff = {};
+        if (status) {
+            for (i = 0, ii = animationElements.length; i < ii; i++) {
+                var e = animationElements[i];
+                if (e.el.id == element.id && e.anim == anim) {
+                    if (e.percent != percent) {
+                        animationElements.splice(i, 1);
+                        isInAnimSet = 1;
+                    } else {
+                        isInAnim = e;
+                    }
+                    element.attr(e.totalOrigin);
+                    break;
+                }
+            }
+        } else {
+            status = +to; // NaN
+        }
+        for (var i = 0, ii = anim.percents.length; i < ii; i++) {
+            if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
+                percent = anim.percents[i];
+                prev = anim.percents[i - 1] || 0;
+                ms = ms / anim.top * (percent - prev);
+                next = anim.percents[i + 1];
+                params = anim.anim[percent];
+                break;
+            } else if (status) {
+                element.attr(anim.anim[anim.percents[i]]);
+            }
+        }
+        if (!params) {
+            return;
+        }
+        if (!isInAnim) {
+            for (var attr in params) if (params[has](attr)) {
+                if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
+                    from[attr] = element.attr(attr);
+                    (from[attr] == null) && (from[attr] = availableAttrs[attr]);
+                    to[attr] = params[attr];
+                    switch (availableAnimAttrs[attr]) {
+                        case nu:
+                            diff[attr] = (to[attr] - from[attr]) / ms;
+                            break;
+                        case "colour":
+                            from[attr] = R.getRGB(from[attr]);
+                            var toColour = R.getRGB(to[attr]);
+                            diff[attr] = {
+                                r: (toColour.r - from[attr].r) / ms,
+                                g: (toColour.g - from[attr].g) / ms,
+                                b: (toColour.b - from[attr].b) / ms
+                            };
+                            break;
+                        case "path":
+                            var pathes = path2curve(from[attr], to[attr]),
+                                toPath = pathes[1];
+                            from[attr] = pathes[0];
+                            diff[attr] = [];
+                            for (i = 0, ii = from[attr].length; i < ii; i++) {
+                                diff[attr][i] = [0];
+                                for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                    diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
+                                }
+                            }
+                            break;
+                        case "transform":
+                            var _ = element._,
+                                eq = equaliseTransform(_[attr], to[attr]);
+                            if (eq) {
+                                from[attr] = eq.from;
+                                to[attr] = eq.to;
+                                diff[attr] = [];
+                                diff[attr].real = true;
+                                for (i = 0, ii = from[attr].length; i < ii; i++) {
+                                    diff[attr][i] = [from[attr][i][0]];
+                                    for (j = 1, jj = from[attr][i].length; j < jj; j++) {
+                                        diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
+                                    }
+                                }
+                            } else {
+                                var m = (element.matrix || new Matrix),
+                                    to2 = {
+                                        _: {transform: _.transform},
+                                        getBBox: function () {
+                                            return element.getBBox(1);
+                                        }
+                                    };
+                                from[attr] = [
+                                    m.a,
+                                    m.b,
+                                    m.c,
+                                    m.d,
+                                    m.e,
+                                    m.f
+                                ];
+                                extractTransform(to2, to[attr]);
+                                to[attr] = to2._.transform;
+                                diff[attr] = [
+                                    (to2.matrix.a - m.a) / ms,
+                                    (to2.matrix.b - m.b) / ms,
+                                    (to2.matrix.c - m.c) / ms,
+                                    (to2.matrix.d - m.d) / ms,
+                                    (to2.matrix.e - m.e) / ms,
+                                    (to2.matrix.f - m.f) / ms
+                                ];
+                                // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
+                                // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
+                                // extractTransform(to2, to[attr]);
+                                // diff[attr] = [
+                                //     (to2._.sx - _.sx) / ms,
+                                //     (to2._.sy - _.sy) / ms,
+                                //     (to2._.deg - _.deg) / ms,
+                                //     (to2._.dx - _.dx) / ms,
+                                //     (to2._.dy - _.dy) / ms
+                                // ];
+                            }
+                            break;
+                        case "csv":
+                            var values = Str(params[attr])[split](separator),
+                                from2 = Str(from[attr])[split](separator);
+                            if (attr == "clip-rect") {
+                                from[attr] = from2;
+                                diff[attr] = [];
+                                i = from2.length;
+                                while (i--) {
+                                    diff[attr][i] = (values[i] - from[attr][i]) / ms;
+                                }
+                            }
+                            to[attr] = values;
+                            break;
+                        default:
+                            values = [][concat](params[attr]);
+                            from2 = [][concat](from[attr]);
+                            diff[attr] = [];
+                            i = element.paper.customAttributes[attr].length;
+                            while (i--) {
+                                diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
+                            }
+                            break;
+                    }
+                }
+            }
+            var easing = params.easing,
+                easyeasy = R.easing_formulas[easing];
+            if (!easyeasy) {
+                easyeasy = Str(easing).match(bezierrg);
+                if (easyeasy && easyeasy.length == 5) {
+                    var curve = easyeasy;
+                    easyeasy = function (t) {
+                        return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
+                    };
+                } else {
+                    easyeasy = pipe;
+                }
+            }
+            timestamp = params.start || anim.start || +new Date;
+            e = {
+                anim: anim,
+                percent: percent,
+                timestamp: timestamp,
+                start: timestamp + (anim.del || 0),
+                status: 0,
+                initstatus: status || 0,
+                stop: false,
+                ms: ms,
+                easing: easyeasy,
+                from: from,
+                diff: diff,
+                to: to,
+                el: element,
+                callback: params.callback,
+                prev: prev,
+                next: next,
+                repeat: times || anim.times,
+                origin: element.attr(),
+                totalOrigin: totalOrigin
+            };
+            animationElements.push(e);
+            if (status && !isInAnim && !isInAnimSet) {
+                e.stop = true;
+                e.start = new Date - ms * status;
+                if (animationElements.length == 1) {
+                    return animation();
+                }
+            }
+            if (isInAnimSet) {
+                e.start = new Date - e.ms * status;
+            }
+            animationElements.length == 1 && requestAnimFrame(animation);
+        } else {
+            isInAnim.initstatus = status;
+            isInAnim.start = new Date - isInAnim.ms * status;
+        }
+        eve("raphael.anim.start." + element.id, element, anim);
+    }
+    /*\
+     * Raphael.animation
+     [ method ]
+     **
+     * Creates an animation object that can be passed to the @Element.animate or @Element.animateWith methods.
+     * See also @Animation.delay and @Animation.repeat methods.
+     **
+     > Parameters
+     **
+     - params (object) final attributes for the element, see also @Element.attr
+     - ms (number) number of milliseconds for animation to run
+     - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
+     - callback (function) #optional callback function. Will be called at the end of animation.
+     **
+     = (object) @Animation
+    \*/
+    R.animation = function (params, ms, easing, callback) {
+        if (params instanceof Animation) {
+            return params;
+        }
+        if (R.is(easing, "function") || !easing) {
+            callback = callback || easing || null;
+            easing = null;
+        }
+        params = Object(params);
+        ms = +ms || 0;
+        var p = {},
+            json,
+            attr;
+        for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
+            json = true;
+            p[attr] = params[attr];
+        }
+        if (!json) {
+            return new Animation(params, ms);
+        } else {
+            easing && (p.easing = easing);
+            callback && (p.callback = callback);
+            return new Animation({100: p}, ms);
+        }
+    };
+    /*\
+     * Element.animate
+     [ method ]
+     **
+     * Creates and starts animation for given element.
+     **
+     > Parameters
+     **
+     - params (object) final attributes for the element, see also @Element.attr
+     - ms (number) number of milliseconds for animation to run
+     - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
+     - callback (function) #optional callback function. Will be called at the end of animation.
+     * or
+     - animation (object) animation object, see @Raphael.animation
+     **
+     = (object) original element
+    \*/
+    elproto.animate = function (params, ms, easing, callback) {
+        var element = this;
+        if (element.removed) {
+            callback && callback.call(element);
+            return element;
+        }
+        var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
+        runAnimation(anim, element, anim.percents[0], null, element.attr());
+        return element;
+    };
+    /*\
+     * Element.setTime
+     [ method ]
+     **
+     * Sets the status of animation of the element in milliseconds. Similar to @Element.status method.
+     **
+     > Parameters
+     **
+     - anim (object) animation object
+     - value (number) number of milliseconds from the beginning of the animation
+     **
+     = (object) original element if `value` is specified
+     * Note, that during animation following events are triggered:
+     *
+     * On each animation frame event `anim.frame.<id>`, on start `anim.start.<id>` and on end `anim.finish.<id>`.
+    \*/
+    elproto.setTime = function (anim, value) {
+        if (anim && value != null) {
+            this.status(anim, mmin(value, anim.ms) / anim.ms);
+        }
+        return this;
+    };
+    /*\
+     * Element.status
+     [ method ]
+     **
+     * Gets or sets the status of animation of the element.
+     **
+     > Parameters
+     **
+     - anim (object) #optional animation object
+     - value (number) #optional 0 – 1. If specified, method works like a setter and sets the status of a given animation to the value. This will cause animation to jump to the given position.
+     **
+     = (number) status
+     * or
+     = (array) status if `anim` is not specified. Array of objects in format:
+     o {
+     o     anim: (object) animation object
+     o     status: (number) status
+     o }
+     * or
+     = (object) original element if `value` is specified
+    \*/
+    elproto.status = function (anim, value) {
+        var out = [],
+            i = 0,
+            len,
+            e;
+        if (value != null) {
+            runAnimation(anim, this, -1, mmin(value, 1));
+            return this;
+        } else {
+            len = animationElements.length;
+            for (; i < len; i++) {
+                e = animationElements[i];
+                if (e.el.id == this.id && (!anim || e.anim == anim)) {
+                    if (anim) {
+                        return e.status;
+                    }
+                    out.push({
+                        anim: e.anim,
+                        status: e.status
+                    });
+                }
+            }
+            if (anim) {
+                return 0;
+            }
+            return out;
+        }
+    };
+    /*\
+     * Element.pause
+     [ method ]
+     **
+     * Stops animation of the element with ability to resume it later on.
+     **
+     > Parameters
+     **
+     - anim (object) #optional animation object
+     **
+     = (object) original element
+    \*/
+    elproto.pause = function (anim) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+            if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
+                animationElements[i].paused = true;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.resume
+     [ method ]
+     **
+     * Resumes animation if it was paused with @Element.pause method.
+     **
+     > Parameters
+     **
+     - anim (object) #optional animation object
+     **
+     = (object) original element
+    \*/
+    elproto.resume = function (anim) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+            var e = animationElements[i];
+            if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
+                delete e.paused;
+                this.status(e.anim, e.status);
+            }
+        }
+        return this;
+    };
+    /*\
+     * Element.stop
+     [ method ]
+     **
+     * Stops animation of the element.
+     **
+     > Parameters
+     **
+     - anim (object) #optional animation object
+     **
+     = (object) original element
+    \*/
+    elproto.stop = function (anim) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+            if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
+                animationElements.splice(i--, 1);
+            }
+        }
+        return this;
+    };
+    function stopAnimation(paper) {
+        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
+            animationElements.splice(i--, 1);
+        }
+    }
+    eve.on("raphael.remove", stopAnimation);
+    eve.on("raphael.clear", stopAnimation);
+    elproto.toString = function () {
+        return "Rapha\xebl\u2019s object";
+    };
+
+    // Set
+    var Set = function (items) {
+        this.items = [];
+        this.length = 0;
+        this.type = "set";
+        if (items) {
+            for (var i = 0, ii = items.length; i < ii; i++) {
+                if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
+                    this[this.items.length] = this.items[this.items.length] = items[i];
+                    this.length++;
+                }
+            }
+        }
+    },
+    setproto = Set.prototype;
+    /*\
+     * Set.push
+     [ method ]
+     **
+     * Adds each argument to the current set.
+     = (object) original element
+    \*/
+    setproto.push = function () {
+        var item,
+            len;
+        for (var i = 0, ii = arguments.length; i < ii; i++) {
+            item = arguments[i];
+            if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
+                len = this.items.length;
+                this[len] = this.items[len] = item;
+                this.length++;
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.pop
+     [ method ]
+     **
+     * Removes last element and returns it.
+     = (object) element
+    \*/
+    setproto.pop = function () {
+        this.length && delete this[this.length--];
+        return this.items.pop();
+    };
+    /*\
+     * Set.forEach
+     [ method ]
+     **
+     * Executes given function for each element in the set.
+     *
+     * If function returns `false` it will stop loop running.
+     **
+     > Parameters
+     **
+     - callback (function) function to run
+     - thisArg (object) context object for the callback
+     = (object) Set object
+    \*/
+    setproto.forEach = function (callback, thisArg) {
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
+                return this;
+            }
+        }
+        return this;
+    };
+    for (var method in elproto) if (elproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname][apply](el, arg);
+                });
+            };
+        })(method);
+    }
+    setproto.attr = function (name, value) {
+        if (name && R.is(name, array) && R.is(name[0], "object")) {
+            for (var j = 0, jj = name.length; j < jj; j++) {
+                this.items[j].attr(name[j]);
+            }
+        } else {
+            for (var i = 0, ii = this.items.length; i < ii; i++) {
+                this.items[i].attr(name, value);
+            }
+        }
+        return this;
+    };
+    /*\
+     * Set.clear
+     [ method ]
+     **
+     * Removeds all elements from the set
+    \*/
+    setproto.clear = function () {
+        while (this.length) {
+            this.pop();
+        }
+    };
+    /*\
+     * Set.splice
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     > Parameters
+     **
+     - index (number) position of the deletion
+     - count (number) number of element to remove
+     - insertion… (object) #optional elements to insert
+     = (object) set elements that were deleted
+    \*/
+    setproto.splice = function (index, count, insertion) {
+        index = index < 0 ? mmax(this.length + index, 0) : index;
+        count = mmax(0, mmin(this.length - index, count));
+        var tail = [],
+            todel = [],
+            args = [],
+            i;
+        for (i = 2; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        }
+        for (i = 0; i < count; i++) {
+            todel.push(this[index + i]);
+        }
+        for (; i < this.length - index; i++) {
+            tail.push(this[index + i]);
+        }
+        var arglen = args.length;
+        for (i = 0; i < arglen + tail.length; i++) {
+            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+        }
+        i = this.items.length = this.length -= count - arglen;
+        while (this[i]) {
+            delete this[i++];
+        }
+        return new Set(todel);
+    };
+    /*\
+     * Set.exclude
+     [ method ]
+     **
+     * Removes given element from the set
+     **
+     > Parameters
+     **
+     - element (object) element to remove
+     = (boolean) `true` if object was found & removed from the set
+    \*/
+    setproto.exclude = function (el) {
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
+            return true;
+        }
+    };
+    setproto.animate = function (params, ms, easing, callback) {
+        (R.is(easing, "function") || !easing) && (callback = easing || null);
+        var len = this.items.length,
+            i = len,
+            item,
+            set = this,
+            collector;
+        if (!len) {
+            return this;
+        }
+        callback && (collector = function () {
+            !--len && callback.call(set);
+        });
+        easing = R.is(easing, string) ? easing : collector;
+        var anim = R.animation(params, ms, easing, collector);
+        item = this.items[--i].animate(anim);
+        while (i--) {
+            this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
+        }
+        return this;
+    };
+    setproto.insertAfter = function (el) {
+        var i = this.items.length;
+        while (i--) {
+            this.items[i].insertAfter(el);
+        }
+        return this;
+    };
+    setproto.getBBox = function () {
+        var x = [],
+            y = [],
+            x2 = [],
+            y2 = [];
+        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+            var box = this.items[i].getBBox();
+            x.push(box.x);
+            y.push(box.y);
+            x2.push(box.x + box.width);
+            y2.push(box.y + box.height);
+        }
+        x = mmin[apply](0, x);
+        y = mmin[apply](0, y);
+        x2 = mmax[apply](0, x2);
+        y2 = mmax[apply](0, y2);
+        return {
+            x: x,
+            y: y,
+            x2: x2,
+            y2: y2,
+            width: x2 - x,
+            height: y2 - y
+        };
+    };
+    setproto.clone = function (s) {
+        s = this.paper.set();
+        for (var i = 0, ii = this.items.length; i < ii; i++) {
+            s.push(this.items[i].clone());
+        }
+        return s;
+    };
+    setproto.toString = function () {
+        return "Rapha\xebl\u2018s set";
+    };
+
+    setproto.glow = function(glowConfig) {
+        var ret = this.paper.set();
+        this.forEach(function(shape, index){
+            var g = shape.glow(glowConfig);
+            if(g != null){
+                g.forEach(function(shape2, index2){
+                    ret.push(shape2);
+                });
+            }
+        });
+        return ret;
+    };
+
+    /*\
+     * Raphael.registerFont
+     [ method ]
+     **
+     * Adds given font to the registered set of fonts for Raphaël. Should be used as an internal call from within Cufón’s font file.
+     * Returns original parameter, so it could be used with chaining.
+     # <a href="http://wiki.github.com/sorccu/cufon/about">More about Cufón and how to convert your font form TTF, OTF, etc to JavaScript file.</a>
+     **
+     > Parameters
+     **
+     - font (object) the font to register
+     = (object) the font you passed in
+     > Usage
+     | Cufon.registerFont(Raphael.registerFont({…}));
+    \*/
+    R.registerFont = function (font) {
+        if (!font.face) {
+            return font;
+        }
+        this.fonts = this.fonts || {};
+        var fontcopy = {
+                w: font.w,
+                face: {},
+                glyphs: {}
+            },
+            family = font.face["font-family"];
+        for (var prop in font.face) if (font.face[has](prop)) {
+            fontcopy.face[prop] = font.face[prop];
+        }
+        if (this.fonts[family]) {
+            this.fonts[family].push(fontcopy);
+        } else {
+            this.fonts[family] = [fontcopy];
+        }
+        if (!font.svg) {
+            fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
+            for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
+                var path = font.glyphs[glyph];
+                fontcopy.glyphs[glyph] = {
+                    w: path.w,
+                    k: {},
+                    d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
+                            return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
+                        }) + "z"
+                };
+                if (path.k) {
+                    for (var k in path.k) if (path[has](k)) {
+                        fontcopy.glyphs[glyph].k[k] = path.k[k];
+                    }
+                }
+            }
+        }
+        return font;
+    };
+    /*\
+     * Paper.getFont
+     [ method ]
+     **
+     * Finds font object in the registered fonts by given parameters. You could specify only one word from the font name, like “Myriad” for “Myriad Pro”.
+     **
+     > Parameters
+     **
+     - family (string) font family name or any word from it
+     - weight (string) #optional font weight
+     - style (string) #optional font style
+     - stretch (string) #optional font stretch
+     = (object) the font object
+     > Usage
+     | paper.print(100, 100, "Test string", paper.getFont("Times", 800), 30);
+    \*/
+    paperproto.getFont = function (family, weight, style, stretch) {
+        stretch = stretch || "normal";
+        style = style || "normal";
+        weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
+        if (!R.fonts) {
+            return;
+        }
+        var font = R.fonts[family];
+        if (!font) {
+            var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
+            for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
+                if (name.test(fontName)) {
+                    font = R.fonts[fontName];
+                    break;
+                }
+            }
+        }
+        var thefont;
+        if (font) {
+            for (var i = 0, ii = font.length; i < ii; i++) {
+                thefont = font[i];
+                if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
+                    break;
+                }
+            }
+        }
+        return thefont;
+    };
+    /*\
+     * Paper.print
+     [ method ]
+     **
+     * Creates path that represent given text written using given font at given position with given size.
+     * Result of the method is path element that contains whole text as a separate path.
+     **
+     > Parameters
+     **
+     - x (number) x position of the text
+     - y (number) y position of the text
+     - string (string) text to print
+     - font (object) font object, see @Paper.getFont
+     - size (number) #optional size of the font, default is `16`
+     - origin (string) #optional could be `"baseline"` or `"middle"`, default is `"middle"`
+     - letter_spacing (number) #optional number in range `-1..1`, default is `0`
+     = (object) resulting path element, which consist of all letters
+     > Usage
+     | var txt = r.print(10, 50, "print", r.getFont("Museo"), 30).attr({fill: "#fff"});
+    \*/
+    paperproto.print = function (x, y, string, font, size, origin, letter_spacing) {
+        origin = origin || "middle"; // baseline|middle
+        letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
+        var letters = Str(string)[split](E),
+            shift = 0,
+            notfirst = 0,
+            path = E,
+            scale;
+        R.is(font, string) && (font = this.getFont(font));
+        if (font) {
+            scale = (size || 16) / font.face["units-per-em"];
+            var bb = font.face.bbox[split](separator),
+                top = +bb[0],
+                lineHeight = bb[3] - bb[1],
+                shifty = 0,
+                height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
+            for (var i = 0, ii = letters.length; i < ii; i++) {
+                if (letters[i] == "\n") {
+                    shift = 0;
+                    curr = 0;
+                    notfirst = 0;
+                    shifty += lineHeight;
+                } else {
+                    var prev = notfirst && font.glyphs[letters[i - 1]] || {},
+                        curr = font.glyphs[letters[i]];
+                    shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
+                    notfirst = 1;
+                }
+                if (curr && curr.d) {
+                    path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
+                }
+            }
+        }
+        return this.path(path).attr({
+            fill: "#000",
+            stroke: "none"
+        });
+    };
+
+    /*\
+     * Paper.add
+     [ method ]
+     **
+     * Imports elements in JSON array in format `{type: type, <attributes>}`
+     **
+     > Parameters
+     **
+     - json (array)
+     = (object) resulting set of imported elements
+     > Usage
+     | paper.add([
+     |     {
+     |         type: "circle",
+     |         cx: 10,
+     |         cy: 10,
+     |         r: 5
+     |     },
+     |     {
+     |         type: "rect",
+     |         x: 10,
+     |         y: 10,
+     |         width: 10,
+     |         height: 10,
+     |         fill: "#fc0"
+     |     }
+     | ]);
+    \*/
+    paperproto.add = function (json) {
+        if (R.is(json, "array")) {
+            var res = this.set(),
+                i = 0,
+                ii = json.length,
+                j;
+            for (; i < ii; i++) {
+                j = json[i] || {};
+                elements[has](j.type) && res.push(this[j.type]().attr(j));
+            }
+        }
+        return res;
+    };
+
+    /*\
+     * Raphael.format
+     [ method ]
+     **
+     * Simple format function. Replaces construction of type “`{<number>}`” to the corresponding argument.
+     **
+     > Parameters
+     **
+     - token (string) string to format
+     - … (string) rest of arguments will be treated as parameters for replacement
+     = (string) formated string
+     > Usage
+     | var x = 10,
+     |     y = 20,
+     |     width = 40,
+     |     height = 50;
+     | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
+     | paper.path(Raphael.format("M{0},{1}h{2}v{3}h{4}z", x, y, width, height, -width));
+    \*/
+    R.format = function (token, params) {
+        var args = R.is(params, array) ? [0][concat](params) : arguments;
+        token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
+            return args[++i] == null ? E : args[i];
+        }));
+        return token || E;
+    };
+    /*\
+     * Raphael.fullfill
+     [ method ]
+     **
+     * A little bit more advanced format function than @Raphael.format. Replaces construction of type “`{<name>}`” to the corresponding argument.
+     **
+     > Parameters
+     **
+     - token (string) string to format
+     - json (object) object which properties will be used as a replacement
+     = (string) formated string
+     > Usage
+     | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
+     | paper.path(Raphael.fullfill("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
+     |     x: 10,
+     |     y: 20,
+     |     dim: {
+     |         width: 40,
+     |         height: 50,
+     |         "negative width": -40
+     |     }
+     | }));
+    \*/
+    R.fullfill = (function () {
+        var tokenRegex = /\{([^\}]+)\}/g,
+            objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+            replacer = function (all, key, obj) {
+                var res = obj;
+                key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+                    name = name || quotedName;
+                    if (res) {
+                        if (name in res) {
+                            res = res[name];
+                        }
+                        typeof res == "function" && isFunc && (res = res());
+                    }
+                });
+                res = (res == null || res == obj ? all : res) + "";
+                return res;
+            };
+        return function (str, obj) {
+            return String(str).replace(tokenRegex, function (all, key) {
+                return replacer(all, key, obj);
+            });
+        };
+    })();
+    /*\
+     * Raphael.ninja
+     [ method ]
+     **
+     * If you want to leave no trace of Raphaël (Well, Raphaël creates only one global variable `Raphael`, but anyway.) You can use `ninja` method.
+     * Beware, that in this case plugins could stop working, because they are depending on global variable existance.
+     **
+     = (object) Raphael object
+     > Usage
+     | (function (local_raphael) {
+     |     var paper = local_raphael(10, 10, 320, 200);
+     |     …
+     | })(Raphael.ninja());
+    \*/
+    R.ninja = function () {
+        oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
+        return R;
+    };
+    /*\
+     * Raphael.st
+     [ property (object) ]
+     **
+     * You can add your own method to elements and sets. It is wise to add a set method for each element method
+     * you added, so you will be able to call the same method on sets too.
+     **
+     * See also @Raphael.el.
+     > Usage
+     | Raphael.el.red = function () {
+     |     this.attr({fill: "#f00"});
+     | };
+     | Raphael.st.red = function () {
+     |     this.forEach(function (el) {
+     |         el.red();
+     |     });
+     | };
+     | // then use it
+     | paper.set(paper.circle(100, 100, 20), paper.circle(110, 100, 20)).red();
+    \*/
+    R.st = setproto;
+    // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
+    (function (doc, loaded, f) {
+        if (doc.readyState == null && doc.addEventListener){
+            doc.addEventListener(loaded, f = function () {
+                doc.removeEventListener(loaded, f, false);
+                doc.readyState = "complete";
+            }, false);
+            doc.readyState = "loading";
+        }
+        function isLoaded() {
+            (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
+        }
+        isLoaded();
+    })(document, "DOMContentLoaded");
+
+    oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
+    
+    eve.on("raphael.DOMload", function () {
+        loaded = true;
+    });
+})();
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël - JavaScript Vector Library                                 │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ SVG Module                                                          │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+window.Raphael && window.Raphael.svg && function (R) {
+    var has = "hasOwnProperty",
+        Str = String,
+        toFloat = parseFloat,
+        toInt = parseInt,
+        math = Math,
+        mmax = math.max,
+        abs = math.abs,
+        pow = math.pow,
+        separator = /[, ]+/,
+        eve = R.eve,
+        E = "",
+        S = " ";
+    var xlink = "http://www.w3.org/1999/xlink",
+        markers = {
+            block: "M5,0 0,2.5 5,5z",
+            classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z",
+            diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
+            open: "M6,1 1,3.5 6,6",
+            oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
+        },
+        markerCounter = {};
+    R.toString = function () {
+        return  "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
+    };
+    var $ = function (el, attr) {
+        if (attr) {
+            if (typeof el == "string") {
+                el = $(el);
+            }
+            for (var key in attr) if (attr[has](key)) {
+                if (key.substring(0, 6) == "xlink:") {
+                    el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
+                } else {
+                    el.setAttribute(key, Str(attr[key]));
+                }
+            }
+        } else {
+            el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
+            el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
+        }
+        return el;
+    },
+    addGradientFill = function (element, gradient) {
+        var type = "linear",
+            id = element.id + gradient,
+            fx = .5, fy = .5,
+            o = element.node,
+            SVG = element.paper,
+            s = o.style,
+            el = R._g.doc.getElementById(id);
+        if (!el) {
+            gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
+                type = "radial";
+                if (_fx && _fy) {
+                    fx = toFloat(_fx);
+                    fy = toFloat(_fy);
+                    var dir = ((fy > .5) * 2 - 1);
+                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
+                        (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
+                        fy != .5 &&
+                        (fy = fy.toFixed(5) - 1e-5 * dir);
+                }
+                return E;
+            });
+            gradient = gradient.split(/\s*\-\s*/);
+            if (type == "linear") {
+                var angle = gradient.shift();
+                angle = -toFloat(angle);
+                if (isNaN(angle)) {
+                    return null;
+                }
+                var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
+                    max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
+                vector[2] *= max;
+                vector[3] *= max;
+                if (vector[2] < 0) {
+                    vector[0] = -vector[2];
+                    vector[2] = 0;
+                }
+                if (vector[3] < 0) {
+                    vector[1] = -vector[3];
+                    vector[3] = 0;
+                }
+            }
+            var dots = R._parseDots(gradient);
+            if (!dots) {
+                return null;
+            }
+            id = id.replace(/[\(\)\s,\xb0#]/g, "_");
+            
+            if (element.gradient && id != element.gradient.id) {
+                SVG.defs.removeChild(element.gradient);
+                delete element.gradient;
+            }
+
+            if (!element.gradient) {
+                el = $(type + "Gradient", {id: id});
+                element.gradient = el;
+                $(el, type == "radial" ? {
+                    fx: fx,
+                    fy: fy
+                } : {
+                    x1: vector[0],
+                    y1: vector[1],
+                    x2: vector[2],
+                    y2: vector[3],
+                    gradientTransform: element.matrix.invert()
+                });
+                SVG.defs.appendChild(el);
+                for (var i = 0, ii = dots.length; i < ii; i++) {
+                    el.appendChild($("stop", {
+                        offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
+                        "stop-color": dots[i].color || "#fff"
+                    }));
+                }
+            }
+        }
+        $(o, {
+            fill: "url(#" + id + ")",
+            opacity: 1,
+            "fill-opacity": 1
+        });
+        s.fill = E;
+        s.opacity = 1;
+        s.fillOpacity = 1;
+        return 1;
+    },
+    updatePosition = function (o) {
+        var bbox = o.getBBox(1);
+        $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
+    },
+    addArrow = function (o, value, isEnd) {
+        if (o.type == "path") {
+            var values = Str(value).toLowerCase().split("-"),
+                p = o.paper,
+                se = isEnd ? "end" : "start",
+                node = o.node,
+                attrs = o.attrs,
+                stroke = attrs["stroke-width"],
+                i = values.length,
+                type = "classic",
+                from,
+                to,
+                dx,
+                refX,
+                attr,
+                w = 3,
+                h = 3,
+                t = 5;
+            while (i--) {
+                switch (values[i]) {
+                    case "block":
+                    case "classic":
+                    case "oval":
+                    case "diamond":
+                    case "open":
+                    case "none":
+                        type = values[i];
+                        break;
+                    case "wide": h = 5; break;
+                    case "narrow": h = 2; break;
+                    case "long": w = 5; break;
+                    case "short": w = 2; break;
+                }
+            }
+            if (type == "open") {
+                w += 2;
+                h += 2;
+                t += 2;
+                dx = 1;
+                refX = isEnd ? 4 : 1;
+                attr = {
+                    fill: "none",
+                    stroke: attrs.stroke
+                };
+            } else {
+                refX = dx = w / 2;
+                attr = {
+                    fill: attrs.stroke,
+                    stroke: "none"
+                };
+            }
+            if (o._.arrows) {
+                if (isEnd) {
+                    o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
+                    o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
+                } else {
+                    o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
+                    o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
+                }
+            } else {
+                o._.arrows = {};
+            }
+            if (type != "none") {
+                var pathId = "raphael-marker-" + type,
+                    markerId = "raphael-marker-" + se + type + w + h;
+                if (!R._g.doc.getElementById(pathId)) {
+                    p.defs.appendChild($($("path"), {
+                        "stroke-linecap": "round",
+                        d: markers[type],
+                        id: pathId
+                    }));
+                    markerCounter[pathId] = 1;
+                } else {
+                    markerCounter[pathId]++;
+                }
+                var marker = R._g.doc.getElementById(markerId),
+                    use;
+                if (!marker) {
+                    marker = $($("marker"), {
+                        id: markerId,
+                        markerHeight: h,
+                        markerWidth: w,
+                        orient: "auto",
+                        refX: refX,
+                        refY: h / 2
+                    });
+                    use = $($("use"), {
+                        "xlink:href": "#" + pathId,
+                        transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
+                        "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
+                    });
+                    marker.appendChild(use);
+                    p.defs.appendChild(marker);
+                    markerCounter[markerId] = 1;
+                } else {
+                    markerCounter[markerId]++;
+                    use = marker.getElementsByTagName("use")[0];
+                }
+                $(use, attr);
+                var delta = dx * (type != "diamond" && type != "oval");
+                if (isEnd) {
+                    from = o._.arrows.startdx * stroke || 0;
+                    to = R.getTotalLength(attrs.path) - delta * stroke;
+                } else {
+                    from = delta * stroke;
+                    to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
+                }
+                attr = {};
+                attr["marker-" + se] = "url(#" + markerId + ")";
+                if (to || from) {
+                    attr.d = Raphael.getSubpath(attrs.path, from, to);
+                }
+                $(node, attr);
+                o._.arrows[se + "Path"] = pathId;
+                o._.arrows[se + "Marker"] = markerId;
+                o._.arrows[se + "dx"] = delta;
+                o._.arrows[se + "Type"] = type;
+                o._.arrows[se + "String"] = value;
+            } else {
+                if (isEnd) {
+                    from = o._.arrows.startdx * stroke || 0;
+                    to = R.getTotalLength(attrs.path) - from;
+                } else {
+                    from = 0;
+                    to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
+                }
+                o._.arrows[se + "Path"] && $(node, {d: Raphael.getSubpath(attrs.path, from, to)});
+                delete o._.arrows[se + "Path"];
+                delete o._.arrows[se + "Marker"];
+                delete o._.arrows[se + "dx"];
+                delete o._.arrows[se + "Type"];
+                delete o._.arrows[se + "String"];
+            }
+            for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
+                var item = R._g.doc.getElementById(attr);
+                item && item.parentNode.removeChild(item);
+            }
+        }
+    },
+    dasharray = {
+        "": [0],
+        "none": [0],
+        "-": [3, 1],
+        ".": [1, 1],
+        "-.": [3, 1, 1, 1],
+        "-..": [3, 1, 1, 1, 1, 1],
+        ". ": [1, 3],
+        "- ": [4, 3],
+        "--": [8, 3],
+        "- .": [4, 3, 1, 3],
+        "--.": [8, 3, 1, 3],
+        "--..": [8, 3, 1, 3, 1, 3]
+    },
+    addDashes = function (o, value, params) {
+        value = dasharray[Str(value).toLowerCase()];
+        if (value) {
+            var width = o.attrs["stroke-width"] || "1",
+                butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
+                dashes = [],
+                i = value.length;
+            while (i--) {
+                dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
+            }
+            $(o.node, {"stroke-dasharray": dashes.join(",")});
+        }
+    },
+    setFillAndStroke = function (o, params) {
+        var node = o.node,
+            attrs = o.attrs,
+            vis = node.style.visibility;
+        node.style.visibility = "hidden";
+        for (var att in params) {
+            if (params[has](att)) {
+                if (!R._availableAttrs[has](att)) {
+                    continue;
+                }
+                var value = params[att];
+                attrs[att] = value;
+                switch (att) {
+                    case "blur":
+                        o.blur(value);
+                        break;
+                    case "href":
+                    case "title":
+                    case "target":
+                        var pn = node.parentNode;
+                        if (pn.tagName.toLowerCase() != "a") {
+                            var hl = $("a");
+                            pn.insertBefore(hl, node);
+                            hl.appendChild(node);
+                            pn = hl;
+                        }
+                        if (att == "target") {
+                            pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
+                        } else {
+                            pn.setAttributeNS(xlink, att, value);
+                        }
+                        break;
+                    case "cursor":
+                        node.style.cursor = value;
+                        break;
+                    case "transform":
+                        o.transform(value);
+                        break;
+                    case "arrow-start":
+                        addArrow(o, value);
+                        break;
+                    case "arrow-end":
+                        addArrow(o, value, 1);
+                        break;
+                    case "clip-rect":
+                        var rect = Str(value).split(separator);
+                        if (rect.length == 4) {
+                            o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
+                            var el = $("clipPath"),
+                                rc = $("rect");
+                            el.id = R.createUUID();
+                            $(rc, {
+                                x: rect[0],
+                                y: rect[1],
+                                width: rect[2],
+                                height: rect[3]
+                            });
+                            el.appendChild(rc);
+                            o.paper.defs.appendChild(el);
+                            $(node, {"clip-path": "url(#" + el.id + ")"});
+                            o.clip = rc;
+                        }
+                        if (!value) {
+                            var path = node.getAttribute("clip-path");
+                            if (path) {
+                                var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
+                                clip && clip.parentNode.removeChild(clip);
+                                $(node, {"clip-path": E});
+                                delete o.clip;
+                            }
+                        }
+                    break;
+                    case "path":
+                        if (o.type == "path") {
+                            $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
+                            o._.dirty = 1;
+                            if (o._.arrows) {
+                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+                            }
+                        }
+                        break;
+                    case "width":
+                        node.setAttribute(att, value);
+                        o._.dirty = 1;
+                        if (attrs.fx) {
+                            att = "x";
+                            value = attrs.x;
+                        } else {
+                            break;
+                        }
+                    case "x":
+                        if (attrs.fx) {
+                            value = -attrs.x - (attrs.width || 0);
+                        }
+                    case "rx":
+                        if (att == "rx" && o.type == "rect") {
+                            break;
+                        }
+                    case "cx":
+                        node.setAttribute(att, value);
+                        o.pattern && updatePosition(o);
+                        o._.dirty = 1;
+                        break;
+                    case "height":
+                        node.setAttribute(att, value);
+                        o._.dirty = 1;
+                        if (attrs.fy) {
+                            att = "y";
+                            value = attrs.y;
+                        } else {
+                            break;
+                        }
+                    case "y":
+                        if (attrs.fy) {
+                            value = -attrs.y - (attrs.height || 0);
+                        }
+                    case "ry":
+                        if (att == "ry" && o.type == "rect") {
+                            break;
+                        }
+                    case "cy":
+                        node.setAttribute(att, value);
+                        o.pattern && updatePosition(o);
+                        o._.dirty = 1;
+                        break;
+                    case "r":
+                        if (o.type == "rect") {
+                            $(node, {rx: value, ry: value});
+                        } else {
+                            node.setAttribute(att, value);
+                        }
+                        o._.dirty = 1;
+                        break;
+                    case "src":
+                        if (o.type == "image") {
+                            node.setAttributeNS(xlink, "href", value);
+                        }
+                        break;
+                    case "stroke-width":
+                        if (o._.sx != 1 || o._.sy != 1) {
+                            value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
+                        }
+                        if (o.paper._vbSize) {
+                            value *= o.paper._vbSize;
+                        }
+                        node.setAttribute(att, value);
+                        if (attrs["stroke-dasharray"]) {
+                            addDashes(o, attrs["stroke-dasharray"], params);
+                        }
+                        if (o._.arrows) {
+                            "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+                            "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+                        }
+                        break;
+                    case "stroke-dasharray":
+                        addDashes(o, value, params);
+                        break;
+                    case "fill":
+                        var isURL = Str(value).match(R._ISURL);
+                        if (isURL) {
+                            el = $("pattern");
+                            var ig = $("image");
+                            el.id = R.createUUID();
+                            $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
+                            $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
+                            el.appendChild(ig);
+
+                            (function (el) {
+                                R._preload(isURL[1], function () {
+                                    var w = this.offsetWidth,
+                                        h = this.offsetHeight;
+                                    $(el, {width: w, height: h});
+                                    $(ig, {width: w, height: h});
+                                    o.paper.safari();
+                                });
+                            })(el);
+                            o.paper.defs.appendChild(el);
+                            $(node, {fill: "url(#" + el.id + ")"});
+                            o.pattern = el;
+                            o.pattern && updatePosition(o);
+                            break;
+                        }
+                        var clr = R.getRGB(value);
+                        if (!clr.error) {
+                            delete params.gradient;
+                            delete attrs.gradient;
+                            !R.is(attrs.opacity, "undefined") &&
+                                R.is(params.opacity, "undefined") &&
+                                $(node, {opacity: attrs.opacity});
+                            !R.is(attrs["fill-opacity"], "undefined") &&
+                                R.is(params["fill-opacity"], "undefined") &&
+                                $(node, {"fill-opacity": attrs["fill-opacity"]});
+                        } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
+                            if ("opacity" in attrs || "fill-opacity" in attrs) {
+                                var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
+                                if (gradient) {
+                                    var stops = gradient.getElementsByTagName("stop");
+                                    $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
+                                }
+                            }
+                            attrs.gradient = value;
+                            attrs.fill = "none";
+                            break;
+                        }
+                        clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+                    case "stroke":
+                        clr = R.getRGB(value);
+                        node.setAttribute(att, clr.hex);
+                        att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+                        if (att == "stroke" && o._.arrows) {
+                            "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+                            "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+                        }
+                        break;
+                    case "gradient":
+                        (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
+                        break;
+                    case "opacity":
+                        if (attrs.gradient && !attrs[has]("stroke-opacity")) {
+                            $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
+                        }
+                        // fall
+                    case "fill-opacity":
+                        if (attrs.gradient) {
+                            gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
+                            if (gradient) {
+                                stops = gradient.getElementsByTagName("stop");
+                                $(stops[stops.length - 1], {"stop-opacity": value});
+                            }
+                            break;
+                        }
+                    default:
+                        att == "font-size" && (value = toInt(value, 10) + "px");
+                        var cssrule = att.replace(/(\-.)/g, function (w) {
+                            return w.substring(1).toUpperCase();
+                        });
+                        node.style[cssrule] = value;
+                        o._.dirty = 1;
+                        node.setAttribute(att, value);
+                        break;
+                }
+            }
+        }
+
+        tuneText(o, params);
+        node.style.visibility = vis;
+    },
+    leading = 1.2,
+    tuneText = function (el, params) {
+        if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
+            return;
+        }
+        var a = el.attrs,
+            node = el.node,
+            fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
+
+        if (params[has]("text")) {
+            a.text = params.text;
+            while (node.firstChild) {
+                node.removeChild(node.firstChild);
+            }
+            var texts = Str(params.text).split("\n"),
+                tspans = [],
+                tspan;
+            for (var i = 0, ii = texts.length; i < ii; i++) {
+                tspan = $("tspan");
+                i && $(tspan, {dy: fontSize * leading, x: a.x});
+                tspan.appendChild(R._g.doc.createTextNode(texts[i]));
+                node.appendChild(tspan);
+                tspans[i] = tspan;
+            }
+        } else {
+            tspans = node.getElementsByTagName("tspan");
+            for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
+                $(tspans[i], {dy: fontSize * leading, x: a.x});
+            } else {
+                $(tspans[0], {dy: 0});
+            }
+        }
+        $(node, {x: a.x, y: a.y});
+        el._.dirty = 1;
+        var bb = el._getBBox(),
+            dif = a.y - (bb.y + bb.height / 2);
+        dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
+    },
+    Element = function (node, svg) {
+        var X = 0,
+            Y = 0;
+        /*\
+         * Element.node
+         [ property (object) ]
+         **
+         * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
+         **
+         * Note: Don’t mess with it.
+         > Usage
+         | // draw a circle at coordinate 10,10 with radius of 10
+         | var c = paper.circle(10, 10, 10);
+         | c.node.onclick = function () {
+         |     c.attr("fill", "red");
+         | };
+        \*/
+        this[0] = this.node = node;
+        /*\
+         * Element.raphael
+         [ property (object) ]
+         **
+         * Internal reference to @Raphael object. In case it is not available.
+         > Usage
+         | Raphael.el.red = function () {
+         |     var hsb = this.paper.raphael.rgb2hsb(this.attr("fill"));
+         |     hsb.h = 1;
+         |     this.attr({fill: this.paper.raphael.hsb2rgb(hsb).hex});
+         | }
+        \*/
+        node.raphael = true;
+        /*\
+         * Element.id
+         [ property (number) ]
+         **
+         * Unique id of the element. Especially usesful when you want to listen to events of the element, 
+         * because all events are fired in format `<module>.<action>.<id>`. Also useful for @Paper.getById method.
+        \*/
+        this.id = R._oid++;
+        node.raphaelid = this.id;
+        this.matrix = R.matrix();
+        this.realPath = null;
+        /*\
+         * Element.paper
+         [ property (object) ]
+         **
+         * Internal reference to “paper” where object drawn. Mainly for use in plugins and element extensions.
+         > Usage
+         | Raphael.el.cross = function () {
+         |     this.attr({fill: "red"});
+         |     this.paper.path("M10,10L50,50M50,10L10,50")
+         |         .attr({stroke: "red"});
+         | }
+        \*/
+        this.paper = svg;
+        this.attrs = this.attrs || {};
+        this._ = {
+            transform: [],
+            sx: 1,
+            sy: 1,
+            deg: 0,
+            dx: 0,
+            dy: 0,
+            dirty: 1
+        };
+        !svg.bottom && (svg.bottom = this);
+        /*\
+         * Element.prev
+         [ property (object) ]
+         **
+         * Reference to the previous element in the hierarchy.
+        \*/
+        this.prev = svg.top;
+        svg.top && (svg.top.next = this);
+        svg.top = this;
+        /*\
+         * Element.next
+         [ property (object) ]
+         **
+         * Reference to the next element in the hierarchy.
+        \*/
+        this.next = null;
+    },
+    elproto = R.el;
+
+    Element.prototype = elproto;
+    elproto.constructor = Element;
+
+    R._engine.path = function (pathString, SVG) {
+        var el = $("path");
+        SVG.canvas && SVG.canvas.appendChild(el);
+        var p = new Element(el, SVG);
+        p.type = "path";
+        setFillAndStroke(p, {
+            fill: "none",
+            stroke: "#000",
+            path: pathString
+        });
+        return p;
+    };
+    /*\
+     * Element.rotate
+     [ method ]
+     **
+     * Deprecated! Use @Element.transform instead.
+     * Adds rotation by given angle around given point to the list of
+     * transformations of the element.
+     > Parameters
+     - deg (number) angle in degrees
+     - cx (number) #optional x coordinate of the centre of rotation
+     - cy (number) #optional y coordinate of the centre of rotation
+     * If cx & cy aren’t specified centre of the shape is used as a point of rotation.
+     = (object) @Element
+    \*/
+    elproto.rotate = function (deg, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        deg = Str(deg).split(separator);
+        if (deg.length - 1) {
+            cx = toFloat(deg[1]);
+            cy = toFloat(deg[2]);
+        }
+        deg = toFloat(deg[0]);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+            cx = bbox.x + bbox.width / 2;
+            cy = bbox.y + bbox.height / 2;
+        }
+        this.transform(this._.transform.concat([["r", deg, cx, cy]]));
+        return this;
+    };
+    /*\
+     * Element.scale
+     [ method ]
+     **
+     * Deprecated! Use @Element.transform instead.
+     * Adds scale by given amount relative to given point to the list of
+     * transformations of the element.
+     > Parameters
+     - sx (number) horisontal scale amount
+     - sy (number) vertical scale amount
+     - cx (number) #optional x coordinate of the centre of scale
+     - cy (number) #optional y coordinate of the centre of scale
+     * If cx & cy aren’t specified centre of the shape is used instead.
+     = (object) @Element
+    \*/
+    elproto.scale = function (sx, sy, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        sx = Str(sx).split(separator);
+        if (sx.length - 1) {
+            sy = toFloat(sx[1]);
+            cx = toFloat(sx[2]);
+            cy = toFloat(sx[3]);
+        }
+        sx = toFloat(sx[0]);
+        (sy == null) && (sy = sx);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+        }
+        cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+        cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+        this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
+        return this;
+    };
+    /*\
+     * Element.translate
+     [ method ]
+     **
+     * Deprecated! Use @Element.transform instead.
+     * Adds translation by given amount to the list of transformations of the element.
+     > Parameters
+     - dx (number) horisontal shift
+     - dy (number) vertical shift
+     = (object) @Element
+    \*/
+    elproto.translate = function (dx, dy) {
+        if (this.removed) {
+            return this;
+        }
+        dx = Str(dx).split(separator);
+        if (dx.length - 1) {
+            dy = toFloat(dx[1]);
+        }
+        dx = toFloat(dx[0]) || 0;
+        dy = +dy || 0;
+        this.transform(this._.transform.concat([["t", dx, dy]]));
+        return this;
+    };
+    /*\
+     * Element.transform
+     [ method ]
+     **
+     * Adds transformation to the element which is separate to other attributes,
+     * i.e. translation doesn’t change `x` or `y` of the rectange. The format
+     * of transformation string is similar to the path string syntax:
+     | "t100,100r30,100,100s2,2,100,100r45s1.5"
+     * Each letter is a command. There are four commands: `t` is for translate, `r` is for rotate, `s` is for
+     * scale and `m` is for matrix.
+     *
+     * There are also alternative “absolute” translation, rotation and scale: `T`, `R` and `S`. They will not take previous transformation into account. For example, `...T100,0` will always move element 100 px horisontally, while `...t100,0` could move it vertically if there is `r90` before. Just compare results of `r90t100,0` and `r90T100,0`.
+     *
+     * So, the example line above could be read like “translate by 100, 100; rotate 30° around 100, 100; scale twice around 100, 100;
+     * rotate 45° around centre; scale 1.5 times relative to centre”. As you can see rotate and scale commands have origin
+     * coordinates as optional parameters, the default is the centre point of the element.
+     * Matrix accepts six parameters.
+     > Usage
+     | var el = paper.rect(10, 20, 300, 200);
+     | // translate 100, 100, rotate 45°, translate -100, 0
+     | el.transform("t100,100r45t-100,0");
+     | // if you want you can append or prepend transformations
+     | el.transform("...t50,50");
+     | el.transform("s2...");
+     | // or even wrap
+     | el.transform("t50,50...t-50-50");
+     | // to reset transformation call method with empty string
+     | el.transform("");
+     | // to get current value call it without parameters
+     | console.log(el.transform());
+     > Parameters
+     - tstr (string) #optional transformation string
+     * If tstr isn’t specified
+     = (string) current transformation string
+     * else
+     = (object) @Element
+    \*/
+    elproto.transform = function (tstr) {
+        var _ = this._;
+        if (tstr == null) {
+            return _.transform;
+        }
+        R._extractTransform(this, tstr);
+
+        this.clip && $(this.clip, {transform: this.matrix.invert()});
+        this.pattern && updatePosition(this);
+        this.node && $(this.node, {transform: this.matrix});
+    
+        if (_.sx != 1 || _.sy != 1) {
+            var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
+            this.attr({"stroke-width": sw});
+        }
+
+        return this;
+    };
+    /*\
+     * Element.hide
+     [ method ]
+     **
+     * Makes element invisible. See @Element.show.
+     = (object) @Element
+    \*/
+    elproto.hide = function () {
+        !this.removed && this.paper.safari(this.node.style.display = "none");
+        return this;
+    };
+    /*\
+     * Element.show
+     [ method ]
+     **
+     * Makes element visible. See @Element.hide.
+     = (object) @Element
+    \*/
+    elproto.show = function () {
+        !this.removed && this.paper.safari(this.node.style.display = "");
+        return this;
+    };
+    /*\
+     * Element.remove
+     [ method ]
+     **
+     * Removes element from the paper.
+    \*/
+    elproto.remove = function () {
+        if (this.removed || !this.node.parentNode) {
+            return;
+        }
+        var paper = this.paper;
+        paper.__set__ && paper.__set__.exclude(this);
+        eve.unbind("raphael.*.*." + this.id);
+        if (this.gradient) {
+            paper.defs.removeChild(this.gradient);
+        }
+        R._tear(this, paper);
+        if (this.node.parentNode.tagName.toLowerCase() == "a") {
+            this.node.parentNode.parentNode.removeChild(this.node.parentNode);
+        } else {
+            this.node.parentNode.removeChild(this.node);
+        }
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+        this.removed = true;
+    };
+    elproto._getBBox = function () {
+        if (this.node.style.display == "none") {
+            this.show();
+            var hide = true;
+        }
+        var bbox = {};
+        try {
+            bbox = this.node.getBBox();
+        } catch(e) {
+            // Firefox 3.0.x plays badly here
+        } finally {
+            bbox = bbox || {};
+        }
+        hide && this.hide();
+        return bbox;
+    };
+    /*\
+     * Element.attr
+     [ method ]
+     **
+     * Sets the attributes of the element.
+     > Parameters
+     - attrName (string) attribute’s name
+     - value (string) value
+     * or
+     - params (object) object of name/value pairs
+     * or
+     - attrName (string) attribute’s name
+     * or
+     - attrNames (array) in this case method returns array of current values for given attribute names
+     = (object) @Element if attrsName & value or params are passed in.
+     = (...) value of the attribute if only attrsName is passed in.
+     = (array) array of values of the attribute if attrsNames is passed in.
+     = (object) object of attributes if nothing is passed in.
+     > Possible parameters
+     # <p>Please refer to the <a href="http://www.w3.org/TR/SVG/" title="The W3C Recommendation for the SVG language describes these properties in detail.">SVG specification</a> for an explanation of these parameters.</p>
+     o arrow-end (string) arrowhead on the end of the path. The format for string is `<type>[-<width>[-<length>]]`. Possible types: `classic`, `block`, `open`, `oval`, `diamond`, `none`, width: `wide`, `narrow`, `medium`, length: `long`, `short`, `midium`.
+     o clip-rect (string) comma or space separated values: x, y, width and height
+     o cursor (string) CSS type of the cursor
+     o cx (number) the x-axis coordinate of the center of the circle, or ellipse
+     o cy (number) the y-axis coordinate of the center of the circle, or ellipse
+     o fill (string) colour, gradient or image
+     o fill-opacity (number)
+     o font (string)
+     o font-family (string)
+     o font-size (number) font size in pixels
+     o font-weight (string)
+     o height (number)
+     o href (string) URL, if specified element behaves as hyperlink
+     o opacity (number)
+     o path (string) SVG path string format
+     o r (number) radius of the circle, ellipse or rounded corner on the rect
+     o rx (number) horisontal radius of the ellipse
+     o ry (number) vertical radius of the ellipse
+     o src (string) image URL, only works for @Element.image element
+     o stroke (string) stroke colour
+     o stroke-dasharray (string) [“”, “`-`”, “`.`”, “`-.`”, “`-..`”, “`. `”, “`- `”, “`--`”, “`- .`”, “`--.`”, “`--..`”]
+     o stroke-linecap (string) [“`butt`”, “`square`”, “`round`”]
+     o stroke-linejoin (string) [“`bevel`”, “`round`”, “`miter`”]
+     o stroke-miterlimit (number)
+     o stroke-opacity (number)
+     o stroke-width (number) stroke width in pixels, default is '1'
+     o target (string) used with href
+     o text (string) contents of the text element. Use `\n` for multiline text
+     o text-anchor (string) [“`start`”, “`middle`”, “`end`”], default is “`middle`”
+     o title (string) will create tooltip with a given text
+     o transform (string) see @Element.transform
+     o width (number)
+     o x (number)
+     o y (number)
+     > Gradients
+     * Linear gradient format: “`‹angle›-‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`90-#fff-#000`” – 90°
+     * gradient from white to black or “`0-#fff-#f00:20-#000`” – 0° gradient from white via red (at 20%) to black.
+     *
+     * radial gradient: “`r[(‹fx›, ‹fy›)]‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`r#fff-#000`” –
+     * gradient from white to black or “`r(0.25, 0.75)#fff-#000`” – gradient from white to black with focus point
+     * at 0.25, 0.75. Focus point coordinates are in 0..1 range. Radial gradients can only be applied to circles and ellipses.
+     > Path String
+     # <p>Please refer to <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path’s data attribute’s format are described in the SVG specification.">SVG documentation regarding path string</a>. Raphaël fully supports it.</p>
+     > Colour Parsing
+     # <ul>
+     #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
+     #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
+     #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
+     #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
+     #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
+     #     <li>rgba(•••, •••, •••, •••) — red, green and blue channels’ values: (“<code>rgba(200,&nbsp;100,&nbsp;0, .5)</code>”)</li>
+     #     <li>rgba(•••%, •••%, •••%, •••%) — same as above, but in %: (“<code>rgba(100%,&nbsp;175%,&nbsp;0%, 50%)</code>”)</li>
+     #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
+     #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
+     #     <li>hsba(•••, •••, •••, •••) — same as above, but with opacity</li>
+     #     <li>hsl(•••, •••, •••) — almost the same as hsb, see <a href="http://en.wikipedia.org/wiki/HSL_and_HSV" title="HSL and HSV - Wikipedia, the free encyclopedia">Wikipedia page</a></li>
+     #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
+     #     <li>hsla(•••, •••, •••, •••) — same as above, but with opacity</li>
+     #     <li>Optionally for hsb and hsl you could specify hue as a degree: “<code>hsl(240deg,&nbsp;1,&nbsp;.5)</code>” or, if you want to go fancy, “<code>hsl(240°,&nbsp;1,&nbsp;.5)</code>”</li>
+     # </ul>
+    \*/
+    elproto.attr = function (name, value) {
+        if (this.removed) {
+            return this;
+        }
+        if (name == null) {
+            var res = {};
+            for (var a in this.attrs) if (this.attrs[has](a)) {
+                res[a] = this.attrs[a];
+            }
+            res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+            res.transform = this._.transform;
+            return res;
+        }
+        if (value == null && R.is(name, "string")) {
+            if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
+                return this.attrs.gradient;
+            }
+            if (name == "transform") {
+                return this._.transform;
+            }
+            var names = name.split(separator),
+                out = {};
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                name = names[i];
+                if (name in this.attrs) {
+                    out[name] = this.attrs[name];
+                } else if (R.is(this.paper.customAttributes[name], "function")) {
+                    out[name] = this.paper.customAttributes[name].def;
+                } else {
+                    out[name] = R._availableAttrs[name];
+                }
+            }
+            return ii - 1 ? out : out[names[0]];
+        }
+        if (value == null && R.is(name, "array")) {
+            out = {};
+            for (i = 0, ii = name.length; i < ii; i++) {
+                out[name[i]] = this.attr(name[i]);
+            }
+            return out;
+        }
+        if (value != null) {
+            var params = {};
+            params[name] = value;
+        } else if (name != null && R.is(name, "object")) {
+            params = name;
+        }
+        for (var key in params) {
+            eve("raphael.attr." + key + "." + this.id, this, params[key]);
+        }
+        for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+            var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
+            this.attrs[key] = params[key];
+            for (var subkey in par) if (par[has](subkey)) {
+                params[subkey] = par[subkey];
+            }
+        }
+        setFillAndStroke(this, params);
+        return this;
+    };
+    /*\
+     * Element.toFront
+     [ method ]
+     **
+     * Moves the element so it is the closest to the viewer’s eyes, on top of other elements.
+     = (object) @Element
+    \*/
+    elproto.toFront = function () {
+        if (this.removed) {
+            return this;
+        }
+        if (this.node.parentNode.tagName.toLowerCase() == "a") {
+            this.node.parentNode.parentNode.appendChild(this.node.parentNode);
+        } else {
+            this.node.parentNode.appendChild(this.node);
+        }
+        var svg = this.paper;
+        svg.top != this && R._tofront(this, svg);
+        return this;
+    };
+    /*\
+     * Element.toBack
+     [ method ]
+     **
+     * Moves the element so it is the furthest from the viewer’s eyes, behind other elements.
+     = (object) @Element
+    \*/
+    elproto.toBack = function () {
+        if (this.removed) {
+            return this;
+        }
+        var parent = this.node.parentNode;
+        if (parent.tagName.toLowerCase() == "a") {
+            parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild); 
+        } else if (parent.firstChild != this.node) {
+            parent.insertBefore(this.node, this.node.parentNode.firstChild);
+        }
+        R._toback(this, this.paper);
+        var svg = this.paper;
+        return this;
+    };
+    /*\
+     * Element.insertAfter
+     [ method ]
+     **
+     * Inserts current object after the given one.
+     = (object) @Element
+    \*/
+    elproto.insertAfter = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        var node = element.node || element[element.length - 1].node;
+        if (node.nextSibling) {
+            node.parentNode.insertBefore(this.node, node.nextSibling);
+        } else {
+            node.parentNode.appendChild(this.node);
+        }
+        R._insertafter(this, element, this.paper);
+        return this;
+    };
+    /*\
+     * Element.insertBefore
+     [ method ]
+     **
+     * Inserts current object before the given one.
+     = (object) @Element
+    \*/
+    elproto.insertBefore = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        var node = element.node || element[0].node;
+        node.parentNode.insertBefore(this.node, node);
+        R._insertbefore(this, element, this.paper);
+        return this;
+    };
+    elproto.blur = function (size) {
+        // Experimental. No Safari support. Use it on your own risk.
+        var t = this;
+        if (+size !== 0) {
+            var fltr = $("filter"),
+                blur = $("feGaussianBlur");
+            t.attrs.blur = size;
+            fltr.id = R.createUUID();
+            $(blur, {stdDeviation: +size || 1.5});
+            fltr.appendChild(blur);
+            t.paper.defs.appendChild(fltr);
+            t._blur = fltr;
+            $(t.node, {filter: "url(#" + fltr.id + ")"});
+        } else {
+            if (t._blur) {
+                t._blur.parentNode.removeChild(t._blur);
+                delete t._blur;
+                delete t.attrs.blur;
+            }
+            t.node.removeAttribute("filter");
+        }
+    };
+    R._engine.circle = function (svg, x, y, r) {
+        var el = $("circle");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
+        res.type = "circle";
+        $(el, res.attrs);
+        return res;
+    };
+    R._engine.rect = function (svg, x, y, w, h, r) {
+        var el = $("rect");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
+        res.type = "rect";
+        $(el, res.attrs);
+        return res;
+    };
+    R._engine.ellipse = function (svg, x, y, rx, ry) {
+        var el = $("ellipse");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
+        res.type = "ellipse";
+        $(el, res.attrs);
+        return res;
+    };
+    R._engine.image = function (svg, src, x, y, w, h) {
+        var el = $("image");
+        $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
+        el.setAttributeNS(xlink, "href", src);
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {x: x, y: y, width: w, height: h, src: src};
+        res.type = "image";
+        return res;
+    };
+    R._engine.text = function (svg, x, y, text) {
+        var el = $("text");
+        svg.canvas && svg.canvas.appendChild(el);
+        var res = new Element(el, svg);
+        res.attrs = {
+            x: x,
+            y: y,
+            "text-anchor": "middle",
+            text: text,
+            font: R._availableAttrs.font,
+            stroke: "none",
+            fill: "#000"
+        };
+        res.type = "text";
+        setFillAndStroke(res, res.attrs);
+        return res;
+    };
+    R._engine.setSize = function (width, height) {
+        this.width = width || this.width;
+        this.height = height || this.height;
+        this.canvas.setAttribute("width", this.width);
+        this.canvas.setAttribute("height", this.height);
+        if (this._viewBox) {
+            this.setViewBox.apply(this, this._viewBox);
+        }
+        return this;
+    };
+    R._engine.create = function () {
+        var con = R._getContainer.apply(0, arguments),
+            container = con && con.container,
+            x = con.x,
+            y = con.y,
+            width = con.width,
+            height = con.height;
+        if (!container) {
+            throw new Error("SVG container not found.");
+        }
+        var cnvs = $("svg"),
+            css = "overflow:hidden;",
+            isFloating;
+        x = x || 0;
+        y = y || 0;
+        width = width || 512;
+        height = height || 342;
+        $(cnvs, {
+            height: height,
+            version: 1.1,
+            width: width,
+            xmlns: "http://www.w3.org/2000/svg"
+        });
+        if (container == 1) {
+            cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
+            R._g.doc.body.appendChild(cnvs);
+            isFloating = 1;
+        } else {
+            cnvs.style.cssText = css + "position:relative";
+            if (container.firstChild) {
+                container.insertBefore(cnvs, container.firstChild);
+            } else {
+                container.appendChild(cnvs);
+            }
+        }
+        container = new R._Paper;
+        container.width = width;
+        container.height = height;
+        container.canvas = cnvs;
+        container.clear();
+        container._left = container._top = 0;
+        isFloating && (container.renderfix = function () {});
+        container.renderfix();
+        return container;
+    };
+    R._engine.setViewBox = function (x, y, w, h, fit) {
+        eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
+        var size = mmax(w / this.width, h / this.height),
+            top = this.top,
+            aspectRatio = fit ? "meet" : "xMinYMin",
+            vb,
+            sw;
+        if (x == null) {
+            if (this._vbSize) {
+                size = 1;
+            }
+            delete this._vbSize;
+            vb = "0 0 " + this.width + S + this.height;
+        } else {
+            this._vbSize = size;
+            vb = x + S + y + S + w + S + h;
+        }
+        $(this.canvas, {
+            viewBox: vb,
+            preserveAspectRatio: aspectRatio
+        });
+        while (size && top) {
+            sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
+            top.attr({"stroke-width": sw});
+            top._.dirty = 1;
+            top._.dirtyT = 1;
+            top = top.prev;
+        }
+        this._viewBox = [x, y, w, h, !!fit];
+        return this;
+    };
+    /*\
+     * Paper.renderfix
+     [ method ]
+     **
+     * Fixes the issue of Firefox and IE9 regarding subpixel rendering. If paper is dependant
+     * on other elements after reflow it could shift half pixel which cause for lines to lost their crispness.
+     * This method fixes the issue.
+     **
+       Special thanks to Mariusz Nowak (http://www.medikoo.com/) for this method.
+    \*/
+    R.prototype.renderfix = function () {
+        var cnvs = this.canvas,
+            s = cnvs.style,
+            pos;
+        try {
+            pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
+        } catch (e) {
+            pos = cnvs.createSVGMatrix();
+        }
+        var left = -pos.e % 1,
+            top = -pos.f % 1;
+        if (left || top) {
+            if (left) {
+                this._left = (this._left + left) % 1;
+                s.left = this._left + "px";
+            }
+            if (top) {
+                this._top = (this._top + top) % 1;
+                s.top = this._top + "px";
+            }
+        }
+    };
+    /*\
+     * Paper.clear
+     [ method ]
+     **
+     * Clears the paper, i.e. removes all the elements.
+    \*/
+    R.prototype.clear = function () {
+        R.eve("raphael.clear", this);
+        var c = this.canvas;
+        while (c.firstChild) {
+            c.removeChild(c.firstChild);
+        }
+        this.bottom = this.top = null;
+        (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
+        c.appendChild(this.desc);
+        c.appendChild(this.defs = $("defs"));
+    };
+    /*\
+     * Paper.remove
+     [ method ]
+     **
+     * Removes the paper from the DOM.
+    \*/
+    R.prototype.remove = function () {
+        eve("raphael.remove", this);
+        this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+    };
+    var setproto = R.st;
+    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname].apply(el, arg);
+                });
+            };
+        })(method);
+    }
+}(window.Raphael);
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël - JavaScript Vector Library                                 │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ VML Module                                                          │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+window.Raphael && window.Raphael.vml && function (R) {
+    var has = "hasOwnProperty",
+        Str = String,
+        toFloat = parseFloat,
+        math = Math,
+        round = math.round,
+        mmax = math.max,
+        mmin = math.min,
+        abs = math.abs,
+        fillString = "fill",
+        separator = /[, ]+/,
+        eve = R.eve,
+        ms = " progid:DXImageTransform.Microsoft",
+        S = " ",
+        E = "",
+        map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
+        bites = /([clmz]),?([^clmz]*)/gi,
+        blurregexp = / progid:\S+Blur\([^\)]+\)/g,
+        val = /-?[^,\s-]+/g,
+        cssDot = "position:absolute;left:0;top:0;width:1px;height:1px",
+        zoom = 21600,
+        pathTypes = {path: 1, rect: 1, image: 1},
+        ovalTypes = {circle: 1, ellipse: 1},
+        path2vml = function (path) {
+            var total =  /[ahqstv]/ig,
+                command = R._pathToAbsolute;
+            Str(path).match(total) && (command = R._path2curve);
+            total = /[clmz]/g;
+            if (command == R._pathToAbsolute && !Str(path).match(total)) {
+                var res = Str(path).replace(bites, function (all, command, args) {
+                    var vals = [],
+                        isMove = command.toLowerCase() == "m",
+                        res = map[command];
+                    args.replace(val, function (value) {
+                        if (isMove && vals.length == 2) {
+                            res += vals + map[command == "m" ? "l" : "L"];
+                            vals = [];
+                        }
+                        vals.push(round(value * zoom));
+                    });
+                    return res + vals;
+                });
+                return res;
+            }
+            var pa = command(path), p, r;
+            res = [];
+            for (var i = 0, ii = pa.length; i < ii; i++) {
+                p = pa[i];
+                r = pa[i][0].toLowerCase();
+                r == "z" && (r = "x");
+                for (var j = 1, jj = p.length; j < jj; j++) {
+                    r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
+                }
+                res.push(r);
+            }
+            return res.join(S);
+        },
+        compensation = function (deg, dx, dy) {
+            var m = R.matrix();
+            m.rotate(-deg, .5, .5);
+            return {
+                dx: m.x(dx, dy),
+                dy: m.y(dx, dy)
+            };
+        },
+        setCoords = function (p, sx, sy, dx, dy, deg) {
+            var _ = p._,
+                m = p.matrix,
+                fillpos = _.fillpos,
+                o = p.node,
+                s = o.style,
+                y = 1,
+                flip = "",
+                dxdy,
+                kx = zoom / sx,
+                ky = zoom / sy;
+            s.visibility = "hidden";
+            if (!sx || !sy) {
+                return;
+            }
+            o.coordsize = abs(kx) + S + abs(ky);
+            s.rotation = deg * (sx * sy < 0 ? -1 : 1);
+            if (deg) {
+                var c = compensation(deg, dx, dy);
+                dx = c.dx;
+                dy = c.dy;
+            }
+            sx < 0 && (flip += "x");
+            sy < 0 && (flip += " y") && (y = -1);
+            s.flip = flip;
+            o.coordorigin = (dx * -kx) + S + (dy * -ky);
+            if (fillpos || _.fillsize) {
+                var fill = o.getElementsByTagName(fillString);
+                fill = fill && fill[0];
+                o.removeChild(fill);
+                if (fillpos) {
+                    c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
+                    fill.position = c.dx * y + S + c.dy * y;
+                }
+                if (_.fillsize) {
+                    fill.size = _.fillsize[0] * abs(sx)/1.34 + "pt" + S + _.fillsize[1] * abs(sy)/1.34 + "pt";
+                }
+                o.appendChild(fill);
+            }
+            s.visibility = "visible";
+        };
+    R.toString = function () {
+        return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
+    };
+    var addArrow = function (o, value, isEnd) {
+        var values = Str(value).toLowerCase().split("-"),
+            se = isEnd ? "end" : "start",
+            i = values.length,
+            type = "classic",
+            w = "medium",
+            h = "medium";
+        while (i--) {
+            switch (values[i]) {
+                case "block":
+                case "classic":
+                case "oval":
+                case "diamond":
+                case "open":
+                case "none":
+                    type = values[i];
+                    break;
+                case "wide":
+                case "narrow": h = values[i]; break;
+                case "long":
+                case "short": w = values[i]; break;
+            }
+        }
+        var stroke = o.node.getElementsByTagName("stroke")[0];
+        stroke[se + "arrow"] = type;
+        stroke[se + "arrowlength"] = w;
+        stroke[se + "arrowwidth"] = h;
+    },
+    setFillAndStroke = function (o, params) {
+        // o.paper.canvas.style.display = "none";
+        o.attrs = o.attrs || {};
+        var node = o.node,
+            a = o.attrs,
+            s = node.style,
+            xy,
+            newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
+            isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
+            res = o;
+
+
+        for (var par in params) if (params[has](par)) {
+            a[par] = params[par];
+        }
+        if (newpath) {
+            a.path = R._getPath[o.type](o);
+            o._.dirty = 1;
+        }
+        params.href && (node.href = params.href);
+        params.title && (node.title = params.title);
+        params.target && (node.target = params.target);
+        params.cursor && (s.cursor = params.cursor);
+        "blur" in params && o.blur(params.blur);
+        if (params.path && o.type == "path" || newpath) {
+            node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
+            if (o.type == "image") {
+                o._.fillpos = [a.x, a.y];
+                o._.fillsize = [a.width, a.height];
+                setCoords(o, 1, 1, 0, 0, 0);
+            }
+        }
+        "transform" in params && o.transform(params.transform);
+        if (isOval) {
+            var cx = +a.cx,
+                cy = +a.cy,
+                rx = +a.rx || +a.r || 0,
+                ry = +a.ry || +a.r || 0;
+            node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
+        }
+        if ("clip-rect" in params) {
+            var rect = Str(params["clip-rect"]).split(separator);
+            if (rect.length == 4) {
+                rect[2] = +rect[2] + (+rect[0]);
+                rect[3] = +rect[3] + (+rect[1]);
+                var div = node.clipRect || R._g.doc.createElement("div"),
+                    dstyle = div.style;
+                dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
+                if (!node.clipRect) {
+                    dstyle.position = "absolute";
+                    dstyle.top = 0;
+                    dstyle.left = 0;
+                    dstyle.width = o.paper.width + "px";
+                    dstyle.height = o.paper.height + "px";
+                    node.parentNode.insertBefore(div, node);
+                    div.appendChild(node);
+                    node.clipRect = div;
+                }
+            }
+            if (!params["clip-rect"]) {
+                node.clipRect && (node.clipRect.style.clip = "auto");
+            }
+        }
+        if (o.textpath) {
+            var textpathStyle = o.textpath.style;
+            params.font && (textpathStyle.font = params.font);
+            params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
+            params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
+            params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
+            params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
+        }
+        if ("arrow-start" in params) {
+            addArrow(res, params["arrow-start"]);
+        }
+        if ("arrow-end" in params) {
+            addArrow(res, params["arrow-end"], 1);
+        }
+        if (params.opacity != null || 
+            params["stroke-width"] != null ||
+            params.fill != null ||
+            params.src != null ||
+            params.stroke != null ||
+            params["stroke-width"] != null ||
+            params["stroke-opacity"] != null ||
+            params["fill-opacity"] != null ||
+            params["stroke-dasharray"] != null ||
+            params["stroke-miterlimit"] != null ||
+            params["stroke-linejoin"] != null ||
+            params["stroke-linecap"] != null) {
+            var fill = node.getElementsByTagName(fillString),
+                newfill = false;
+            fill = fill && fill[0];
+            !fill && (newfill = fill = createNode(fillString));
+            if (o.type == "image" && params.src) {
+                fill.src = params.src;
+            }
+            params.fill && (fill.on = true);
+            if (fill.on == null || params.fill == "none" || params.fill === null) {
+                fill.on = false;
+            }
+            if (fill.on && params.fill) {
+                var isURL = Str(params.fill).match(R._ISURL);
+                if (isURL) {
+                    fill.parentNode == node && node.removeChild(fill);
+                    fill.rotate = true;
+                    fill.src = isURL[1];
+                    fill.type = "tile";
+                    var bbox = o.getBBox(1);
+                    fill.position = bbox.x + S + bbox.y;
+                    o._.fillpos = [bbox.x, bbox.y];
+
+                    R._preload(isURL[1], function () {
+                        o._.fillsize = [this.offsetWidth, this.offsetHeight];
+                    });
+                } else {
+                    fill.color = R.getRGB(params.fill).hex;
+                    fill.src = E;
+                    fill.type = "solid";
+                    if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
+                        a.fill = "none";
+                        a.gradient = params.fill;
+                        fill.rotate = false;
+                    }
+                }
+            }
+            if ("fill-opacity" in params || "opacity" in params) {
+                var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
+                opacity = mmin(mmax(opacity, 0), 1);
+                fill.opacity = opacity;
+                if (fill.src) {
+                    fill.color = "none";
+                }
+            }
+            node.appendChild(fill);
+            var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
+            newstroke = false;
+            !stroke && (newstroke = stroke = createNode("stroke"));
+            if ((params.stroke && params.stroke != "none") ||
+                params["stroke-width"] ||
+                params["stroke-opacity"] != null ||
+                params["stroke-dasharray"] ||
+                params["stroke-miterlimit"] ||
+                params["stroke-linejoin"] ||
+                params["stroke-linecap"]) {
+                stroke.on = true;
+            }
+            (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
+            var strokeColor = R.getRGB(params.stroke);
+            stroke.on && params.stroke && (stroke.color = strokeColor.hex);
+            opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
+            var width = (toFloat(params["stroke-width"]) || 1) * .75;
+            opacity = mmin(mmax(opacity, 0), 1);
+            params["stroke-width"] == null && (width = a["stroke-width"]);
+            params["stroke-width"] && (stroke.weight = width);
+            width && width < 1 && (opacity *= width) && (stroke.weight = 1);
+            stroke.opacity = opacity;
+        
+            params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
+            stroke.miterlimit = params["stroke-miterlimit"] || 8;
+            params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
+            if (params["stroke-dasharray"]) {
+                var dasharray = {
+                    "-": "shortdash",
+                    ".": "shortdot",
+                    "-.": "shortdashdot",
+                    "-..": "shortdashdotdot",
+                    ". ": "dot",
+                    "- ": "dash",
+                    "--": "longdash",
+                    "- .": "dashdot",
+                    "--.": "longdashdot",
+                    "--..": "longdashdotdot"
+                };
+                stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
+            }
+            newstroke && node.appendChild(stroke);
+        }
+        if (res.type == "text") {
+            res.paper.canvas.style.display = E;
+            var span = res.paper.span,
+                m = 100,
+                fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
+            s = span.style;
+            a.font && (s.font = a.font);
+            a["font-family"] && (s.fontFamily = a["font-family"]);
+            a["font-weight"] && (s.fontWeight = a["font-weight"]);
+            a["font-style"] && (s.fontStyle = a["font-style"]);
+            fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
+            s.fontSize = fontSize * m + "px";
+            res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
+            var brect = span.getBoundingClientRect();
+            res.W = a.w = (brect.right - brect.left) / m;
+            res.H = a.h = (brect.bottom - brect.top) / m;
+            // res.paper.canvas.style.display = "none";
+            res.X = a.x;
+            res.Y = a.y + res.H / 2;
+
+            ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
+            var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
+            for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
+                res._.dirty = 1;
+                break;
+            }
+        
+            // text-anchor emulation
+            switch (a["text-anchor"]) {
+                case "start":
+                    res.textpath.style["v-text-align"] = "left";
+                    res.bbx = res.W / 2;
+                break;
+                case "end":
+                    res.textpath.style["v-text-align"] = "right";
+                    res.bbx = -res.W / 2;
+                break;
+                default:
+                    res.textpath.style["v-text-align"] = "center";
+                    res.bbx = 0;
+                break;
+            }
+            res.textpath.style["v-text-kern"] = true;
+        }
+        // res.paper.canvas.style.display = E;
+    },
+    addGradientFill = function (o, gradient, fill) {
+        o.attrs = o.attrs || {};
+        var attrs = o.attrs,
+            pow = Math.pow,
+            opacity,
+            oindex,
+            type = "linear",
+            fxfy = ".5 .5";
+        o.attrs.gradient = gradient;
+        gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
+            type = "radial";
+            if (fx && fy) {
+                fx = toFloat(fx);
+                fy = toFloat(fy);
+                pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
+                fxfy = fx + S + fy;
+            }
+            return E;
+        });
+        gradient = gradient.split(/\s*\-\s*/);
+        if (type == "linear") {
+            var angle = gradient.shift();
+            angle = -toFloat(angle);
+            if (isNaN(angle)) {
+                return null;
+            }
+        }
+        var dots = R._parseDots(gradient);
+        if (!dots) {
+            return null;
+        }
+        o = o.shape || o.node;
+        if (dots.length) {
+            o.removeChild(fill);
+            fill.on = true;
+            fill.method = "none";
+            fill.color = dots[0].color;
+            fill.color2 = dots[dots.length - 1].color;
+            var clrs = [];
+            for (var i = 0, ii = dots.length; i < ii; i++) {
+                dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
+            }
+            fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
+            if (type == "radial") {
+                fill.type = "gradientTitle";
+                fill.focus = "100%";
+                fill.focussize = "0 0";
+                fill.focusposition = fxfy;
+                fill.angle = 0;
+            } else {
+                // fill.rotate= true;
+                fill.type = "gradient";
+                fill.angle = (270 - angle) % 360;
+            }
+            o.appendChild(fill);
+        }
+        return 1;
+    },
+    Element = function (node, vml) {
+        this[0] = this.node = node;
+        node.raphael = true;
+        this.id = R._oid++;
+        node.raphaelid = this.id;
+        this.X = 0;
+        this.Y = 0;
+        this.attrs = {};
+        this.paper = vml;
+        this.matrix = R.matrix();
+        this._ = {
+            transform: [],
+            sx: 1,
+            sy: 1,
+            dx: 0,
+            dy: 0,
+            deg: 0,
+            dirty: 1,
+            dirtyT: 1
+        };
+        !vml.bottom && (vml.bottom = this);
+        this.prev = vml.top;
+        vml.top && (vml.top.next = this);
+        vml.top = this;
+        this.next = null;
+    };
+    var elproto = R.el;
+
+    Element.prototype = elproto;
+    elproto.constructor = Element;
+    elproto.transform = function (tstr) {
+        if (tstr == null) {
+            return this._.transform;
+        }
+        var vbs = this.paper._viewBoxShift,
+            vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
+            oldt;
+        if (vbs) {
+            oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
+        }
+        R._extractTransform(this, vbt + tstr);
+        var matrix = this.matrix.clone(),
+            skew = this.skew,
+            o = this.node,
+            split,
+            isGrad = ~Str(this.attrs.fill).indexOf("-"),
+            isPatt = !Str(this.attrs.fill).indexOf("url(");
+        matrix.translate(-.5, -.5);
+        if (isPatt || isGrad || this.type == "image") {
+            skew.matrix = "1 0 0 1";
+            skew.offset = "0 0";
+            split = matrix.split();
+            if ((isGrad && split.noRotation) || !split.isSimple) {
+                o.style.filter = matrix.toFilter();
+                var bb = this.getBBox(),
+                    bbt = this.getBBox(1),
+                    dx = bb.x - bbt.x,
+                    dy = bb.y - bbt.y;
+                o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
+                setCoords(this, 1, 1, dx, dy, 0);
+            } else {
+                o.style.filter = E;
+                setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
+            }
+        } else {
+            o.style.filter = E;
+            skew.matrix = Str(matrix);
+            skew.offset = matrix.offset();
+        }
+        oldt && (this._.transform = oldt);
+        return this;
+    };
+    elproto.rotate = function (deg, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        if (deg == null) {
+            return;
+        }
+        deg = Str(deg).split(separator);
+        if (deg.length - 1) {
+            cx = toFloat(deg[1]);
+            cy = toFloat(deg[2]);
+        }
+        deg = toFloat(deg[0]);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+            cx = bbox.x + bbox.width / 2;
+            cy = bbox.y + bbox.height / 2;
+        }
+        this._.dirtyT = 1;
+        this.transform(this._.transform.concat([["r", deg, cx, cy]]));
+        return this;
+    };
+    elproto.translate = function (dx, dy) {
+        if (this.removed) {
+            return this;
+        }
+        dx = Str(dx).split(separator);
+        if (dx.length - 1) {
+            dy = toFloat(dx[1]);
+        }
+        dx = toFloat(dx[0]) || 0;
+        dy = +dy || 0;
+        if (this._.bbox) {
+            this._.bbox.x += dx;
+            this._.bbox.y += dy;
+        }
+        this.transform(this._.transform.concat([["t", dx, dy]]));
+        return this;
+    };
+    elproto.scale = function (sx, sy, cx, cy) {
+        if (this.removed) {
+            return this;
+        }
+        sx = Str(sx).split(separator);
+        if (sx.length - 1) {
+            sy = toFloat(sx[1]);
+            cx = toFloat(sx[2]);
+            cy = toFloat(sx[3]);
+            isNaN(cx) && (cx = null);
+            isNaN(cy) && (cy = null);
+        }
+        sx = toFloat(sx[0]);
+        (sy == null) && (sy = sx);
+        (cy == null) && (cx = cy);
+        if (cx == null || cy == null) {
+            var bbox = this.getBBox(1);
+        }
+        cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+        cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+    
+        this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
+        this._.dirtyT = 1;
+        return this;
+    };
+    elproto.hide = function () {
+        !this.removed && (this.node.style.display = "none");
+        return this;
+    };
+    elproto.show = function () {
+        !this.removed && (this.node.style.display = E);
+        return this;
+    };
+    elproto._getBBox = function () {
+        if (this.removed) {
+            return {};
+        }
+        return {
+            x: this.X + (this.bbx || 0) - this.W / 2,
+            y: this.Y - this.H,
+            width: this.W,
+            height: this.H
+        };
+    };
+    elproto.remove = function () {
+        if (this.removed || !this.node.parentNode) {
+            return;
+        }
+        this.paper.__set__ && this.paper.__set__.exclude(this);
+        R.eve.unbind("raphael.*.*." + this.id);
+        R._tear(this, this.paper);
+        this.node.parentNode.removeChild(this.node);
+        this.shape && this.shape.parentNode.removeChild(this.shape);
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+        this.removed = true;
+    };
+    elproto.attr = function (name, value) {
+        if (this.removed) {
+            return this;
+        }
+        if (name == null) {
+            var res = {};
+            for (var a in this.attrs) if (this.attrs[has](a)) {
+                res[a] = this.attrs[a];
+            }
+            res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+            res.transform = this._.transform;
+            return res;
+        }
+        if (value == null && R.is(name, "string")) {
+            if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
+                return this.attrs.gradient;
+            }
+            var names = name.split(separator),
+                out = {};
+            for (var i = 0, ii = names.length; i < ii; i++) {
+                name = names[i];
+                if (name in this.attrs) {
+                    out[name] = this.attrs[name];
+                } else if (R.is(this.paper.customAttributes[name], "function")) {
+                    out[name] = this.paper.customAttributes[name].def;
+                } else {
+                    out[name] = R._availableAttrs[name];
+                }
+            }
+            return ii - 1 ? out : out[names[0]];
+        }
+        if (this.attrs && value == null && R.is(name, "array")) {
+            out = {};
+            for (i = 0, ii = name.length; i < ii; i++) {
+                out[name[i]] = this.attr(name[i]);
+            }
+            return out;
+        }
+        var params;
+        if (value != null) {
+            params = {};
+            params[name] = value;
+        }
+        value == null && R.is(name, "object") && (params = name);
+        for (var key in params) {
+            eve("raphael.attr." + key + "." + this.id, this, params[key]);
+        }
+        if (params) {
+            for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+                var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
+                this.attrs[key] = params[key];
+                for (var subkey in par) if (par[has](subkey)) {
+                    params[subkey] = par[subkey];
+                }
+            }
+            // this.paper.canvas.style.display = "none";
+            if (params.text && this.type == "text") {
+                this.textpath.string = params.text;
+            }
+            setFillAndStroke(this, params);
+            // this.paper.canvas.style.display = E;
+        }
+        return this;
+    };
+    elproto.toFront = function () {
+        !this.removed && this.node.parentNode.appendChild(this.node);
+        this.paper && this.paper.top != this && R._tofront(this, this.paper);
+        return this;
+    };
+    elproto.toBack = function () {
+        if (this.removed) {
+            return this;
+        }
+        if (this.node.parentNode.firstChild != this.node) {
+            this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
+            R._toback(this, this.paper);
+        }
+        return this;
+    };
+    elproto.insertAfter = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        if (element.constructor == R.st.constructor) {
+            element = element[element.length - 1];
+        }
+        if (element.node.nextSibling) {
+            element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
+        } else {
+            element.node.parentNode.appendChild(this.node);
+        }
+        R._insertafter(this, element, this.paper);
+        return this;
+    };
+    elproto.insertBefore = function (element) {
+        if (this.removed) {
+            return this;
+        }
+        if (element.constructor == R.st.constructor) {
+            element = element[0];
+        }
+        element.node.parentNode.insertBefore(this.node, element.node);
+        R._insertbefore(this, element, this.paper);
+        return this;
+    };
+    elproto.blur = function (size) {
+        var s = this.node.runtimeStyle,
+            f = s.filter;
+        f = f.replace(blurregexp, E);
+        if (+size !== 0) {
+            this.attrs.blur = size;
+            s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
+            s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
+        } else {
+            s.filter = f;
+            s.margin = 0;
+            delete this.attrs.blur;
+        }
+    };
+
+    R._engine.path = function (pathString, vml) {
+        var el = createNode("shape");
+        el.style.cssText = cssDot;
+        el.coordsize = zoom + S + zoom;
+        el.coordorigin = vml.coordorigin;
+        var p = new Element(el, vml),
+            attr = {fill: "none", stroke: "#000"};
+        pathString && (attr.path = pathString);
+        p.type = "path";
+        p.path = [];
+        p.Path = E;
+        setFillAndStroke(p, attr);
+        vml.canvas.appendChild(el);
+        var skew = createNode("skew");
+        skew.on = true;
+        el.appendChild(skew);
+        p.skew = skew;
+        p.transform(E);
+        return p;
+    };
+    R._engine.rect = function (vml, x, y, w, h, r) {
+        var path = R._rectPath(x, y, w, h, r),
+            res = vml.path(path),
+            a = res.attrs;
+        res.X = a.x = x;
+        res.Y = a.y = y;
+        res.W = a.width = w;
+        res.H = a.height = h;
+        a.r = r;
+        a.path = path;
+        res.type = "rect";
+        return res;
+    };
+    R._engine.ellipse = function (vml, x, y, rx, ry) {
+        var res = vml.path(),
+            a = res.attrs;
+        res.X = x - rx;
+        res.Y = y - ry;
+        res.W = rx * 2;
+        res.H = ry * 2;
+        res.type = "ellipse";
+        setFillAndStroke(res, {
+            cx: x,
+            cy: y,
+            rx: rx,
+            ry: ry
+        });
+        return res;
+    };
+    R._engine.circle = function (vml, x, y, r) {
+        var res = vml.path(),
+            a = res.attrs;
+        res.X = x - r;
+        res.Y = y - r;
+        res.W = res.H = r * 2;
+        res.type = "circle";
+        setFillAndStroke(res, {
+            cx: x,
+            cy: y,
+            r: r
+        });
+        return res;
+    };
+    R._engine.image = function (vml, src, x, y, w, h) {
+        var path = R._rectPath(x, y, w, h),
+            res = vml.path(path).attr({stroke: "none"}),
+            a = res.attrs,
+            node = res.node,
+            fill = node.getElementsByTagName(fillString)[0];
+        a.src = src;
+        res.X = a.x = x;
+        res.Y = a.y = y;
+        res.W = a.width = w;
+        res.H = a.height = h;
+        a.path = path;
+        res.type = "image";
+        fill.parentNode == node && node.removeChild(fill);
+        fill.rotate = true;
+        fill.src = src;
+        fill.type = "tile";
+        res._.fillpos = [x, y];
+        res._.fillsize = [w, h];
+        node.appendChild(fill);
+        setCoords(res, 1, 1, 0, 0, 0);
+        return res;
+    };
+    R._engine.text = function (vml, x, y, text) {
+        var el = createNode("shape"),
+            path = createNode("path"),
+            o = createNode("textpath");
+        x = x || 0;
+        y = y || 0;
+        text = text || "";
+        path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
+        path.textpathok = true;
+        o.string = Str(text);
+        o.on = true;
+        el.style.cssText = cssDot;
+        el.coordsize = zoom + S + zoom;
+        el.coordorigin = "0 0";
+        var p = new Element(el, vml),
+            attr = {
+                fill: "#000",
+                stroke: "none",
+                font: R._availableAttrs.font,
+                text: text
+            };
+        p.shape = el;
+        p.path = path;
+        p.textpath = o;
+        p.type = "text";
+        p.attrs.text = Str(text);
+        p.attrs.x = x;
+        p.attrs.y = y;
+        p.attrs.w = 1;
+        p.attrs.h = 1;
+        setFillAndStroke(p, attr);
+        el.appendChild(o);
+        el.appendChild(path);
+        vml.canvas.appendChild(el);
+        var skew = createNode("skew");
+        skew.on = true;
+        el.appendChild(skew);
+        p.skew = skew;
+        p.transform(E);
+        return p;
+    };
+    R._engine.setSize = function (width, height) {
+        var cs = this.canvas.style;
+        this.width = width;
+        this.height = height;
+        width == +width && (width += "px");
+        height == +height && (height += "px");
+        cs.width = width;
+        cs.height = height;
+        cs.clip = "rect(0 " + width + " " + height + " 0)";
+        if (this._viewBox) {
+            R._engine.setViewBox.apply(this, this._viewBox);
+        }
+        return this;
+    };
+    R._engine.setViewBox = function (x, y, w, h, fit) {
+        R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
+        var width = this.width,
+            height = this.height,
+            size = 1 / mmax(w / width, h / height),
+            H, W;
+        if (fit) {
+            H = height / h;
+            W = width / w;
+            if (w * H < width) {
+                x -= (width - w * H) / 2 / H;
+            }
+            if (h * W < height) {
+                y -= (height - h * W) / 2 / W;
+            }
+        }
+        this._viewBox = [x, y, w, h, !!fit];
+        this._viewBoxShift = {
+            dx: -x,
+            dy: -y,
+            scale: size
+        };
+        this.forEach(function (el) {
+            el.transform("...");
+        });
+        return this;
+    };
+    var createNode;
+    R._engine.initWin = function (win) {
+            var doc = win.document;
+            doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
+            try {
+                !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
+                createNode = function (tagName) {
+                    return doc.createElement('<rvml:' + tagName + ' class="rvml">');
+                };
+            } catch (e) {
+                createNode = function (tagName) {
+                    return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
+                };
+            }
+        };
+    R._engine.initWin(R._g.win);
+    R._engine.create = function () {
+        var con = R._getContainer.apply(0, arguments),
+            container = con.container,
+            height = con.height,
+            s,
+            width = con.width,
+            x = con.x,
+            y = con.y;
+        if (!container) {
+            throw new Error("VML container not found.");
+        }
+        var res = new R._Paper,
+            c = res.canvas = R._g.doc.createElement("div"),
+            cs = c.style;
+        x = x || 0;
+        y = y || 0;
+        width = width || 512;
+        height = height || 342;
+        res.width = width;
+        res.height = height;
+        width == +width && (width += "px");
+        height == +height && (height += "px");
+        res.coordsize = zoom * 1e3 + S + zoom * 1e3;
+        res.coordorigin = "0 0";
+        res.span = R._g.doc.createElement("span");
+        res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
+        c.appendChild(res.span);
+        cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
+        if (container == 1) {
+            R._g.doc.body.appendChild(c);
+            cs.left = x + "px";
+            cs.top = y + "px";
+            cs.position = "absolute";
+        } else {
+            if (container.firstChild) {
+                container.insertBefore(c, container.firstChild);
+            } else {
+                container.appendChild(c);
+            }
+        }
+        res.renderfix = function () {};
+        return res;
+    };
+    R.prototype.clear = function () {
+        R.eve("raphael.clear", this);
+        this.canvas.innerHTML = E;
+        this.span = R._g.doc.createElement("span");
+        this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
+        this.canvas.appendChild(this.span);
+        this.bottom = this.top = null;
+    };
+    R.prototype.remove = function () {
+        R.eve("raphael.remove", this);
+        this.canvas.parentNode.removeChild(this.canvas);
+        for (var i in this) {
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+        }
+        return true;
+    };
+
+    var setproto = R.st;
+    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname].apply(el, arg);
+                });
+            };
+        })(method);
+    }
+}(window.Raphael);
diff --git a/modules/question_types/quiz_ddlines/quiz_ddlines.classes.inc b/modules/question_types/quiz_ddlines/quiz_ddlines.classes.inc
new file mode 100644
index 0000000..086d455
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/quiz_ddlines.classes.inc
@@ -0,0 +1,565 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * The main classes for the drag and drop with lines question type.
+ *
+ * These inherit or implement code found in quiz_question.classes.inc.
+ *
+ * Sponsored by: Senter for IKT i utdanningen
+ * Code: paalj
+ *
+ * Based on:
+ * Other question types in the quiz framework.
+ *
+ * @file
+ * Question type, enabling the creation of dragging a choice to the correct location in an image
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class DDLinesQuestion extends QuizQuestion {
+   /**
+   * Get the form used to create a new question.
+   *
+   * @param
+   *  FAPI form state
+   * @return
+   *  Must return a FAPI array.
+   */
+
+  public function getCreationForm(array &$form_state = NULL) {
+
+    $elements = '';
+    if(isset($this->node->translation_source)) {
+      $elements = $this->node->translation_source->ddlines_elements;
+    }
+    elseif(isset($this->node->ddlines_elements)) {
+      $elements = $this->node->ddlines_elements;
+    }
+
+    $form['ddlines_elements'] = array(
+      '#type' => 'hidden',
+      '#default_value' => $elements,
+    );
+
+    $default_settings = $this->getDefaultAltSettings();
+
+    $form['settings'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Settings'),
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+      '#weight' => -3,
+    );
+    $form['settings']['feedback_enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable feedback'),
+      '#description' => t('When taking the test, and this option is enabled, a wrong placement of an alternative, will make it jump back. Also, this makes it possible to add comments to both correct and wrong answers.'),
+      '#default_value' => isset($this->node->translation_source) ? $this->node->translation_source->feedback_enabled : $default_settings['feedback']['enabled'],
+      '#parents' => array('feedback_enabled'),
+    );
+    $form['settings']['hotspot_radius'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Hotspot radius'),
+      '#description' => t('The radius of the hotspot in pixels'),
+      '#default_value' => isset($this->node->translation_source) ? $this->node->translation_source->hotspot_radius : $default_settings['hotspot']['radius'],
+      '#parents' => array('hotspot_radius'),
+    );
+
+    $form['settings']['execution_mode'] = array(
+      '#type' => 'radios',
+      '#title' => t('Execution mode'),
+      '#description' => t('The mode for taking the test.'),
+      '#default_value' => isset($this->node->translation_source) ? $this->node->translation_source->execution_mode : $default_settings['execution_mode'],
+      '#options' => array (
+        0 => t('With lines'),
+        1 => t('Drag label'),
+      ),
+      '#parents' => array('execution_mode'),
+    );
+
+    drupal_add_library('system','ui.resizable');
+
+    $default_settings['mode'] = 'edit';
+    $default_settings['editmode'] = isset($this->node->nid) ? 'update' : 'add';
+    $form['#attached']['js'][] = array(
+  		'data' => array(
+    		'quiz_ddlines' => $default_settings
+        ),
+  		'type' => 'setting'
+    );
+
+    _quiz_ddlines_add_js_and_css();
+
+    return $form;
+  }
+
+  /**
+   * This makes max_score beeing updated for all occurrences of
+   * this question in quizzes.
+   */
+  protected function autoUpdateMaxScore() {
+    return true;
+  }
+
+  /**
+   * Helper function provding the default settings for the creation form.
+   *
+   * @return
+   *  Array with the default settings
+   */
+  private function getDefaultAltSettings() {
+    $settings = array();
+
+    // If the node exists, use saved value
+    if (isset($this->node->nid)) {
+      $settings['feedback']['enabled'] = $this->node->feedback_enabled;
+      $settings['hotspot']['radius'] = $this->node->hotspot_radius;
+      $settings['execution_mode'] = $this->node->execution_mode;
+    }
+    else {
+      $settings['feedback']['enabled'] = 0;
+      $settings['hotspot']['radius'] = variable_get('quiz_ddlines_hotspot_radius', Defaults::HOTSPOT_RADIUS);
+      $settings['execution_mode'] = 0;
+    }
+
+    // Pick these from settings:
+    $settings['feedback']['correct'] = variable_get('quiz_ddlines_feedback_correct', t('Correct'));
+    $settings['feedback']['wrong'] = variable_get('quiz_ddlines_feedback_wrong', t('Wrong'));
+    $settings['canvas']['width'] = variable_get('quiz_ddlines_canvas_width', Defaults::CANVAS_WIDTH);
+    $settings['canvas']['height'] = variable_get('quiz_ddlines_canvas_height', Defaults::CANVAS_HEIGHT);
+    $settings['pointer']['radius'] = variable_get('quiz_ddlines_pointer_radius', Defaults::POINTER_RADIUS);
+
+    return $settings;
+  }
+
+  /**
+   * Generates the question form.
+   *
+   * This is called whenever a question is rendered, either
+   * to an administrator or to a quiz taker.
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+
+    $form = parent::getAnsweringForm($form_state, $result_id);
+
+    $form['helptext'] = array(
+      '#markup' => t('Answer this question by dragging each rectangular label to the correct circular hotspot.'),
+      '#weight' => 0,
+    );
+
+    // Form element containing the correct answers
+    $form['ddlines_elements'] = array(
+      '#type' => 'hidden',
+      '#default_value' => isset($this->node->ddlines_elements) ? $this->node->ddlines_elements : '',
+    );
+
+    // Form element containing the user answers
+    // The quiz module requires this element to be named "tries":
+    $form['tries'] = array (
+      '#type' => 'hidden',
+      '#default_value' => '',
+    );
+
+    $image_uri = $this->node->field_image['und'][0]['uri'];
+    $image_url = image_style_url('large',$image_uri);
+
+    $form['image'] = array (
+      '#markup' => '<div class="image-preview">'.theme('image', array('path' => $image_url)).'</div>',
+      /*'#weight' => 0,*/
+    );
+
+    $default_settings = $this->getDefaultAltSettings();
+    $default_settings['mode'] = 'take';
+    $form['#attached']['js'][] = array(
+  		'data' => array(
+    		'quiz_ddlines' => $default_settings
+        ),
+  		'type' => 'setting'
+    );
+
+    _quiz_ddlines_add_js_and_css();
+
+    return $form;
+  }
+
+  /**
+   * Get the maximum possible score for this question.
+   */
+  public function getMaximumScore() {
+    // 1 point per correct hotspot location
+    $ddlines_elements = json_decode($this->node->ddlines_elements);
+
+    $max_score = isset($ddlines_elements->elements) ? sizeof($ddlines_elements->elements) : 0;
+
+    return $max_score;
+  }
+
+  /**
+   * Save question type specific node properties
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    if ($is_new || $this->node->revision == 1) {
+      $id = db_insert('quiz_ddlines_node')
+        ->fields(array(
+          'nid' => $this->node->nid,
+          'vid' => $this->node->vid,
+          'feedback_enabled' => $this->node->feedback_enabled,
+          'hotspot_radius' => $this->node->hotspot_radius,
+          'ddlines_elements' => $this->node->ddlines_elements,
+          'execution_mode' => $this->node->execution_mode,
+        ))
+        ->execute();
+    }
+    else {
+      db_update('quiz_ddlines_node')
+        ->fields(array(
+          'ddlines_elements' => $this->node->ddlines_elements,
+          'hotspot_radius' => $this->node->hotspot_radius,
+          'feedback_enabled' => $this->node->feedback_enabled,
+          'execution_mode' => $this->node->execution_mode,
+        ))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties) && !empty($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+
+    $res_a = db_query('SELECT feedback_enabled, hotspot_radius, ddlines_elements, execution_mode FROM {quiz_ddlines_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
+
+    if (is_array($res_a)) {
+      $props = array_merge($props, $res_a);
+    }
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Provides validation for question before it is created.
+   *
+   * When a new question is created and initially submited, this is
+   * called to validate that the settings are acceptible.
+   *
+   * @param $form
+   *  The processed form.
+   */
+  public function validateNode(array &$form) {
+    // Nothing todo here
+  }
+
+/**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete()
+   */
+  public function delete($only_this_version = FALSE) {
+    $delete_node = db_delete('quiz_ddlines_node')->condition('nid', $this->node->nid);
+    $delete_results = db_delete('quiz_ddlines_user_answers')->condition('question_nid', $this->node->nid);
+
+    if ($only_this_version) {
+      $delete_node->condition('vid', $this->node->vid);
+      $delete_results->condition('question_vid', $this->node->vid);
+    }
+
+    // Delete from table quiz_ddlines_user_answer_multi
+    $user_answer_ids = array();
+    if ($only_this_version) {
+      $query = db_query('SELECT id FROM {quiz_ddlines_user_answers} WHERE question_nid = :nid AND question_vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+    }
+    else {
+      $query = db_query('SELECT id FROM {quiz_ddlines_user_answers} WHERE question_nid = :nid', array(':nid' => $this->node->nid));
+    }
+    while ($user_answer = $query->fetch()) {
+      $user_answer_ids[] = $user_answer->id;
+    }
+
+    if (count($user_answer_ids)) {
+      db_delete('quiz_ddlines_user_answer_multi')
+        ->condition('user_answer_id', $user_answer_ids, 'IN')
+        ->execute();
+    }
+
+    $delete_node->execute();
+    $delete_results->execute();
+    parent::delete($only_this_version);
+  }
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class DDLinesResponse extends QuizQuestionResponse {
+
+  // Contains a assoc array with label-ID as key
+  // and hotspot-ID as value:
+  protected $user_answers = array();
+
+  /**
+   * Constructor
+   */
+  public function __construct($result_id, stdClass $question_node, $tries = NULL) {
+    parent::__construct($result_id, $question_node, $tries);
+
+    // Is answers set in form?
+    if (isset($tries)) {
+      // Tries contains the answer decoded as JSON:
+      // {"label_id":x,"hotspot_id":y},{...}
+      $decoded = json_decode($tries);
+      if(is_array($decoded)) {
+        foreach ($decoded as $answer) {
+          $this->user_answers[$answer->label_id] = $answer->hotspot_id;
+        }
+      }
+    }
+    // Load from database
+    else {
+      $res = db_query('SELECT label_id, hotspot_id FROM {quiz_ddlines_user_answers} ua
+              LEFT OUTER JOIN {quiz_ddlines_user_answer_multi} uam ON(uam.user_answer_id = ua.id)
+              WHERE ua.result_id = :result_id AND ua.question_nid = :question_nid AND ua.question_vid = :question_vid', array(':result_id' => $result_id, ':question_nid' => $this->question->nid, ':question_vid' => $this->question->vid));
+      while ($row = $res->fetch()) {
+        $this->user_answers[$row->label_id] = $row->hotspot_id;
+      }
+    }
+  }
+
+  /**
+   * Save the current response.
+   */
+  public function save() {
+    $user_answer_id = db_insert('quiz_ddlines_user_answers')
+      ->fields(array(
+        'question_nid' => $this->question->nid,
+        'question_vid' => $this->question->vid,
+        'result_id' => $this->result_id,
+      ))
+      ->execute();
+
+    // Each alternative is inserted as a separate row
+    $query = db_insert('quiz_ddlines_user_answer_multi')
+      ->fields(array('user_answer_id', 'label_id', 'hotspot_id'));
+    foreach ($this->user_answers as $key => $value) {
+      $query->values(array($user_answer_id, $key, $value));
+    }
+    $query->execute();
+
+  }
+
+  /**
+   * Delete the response.
+   */
+  public function delete() {
+
+    $user_answer_ids = array();
+    $query = db_query('SELECT id FROM {quiz_ddlines_user_answers} WHERE question_nid = :nid AND question_vid = :vid AND result_id = :result_id', array(':nid' => $this->question->nid, ':vid' => $this->question->vid, ':result_id' => $this->result_id));
+    while ($answer = $query->fetch()) {
+      $user_answer_ids[] = $answer->id;
+    }
+
+    if (!empty($user_answer_ids)) {
+      db_delete('quiz_ddlines_user_answer_multi')
+        ->condition('user_answer_id', $user_answer_ids, 'IN')
+        ->execute();
+    }
+
+    db_delete('quiz_ddlines_user_answers')
+      ->condition('result_id', $this->result_id)
+      ->condition('question_nid', $this->question->nid)
+      ->condition('question_vid', $this->question->vid)
+      ->execute();
+  }
+
+  /**
+   * Calculate the score for the response.
+   */
+  public function score() {
+    $results = $this->getDragDropResults();
+
+    // Count number of correct answers:
+    $correct_count = 0;
+
+    foreach($results as $result) {
+      $correct_count += ($result == AnswerStatus::CORRECT) ? 1 : 0;
+    }
+
+    return $correct_count;
+  }
+
+  /**
+   * Get the user's response.
+   */
+  public function getResponse() {
+    return $this->user_answers;
+  }
+
+  public function getReportFormResponse() {
+    // Have to do node_load, since quiz does not do this. Need the field_image...
+    $img_field = field_get_items('node', node_load($this->question->nid), 'field_image');
+    $img_rendered = theme('image', array('path' => image_style_url('large',$img_field[0]['uri'])));
+
+    $image_path = base_path() . drupal_get_path('module', 'quiz_ddlines').'/theme/images/';
+
+    $html = '<h3>'.t('Your answers').'</h3>';
+    $html .= '<div class="icon-descriptions"><div><img src="'.$image_path.'icon_ok.gif">'.t('Means alternative is placed on the correct spot').'</div>';
+    $html .= '<div><img src="'.$image_path.'icon_wrong.gif">'.t('Means alternative is placed on the wrong spot, or not placed at all'). '</div></div>';
+    $html .= '<div class="quiz-ddlines-user-answers" id="'.$this->question->nid.'">';
+    $html .= $img_rendered;
+    $html .= '</div>';
+    $html .= '<h3>'.t('Correct answers').'</h3>';
+    $html .= '<div class="quiz-ddlines-correct-answers" id="'.$this->question->nid.'">';
+    $html .= $img_rendered;
+    $html .= '</div>';
+
+    // No form to put things in, are therefore using the js settings instead
+    $settings = array();
+    $correct_id = "correct-{$this->question->nid}";
+    $settings[$correct_id] = json_decode($this->question->ddlines_elements);
+    $elements = $settings[$correct_id]->elements;
+
+    // Convert the user's answers to the same format as the correct answers
+    $answers = clone $settings[$correct_id];
+    // Keep everything except the elements:
+    $answers->elements = array();
+
+    $elements_answered = array();
+
+    foreach ($this->user_answers as $label_id => $hotspot_id ) {
+
+      if(!isset($hotspot_id)) {
+        continue;
+      }
+
+      // Find correct answer:
+      $element = array(
+        'feedback_wrong' => '',
+        'feedback_correct' => '',
+        'color' => $this->getElementColor($elements, $label_id)
+      );
+
+      $label = $this->getLabel($elements, $label_id);
+      $hotspot = $this->getHotspot($elements, $hotspot_id);
+
+      if(isset($hotspot)) {
+        $elements_answered[] = $hotspot->id;
+        $element['hotspot'] = $hotspot;
+      }
+
+      if(isset($label)) {
+        $elements_answered[] = $label->id;
+        $element['label'] = $label;
+      }
+
+      $element['correct'] = $this->isAnswerCorrect($elements, $label_id, $hotspot_id);
+      $answers->elements[] = $element;
+    }
+
+    // Need to add the alternatives not answered by the user.
+    // Create dummy elements for these:
+    foreach ($elements as $el) {
+      if(!in_array($el->label->id, $elements_answered)) {
+        $element = array(
+        	'feedback_wrong' => '',
+        	'feedback_correct' => '',
+        	'color' => $el->color,
+            'label' => $el->label,
+        );
+        $answers->elements[] = $element;
+      }
+
+      if(!in_array($el->hotspot->id, $elements_answered)) {
+        $element = array(
+        	'feedback_wrong' => '',
+        	'feedback_correct' => '',
+        	'color' => $el->color,
+            'hotspot' => $el->hotspot,
+        );
+        $answers->elements[] = $element;
+      }
+    }
+
+    $settings["answers-{$this->question->nid}"] = $answers;
+    $settings['mode'] = 'result';
+    $settings['execution_mode'] = $this->question->execution_mode;
+    $settings['hotspot']['radius'] = $this->question->hotspot_radius;
+
+    // Image path:
+    $settings['quiz_imagepath'] = base_path() . drupal_get_path('module', 'quiz_ddlines').'/theme/images/';
+
+    drupal_add_js(array('quiz_ddlines' => $settings), 'setting');
+
+    _quiz_ddlines_add_js_and_css();
+
+    return array('#markup' => $html);
+  }
+
+  private function getElementColor($list, $id) {
+    foreach($list as $element) {
+      if ($element->label->id == $id) {
+        return $element->color;
+      }
+    }
+  }
+
+  private function getHotspot($list, $id) {
+    foreach($list as $element) {
+      if ($element->hotspot->id == $id) {
+        return $element->hotspot;
+      }
+    }
+  }
+
+  private function getLabel($list, $id) {
+    foreach($list as $element) {
+      if ($element->label->id == $id) {
+        return $element->label;
+      }
+    }
+  }
+
+  private function isAnswerCorrect($list, $label_id, $hotspot_id) {
+    foreach ($list as $element) {
+      if($element->label->id == $label_id) {
+        return ($element->hotspot->id == $hotspot_id);
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   *
+   * Get a list of the labels, tagged correct, false, or no answer
+   */
+  private function getDragDropResults() {
+    $results = array();
+
+    // Iterate through the correct answers, and check
+    // the users answer:
+    foreach(json_decode($this->question->ddlines_elements)->elements as $element) {
+      $source_id = $element->label->id;
+
+      if(isset($this->user_answers[$source_id])) {
+        $results[$element->label->id] = ($this->user_answers[$source_id] == $element->hotspot->id) ? AnswerStatus::CORRECT : AnswerStatus::WRONG;
+      }
+      else {
+        $results[$element->label->id] = AnswerStatus::NO_ANSWER;
+      }
+    }
+
+    return $results;
+  }
+}
diff --git a/modules/question_types/quiz_ddlines/quiz_ddlines.info b/modules/question_types/quiz_ddlines/quiz_ddlines.info
new file mode 100644
index 0000000..6824e1d
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/quiz_ddlines.info
@@ -0,0 +1,10 @@
+name = Drag and drop with lines for Quiz
+package = Quiz Question
+description = Drag and drop with lines question type for quiz.
+core = 7.x
+
+dependencies[] = quiz
+dependencies[] = quiz_question
+dependencies[] = image
+
+files[] = quiz_ddlines.classes.inc
diff --git a/modules/question_types/quiz_ddlines/quiz_ddlines.install b/modules/question_types/quiz_ddlines/quiz_ddlines.install
new file mode 100644
index 0000000..65b1193
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/quiz_ddlines.install
@@ -0,0 +1,218 @@
+<?php
+
+/**
+ * Sponsored by: Senter for IKT i utdanningen
+ * Code: paalj
+ *
+ * @file
+ * Drag and drop Install (a quiz question type)
+ */
+
+
+/**
+ * Implements hook_install().
+ */
+function quiz_ddlines_install() {
+  // Add body field to multichoice node
+  quiz_question_add_body_field('quiz_ddlines');
+
+  // At this point quiz core has created the ddlines node type.
+  // Now it's time to add the image field to the node:
+  if (!field_info_field('field_image')) {
+     $field = array(
+        'field_name' => 'field_image',
+        'type' => 'image',
+    );
+    field_create_field($field);
+  }
+
+  $instance = field_info_instance('node', 'field_image', 'quiz_ddlines');
+  if (empty($instance)) {
+
+    $help_text = t("<p>Start by uploading a background image. The image is movable within the canvas.<br/>
+    				The next step is to add the alternatives, by clicking in the canvas.
+    				Each alternative consists of a circular hotspot, a label, and a connecting line. You need
+    				to double click the rectangular label to add the text, and move the hotspot to the correct
+    				position. When selecting a label, a popup-window is displayed, which gives you the following
+    				alternatives:
+    				<ul>
+    					<li>Set the alternative's feedback (only possible if feedback is enabled)</li>
+    					<li>Set the color of each alternative</li>
+    					<li>Delete the alternative</li>
+    				</ul>
+    				</p>");
+
+    $instance = array(
+      'field_name' => 'field_image',
+      'entity_type' => 'node',
+      'bundle' => 'quiz_ddlines',
+      'label' => 'Background image',
+      'description' => $help_text,
+      'required' => TRUE,
+      'settings' => array(
+        'no_ui' => TRUE,
+      ),
+      'widget' => array (
+        'settings' => array (
+          'preview_image_style' => 'quiz_ddlines',
+      	  'no_ui' => TRUE,
+        ),
+      ),
+    );
+
+    field_create_instance($instance);
+  }
+}
+
+/**
+ * Implements hook_schema().
+ */
+function quiz_ddlines_schema() {
+  $schema['quiz_ddlines_node'] = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'feedback_enabled' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+	  'hotspot_radius' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 10,
+      ),
+      'ddlines_elements' => array(
+        'type' => 'text',
+      ),
+  	  'execution_mode' => array(
+        'type' => 'int',
+        'description' => "Execution mode. 0->with lines, 1->without lines",
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'foreign keys' => array(
+      'node' => array(
+        'table' => 'node',
+        'columns' => array('nid' => 'nid'),
+      ),
+    ),
+    'primary key' => array(
+      'vid', 'nid'
+    ),
+  );
+
+  // Place to store user answers
+  $schema['quiz_ddlines_user_answers'] = array(
+    'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'id'
+    ),
+    'indexes' => array(
+      'answer_id' => array('result_id', 'question_nid', 'question_vid'),
+    ),
+  );
+
+  $schema['quiz_ddlines_user_answer_multi'] = array(
+    'fields' => array(
+      'user_answer_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'hotspot_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'label_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'foreign keys' => array(
+      'answer_id' => array(
+        'table' => 'quiz_ddlines_user_answers',
+        'columns' => array('user_answer_id' => 'id'),
+      ),
+    ),
+    'indexes' => array(
+      'answer_id' => array('user_answer_id'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function quiz_ddlines_uninstall() {
+  // Delete instance:
+
+  $instance = field_info_instance('node', 'field_image', 'quiz_ddlines');
+  field_delete_instance($instance, FALSE);
+
+  $var = array(
+    'quiz_ddlines_canvas_width',
+    'quiz_ddlines_canvas_height',
+    'quiz_ddlines_hotspot_radius',
+    'quiz_ddlines_pointer_radius',
+    'quiz_ddlines_feedback_correct',
+    'quiz_ddlines_feedback_wrong',
+  );
+  foreach ($var as $v) {
+    variable_del($v);
+  }
+  drupal_set_message(t("The Quiz drag and drop module has been uninstalled. Nodes of this type may still exist, but they will not function properly."));
+}
+
+/**
+ * Adding Execution mode column. Makes it possible to
+ * run the test in two different modes.
+ */
+function quiz_ddlines_update_7401() {
+  // Add field for execution mode
+  $spec = array(
+    'type' => 'int',
+    'description' => "Execution mode. 0->with lines, 1->without lines",
+    'not null' => TRUE,
+    'unsigned' => TRUE,
+    'default' => 0,
+  );
+  db_add_field('quiz_ddlines_node', 'execution_mode', $spec);
+}
diff --git a/modules/question_types/quiz_ddlines/quiz_ddlines.module b/modules/question_types/quiz_ddlines/quiz_ddlines.module
new file mode 100644
index 0000000..c43f344
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/quiz_ddlines.module
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * The main file for Quiz drag and drop with lines.
+ *
+ * Sponsored by: Senter for IKT i utdanningen
+ * Code: paalj
+ *
+ * @file
+ * Drag and drop with lines question type for the Quiz module.
+ *
+ */
+
+/**
+ * Default values
+ */
+class Defaults {
+  const CANVAS_WIDTH = 700;
+  const CANVAS_HEIGHT = 500;
+  const HOTSPOT_RADIUS = 10;
+  const POINTER_RADIUS = 5;
+}
+
+/**
+ * Status for each alternative as "enum"
+ */
+class AnswerStatus {
+  const NO_ANSWER = 0;
+  const WRONG = 1;
+  const CORRECT = 2;
+
+  private static $titles;
+  private static $css_class;
+
+  public static function init() {
+    self::$titles = array(
+      t('You did not move this alternative to any hotspot'),
+      t('Wrong answer'),
+      t('Correct answer'));
+
+    self::$css_class = array('no-answer','wrong','correct');
+  }
+
+  public static function getTitle($status) {
+    return self::$titles[$status];
+  }
+
+  public static function getCssClass($status) {
+    return self::$css_class[$status];
+  }
+}
+AnswerStatus::init();
+
+/**
+ * Implements hook_help().
+ */
+function quiz_ddlines_help($path, $args) {
+  if ($path == 'admin/help#quiz_ddlines') {
+    return t("Help text TBD");
+  }
+}
+/**
+ * Implements hook_quiz_question_info().
+ */
+function quiz_ddlines_quiz_question_info() {
+  return array(
+    'quiz_ddlines' => array(
+      'name' => t('Drag and drop question with lines'),
+      'description' => t('This provides drag and drop question types with lines for the Quiz module.'),
+      'question provider' => 'DDLinesQuestion',
+      'response provider' => 'DDLinesResponse',
+      'module' => 'quiz_question', // All wrapper functions are in that module.
+    ),
+  );
+}
+
+/**
+ * Implements hook_image_default_styles
+ */
+function quiz_ddlines_image_default_styles() {
+  $styles = array();
+
+  $styles['quiz_ddlines'] = array(
+    'effects' => array(
+      array(
+        'name' => 'image_scale',
+        'data' => array(
+          'width' => 700,
+          'height' => null,
+          'upscale' => 0,
+        ),
+        'weight' => 0,
+      ),
+    ),
+  );
+
+  return $styles;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function quiz_ddlines_theme() {
+  return array(
+    'quiz_ddlines_response' => array(
+      'variables' => array(
+        'result' => array(),
+      ),
+      'path' => drupal_get_path('module', 'quiz_ddlines') . '/theme',
+      'file' => 'quiz_ddlines.theme.inc',
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function quiz_ddlines_quiz_question_config() {
+
+  $form['quiz_ddlines_canvas_width'] = array(
+    '#type' => 'textfield',
+    '#title' => t('The default width of the canvas'),
+    '#default_value' => variable_get('quiz_ddlines_canvas_width', Defaults::CANVAS_WIDTH),
+  );
+  $form['quiz_ddlines_canvas_height'] = array(
+    '#type' => 'textfield',
+    '#title' => t('The default height of the canvas'),
+    '#default_value' => variable_get('quiz_ddlines_canvas_height', Defaults::CANVAS_HEIGHT),
+  );
+  $form['quiz_ddlines_hotspot_radius'] = array(
+    '#type' => 'textfield',
+    '#title' => t('The radius of the hotspot'),
+    '#default_value' => variable_get('quiz_ddlines_hotspot_radius', Defaults::HOTSPOT_RADIUS),
+  );
+  $form['quiz_ddlines_pointer_radius'] = array(
+    '#type' => 'textfield',
+    '#title' => t('The radius of the pointer'),
+    '#default_value' => variable_get('quiz_ddlines_pointer_radius', Defaults::POINTER_RADIUS),
+  );
+  $form['quiz_ddlines_pointer_radius'] = array(
+    '#type' => 'textfield',
+    '#title' => t('The radius of the pointer'),
+    '#default_value' => variable_get('quiz_ddlines_pointer_radius', Defaults::POINTER_RADIUS),
+  );
+  $form['quiz_ddlines_feedback_correct'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default feedback for correct answers'),
+    '#default_value' => variable_get('quiz_ddlines_feedback_correct', ""),
+  );
+   $form['quiz_ddlines_feedback_wrong'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default feedback for wrong answers'),
+    '#default_value' => variable_get('quiz_ddlines_feedback_wrong', ""),
+  );
+
+  $form['#validate'][] = 'quiz_ddlines_config_validate';
+  return $form;
+}
+
+/**
+ * Validate the config form values
+ */
+function quiz_ddlines_config_validate($form, $form_state) {
+
+  $int_fields = array('quiz_ddlines_canvas_width', 'quiz_ddlines_canvas_height', 'quiz_ddlines_hotspot_radius', 'quiz_ddlines_pointer_radius');
+  foreach ($int_fields as $field) {
+    if (!_quiz_is_int($form_state['values'][$field])) {
+      form_set_error($field, t('The value must be a positive number'));
+    }
+  }
+
+  // Feedback fields may contain nothing or everything; no need to validate it
+}
+
+function _quiz_ddlines_add_js_and_css() {
+  $module_path = drupal_get_path('module', 'quiz_ddlines');
+
+  drupal_add_js($module_path.'/js/raphael.js');
+  drupal_add_js($module_path.'/js/colorwheel.js');
+  drupal_add_js($module_path.'/js/quiz_ddlines.js');
+  drupal_add_css($module_path.'/theme/quiz_ddlines.css');
+}
+
diff --git a/modules/question_types/quiz_ddlines/theme/images/close.png b/modules/question_types/quiz_ddlines/theme/images/close.png
new file mode 100644
index 0000000000000000000000000000000000000000..7cb985935f1ec74ff5923e7e1a47991ba4a46135
GIT binary patch
literal 1119
zcmV-l1fctgP)<h;3K|Lk000e1NJLTq000&M000&U1^@s6#I$TX00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#@kvBMRCwBA
z{Qv(y11$hR0I`4wMn*=Q3aqW89DtMq%mE-8h!31NvGM>eJs>pz0mKL<&<wV*i4g!&
z1^@s5%V%U{5@2OzVPIop18eyC^B2RfUq2cC{{8a-sAeKiY{Kc&Yd@eH3=lvV1_KQ>
z08;arn3xPqO^q4+{T&%JHRTw%xY*%pu3vx5uxr<GhRvI|GyM7U`vTC6#4~5sT|hP%
zAb?N|h8ntGNJvPaurP^1U0oJ$I6i*-ied8Pr9g|Wf?Vu)_U!r#FoOXC2yQUc`}+k2
z1q})c5*Z{Vg@NAv$H2_Ygw;@(-1qN48M?dYG2FU!17xV<xpNynfXoC4AY@$yAnykS
zc`<Nsuz_8ppdij5D$39B{{3eZ{N%|i25oHxV7Rj|{P^*UAtuJ3ft#Dx0BB$V%rJle
zVnT$=zkC%HWuQR<44*!IWw5f+1}f%c5EkZVke8Q$8~X0uCk7W66R;j7B`F4Wb~Z2@
z6d(Wo{mX}$2M|Ckup|YFePv~NutpGg@!~y$vN9)_EhQxkObNdkE?l_H5D;Js_u{i>
zZy7diI>7Ms=?kExAAmu_ETE_714^C;00M{ws>T79q~5*z2o~77^(cd@s~Ipw$b<O`
z3KC!p1CJiPWT>s3%JB8;SGX7(8#^$S`~e#b5I{_@SY%~oW%&I03((*XaJ+KmE}$##
zU_=}?LqUL(lam3KkpKdS1!gb^ynXut!;sUbuQ3=HsGx{Fc<=(lP!Ra{50oqaf!P27
z1Pcmqj(Yd*J&Hn0OJj!AR6h&@?CkV`WE{h!NsCa#zkT}xcO*an!GhudD4YKJ^^4*6
z@84i-W@f;UlM{^M{X>T?q8Of+7YEJ*F#O}k53pGvd4K={8w@H?KzaZB_wS&XWe^t^
zW5~}BL&;%HO*0v$PhZY(<j7?d!(Cl%8CY0Y7`}Y@3@$&eUE2vZ7$AU<GswgbAKn8a
zfCCsTKN%J++5r}P`t%J$Z|`CTVCG@q<>dj2Z)7-m@;X=!M4vfx8La2^>sMeiV1@w%
z5DQYlF##AH{;yuWHc(Sj0j7rQ;PASA`ytp+9v*JE3s$b&!SL|mbD&Qzg2k^~xeCnM
z-#}S<0!$4+0HG8@CMH3kY`I@rT2??yOPv9fw($W_rnz+KDlnDY1s6isukV5vLI44T
zQEVC;2Z9Wr&&kPUprfM$%(mD|xI1?qGF-fP0hsnbfecH8845EPAb_wI^oEB1;DR2M
z_u1Lm1%!k|fTf)TSi`et&lujkc@0bf-{A#4&`^wm9w30Q8jK8}c^_Og{rmS9rtttQ
l_Hk8B00G2EtI8B0zyRRb`ov(v=I8(b002ovPDHLkV1heR;aUIy

literal 0
HcmV?d00001

diff --git a/modules/question_types/quiz_ddlines/theme/images/icon_ok.gif b/modules/question_types/quiz_ddlines/theme/images/icon_ok.gif
new file mode 100644
index 0000000000000000000000000000000000000000..35e949963e4b7a5f04fd1c8932a980901e3d4385
GIT binary patch
literal 263
zcmV+i0r>t$Nk%w1VGsZi0K^{vcEd+Ya4xgmmXXkDk(iA6{P)e~xr@?RgUwTMzeLpS
z#Wq<Qev@rdf-IBfSBd0I?DyrU<#)p7sa<+Te$_{S%TJ8uP)uMvaf@7w&S0e4cYxVQ
zX}CU8XF~t~|NsC0A^8LW0018VEC2ui01yBW000Gq;3s}wX?kNyPDINv6RLWcVMtLP
zHL^$>hu{H$s5p4O4v<)^3=A2^0_EulhRp%QGjIf`R%Ru$RUj;1k~O1%3=|$g0$`b{
z1p`T8E1%cF0ssJ7eF+H<c2yc2f`*119DhC+2MLgu91m$D9+VFhpc|SV1v3{7sHjst
NDJLc>u(2Z{06R}5X@39!

literal 0
HcmV?d00001

diff --git a/modules/question_types/quiz_ddlines/theme/images/icon_wrong.gif b/modules/question_types/quiz_ddlines/theme/images/icon_wrong.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c941c19028c479d4a27a7ee04d6e0e66b6bce599
GIT binary patch
literal 258
zcmZ?wbhEHb6krfwI3myRx}@ZPMa9G9<ZoNH{6Bf}V|@IdsZ;+gTQ)g2_g_|4rMmj{
z(9maDS@+`NX7u!ASy;5{>sPtC-3SYNk)IzaCG{5wQd4hOTmQRpBi`QrZFzZjOw5(w
z;B+Ua|6ri_lZBCiL7qW}0SG{LGO*S^P_K*2tV;HB^Xpa+Tva9QCeXgnd6P%76fc9p
zOamsHcy<jHDaj@V)~7Fd+U7kvs@!3>;@YDln|Vuw<0nl$s8=rCxw48uw_56lMa6}$
zEFa1XibC1A+uFH>WUA~WrMhG$PUPgWRp}FBoy96PRn(YYfNQZp@O&*5b=4JX)+#bs
F0{{l~XioqD

literal 0
HcmV?d00001

diff --git a/modules/question_types/quiz_ddlines/theme/images/icons.png b/modules/question_types/quiz_ddlines/theme/images/icons.png
new file mode 100644
index 0000000000000000000000000000000000000000..efcc6fe283aeda80f8c7492b766cdee6fa43ad70
GIT binary patch
literal 3484
zcmV;N4P)|&P)<h;3K|Lk000e1NJLTq005u>001Be1^@s6m49>f00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmY3labT3lag+-G2N4000McNliru+yx&E1~p-WPP+gA4Jk=P
zK~#9!?VNjXRn-;7fA{9*l7u`UfEWuJrS*;Yz+!zAq1Dk=ic+;w1<}?zt+lp}587&#
zmX0lBp`9v33<hcyNu$_mZ3U|~GWCJ-P(%?!h>1iXkDK>><(&R;*1lP}+>rZ(aVCp7
zGxwac&tChi{hhVGz19veBttSJLoy^oG9*JXBtvqT#fC{X+-JkoH1st!14DGl;RZZ%
zWXoaB-KyP?91c0T{^V=0oqwm-o_=KlpxJDLEysOoB3%jeahnaT(#MiC0&hO>j-vt_
zs0Ho^iscvq6aaaE7sxtXKrAaOD=jN4v&+iL?3F85PCP8m#KyKA3=|F2#+?bpQ$Cwe
zy?xyF)#iLl>6X7?QyS-F>{h?;55eCnU@VX$lj<JCd%gnvQNRFDD&EN}hbM{mz8Yu-
z9stf%*Gq9el3L(fz$nq1FCZKy`VJ`wtE#FN$d~}|`~B}mdGCzMMw|0?+W5@*PN*A5
zzbD<w-Im*a(^YfejgKf25CK>~80d}B#XMjoaJdX{#<j5i4Vd)^Gy#4f82zjblYmzh
z@Cd=<OW+qkk-WI&TqYoW*m1nl%|O{8Z3=-3U=dIWbj#7D#vAS%YUX6ss#U(Grlx)#
zCp$a4S8VT!!~QABi#BT~%iI}hvuPekzboC~vs~7JH{M_xUtxy~AS@H&mm`*BwZQjA
z!XxyJxcv#3Jr_30<ob;PV}Q?B57!(Gn}FfsiEII056~&kTXDQrC2*DLS3vfHfbJ^s
zj$>8(p8{Kfc6IcK?yw5q83A8UPftJa<>cga&YL&y7_mXT_D>po7e|5br7$mCl+B!h
z^k-!fd?z#=wY&MPjyYVwF5yNw68_8=5E`hv-@}ec@X84=(*?s^Fzi(L)^ymj5_Xx1
zw4HvpDDZ9B0@t1hJH+!92?(>42Q)`KK=1)`fM<bgfscTZa_qzTAWl*G&ytV9gnC;(
zlwQ$)$iSDMpI<R|?%XTH28q}|W$<-C?}j?Ayl50l52imY9B_Qw=(cF^mDH5{ddHYO
zUu7D*grFA*FUpRSgW0oS``z&E4%mr<pn|Ee^erb$7Xi68{8xi-5zIKl;T#4eg+Oi;
ze@p}fFNxv4C!RM)@llU>x5aV~U<@!tbQdd5n4|b$TBWkG@>LDKtgI}%uCDI<>#n=*
zJ~fU+?H{-N)8;k}zV~hR7T&{zub1-nVER+$fKxu3Q2l0MO<})_uXADJxsAI;d4URn
z@$w+!H3APp?OX8ur(j_TTmW!3JWvNG7df7BlJQhq;Fi0cXN~8}70(w`M<~wF8!Po<
zz|$-Jbj!7NU_CHX?jH+mSMRL#r!@>$RaGr$YHI42Zo28Fn?}ipm#qC0T5wEu7JUHh
zg7#D0oD{Eq_aJGM(!o+KZ;q|Gpr7a4RkDC{sZ}7z6FnitI|73cU`r;e8)&bFhNIxN
z9S$D@;Aogr<nS*5HpBI2z$a09vqV=r(B?!2x~1<#Kxiw_`GrIJa27@TfxW=Ts(rEO
z&XWnqS6(<S+EZ3mRvJ&6rlzJ}>1z!Q4d;o@RO}zO9PdP+NZ6gQfQgCdcMrtpB#lx#
zAP8+rb*cqmeEVo$<Ala?QCuit$CfbF2DB<tJ{bS#m4SB1WVf6M3%A0G=!W}X?U_!P
zHfa#^(th)-=<Z0;_=dCirH@tt-ag<npfRd_n>^zY8|105oi$h(UbSkK52&rEsOULZ
zyJ5qIeWcHeii*}RT)6O)RPFzw&DuRSJ^!(J^<es6v)NpzKBx^%+{*n}iRh0Bz$iXf
zi;pi#Ta)2u%H9coiGHqrQ`4Ln%DY8zvkamu0T37vb{Qa@4vY7yuMWqxoI2Ub$&Hc)
zhg+UAFYQZEZ!8aJ(l?z7)W!pj6OL%)LjkN?<sh>Ke1q~Y!{-8lKu&pixut9$v42%n
z)q+4EkZqpvdcEP<vu9tQ4*OpWmzO}!beQ_CO=B$m8*R=zA5M?3E{kp77{jxP>W`0%
z7O|7J`En$T?Bn?8&^!L!@pm$fkJM@hLAp;NzdM2ao1XkgINk<lkBus^6~LF)!Na$}
zX)@Wp@}g}@Z;0f$k6y(Fq_0ltx5J4-tWXP~Pl0Ns%N=6^U4wYGh<6Hy!>;AamxtxU
ziiB~W&v$P>Z}i%0Q^fWu+kb<TRRCZj9QnRQW3>KZP-WVD27z<4n4XOOfdF{y0awoA
zbzb5kApeNG5nKA@n!;YIep3C@g8V#1{5ALLQ6w}e540D?oT3PCFLZ<*FfbC1T@1gy
z1->L;@rzEsBL1m};ebH_VnC`N|BJHMFQck0mP)@^@hm`YxZwt`*Xxbs=q$@}m6w;d
znHBYuPd@oZI2?|EI4>`+zM`U{HC^_<z{MG_*i=WjzKbKahWNnff4-CX6X8g6|D8@M
zgy$x#-#x&ZZ#Vln;ehL3W9`_DV{hL#+?T5_`>TUjsPdCX6lpKvPY3Be0-n~Dj>T}(
ze7G~@cssn1_Yf?34lYS^ro&c;CGWBncq)qb-3&B{r|`sB1^33g__^`M8}oB=a=Nr9
zeC)Bu-k&jJ#wqRX?UN%D=yuz4=gvJR-S(gEX8M0Z{M($L=HUx3Hu5k~baoz*h0k`I
zeU}yRn`HI74>H>|yu(+bFYiBV|1bJQXaDdpU*kh@(OwnzJ7f@n6g;0sz!DVa_!REF
z3%36OcK#R^1pD%ETn6)NoDy8FgoErf=HDg&Zv#J2*WLrJp)dKJt5$wJs&ZpM7?(Gm
zH*a2v%hfmRU@%xTYu2o1`tje~+z(~)({BH-d%1dtMN5Pm9?8sq)$rRRTzDf3gyx9%
zOG>}{pmRQ%ZAWcB>YjrvP{?^N|3Q^zaH~Z_XPm3uc))kPl(-yk0MCtsRVJyq9BO95
z&zt*--W&(t-2;D`?JN*XIWE-%>_}jpR6C3;1FjQ*6^kv57w8r*U~Cagz!TYk{qoB%
zpQA4Z0)bKbe9f9QKg^K*ZNNS;i-~^q!CZ12*O=ekU|lwb@>KM@2jg9P&fK%iV|fPr
zV)yRb+bP5|Ta0UJt;yuuVhv&viuc)2cRTzn+U5Tnc3%j0wLxSjL<!7%6rNt_l*$YU
zQz<{*?O;11Kf<Y3j}q_|s6|k>5OAO3K%oSZ&^8cSvu4fC+}zxzSPi^hZ|M2wpZ90T
z{$}Oxf3=C#u^Mb@<6U|8r1iVw7(jQ)`z3e8YB0KaL<^Ti3jVP|PCAmEdTW*MHJJNn
zC!7{V=liz7Czry_&9K=#JRDA#4=-E}<JAf+*$Pmt`u!c8ns1>Z-<tol$l+5wD4YNw
zBKEJYuD&={gNBBNwF3N1*gvBG(lPv`-s+<`MjpDE#i{BaEF@V#u>Q3ArAKy-?25kW
z|2Vu!jPI4UCQZ?kj+Lhkw*eQL^H%uXR42?9i3iM<m+p|9+`iXg_h`7j&e^d7j>{{k
zfO1tDGQ2QRSq^*`m<GH-|GF@$>e3>4uNIYfO~xBpKyby16(1B76l{pTSy@^6-%Q$H
zRiwJ!YouS==hHAfYu4aTRlhslLF9cp^7iN(oxV<=;#L~{!BoAU4aeZ@HjePVV)(1P
zaDjU1Zj~4|M{6nERSEM=nr4K<+lr$=n2aZUMiw1<`@Ko~w5tSlti)?FHDC(}Zr!?7
zFM9BJJiXhtZEMez{iF4Na~x&+ZGz@vV>^waJ0<<`l5%U;s9pW45BXd3*K-NaMtv!!
z&X<mDKKJX;dzZ5aDNtE<ljv+$M88WVpj*EK&uw%(AOC|V<oOZwZPD^3IHvXTj#sFX
znx(+ksyv?tU#c4rtVN3!?eu!R0dryX>eaWZ{xV_z13<XFmrXc|?6aqFcRKVZmYS$x
z;)yxqmNqv8-rhfD|3By}x_8i58*P@aAl2|&fwS$3I=Hsf33;a0n=LQgD&EGYsvXc*
zgUuJPjZj3{l;~PinJylgkFzBo;5pz91!NTYZdZ$r-bB(aamcV`%a+}`cJ11&>(;Fc
zXVU%y>3{rjPMI?YKh7rHl=UZ?8#6mRih<EGu^#!-n*|JQ0*+KuFUJ7$T44Jq*dY3f
zMSrhMbdvz7D@KXQBjI7V0^x9J)1^v8&2bLt5taK}3OuZ+Kv?=T;OnAq+gCb)aLDX`
zsPrewfqD%oRm*OZ`{^W&+k9NkV&gXgnqvAkbB0vtjZSZ93CE~j1l4#GS&;%f3S1-{
z@?SEZEM*t7%Q~p~PPZ32WcEK)`ja8ydRtP6zJ315uJqzf2^bwHp4T;qPO~`B0Q9Ke
zi5Tl0;3O5+3+ewyHqqPeGxf(uhr|9E)1M3gE#+bZ^#5zKPzDUIH>ZYGvNPW8RhA0R
zVft?p2qx~BBhOl@zpxrxCc$?&?4MEno+Q0dNV#8>(R(xC`OG#bo4!A236R+C8cT&n
zSJar38BbUl-z6R)6a~Ue$)U7=#`S+5{_BXWD88jZU@;`0YxzIZ!YBA$vZ3Pu0000<
KMNUMnLSTYxpwT!0

literal 0
HcmV?d00001

diff --git a/modules/question_types/quiz_ddlines/theme/quiz_ddlines.css b/modules/question_types/quiz_ddlines/theme/quiz_ddlines.css
new file mode 100644
index 0000000..d699356
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/theme/quiz_ddlines.css
@@ -0,0 +1,147 @@
+@CHARSET "UTF-8";
+
+/**
+* This container contains the img and svg "panel"
+* The ID is set by javascript.
+*/
+div.ddlines-container {
+	position: relative;
+	margin-top: 5px;
+	border: 3px solid #AAA;
+	padding: 0 !important;
+}
+div.ddlines-container > img {
+	display: none;
+	visibility: hidden;
+}
+ 
+/**
+* The feedback pane for the quiz taker
+*/
+#quiz-ddlines-feedback {
+	position: absolute;
+	top: 100px;
+	right: 0;
+	left: 0;
+	width: 200px;
+	background-color: white;
+	margin: 0px auto;
+	border: 8px double;
+	vertical-align: center;
+	padding: 10px;
+	z-index: 100000;
+}
+
+span#feedback-correct,
+span#feedback-wrong {
+	display: inline-block;
+	font-weight: normal;
+	font-size: 90%;
+	width: 160px;	
+	color: #777;
+	position: relative;
+	top: 3px;
+}
+
+span#feedback-wrong {
+	clear: both;
+}
+
+label#settings-for {
+	font-size: 110%;
+	max-width: 300px;
+	display: inline-block;
+	background-color: #EEE;
+	padding: 0px 5px;
+}
+
+/**
+* The editor toolpane
+*/
+#quiz-ddlines-toolpane {
+	background-color: white;
+	border-style: double;
+	border-width: 5px;
+	width: 400px;
+	margin: 0px auto;
+	position: relative;
+	bottom: 0px;
+	padding: 5px 10px 10px 10px;
+}
+
+#quiz-ddlines-feedback-pane > input.feedback {
+	margin-right: 5px;
+	width: 150px;
+}
+
+#quiz-ddlines-toolpane > #quiz-ddlines-buttonpane {
+	position: absolute;
+	right: 3px;
+	top: 11px;
+}
+
+#quiz-ddlines-buttonpane > input {
+	float: left;
+	clear: left;
+	width: 100%;
+	height: 20px;
+	margin-bottom: 3px;
+	font-size: 90%;
+	padding-top: 0px;
+}
+
+#quiz-ddlines-toolpane > #colorwheel-container {
+	background-color: white;
+	border: 5px double black;
+	position: absolute;
+	z-index: 1000;
+	display: none; /* Hide by default */
+	top: -165px;
+	right: 5px;
+	border-bottom: none;
+	padding: 5px;
+}
+#quiz-ddlines-feedback-pane > #feedback-disabled {
+	position: absolute;
+	top: 37px;
+	height: 42px;
+	background-color: #EEE;
+	text-align: center;
+	vertical-align: middle;
+	opacity: 0.8;
+	filter: alpha(opacity=80);
+	width: 318px;
+}
+#quiz-ddlines-feedback-pane > #feedback-disabled > p {
+	margin-top: 13px;
+	font-size: 150%;
+    font-weight: bold;
+}
+
+/**
+* The response
+*/
+.quiz-ddlines-icon-cell {
+  width: 32px;
+  padding: 0 5px;
+}
+.quiz-ddlines-icon {
+  display: inline-block;
+  width: 32px;
+  height: 32px;
+  background: url(images/icons.png) no-repeat;
+}
+.quiz-ddlines-icon.wrong {
+  background-position: -32px 0;
+}
+.quiz-ddlines-icon.no-answer {
+  background-position: -64px 0;
+}
+#quiz-report-form .icon-descriptions div {
+  margin-bottom: 5px;
+}
+#quiz-report-form .icon-descriptions img {
+  position: relative;
+  top: 3px;
+  margin-right: 5px;
+}
\ No newline at end of file
diff --git a/modules/question_types/quiz_ddlines/theme/quiz_ddlines.theme.inc b/modules/question_types/quiz_ddlines/theme/quiz_ddlines.theme.inc
new file mode 100644
index 0000000..b82386b
--- /dev/null
+++ b/modules/question_types/quiz_ddlines/theme/quiz_ddlines.theme.inc
@@ -0,0 +1,37 @@
+<?php 
+
+/**
+ * The theme file for quiz ddlines.
+ *
+ * Sponsored by: Senter for IKT i utdanningen 
+ * Code: paalj
+ *
+ * @file
+ * Theming functions for the ddlines question type.
+ */
+
+/**
+ * Theming function for the results
+ * 
+ * @param array $vars
+ */
+function theme_quiz_ddlines_response($vars) {
+  static $css_added;
+  if (!$css_added) {
+    drupal_add_css(drupal_get_path('module', 'quiz_ddlines') . '/theme/quiz_ddlines.css');
+    $css_added = TRUE;
+  }
+  
+  $rows = array();
+  foreach ($vars['result'] as $title => $status) {    
+    $rows[] = array(
+      array(
+        'data' => '<span class="quiz-ddlines-icon '.AnswerStatus::getCssClass($status).'" title="' . AnswerStatus::getTitle($status) . '"></span>',
+        'class' => 'selector-td quiz-ddlines-icon-cell',
+      ),
+      $title,
+    );
+  }
+  
+  return theme('table', array('header' => NULL, 'rows' => $rows));;
+}
diff --git a/modules/question_types/quiz_directions/quiz_directions.classes.inc b/modules/question_types/quiz_directions/quiz_directions.classes.inc
new file mode 100644
index 0000000..95f085b
--- /dev/null
+++ b/modules/question_types/quiz_directions/quiz_directions.classes.inc
@@ -0,0 +1,176 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * quiz_directions.classes
+ *
+ * This module uses the question interface to define something which is actually not a question.
+ *
+ * A Quiz Directions node is a placeholder for adding directions to a quiz. It can be inserted any number
+ * of times into a quiz. Example uses may include:
+ *
+ * - Initial quiz-wide directions
+ * - Section directions, e.g. "The next five questions are multiple choice, please..." (Won't work if the question order is randomized)
+ * - Final confirmation, e.g. "You have answered all questions. Click submit to submit this quiz."
+ *
+ * @file
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class QuizDirectionsQuestion extends QuizQuestion {
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validateNode($form)
+   */
+  public function validateNode(array &$form) {
+
+  }
+
+  /**
+   * Implementation of saveNodeProperties
+   *
+   * @see QuizQuestion#saveNodeProperties($is_new)
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+
+  }
+
+  /**
+   * Implementation of getAnsweringForm
+   *
+   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $form = parent::getAnsweringForm($form_state, $result_id);
+
+    $form['tries'] = array(
+        '#type'  => 'hidden',
+        '#value' => 0,
+    );
+    $form['empty_space'] = array(
+        '#type'  => 'markup',
+        '#value' => '<br/>',
+    );
+    return $form;
+  }
+
+  /**
+   * Implementation of getBodyFieldTitle
+   *
+   * @see QuizQuestion#getBodyFieldTitle()
+   */
+  public function getBodyFieldTitle() {
+    return t('Direction');
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm($form_state)
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    return array();
+  }
+
+  /**
+   * Implementation of getMaximumScore
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    return 0;
+  }
+
+  public function isGraded() {
+    return FALSE;
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class QuizDirectionsResponse extends QuizQuestionResponse {
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    // First, due to popular demand, if the directions are at the beginning of
+    // the quiz, we restart the timer after the user has read the question.
+    $quiz_qid = arg(1);
+    $quiz_key = 'quiz_' . $quiz_qid;
+    if (isset($_SESSION[$quiz_key]['previous_quiz_questions']) && count($_SESSION[$quiz_key]['previous_quiz_questions']) === 1) {
+      // reset the timer.
+      $sql = 'UPDATE {quiz_results} SET time_start=%d WHERE result_id = %d';
+      // TODO Please review the conversion of this statement to the D7 database API syntax.
+      /* db_query($sql, REQUEST_TIME, $this->result_id) */
+      db_update('quiz_results')
+        ->fields(array(
+            'time_start' => REQUEST_TIME,
+        ))
+        ->condition('result_id', $this->result_id)
+        ->execute();
+    }
+
+    // Set the score
+    $this->score = 0;
+    return 0;
+  }
+
+  /**
+   * Implementation of isCorrect
+   *
+   * @see QuizQuestionResponse#isCorrect()
+   */
+  public function isCorrect() {
+    return TRUE;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer;
+  }
+
+  /**
+   * Implementation of getReportForm
+   *
+   * @see QuizQuestionResponse#getReportForm()
+   */
+  public function getReportForm() {
+    return array(
+        '#no_report' => TRUE,
+    );
+  }
+
+}
diff --git a/modules/question_types/quiz_directions/quiz_directions.info b/modules/question_types/quiz_directions/quiz_directions.info
new file mode 100644
index 0000000..94d1d22
--- /dev/null
+++ b/modules/question_types/quiz_directions/quiz_directions.info
@@ -0,0 +1,9 @@
+name = Quiz Direction
+package = Quiz Question
+description = Provide a slot to add directions for a quiz or some sub-portion of a quiz.
+core = 7.x
+
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = quiz_directions.classes.inc
diff --git a/modules/question_types/quiz_directions/quiz_directions.install b/modules/question_types/quiz_directions/quiz_directions.install
new file mode 100644
index 0000000..16225e7
--- /dev/null
+++ b/modules/question_types/quiz_directions/quiz_directions.install
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * The installer file for quiz directions module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function quiz_directions_install() {
+  // Add body field to quiz directions node
+  quiz_question_add_body_field('quiz_directions');
+}
diff --git a/modules/question_types/quiz_directions/quiz_directions.module b/modules/question_types/quiz_directions/quiz_directions.module
new file mode 100644
index 0000000..e00a7a3
--- /dev/null
+++ b/modules/question_types/quiz_directions/quiz_directions.module
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * The main file for quiz_directions.
+ *
+ * Directions are implemented as a question type. However, they do not affect the score.
+ * @file
+ */
+
+/**
+ * Implements hook_help().
+ */
+function quiz_directions_help($path, $args) {
+  if ($path == 'admin/help#quiz_directions') {
+    return t('Provides directions which can be inserted alongside questions in a quiz.');
+  }
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function quiz_directions_quiz_question_info() {
+  return array(
+      'quiz_directions' => array(
+          'name'              => t('@quiz directions', array('@quiz' => QUIZ_NAME)),
+          'description'       => t('@quiz directions allow you to drop in directions anywhere in a quiz. You can also use them to insert a confirmation step at the end of the quiz.', array('@quiz' => QUIZ_NAME)),
+          'question provider' => 'QuizDirectionsQuestion',
+          'response provider' => 'QuizDirectionsResponse',
+          'module'            => 'quiz_question', // All wrapper functions are in that module.
+      ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function quiz_directions_quiz_question_config() {
+  return FALSE;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function quiz_directions_theme() {
+  return array(
+      'quiz_directions_answering_form' => array(
+          'render element' => 'form',
+          'path'           => drupal_get_path('module', 'quiz_directions') . '/theme',
+          'template'       => 'quiz-directions-answering-form',
+      ),
+  );
+}
diff --git a/modules/question_types/quiz_directions/theme/quiz-directions-answering-form.tpl.php b/modules/question_types/quiz_directions/theme/quiz-directions-answering-form.tpl.php
new file mode 100644
index 0000000..b55d6e0
--- /dev/null
+++ b/modules/question_types/quiz_directions/theme/quiz-directions-answering-form.tpl.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Handles the layout of the quiz_directions answering form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render($form);
+
+?>
\ No newline at end of file
diff --git a/modules/question_types/quiz_page/quiz_page.classes.inc b/modules/question_types/quiz_page/quiz_page.classes.inc
new file mode 100644
index 0000000..b213a6f
--- /dev/null
+++ b/modules/question_types/quiz_page/quiz_page.classes.inc
@@ -0,0 +1,135 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * @file quiz_page.classes.inc
+ * Support class for Quiz pages.
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class QuizPagesQuestion extends QuizQuestion {
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validateNode($form)
+   */
+  public function validateNode(array &$form) {
+
+  }
+
+  /**
+   * Implementation of saveNodeProperties
+   *
+   * @see QuizQuestion#saveNodeProperties($is_new)
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+
+  }
+
+  /**
+   * Implementation of getBodyFieldTitle
+   *
+   * @see QuizQuestion#getBodyFieldTitle()
+   */
+  public function getBodyFieldTitle() {
+    return t('Page');
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm($form_state)
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    return array();
+  }
+
+  /**
+   * Implementation of getMaximumScore
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    return 0;
+  }
+
+  public function isGraded() {
+    return FALSE;
+  }
+
+  function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $element = array(
+      '#type' => 'hidden',
+    );
+    return $element;
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class QuizPagesResponse extends QuizQuestionResponse {
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+
+  }
+
+  /**
+   * Implementation of isCorrect
+   *
+   * @see QuizQuestionResponse#isCorrect()
+   */
+  public function isCorrect() {
+    return TRUE;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer;
+  }
+
+  /**
+   * Implementation of getReportForm
+   *
+   * @see QuizQuestionResponse#getReportForm()
+   */
+  public function getReportForm() {
+    return array(
+      '#no_report' => TRUE,
+    );
+  }
+
+}
diff --git a/modules/question_types/quiz_page/quiz_page.info b/modules/question_types/quiz_page/quiz_page.info
new file mode 100644
index 0000000..1157f05
--- /dev/null
+++ b/modules/question_types/quiz_page/quiz_page.info
@@ -0,0 +1,9 @@
+name = Quiz pages
+package = Quiz
+description = Provides support for pages of questions.
+core = 7.x
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = quiz_page.classes.inc
+files[] = tests/QuizPageTestCase.test
diff --git a/modules/question_types/quiz_page/quiz_page.install b/modules/question_types/quiz_page/quiz_page.install
new file mode 100644
index 0000000..f134a69
--- /dev/null
+++ b/modules/question_types/quiz_page/quiz_page.install
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * The installer file for quiz pages module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function quiz_page_install() {
+  // Add body field to quiz pages node
+  quiz_question_add_body_field('quiz_page');
+}
diff --git a/modules/question_types/quiz_page/quiz_page.module b/modules/question_types/quiz_page/quiz_page.module
new file mode 100644
index 0000000..073557a
--- /dev/null
+++ b/modules/question_types/quiz_page/quiz_page.module
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file quiz_page.module
+ *
+ * Pages are implemented as a question type. They do not affect the score.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function quiz_page_help($path, $args) {
+  if ($path == 'admin/help#quiz_pages') {
+    return t('Provides pages which can contain questions in a quiz.');
+  }
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function quiz_page_quiz_question_info() {
+  return array(
+      'quiz_page' => array(
+          'name'              => t('@quiz page', array('@quiz' => QUIZ_NAME)),
+          'description'       => t('Quiz pages allow you display questions across multiple pages.'),
+          'question provider' => 'QuizPagesQuestion',
+          'response provider' => 'QuizPagesResponse',
+          'module'            => 'quiz_question', // All wrapper functions are in that module.
+      ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function quiz_page_quiz_question_config() {
+  return FALSE;
+}
diff --git a/modules/question_types/quiz_page/tests/QuizPageTestCase.test b/modules/question_types/quiz_page/tests/QuizPageTestCase.test
new file mode 100644
index 0000000..e94d81d
--- /dev/null
+++ b/modules/question_types/quiz_page/tests/QuizPageTestCase.test
@@ -0,0 +1,102 @@
+<?php
+
+class QuizPageTestCase extends QuizQuestionTestCase {
+
+  var $question_node_type = 'quiz_page';
+
+  public static function getInfo() {
+    return array(
+      'name'        => t('Quiz page'),
+      'description' => t('Unit test for quiz pages'),
+      'group'       => t('Quiz'),
+    );
+  }
+
+  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
+    $modules[] = 'quiz_page';
+    $modules[] = 'truefalse';
+    parent::setUp($modules, $admin_permissions, $user_permissions);
+  }
+
+  /**
+   * Test that question parentage saves.
+   */
+  function testQuizPageParentage() {
+    $this->drupalLogin($this->admin);
+
+    // Create Quiz with review of score.
+    $quiz = $this->drupalCreateQuiz();
+
+    // Create the questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 1 body text')))));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 2 body text')))));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 3 body text')))));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    // Create the pages.
+    $page_node1 = $this->drupalCreateNode(array('type' => 'quiz_page'));
+    $this->linkQuestionToQuiz($page_node1, $quiz);
+    $page_node2 = $this->drupalCreateNode(array('type' => 'quiz_page'));
+    $this->linkQuestionToQuiz($page_node2, $quiz);
+
+    // Go to the manage questions form.
+    $this->drupalGet("quiz/$quiz->qid/questions");
+    $post = array(
+      // Make the questions have parents.
+      "qr_pids[{$question_node1->nid}-{$question_node1->vid}]" => 4,
+      "qr_pids[{$question_node2->nid}-{$question_node2->vid}]" => 4,
+      "qr_pids[{$question_node3->nid}-{$question_node3->vid}]" => 5,
+      // Mirror what JS would have done by adjusting the weights.
+      "weights[{$page_node1->nid}-{$page_node1->vid}]"         => 2,
+      "weights[{$question_node1->nid}-{$question_node1->vid}]" => 3,
+      "weights[{$question_node2->nid}-{$question_node2->vid}]" => 4,
+      "weights[{$page_node2->nid}-{$page_node2->vid}]"         => 3,
+      "weights[{$question_node3->nid}-{$question_node3->vid}]" => 4,
+    );
+    $this->drupalPost(NULL, $post, t('Submit'));
+
+    $sql = "SELECT * FROM {quiz_relationship}";
+    $data = db_query($sql)->fetchAllAssoc('qr_id');
+    // Check the relationships properly saved.
+    foreach ($data as $qr_id => $rel) {
+      switch ($qr_id) {
+        case 1:
+        case 2:
+          $this->assertEqual($rel->qr_pid, 4);
+          break;
+        case 3:
+          $this->assertEqual($rel->qr_pid, 5);
+          break;
+        case 4:
+        case 5:
+          $this->assertNull($rel->qr_pid);
+          break;
+      }
+    }
+
+    // Take the quiz. Ensure the pages are correct.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    // Questions 1 and 2 are present. Question 3 is hidden.
+    $this->assertFieldByName("question[{$question_node1->nid}]");
+    $this->assertFieldByName("question[{$question_node2->nid}]");
+    $this->assertNoFieldByName("question[{$question_node3->nid}]");
+    $this->drupalPost(NULL, array(
+      "question[{$question_node1->nid}]" => 1,
+      "question[{$question_node2->nid}]" => 1), t('Next'));
+    // Questions 1 and 2 are gone. Question 3 is present.
+    $this->assertNoFieldByName("question[{$question_node1->nid}]");
+    $this->assertNoFieldByName("question[{$question_node2->nid}]");
+    $this->assertFieldByName("question[{$question_node3->nid}]");
+    $this->drupalPost(NULL, array("question[{$question_node3->nid}]" => 1), t('Finish'));
+
+    // Check that the results page contains all the questions.
+    $this->assertText('You got 3 of 3 possible points.');
+    $this->assertText('TF 1 body text');
+    $this->assertText('TF 2 body text');
+    $this->assertText('TF 3 body text');
+  }
+
+}
diff --git a/modules/question_types/scale/README.txt b/modules/question_types/scale/README.txt
new file mode 100644
index 0000000..264909a
--- /dev/null
+++ b/modules/question_types/scale/README.txt
@@ -0,0 +1,44 @@
+
+/**
+ * @file
+ * README file for Quiz Scale.
+ */
+
+Quiz Scale
+Allows quiz creators to make a special question type for surveys.
+
+Sponsored by: Norwegian Centre for Telemedicine
+Code: falcon
+
+
+CONTENTS
+--------
+
+1.  Introduction
+2.  Installation
+3.  Configuration
+
+----
+1. Introduction
+
+This module is an attempt to make it easy to create simple surveys using the quiz module.
+
+The Scale module lets the quiz creator store preset answer collections, enabling rapid
+creation of survey questions.
+
+----
+2. Installation
+
+To install, unpack the module to your modules folder, and simply enable the module at Admin > Build > Modules.
+
+Several database tables prefixed with quiz_scale are installed to have a separate storage for this module.
+
+----
+3.  Configuration
+Settings are to be found here: admin/quiz/questions_settings
+
+Preset answer collections can be configured at: scale/collection/manage
+Here you can change and delete presets(answer collections).
+
+You can give roles the 'Edit global presets' permission in the permission administration(admin/user/permissions)
+With this permission users can make presets available to all users, and not only themselves.
\ No newline at end of file
diff --git a/modules/question_types/scale/scale.classes.inc b/modules/question_types/scale/scale.classes.inc
new file mode 100644
index 0000000..9b6f492
--- /dev/null
+++ b/modules/question_types/scale/scale.classes.inc
@@ -0,0 +1,638 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * The main classes for the scale question type.
+ *
+ * These extend code found in quiz_question.classes.inc.
+ *
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * Based on:
+ * Other question types in the quiz framework.
+ *
+ *
+ *
+ * @file
+ * Question type, enabling rapid creation of simple surveys using the quiz framework.
+ */
+// @todo: We mix the names answer_collection and alternatives. Use either alternative or answer consistently
+
+/**
+ * Extension of QuizQuestion.
+ */
+class ScaleQuestion extends QuizQuestion {
+
+  // $util will be set to true if an instance of this class is used only as a utility
+  protected $util = FALSE;
+  // (answer)Collection id
+  protected $col_id = NULL;
+
+  /**
+   * Tells the instance that it is beeing used as a utility.
+   *
+   * @param $c_id - answer collection id
+   */
+  public function initUtil($c_id) {
+    $this->util = TRUE;
+    $this->col_id = $c_id;
+  }
+
+  /**
+   * Implementation of saveNodeProperties
+   *
+   * @see QuizQuestion#saveNodeProperties()
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    $is_new_node = $is_new || $this->node->revision == 1;
+    $answer_collection_id = $this->saveAnswerCollection($is_new_node);
+    // Save the answer collection as a preset if the save preset option is checked
+    if (!empty($this->node->save)) {
+      $this->setPreset($answer_collection_id);
+    }
+    if ($is_new_node) {
+      $id = db_insert('quiz_scale_node_properties')
+        ->fields(array(
+          'nid' => $this->node->nid,
+          'vid' => $this->node->vid,
+          'answer_collection_id' => $answer_collection_id,
+        ))
+        ->execute();
+    }
+    else {
+      db_update('quiz_scale_node_properties')
+        ->fields(array(
+          'answer_collection_id' => $answer_collection_id,
+        ))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+  }
+
+  /**
+   * Add a preset for the current user.
+   *
+   * @param $col_id - answer collection id of the collection this user wants to have as a preset
+   */
+  private function setPreset($col_id) {
+    db_merge('quiz_scale_user')
+      ->key(array(
+        'uid' => $GLOBALS['user']->uid,
+        'answer_collection_id' => $col_id
+      ))
+      ->fields(array(
+        'uid' => $GLOBALS['user']->uid,
+        'answer_collection_id' => $col_id
+      ))
+      ->execute();
+  }
+
+  /**
+   * Stores the answer collection to the database, or identifies an existing collection.
+   *
+   * We try to reuse answer collections as much as possible to minimize the amount of rows in the database,
+   * and thereby improving performance when surveys are beeing taken.
+   *
+   * @param $is_new_node - the question is beeing inserted(not updated)
+   * @param $alt_input - the alternatives array to be saved.
+   * @param $preset - 1 | 0 = preset | not preset
+   * @return
+   *  Answer collection id
+   */
+  public function saveAnswerCollection($is_new_node, array $alt_input = NULL, $preset = NULL) {
+    global $user;
+    if (!isset($alt_input)) {
+      $alt_input = get_object_vars($this->node);
+    }
+    if (!isset($preset) && isset($this->node->save)) {
+      $preset = $this->node->save;
+    }
+
+    $alternatives = array();
+    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
+      if (isset($alt_input['alternative' . $i]) && drupal_strlen($alt_input['alternative' . $i]) > 0) {
+        $alternatives[] = $alt_input['alternative' . $i];
+      }
+    }
+
+    // If an identical answer collection already exists
+    if ($answer_collection_id = $this->existingCollection($alternatives)) {
+      if ($preset == 1) {
+        $this->setPreset($answer_collection_id);
+      }
+      if (!$is_new_node || $this->util) {
+        $col_to_delete = $this->util ? $this->col_id : $this->node->{0}->answer_collection_id;
+        if ($col_to_delete != $answer_collection_id) {
+          // We try to delete the old answer collection
+          $this->deleteCollectionIfNotUsed($col_to_delete, 1);
+        }
+      }
+      return $answer_collection_id;
+    }
+    // Register a new answer collection
+    $answer_collection_id = db_insert('quiz_scale_answer_collection')
+      ->fields(array('for_all' => 1))
+      ->execute();
+
+    // Save as preset if checkbox for preset has been checked
+    if ($preset == 1) {
+      $id = db_insert('quiz_scale_user')
+        ->fields(array(
+          'uid' => $user->uid,
+          'answer_collection_id' => $answer_collection_id,
+        ))
+        ->execute();
+    }
+    // Save the alternatives in the answer collection
+    //db_lock_table('quiz_scale_answer');
+    for ($i = 0; $i < count($alternatives); $i++) {
+      $this->saveAlternative($alternatives[$i], $answer_collection_id);
+    }
+    //db_unlock_tables();
+    return $answer_collection_id;
+  }
+
+  /**
+   * Saves one alternative to the database
+   *
+   * @param $alternative - the alternative(String) to be saved.
+   * @param $answer_collection_id - the id of the answer collection this alternative shall belong to.
+   */
+  private function saveAlternative($alternative, $answer_collection_id) {
+    $id = db_insert('quiz_scale_answer')
+      ->fields(array(
+        'answer_collection_id' => $answer_collection_id,
+        'answer' => $alternative,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Deletes an answer collection if it isn't beeing used.
+   *
+   * @param $answer_collection_id
+   * @param $accept
+   *  If collection is used more than this many times we keep it.
+   * @return
+   *  true if deleted, false if not deleted.
+   */
+  public function deleteCollectionIfNotUsed($answer_collection_id, $accept = 0) {
+    // Check if the collection is someones preset. If it is we can't delete it.
+    $count = db_query('SELECT COUNT(*) FROM {quiz_scale_user} WHERE answer_collection_id = :acid', array(':acid' => $answer_collection_id))->fetchField();
+    if ($count > 0) {
+      return FALSE;
+    }
+
+    // Check if the collection is a global preset. If it is we can't delete it.
+    $for_all = db_query('SELECT for_all FROM {quiz_scale_answer_collection} WHERE id = :id', array(':id' => $answer_collection_id))->fetchField();
+    if ($for_all == 1) {
+      return FALSE;
+    }
+
+    // Check if the collection is used in an existing question. If it is we can't delete it.
+    $count = db_query('SELECT COUNT(*) FROM {quiz_scale_node_properties} WHERE answer_collection_id = :acid', array(':acid' => $answer_collection_id))->fetchField();
+
+    // We delete the answer collection if it isnt beeing used by enough questions
+    if ($count <= $accept) {
+      db_delete('quiz_scale_answer_collection')
+        ->condition('id', $answer_collection_id)
+        ->execute();
+
+      db_delete('quiz_scale_answer')
+        ->condition('answer_collection_id', $answer_collection_id)
+        ->execute();
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Finds out if a collection already exists.
+   *
+   * @param $alternatives
+   *  This is the collection that will be compared with the database.
+   * @param $answer_collection_id
+   *  If we are matching a set of alternatives with a given collection that exists in the database.
+   * @param $last_id - The id of the last alternative we compared with.
+   * @return
+   *  TRUE if the collection exists
+   *  FALSE otherwise
+   */
+  private function existingCollection(array $alternatives, $answer_collection_id = NULL, $last_id = NULL) {
+    $my_alts = isset($answer_collection_id) ? $alternatives : array_reverse($alternatives);
+
+    // Find all answers identical to the next answer in $alternatives
+    $sql = 'SELECT id, answer_collection_id FROM {quiz_scale_answer} WHERE answer = :answer';
+    $args[':answer'] = array_pop($my_alts);
+    // Filter on collection id
+    if (isset($answer_collection_id)) {
+      $sql .= ' AND answer_collection_id = :acid';
+      $args[':acid'] = $answer_collection_id;
+    }
+    // Filter on alternative id(If we are investigating a specific collection, the alternatives needs to be in a correct order)
+    if (isset($last_id)) {
+      $sql .= ' AND id = :id';
+      $args[':id'] = $last_id + 1;
+    }
+    $res = db_query($sql, $args);
+    if (!$res_o = $res->fetch()) {
+      return FALSE;
+    }
+
+    /*
+     * If all alternatives has matched make sure the collection we are comparing against in the database
+     * doesn't have more alternatives.
+     */
+    if (count($my_alts) == 0) {
+      $res_o2 = db_query('SELECT * FROM {quiz_scale_answer}
+              WHERE answer_collection_id = :answer_collection_id
+              AND id = :id', array(':answer_collection_id' => $answer_collection_id, ':id' => ($last_id + 2)))->fetch();
+      return ($res_o2) ? FALSE : $answer_collection_id;
+    }
+
+    // Do a recursive call to this function on all answer collection candidates
+    do {
+      $col_id = $this->existingCollection($my_alts, $res_o->answer_collection_id, $res_o->id);
+      if ($col_id) {
+        return $col_id;
+      }
+    } while ($res_o = $res->fetch());
+    return FALSE;
+  }
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validate()
+   */
+  public function validateNode(array &$form) {
+
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete()
+   */
+  public function delete($only_this_version = FALSE) {
+    parent::delete($only_this_version);
+    if ($only_this_version) {
+      db_delete('quiz_scale_user_answers')
+        ->condition('question_nid', $this->node->nid)
+        ->condition('question_vid', $this->node->vid)
+        ->execute();
+
+      db_delete('quiz_scale_node_properties')
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+    else {
+      db_delete('quiz_scale_user_answers')
+        ->condition('question_nid', $this->node->nid)
+        ->execute();
+
+      db_delete('quiz_scale_node_properties')
+        ->condition('nid', $this->node->nid)
+        ->execute();
+    }
+    $this->deleteCollectionIfNotUsed($this->node->{0}->answer_collection_id, 0);
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+
+    $res = db_query('SELECT id, answer, a.answer_collection_id
+            FROM {quiz_scale_node_properties} p
+            JOIN {quiz_scale_answer} a ON (p.answer_collection_id = a.answer_collection_id)
+            WHERE nid = :nid AND vid = :vid
+            ORDER BY a.id', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+    foreach ($res as $res_o) {
+      $props[] = $res_o;
+    }
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Implementation of getNodeView
+   *
+   * @see QuizQuestion#view()
+   */
+  public function getNodeView() {
+    $content = parent::getNodeView();
+    $alternatives = array();
+    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
+      if (isset($this->node->{$i}->answer) && drupal_strlen($this->node->{$i}->answer) > 0) {
+        $alternatives[] = check_plain($this->node->{$i}->answer);
+      }
+    }
+    $content['answer'] = array(
+      '#markup' => theme('scale_answer_node_view', array('alternatives' => $alternatives)),
+      '#weight' => 2,
+    );
+    return $content;
+  }
+
+  /**
+   * Implementation of getAnsweringForm
+   *
+   * @see getAnsweringForm($form_state, $result_id)
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $form = parent::getAnsweringForm($form_state, $result_id);
+    //$form['#theme'] = 'scale_answering_form';
+    $options = array();
+    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
+      if (isset($this->node->{$i}) && drupal_strlen($this->node->{$i}->answer) > 0) {
+        $options[$this->node->{$i}->id] = check_plain($this->node->{$i}->answer);
+      }
+    }
+
+    $form = array(
+      '#type' => 'radios',
+      '#title' => t('Choose one'),
+      '#options' => $options,
+    );
+    if (isset($result_id)) {
+      $response = new ScaleResponse($result_id, $this->node);
+      $form['#default_value'] = $response->getResponse();
+    }
+    return $form;
+  }
+
+  /**
+   * Question response validator.
+   */
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+    if ($form_state['values']['question'][$this->node->nid] == '') {
+      form_set_error('', t('You must provide an answer.'));
+    }
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm()
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    $form = array();
+    /*
+     * Getting presets from the database
+     */
+    $collections = $this->getPresetCollections(TRUE);
+    $options = $this->makeOptions($collections);
+    $options['d'] = '-'; // Default
+    // We need to add the available preset collections as javascript so that the alternatives can be populated instantly
+    // when a
+    $jsArray = $this->makeJSArray($collections);
+
+    $form['answer'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Answer'),
+      '#description' => t('Provide alternatives for the user to answer.'),
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+      '#weight' => -4,
+    );
+    $form['answer']['#theme'][] = 'scale_creation_form';
+    $form['answer']['presets'] = array(
+      '#type' => 'select',
+      '#title' => t('Presets'),
+      '#options' => $options,
+      '#default_value' => 'd',
+      '#description' => t('Select a set of alternatives'),
+      '#attributes' => array('onchange' => 'refreshAlternatives(this)'),
+    );
+    $max_num_alts = variable_get('scale_max_num_of_alts', 10);
+    // TODO: use drupal_add_js($path, 'settings');
+    $form['jsArray'] = array('#markup' => "<script type='text/javascript'>$jsArray var scale_max_num_of_alts = $max_num_alts;</script>");
+    $form['answer']['alternatives'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Alternatives'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    for ($i = 0; $i < $max_num_alts; $i++) {
+      $form['answer']['alternatives']["alternative$i"] = array(
+        '#type' => 'textfield',
+        '#title' => t('Alternative !i', array('!i' => ($i + 1))),
+        '#size' => 60,
+        '#maxlength' => 256,
+        '#default_value' => isset($this->node->{$i}->answer) ? $this->node->{$i}->answer : '',
+        '#required' => $i < 2,
+      );
+    }
+    $form['answer']['alternatives']['save'] = array(// @todo: Rename save to save_as_preset or something
+      '#type' => 'checkbox',
+      '#title' => t('Save as a new preset'),
+      '#description' => t('Current alternatives will be saved as a new preset'),
+      '#default_value' => FALSE,
+    );
+    $form['answer']['manage'] = array('#markup' => l(t('Manage presets'), 'scale/collection/manage'));
+    return $form;
+  }
+
+  /**
+   * Get all available presets for the current user.
+   *
+   * @param $with_defaults
+   * @return
+   *  array holding all the preset collections as an array of objects.
+   *  each object in the array has the following properties:
+   *   ->alternatives(array)
+   *   ->name(string)
+   *   ->for_all(int, 0|1)
+   */
+  public function getPresetCollections($with_defaults = FALSE) {
+    global $user;
+    $collections = array(); // array holding data for each collection
+    $scale_element_names = array();
+    $sql = 'SELECT DISTINCT ac.id AS answer_collection_id, a.answer, ac.for_all
+            FROM {quiz_scale_user} au
+            JOIN {quiz_scale_answer_collection} ac ON(au.answer_collection_id = ac.id)
+            JOIN {quiz_scale_answer} a ON(a.answer_collection_id = ac.id)
+            WHERE au.uid = :uid';
+    if ($with_defaults) {
+      $sql .= ' OR ac.for_all = 1';
+    }
+    $sql .= ' ORDER BY au.answer_collection_id, a.id';
+    $res = db_query($sql, array(':uid' => $user->uid));
+    $col_id = NULL;
+
+    // Populate the $collections array
+    while (true) {
+      if (!($res_o = $res->fetch()) || ($res_o->answer_collection_id != $col_id)) {
+        /*
+         * We have gone through all elements for one answer collection,
+         * and needs to store the answer collection name and id in the options array...
+         */
+        if (isset($col_id)) {
+          $num_scale_elements = count($collections[$col_id]->alternatives);
+          $collections[$col_id]->name = check_plain($collections[$col_id]->alternatives[0] . ' - ' . $collections[$col_id]->alternatives[$num_scale_elements - 1] . ' (' . $num_scale_elements . ')');
+        }
+        // Break the loop if there are no more answer collections to process
+        if (!$res_o) {
+          break;
+        }
+
+        // Init the next collection in the $collections array
+        $col_id = $res_o->answer_collection_id;
+        if (!isset($collections[$col_id])) {
+          $collections[$col_id] = new stdClass();
+          $collections[$col_id]->alternatives = array();
+          $collections[$col_id]->for_all = $res_o->for_all;
+        }
+      }
+      $collections[$col_id]->alternatives[] = check_plain($res_o->answer);
+    }
+    return $collections;
+  }
+
+  /**
+   * Makes options array for form elements.
+   *
+   * @param $collections
+   *  collections array, from getPresetCollections() for instance...
+   * @return
+   *  #options array.
+   */
+  private function makeOptions(array $collections = NULL) {
+    $options = array();
+    foreach ($collections as $col_id => $obj) {
+      $options[$col_id] = $obj->name;
+    }
+    return $options;
+  }
+
+  /**
+   * Makes a javascript constructing an answer collection array.
+   *
+   * @param $collections
+   *  collections array, from getPresetCollections() for instance...
+   * @return
+   *  javascript(string)
+   */
+  private function makeJSArray(array $collections = NULL) {
+    $jsArray = 'scaleCollections = new Array();';
+    foreach ($collections as $col_id => $obj) {
+      if (is_array($collections[$col_id]->alternatives)) {
+        $jsArray .= "scaleCollections[$col_id] = new Array();";
+        foreach ($collections[$col_id]->alternatives as $alt_id => $text) {
+          $jsArray .= "scaleCollections[$col_id][$alt_id] = '" . check_plain($text) . "';";
+        }
+      }
+    }
+    return $jsArray;
+  }
+
+  /**
+   * Implementation of getMaximumScore.
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    // In some use-cases we want to reward users for answering a survey question.
+    // This is why 1 is returned and not zero.
+    return 1;
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class ScaleResponse extends QuizQuestionResponse {
+
+  /**
+   * ID of the answer.
+   */
+  protected $answer_id = 0;
+
+  /**
+   * Constructor
+   */
+  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
+    parent::__construct($result_id, $question_node, $answer);
+
+    if (isset($answer)) {
+      $this->answer_id = intval($answer);
+    }
+    else {
+      $this->answer_id = db_query('SELECT answer_id FROM {quiz_scale_user_answers} WHERE result_id = :rid AND question_nid = :qnid AND question_vid = :qvid', array(':rid' => $result_id, ':qnid' => $this->question->nid, ':qvid' => $this->question->vid))->fetchField();
+    }
+    $answer = db_query('SELECT answer FROM {quiz_scale_answer} WHERE id = :id', array(':id' => $this->answer_id))->fetchField();
+    $this->answer = check_plain($answer);
+  }
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+    $id = db_insert('quiz_scale_user_answers')
+      ->fields(array(
+        'answer_id' => $this->answer_id,
+        'result_id' => $this->result_id,
+        'question_vid' => $this->question->vid,
+        'question_nid' => $this->question->nid,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+    db_delete('quiz_scale_user_answers')
+      ->condition('result_id', $this->result_id)
+      ->condition('question_nid', $this->question->nid)
+      ->condition('question_vid', $this->question->vid)
+      ->execute();
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    return $this->isValid() ? 1 : 0;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer_id;
+  }
+
+  /**
+   * Implmenets QuizQuestionResponse::getReportFormResponse().
+   */
+  public function getReportFormResponse() {
+    $data = array();
+    $data[] = array(
+      'choice' => $this->answer,
+    );
+    return $data;
+  }
+
+}
diff --git a/modules/question_types/scale/scale.info b/modules/question_types/scale/scale.info
new file mode 100644
index 0000000..1691037
--- /dev/null
+++ b/modules/question_types/scale/scale.info
@@ -0,0 +1,9 @@
+name = Scale
+package = Quiz Question
+description = Scale question type for quiz.
+core = 7.x
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = scale.classes.inc
+files[] = tests/ScaleTestCase.test
diff --git a/modules/question_types/scale/scale.install b/modules/question_types/scale/scale.install
new file mode 100644
index 0000000..ab544ab
--- /dev/null
+++ b/modules/question_types/scale/scale.install
@@ -0,0 +1,193 @@
+<?php
+
+/**
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * @file
+ * Scale Install (a quiz question type)
+ */
+
+
+/**
+ * Implements hook_install().
+ */
+function scale_install() {
+  // Add body field to scale node
+  quiz_question_add_body_field('scale');
+
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_install_schema('scale')
+  _scale_insert_collection(array('Always', 'Very often', 'Some times', 'Rarely', 'Very rarely', 'Never'));
+  _scale_insert_collection(array('Excellent', 'Very good', 'Good', 'Ok', 'Poor', 'Very poor'));
+  _scale_insert_collection(array('Totally agree', 'Agree', 'Not sure', 'Disagree', 'Totally disagree'));
+  _scale_insert_collection(array('Very important', 'Important', 'Moderately important', 'Less important', 'Least important'));
+  variable_set('node_options_scale', array('status'));
+}
+
+/**
+ * Implements hook_schema().
+ */
+function scale_schema() {
+  $schema['quiz_scale_node_properties'] = array(
+    'description' => 'Properties specific to this question type. Holds information about what answer collection this node uses',
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer_collection_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'nid', 'vid',
+    ),
+  );
+  // Stores the users answers to a question.
+  $schema['quiz_scale_user_answers'] = array(
+    'description' => 'Store the users answers',
+    'fields' => array(
+      'answer_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'result_id', 'question_nid', 'question_vid'
+    ),
+  );
+
+  $schema['quiz_scale_answer_collection'] = array(
+    'description' => 'Answer collection ids and properties',
+    'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'for_all' => array(
+        'description' => '1 for global presets, 0 otherwise',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array(
+      'id',
+    ),
+  );
+
+  $schema['quiz_scale_user'] = array(
+    'description' => 'User settings, store what answer collections the user have as presets',
+    'fields' => array(
+      'uid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer_collection_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array(
+      'uid', 'answer_collection_id'
+    ),
+  );
+  /*
+   * Holds each answer in the answer_collections.
+   */
+  $schema['quiz_scale_answer'] = array(
+    'description' => 'Holds all the possible answers and what answer collections they belong to',
+    'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer_collection_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer' => array(
+        'type' => 'text',
+      ),
+    ),
+    'primary key' => array(
+      'id',
+    ),
+  );
+  return $schema;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function scale_uninstall() {
+  variable_del('scale_max_num_of_alts');
+  cache_clear_all('variables', 'cache');
+  drupal_set_message(t('The Scale module has been uninstalled. Scale nodes may still exist, but they will not function properly.'));
+}
+
+/**
+ * Inserts answer collections into the database
+ *
+ * @param $answers
+ *  Array of answers to include in the answer collection
+ */
+function _scale_insert_collection($answers) {
+  // Save the collection as a global preset
+  $collection_id = db_insert('quiz_scale_answer_collection')
+    ->fields(array(
+      'for_all' => 1
+    ))
+    ->execute();
+
+  // Save the collections as a preset collection for user 1
+  db_insert('quiz_scale_user')
+    ->fields(array(
+      'answer_collection_id' => $collection_id,
+      'uid' => 1,
+    ))
+    ->execute();
+
+  $insert = db_insert('quiz_scale_answer')->fields(array('answer_collection_id', 'answer'));
+  $values = array();
+  // Save the answers belonging to the collection
+  for ($i = 0; $i < count($answers); $i++) {
+    $values = array(
+      'answer_collection_id' => $collection_id,
+      'answer' => $answers[$i],
+    );
+    $insert->values($values);
+  }
+  $insert->execute();
+}
diff --git a/modules/question_types/scale/scale.js b/modules/question_types/scale/scale.js
new file mode 100644
index 0000000..2e9e8b4
--- /dev/null
+++ b/modules/question_types/scale/scale.js
@@ -0,0 +1,28 @@
+/**
+ * @file
+ * Javascript functions for the scale question type.
+ */
+
+/**
+ * Refreshes alternatives when a preset is selected.
+ *
+ * @param selection
+ *  The select item used to select answer collection
+ */
+function refreshAlternatives(selection) {
+  clearAlternatives();
+  var colId = selection.options[selection.selectedIndex].value;
+  var numberOfOptions = scaleCollections[colId].length;
+  for(var i = 0; i<numberOfOptions;i++){
+	jQuery('#edit-alternative' + (i)).val(scaleCollections[colId][i]);
+  }
+}
+
+/**
+ * Clears all the alternatives on the scale node form
+ */
+function clearAlternatives() {
+  for ( var i = 0; i < scale_max_num_of_alts; i++) {
+	jQuery('#edit-alternative' + (i)).val('');
+  }
+}
diff --git a/modules/question_types/scale/scale.module b/modules/question_types/scale/scale.module
new file mode 100644
index 0000000..fb98e21
--- /dev/null
+++ b/modules/question_types/scale/scale.module
@@ -0,0 +1,391 @@
+<?php
+
+/**
+ * The main file for scale.
+ *
+ * Sponsored by: Norwegian Centre for Telemedicine
+ * Code: falcon
+ *
+ * @file
+ * Scale question type for the Quiz module.
+ *
+ * Allows the creation of scale questions (ex: likert scale)
+ */
+
+/**
+ * Implements hook_help().
+ */
+function scale_help($path, $args) {
+  if ($path == 'admin/help#scale') {
+    return t('This module provides a scale question type for Quiz. It may be used to construct surveys.');
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function scale_menu() {
+  $items['scale/collection/manage'] = array(
+    'title' => 'Manage your preset collections',
+    'access callback' => 'node_access',
+    'access arguments' => array('create', 'scale'),
+    'type' => MENU_SUGGESTED_ITEM,
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('scale_manage_collection_form'),
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function scale_theme($existing, $type, $theme, $path) {
+  $module_path = drupal_get_path('module', 'scale');
+  return array(
+    'scale_creation_form' => array(
+      'render element' => 'form',
+      'path' =>  $module_path . '/theme',
+      'file' => 'scale.theme.inc',
+    ),
+    'scale_response_form' => array(
+      'render element' => 'form',
+      'path' => $module_path . '/theme',
+      'file' => 'scale.theme.inc',
+    ),
+    'scale_answer_node_view' => array(
+      'variables' => array(
+        'alternatives' => NULL,
+      ),
+      'path' => $module_path . '/theme',
+      'file' => 'scale.theme.inc',
+    ),
+    'scale_answering_form' => array(
+      'render element' => 'form',
+      'path' => $module_path . '/theme',
+      'template' => 'scale-answering-form',
+    ),
+  );
+}
+
+/**
+ * Implements hook_permission().
+ */
+function scale_permission() {
+  return array(
+    'Edit global presets' => array(
+      'title' => t('Administer presets'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function scale_quiz_question_info() {
+  return array(
+    'scale' => array(
+      'name' => t('Scale question'),
+      'description' => t('Quiz questions that allow a user to choose from a scale.'),
+      'question provider' => 'ScaleQuestion',
+      'response provider' => 'ScaleResponse',
+      'module' => 'quiz_question', // All wrapper functions are in that module.
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function scale_quiz_question_config() {
+  $form['manage'] = array('#markup' => l(t('Manage presets'), 'scale/collection/manage'));
+  $form['scale_max_num_of_alts'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Maximum number of alternatives allowed'),
+    '#default_value' => variable_get('scale_max_num_of_alts', 10),
+  );
+  $form['#validate'][] = 'scale_config_validate';
+  return $form;
+}
+
+/**
+ * Validate the scale config form values
+ */
+function scale_config_validate($form, $form_state) {
+  if (!_quiz_is_int($form_state['values']['scale_max_num_of_alts'], 2, 50)) {
+    form_set_error('scale_max_num_of_alts', t('The number of alternatives must be between 2 and 50'));
+  }
+}
+
+/**
+ * Implements hook_get_answer().
+ */
+function scale_get_answer($question_nid, $question_vid, $result_id) {
+  $results = db_query('SELECT answer_id, question_vid, question_nid, result_id
+    FROM {quiz_scale_user_answers}
+    WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(':qnid' => $question_nid, ':qvid' => $question_vid, ':rid' => $result_id))->fetchAssoc();
+  return $results ? $results : FALSE;
+}
+
+/**
+ * Implements hook_user_cancel().
+ */
+function scale_user_cancel($edit, $account, $method) {
+  $results = db_query('SELECT id FROM {quiz_scale_answer_collection} ac
+              JOIN {quiz_scale_user} u ON(ac.id = u.answer_collection_id)
+              WHERE uid = :uid
+              AND ac.for_all = :for_all
+              AND ac.id NOT IN
+                (SELECT answer_collection_id
+                 FROM {quiz_scale_node_properties})
+              AND ac.id NOT IN
+                (SELECT answer_collection_id
+                 FROM {quiz_scale_user}
+                 WHERE NOT uid = :uid)', array(':uid' => $account->uid, ':for_all' => 0));
+  foreach ($results as $result) {
+    db_delete('quiz_scale_answer')
+      ->condition('answer_collection_id', $result->id)
+      ->execute();
+    db_delete('quiz_scale_answer_collection')
+      ->condition('id', $result->id)
+      ->execute();
+  }
+
+  db_delete('quiz_scale_user')
+    ->condition('uid', $account->uid)
+    ->execute();
+}
+
+/**
+ * Form for changing and deleting the current users preset answer collections.
+ *
+ * Users with the Edit global presets permissions can also add new global
+ * presets here.
+ *
+ * @param $form_state
+ * @return form
+ */
+function scale_manage_collection_form($form, &$form_state) {
+  global $user;
+
+  $edit_globals_access = user_access('Edit global presets');
+  // We create an instance of ScaleQuestion. We want to use some of its methods.
+  $scale_question = new ScaleQuestion(new stdClass());
+  $collections = $scale_question->getPresetCollections($edit_globals_access);
+
+  // If user is allowed to edit global answer collections he is also allowed to add new global presets
+  if ($edit_globals_access) {
+    $new_col = new stdClass();
+    $new_col->for_all = 1;
+    $new_col->name = t('New global collection(available to all users)');
+    $collections['new'] = $new_col;
+  }
+
+  $form = array();
+  if (count($collections) == 0) {
+    $form['no_col'] = array(
+      '#markup' => t("You don't have any preset collections."),
+    );
+    return $form;
+  }
+  // Populate the form
+  foreach ($collections as $col_id => $obj) {
+    $form["collection$col_id"] = array(
+      '#type' => 'fieldset',
+      '#title' => $collections[$col_id]->name,
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'scale_manage_collection_form',
+    );
+    $alternatives = isset($collections[$col_id]->alternatives) ? $collections[$col_id]->alternatives : array();
+    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
+      $form["collection$col_id"]["alternative$i"] = array(
+        '#title' => t('Alternative !i', array('!i' => ($i + 1))),
+        '#size' => 60,
+        '#maxlength' => 256,
+        '#type' => 'textfield',
+        '#default_value' => isset($alternatives[$i]) ? $alternatives[$i] : '',
+        '#required' => (($i < 2) && $col_id != 'new'),
+      );
+    }
+    if ($col_id != 'new') {
+      if ($edit_globals_access) {
+        $form["collection$col_id"]['for_all'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Available to all users'),
+          '#default_value' => $collections[$col_id]->for_all,
+        );
+      }
+      $form["collection$col_id"]['to-do'] = array(
+        '#type' => 'radios',
+        '#title' => t('What will you do?'),
+        '#default_value' => '0',
+        '#options' => array(
+          t('Save changes, do not change questions using this preset'),
+          t('Save changes, and change your own questions who uses this preset'),
+          t('Delete this preset(This will not affect existing questions)')),
+      );
+    }
+    else {
+      $form["collection$col_id"]["to-do"] = array(
+        '#type' => 'value',
+        '#value' => 3,
+      );
+      $form["collection$col_id"]["for_all"] = array(
+        '#type' => 'value',
+        '#value' => 1,
+      );
+    }
+  }
+  $form['process'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+  $form['#submit'][] = 'scale_collection_form_submit';
+  if ($edit_globals_access) {
+    $form['#validate'][] = 'scale_collection_form_validate';
+  }
+  $form['#tree'] = TRUE;
+  return $form;
+}
+
+/**
+ * Handles the scale collection form.
+ *
+ * @param $form
+ * @param $form_state
+ */
+function scale_collection_form_submit($form, &$form_state) {
+  global $user;
+
+  $changed = 0;
+  $deleted = 0;
+
+  foreach ($form_state['values'] as $key => $alternatives) {
+    if ($col_id = _scale_get_col_id($key)) {
+      $s_q = new ScaleQuestion(new stdClass());
+      $s_q->initUtil($col_id);
+      switch ($alternatives['to-do']) { // @todo: Rename to-do to $op
+        case 0: //Save, but don't change
+        case 1: //Save and change existing questions
+          $new_col_id = $s_q->saveAnswerCollection(FALSE, $alternatives, 1);
+          if (isset($alternatives['for_all'])) {
+            _scale_set_for_all($new_col_id, $alternatives['for_all']);
+          }
+          if ($new_col_id == $col_id) {
+            break;
+          }
+          $changed++;
+          // We save the changes, but don't change existing questions
+          if ($alternatives['to-do'] == 0) {
+            // The old version of the collection shall not be a preset anymore
+            _scale_unpreset_collection($col_id, $user->uid);
+            // If the old version of the collection doesn't belong to any questions it is safe to delete it.
+            $s_q->deleteCollectionIfNotUsed($col_id);
+
+            if (isset($alternatives['for_all'])) {
+              _scale_set_for_all($new_col_id, $alternatives['for_all']);
+            }
+          }
+          elseif ($alternatives['to-do'] == 1) {
+            // Update all the users questions where the collection is used
+            $nids = db_query('SELECT nid FROM {node} WHERE uid = :uid', array(':uid' => 1))->fetchCol();
+            db_update('quiz_scale_node_properties')
+              ->fields(array('answer_collection_id' => $new_col_id))
+              ->condition('answer_collection_id', $nids, 'IN')
+              ->execute();
+
+            db_delete('quiz_scale_user')
+              ->condition('answer_collection_id', $col_id)
+              ->condition('uid', $user->uid)
+              ->execute();
+            $s_q->deleteCollectionIfNotUsed($col_id);
+          }
+          break;
+        case 2: //Delete
+          $got_deleted = $s_q->deleteCollectionIfNotUsed($col_id);
+          if (!$got_deleted) {
+            _scale_unpreset_collection($col_id, $user->uid);
+          }
+          $deleted++;
+          break;
+        case 3: //New
+          if (drupal_strlen($alternatives['alternative0']) > 0) {
+            $new_col_id = $s_q->saveAnswerCollection(FALSE, $alternatives, 1);
+            _scale_set_for_all($new_col_id, $alternatives['for_all']);
+            drupal_set_message(t('New preset has been added'));
+          }
+          break;
+      }
+    }
+  }
+  if ($changed > 0) {
+    drupal_set_message(t('!changed presets have been changed.', array('!changed' => $changed)));
+  }
+  if ($deleted > 0) {
+    drupal_set_message(t('!deleted presets have been deleted.', array('!deleted' => $deleted)));
+  }
+}
+
+/**
+ * Validates the scale collection form
+ */
+function scale_collection_form_validate($form, &$form_state) {
+  // If the user is trying to create a new collection
+  if (drupal_strlen($form_state['values']['collectionnew']['alternative0']) > 0) {
+    // If the new collection don't have two alternatives
+    if (drupal_strlen($form_state['values']['collectionnew']['alternative1']) == 0) {
+      // This can't be replaced by adding #required to the form elements. If we did so we would always have to
+      // create a new collection when we press submit
+      form_set_error('collectionnew][alternative1', t('New preset must have atleast two alternatives.'));
+    }
+  }
+}
+
+/**
+ * Searches a string for the answer collection id
+ *
+ * @param $string
+ * @return answer collection id
+ */
+function _scale_get_col_id($string) {
+  $res = array();
+  $success = preg_match('/^collection([0-9]{1,}|new)$/', $string, $res);
+  return ($success > 0) ? $res[1] : FALSE;
+}
+
+/**
+ * Make sure an answer collection isn't a preset for a given user.
+ *
+ * @param $col_id
+ *  Answer_collection_id
+ * @param $user_id
+ */
+function _scale_unpreset_collection($col_id, $user_id) {
+  db_delete('quiz_scale_user')
+    ->condition('answer_collection_id', $col_id)
+    ->condition('uid', $user_id)
+    ->execute();
+  if (user_access('Edit global presets')) {
+    db_update('quiz_scale_answer_collection')
+      ->fields(array('for_all' =>  0))
+      ->execute();
+  }
+}
+
+/**
+ * Make an answer collection (un)available for all question creators.
+ *
+ * @param $new_col_id
+ *  Answer collection id
+ * @param $for_all
+ *  0 if not for all,
+ *  1 if for all
+ */
+function _scale_set_for_all($new_col_id, $for_all) {
+  db_update('quiz_scale_answer_collection')
+    ->fields(array(
+      'for_all' => $for_all,
+    ))
+    ->condition('id', $new_col_id)
+    ->execute();
+}
diff --git a/modules/question_types/scale/tests/ScaleTestCase.test b/modules/question_types/scale/tests/ScaleTestCase.test
new file mode 100644
index 0000000..bd491e9
--- /dev/null
+++ b/modules/question_types/scale/tests/ScaleTestCase.test
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * Test class for scale questions.
+ */
+class ScaleTestCase extends QuizQuestionTestCase {
+
+  var $question_node_type = 'scale';
+
+  /**
+   * The getInfo() method provides information about the test.
+   * In order for the test to be run, the getInfo() method needs
+   * to be implemented.
+   */
+  public static function getInfo() {
+    return array(
+        'name'        => t('Scale'),
+        'description' => t('Unit test for scale question type.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  /**
+   * Implementing setUp() to enable scale module testing
+   */
+  function setUp() {
+    parent::setUp(array('scale'));
+  }
+
+  /**
+   * Create a new question with default settings.
+   */
+  public function testCreateQuizQuestion() {
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    $settings = array();
+    $settings['type'] = 'scale';
+
+    $question_node = $this->drupalCreateNode(array(
+        'type'         => 'scale',
+        'title'        => 'Scale 1 title',
+        'alternative0' => 'This is alternative 1',
+        'alternative1' => 'This is alternative 2',
+        'alternative2' => 'This is alternative 3',
+        'alternative3' => 'This is alternative 4',
+        'alternative4' => 'This is alternative 5',
+        'body'         => array(LANGUAGE_NONE => array(array('value' => 'Scale 1 body text'))),
+    ));
+
+    return $question_node;
+  }
+
+  function testTakeQuestion() {
+    $question_node = $this->testCreateQuizQuestion();
+
+    // Link the question.
+    $quiz = $this->linkQuestionToQuiz($question_node);
+
+    // Test that question appears in lists.
+    $this->drupalGet("quiz/$quiz->qid/questions");
+    $this->assertText('Scale 1 title');
+
+    // Login as non-admin.
+    $this->drupalLogin($this->user);
+
+    // Take the quiz.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->assertNoText('Scale 1 title');
+    $this->assertText('Scale 1 body text');
+    $this->assertText('This is alternative 1');
+    $this->assertText('This is alternative 2');
+
+    // Test validation.
+    $this->drupalPost(NULL, array(), t('Finish'));
+    $this->assertText('You must provide an answer.');
+
+    // Test any question.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    // Alternative #2 is actually 24 (because of the presets).
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 24), t('Finish'));
+    $this->assertText('You got 1 of 1 possible points.');
+  }
+
+}
diff --git a/modules/question_types/scale/theme/scale-answering-form.tpl.php b/modules/question_types/scale/theme/scale-answering-form.tpl.php
new file mode 100644
index 0000000..2ff1c2c
--- /dev/null
+++ b/modules/question_types/scale/theme/scale-answering-form.tpl.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Handles the layout of the scale answering form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render($form);
+
+?>
\ No newline at end of file
diff --git a/modules/question_types/scale/theme/scale.theme.inc b/modules/question_types/scale/theme/scale.theme.inc
new file mode 100644
index 0000000..27305ff
--- /dev/null
+++ b/modules/question_types/scale/theme/scale.theme.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * The theme file for scale.
+ *
+ * @file
+ * Theming functions for the scale question type.
+ */
+
+/**
+ * Theme function for the answer part of the node view
+ *
+ * @param $alternatives
+ *  Array of alternatives as text
+ */
+function theme_scale_answer_node_view($variables) {
+  $alternatives = $variables['alternatives'];
+  $header = array(t('Alternatives'));
+  $rows = array();
+  foreach ($alternatives as $alternative) {
+    $rows[] = array($alternative);
+  }
+  return theme('table', array('header' => $header, 'rows' => $rows));
+}
+
+/**
+ * Theme the scale response form
+ *
+ * @param $form
+ *  The response form
+ */
+function theme_scale_response_form($variables) {
+  $form = $variables['form'];
+  return drupal_render_children($form);
+}
+
+/**
+ * Theme function for the scale creation form.
+ *
+ * This function only adds js to the page. Js should be added to forms through theme functions
+ * like this one. Js can't be added to the page from the form function because sometimes the form
+ * is returned from cache.
+ *
+ * @param $form
+ *  The scale creation form
+ */
+function theme_scale_creation_form($form) {
+  drupal_add_js(drupal_get_path('module', 'scale') .'/scale.js');
+}
diff --git a/modules/question_types/short_answer/short_answer.admin.inc b/modules/question_types/short_answer/short_answer.admin.inc
new file mode 100644
index 0000000..1dda12b
--- /dev/null
+++ b/modules/question_types/short_answer/short_answer.admin.inc
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * short_answer.admin
+ *
+ * @file
+ */
+
+/**
+ * Generate a view of all unscored short_answer questions.
+ *
+ * @see theme_short_answer_view_unscored()
+ */
+function short_answer_view_unscored() {
+  $unscored = ShortAnswerResponse::fetchAllUnscoredAnswers();
+  return theme('short_answer_view_unscored', array('unscored' => $unscored));
+}
+
+/**
+ * Page handler for displaying a scoring form.
+ * This function is called directly from the menu router. It generates a form for
+ * scoring a quiz.
+ *
+ * @param $vid
+ *  The VID of the question and answer to load.
+ * @param $result_id
+ *  The result ID of the answer to load.
+ * @return
+ *  Text to display.
+ */
+function short_answer_edit_score($vid, $result_id) {
+  $nid = db_query('SELECT nid FROM {node_revision} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
+  if (!$nid) {
+    drupal_not_found();
+    return;
+  }
+
+  $node = node_load($nid, $vid);
+  if (!$node || $node->type != 'short_answer') {
+    drupal_not_found();
+    return;
+  }
+
+  $answer = (object) short_answer_get_answer($node->nid, $node->vid, $result_id);
+  if (!$answer) {
+    drupal_not_found();
+    return;
+  }
+
+  if ($node->max_score > 0) {
+    $answer->rel_score = round($answer->score * $answer->rel_max_score / $node->max_score);
+  }
+  else {
+    $answer->rel_score = 0;
+  }
+
+  drupal_set_title(t('Score answer to "@title"', array('@title' => $node->title)), PASS_THROUGH);
+
+  return drupal_get_form('short_answer_score_form', $node, $answer);
+}
+
+/**
+ * Build a form for scoring long-answer questions.
+ *
+ * @param $node
+ *  The question node.
+ * @param $answer
+ *  An object containing an answer to the question. This form is for scoring that answer.
+ * @return
+ *  The form (as a FAPI array).
+ */
+function short_answer_score_form($form, $context, $node, $answer) {
+
+  if (!$node || $node->type != 'short_answer' || !$answer) {
+    drupal_not_found();
+    return;
+  }
+  // Set up the form
+  $form['question'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Question'),
+      '#markup' => check_markup($node->body['und'][0]['value'], $node->body['und'][0]['format']),
+  );
+  $form['answer'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Answer'),
+      '#markup' => check_markup($answer->answer, variable_get('short_answer_markup_filter')),
+  );
+  $form['show_max_score'] = array(
+      '#type'   => 'item',
+      '#title'  => t('Maximum Score'),
+      '#markup' => (int) $answer->rel_max_score,
+  );
+  $form['score'] = array(
+      '#type'          => 'textfield',
+      '#title'         => t('Score'),
+      '#description'   => t('The score for this essay, between 0 and @max', array('@max' => $answer->rel_max_score)),
+      '#size'          => 3,
+      '#maxlength'     => 3,
+      '#default_value' => (int) $answer->rel_score,
+      '#required'      => TRUE,
+  );
+  $form['answer_feedback'] = array(
+      '#title'         => t('Feedback'),
+      '#type'          => 'text_format',
+      '#description'   => t('The text entered here would be shown to attendees'),
+      '#default_value' => $answer->answer_feedback,
+      '#format'        => isset($answer->answer_feedback_format) ? $answer->answer_feedback_format : NULL
+  );
+  $form['submit'] = array(
+      '#type'  => 'submit',
+      '#value' => t('Save this score'),
+  );
+
+  // Save some work by keeping these.
+  $form['max_score'] = array(
+      '#type'  => 'value',
+      '#value' => $node->max_score,
+  );
+  $form['rel_max_score'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->rel_max_score,
+  );
+  $form['question_nid'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->question_nid,
+  );
+  $form['question_vid'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->question_vid,
+  );
+  $form['result_id'] = array(
+      '#type'  => 'value',
+      '#value' => $answer->result_id,
+  );
+
+  return $form;
+}
+
+/**
+ * Validate the score form
+ */
+function short_answer_score_form_validate($form, $form_state) {
+  // Check to make sure that entered score is not higher than max allowed score.
+  $max = (int) $form_state['values']['rel_max_score'];
+  $given = (int) $form_state['values']['score'];
+
+  if (!_quiz_is_int($form_state['values']['score'], 0, $max)) {
+    $args = array('@score' => $given, '@max' => $max);
+    form_error($form['score'], t('The score needs to be a number between 0 and @max', $args));
+  }
+}
+
+/**
+ * Submit the score form
+ */
+function short_answer_score_form_submit($form, &$form_state) {
+  $result = db_query('SELECT quiz_qid, quiz_vid FROM {quiz_results} WHERE result_id = :result_id', array(
+      ':result_id' => $form_state['values']['result_id']
+    ))->fetchobject();
+
+  $result = short_answer_score_an_answer(array(
+      'quiz'            => node_load($result->quiz_qid, $result->quiz_vid),
+      'nid'             => $form_state['values']['question_nid'],
+      'vid'             => $form_state['values']['question_vid'],
+      'result_id'       => $form_state['values']['result_id'],
+      'score'           => $form_state['values']['score'],
+      'max_score'       => $form_state['values']['rel_max_score'],
+      'answer_feedback' => $form_state['values']['answer_feedback']
+  ));
+
+  if ($result == 1) {
+    drupal_set_message("The score has been saved.");
+    $form_state['redirect'] = 'admin/quiz/reports/score-short-answer';
+  }
+  else {
+    drupal_set_message(t('Error saving the score. The selected answer was not scored.'), 'error');
+  }
+}
diff --git a/modules/question_types/short_answer/short_answer.classes.inc b/modules/question_types/short_answer/short_answer.classes.inc
new file mode 100644
index 0000000..118211e
--- /dev/null
+++ b/modules/question_types/short_answer/short_answer.classes.inc
@@ -0,0 +1,485 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * The main classes for the short answer question type.
+ *
+ * These inherit or implement code found in quiz_question.classes.inc.
+ *
+ * If you are developing your own question type, the easiest place to start is with
+ * multichoice.classes.inc. short_answer and long_answer are good for understanding
+ * question types that involve textual answers.
+ *
+ * @file
+ */
+
+/**
+ * Extension of QuizQuestion.
+ *
+ * This could have extended long answer, except that that would have entailed
+ * adding long answer as a dependency.
+ */
+class ShortAnswerQuestion extends QuizQuestion {
+
+  // Constants for answer matching options
+  const ANSWER_MATCH = 0;
+  const ANSWER_INSENSITIVE_MATCH = 1;
+  const ANSWER_REGEX = 2;
+  const ANSWER_MANUAL = 3;
+
+  /**
+   * Implementation of saveNodeProperties
+   *
+   * @see QuizQuestion#saveNodeProperties($is_new)
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    if ($is_new || $this->node->revision == 1) {
+      $id = db_insert('quiz_short_answer_node_properties')
+        ->fields(array(
+            'nid'                       => $this->node->nid,
+            'vid'                       => $this->node->vid,
+            'correct_answer'            => $this->node->correct_answer,
+            'correct_answer_evaluation' => $this->node->correct_answer_evaluation,
+        ))
+        ->execute();
+    }
+    else {
+
+      db_update('quiz_short_answer_node_properties')
+        ->fields(array(
+            'correct_answer'            => $this->node->correct_answer,
+            'correct_answer_evaluation' => $this->node->correct_answer_evaluation,
+        ))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+  }
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validateNode($form)
+   */
+  public function validateNode(array &$form) {
+    if ($this->node->correct_answer_evaluation != self::ANSWER_MANUAL && empty($this->node->correct_answer)) {
+      form_set_error('correct_answer', t('An answer must be specified for any evaluation type other than manual scoring.'));
+    }
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete($only_this_version)
+   */
+  public function delete($only_this_version = FALSE) {
+    parent::delete($only_this_version);
+    $delete_ans = db_delete('quiz_short_answer_user_answers');
+    $delete_ans->condition('question_nid', $this->node->nid);
+
+    $delete_node = db_delete('quiz_short_answer_node_properties');
+    $delete_node->condition('nid', $this->node->nid);
+
+    if ($only_this_version) {
+      $delete_ans->condition('question_vid', $this->node->vid);
+      $delete_node->condition('vid', $this->node->vid);
+    }
+
+    $delete_ans->execute();
+    $delete_node->execute();
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+    $res_a = db_query('SELECT correct_answer, correct_answer_evaluation FROM {quiz_short_answer_node_properties}
+      WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
+    $this->nodeProperties = (is_array($res_a)) ? array_merge($props, $res_a) : $props;
+    return $this->nodeProperties;
+  }
+
+  /**
+   * Implementation of getNodeView
+   *
+   * @see QuizQuestion#getNodeView()
+   */
+  public function getNodeView() {
+    $content = parent::getNodeView();
+    if ($this->viewCanRevealCorrect()) {
+      $content['answers'] = array(
+          '#markup' => '<div class="quiz-solution">' . check_plain($this->node->correct_answer) . '</div>',
+          '#weight' => 2,
+      );
+    }
+    else {
+      $content['answers'] = array(
+          '#markup' => '<div class="quiz-answer-hidden">Answer hidden</div>',
+          '#weight' => 2,
+      );
+    }
+    return $content;
+  }
+
+  /**
+   * Implementation of getAnsweringForm
+   *
+   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $form = parent::getAnsweringForm($form_state, $result_id);
+    //$form['#theme'] = 'short_answer_answering_form';
+
+    $form = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Answer'),
+        '#description'   => t('Enter your answer here'),
+        '#default_value' => '',
+        '#size'          => 60,
+        '#maxlength'     => 256,
+        '#required'      => FALSE,
+        '#attributes'    => array('autocomplete' => 'off'),
+    );
+
+    if (isset($result_id)) {
+      $response = new ShortAnswerResponse($result_id, $this->node);
+      $form['#default_value'] = $response->getResponse();
+    }
+
+    return $form;
+  }
+
+  /**
+   * Question response validator.
+   */
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+    if ($form_state['values']['question'][$this->node->nid] == '') {
+      form_set_error('', t('You must provide an answer.'));
+    }
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm($form_state)
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    $form['answer'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Answer'),
+        '#description' => t('Provide the answer and the method by which the answer will be evaluated.'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => FALSE,
+        '#weight'      => -4,
+    );
+
+    $options = array(
+        self::ANSWER_MATCH             => t('Automatic and case sensitive'),
+        self::ANSWER_INSENSITIVE_MATCH => t('Automatic. Not case sensitive'),
+    );
+    $access_regex = user_access('use regex for short answer');
+    if ($access_regex) {
+      $options[self::ANSWER_REGEX] = t('Match against a regular expression (answer must match the supplied regular expression)');
+    }
+    $options[self::ANSWER_MANUAL] = t('Manual');
+
+    $form['answer']['correct_answer_evaluation'] = array(
+        '#type'          => 'radios',
+        '#title'         => t('Pick an evaluation method'),
+        '#description'   => t('Choose how the answer shall be evaluated.'),
+        '#options'       => $options,
+        '#default_value' => isset($this->node->correct_answer_evaluation) ? $this->node->correct_answer_evaluation : self::ANSWER_INSENSITIVE_MATCH,
+        '#required'      => FALSE,
+    );
+    if ($access_regex) {
+      $form['answer']['regex_box'] = array(
+          '#type'        => 'fieldset',
+          '#title'       => t('About regular expressions'),
+          '#collapsible' => TRUE,
+          '#collapsed'   => TRUE,
+      );
+
+      $form['answer']['regex_box']['regex_help'] = array(
+          '#markup' => '<p>' .
+          t('Regular expressions are an advanced syntax for pattern matching. They allow you to create a concise set of rules that must be met before a value can be considered a match.') .
+          '</p><p>' .
+          t('For more on regular expression syntax, visit !url.', array('!url' => l('the PHP regular expressions documentation', 'http://www.php.net/manual/en/book.pcre.php'))) .
+          '</p>',
+      );
+    }
+
+    $form['answer']['correct_answer'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Correct answer'),
+        '#description'   => t('Specify the answer. If this question is manually scored, no answer needs to be supplied.'),
+        '#default_value' => isset($this->node->correct_answer) ? $this->node->correct_answer : '',
+        '#size'          => 60,
+        '#maxlength'     => 256,
+        '#required'      => FALSE,
+    );
+    return $form;
+  }
+
+  /**
+   * Implementation of getMaximumScore
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    return variable_get('short_answer_default_max_score', 5);
+  }
+
+  /**
+   * Evaluate the correctness of an answer based on the correct answer and evaluation method.
+   */
+  public function evaluateAnswer($user_answer) {
+    $score = 0;
+
+    // Ignore white spaces for correct answer and user's answer.
+    $user_answer = trim($user_answer);
+    $this->node->correct_answer = trim($this->node->correct_answer);
+
+    switch ($this->node->correct_answer_evaluation) {
+      case self::ANSWER_MATCH:
+        if ($user_answer == $this->node->correct_answer) {
+          $score = $this->node->max_score;
+        }
+        break;
+      case self::ANSWER_INSENSITIVE_MATCH:
+        if (drupal_strtolower($user_answer) == drupal_strtolower($this->node->correct_answer)) {
+          $score = $this->node->max_score;
+        }
+        break;
+      case self::ANSWER_REGEX:
+        if (preg_match($this->node->correct_answer, $user_answer) > 0) {
+          $score = $this->node->max_score;
+        }
+        break;
+    }
+    return $score;
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class ShortAnswerResponse extends QuizQuestionResponse {
+
+  /**
+   * Get all quiz scores that haven't been evaluated yet.
+   *
+   * @param $count
+   *  Number of items to return (default: 50).
+   * @param $offset
+   *  Where in the results we should start (default: 0).
+   *
+   * @return
+   *  Array of objects describing unanswered questions. Each object will have result_id, question_nid, and question_vid.
+   */
+  public static function fetchAllUnscoredAnswers($count = 50, $offset = 0) {
+    global $user;
+
+    $query = db_select('quiz_short_answer_user_answers', 'answer');
+    $query->fields('answer', array('result_id', 'question_nid', 'question_vid', 'answer_feedback', 'answer_feedback_format'));
+    $query->fields('question_revision', array('title'));
+    $query->fields('qr', array('time_end', 'time_start', 'uid'));
+    $query->join('node_revision', 'question_revision', 'answer.question_vid = question_revision.vid');
+    $query->join('quiz_results', 'qr', 'answer.result_id = qr.result_id');
+    $query->join('quiz_entity', 'quiz', 'qr.quiz_qid = quiz.qid');
+    $query->condition('answer.is_evaluated', 0);
+
+    if (user_access('score own quiz') && user_access('score taken quiz answer')) {
+      $query->condition(db_or()->condition('quiz.uid', $user->uid)->condition('qr.uid', $user->uid));
+    }
+    elseif (user_access('score own quiz')) {
+      $query->condition('quiz.uid', $user->uid);
+    }
+    elseif (user_access('score taken quiz answer')) {
+      $query->condition('qr.uid', $user->uid);
+    }
+
+    $unscored = array();
+    foreach ($query->execute() as $row) {
+      $unscored[] = $row;
+    }
+    return $unscored;
+  }
+
+  /**
+   * Given a question, return a list of all of the unscored answers.
+   *
+   * @param $nid
+   *  Node ID for the question to check.
+   * @param $vid
+   *  Version ID for the question to check.
+   * @param $count
+   *  Number of items to return (default: 50).
+   * @param $offset
+   *  Where in the results we should start (default: 0).
+   *
+   * @return
+   *  Indexed array of result IDs that need to be scored.
+   */
+  public static function fetchUnscoredAnswersByQuestion($nid, $vid, $count = 50, $offset = 0) {
+    return db_query('SELECT result_id FROM {quiz_short_answer_user_answers}
+      WHERE is_evaluated = :is_evaluated AND question_nid = :question_nid AND question_vid = :question_vid', array(':is_evaluated' => 0, ':question_nid' => $nid, ':question_vid' => $vid)
+      )->fetchCol();
+  }
+
+  /**
+   * ID of the answer.
+   */
+  protected $answer_id = 0;
+
+  /**
+   * Constructor
+   */
+  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
+    parent::__construct($result_id, $question_node, $answer);
+    if (!isset($answer)) {
+      $r = db_query('SELECT answer_id, answer, is_evaluated, score, question_vid, question_nid, result_id, answer_feedback, answer_feedback_format
+        FROM {quiz_short_answer_user_answers}
+        WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(':qnid' => $question_node->nid, ':qvid' => $question_node->vid, ':rid' => $result_id)
+        )->fetch();
+
+      if (!empty($r)) {
+        $this->answer = $r->answer;
+        $this->score = $r->score;
+        $this->evaluated = $r->is_evaluated;
+        $this->answer_id = $r->answer_id;
+        $this->answer_feedback = $r->answer_feedback;
+        $this->answer_feedback_format = $r->answer_feedback_format;
+      }
+    }
+    else {
+      if (is_array($answer)) {
+        $this->answer = $answer['answer'];
+      }
+      else {
+        $this->answer = $answer;
+        $this->evaluated = $this->question->correct_answer_evaluation != ShortAnswerQuestion::ANSWER_MANUAL;
+      }
+    }
+  }
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+    // We need to set is_evaluated depending on whether the type requires evaluation.
+    $this->is_evaluated = (int) ($this->question->correct_answer_evaluation != ShortAnswerQuestion::ANSWER_MANUAL);
+    $this->answer_id = db_insert('quiz_short_answer_user_answers')
+      ->fields(array(
+          'answer'       => $this->answer,
+          'question_nid' => $this->question->nid,
+          'question_vid' => $this->question->vid,
+          'result_id'    => $this->result_id,
+          'score'        => $this->getScore(FALSE),
+          'is_evaluated' => $this->is_evaluated,
+      ))
+      ->execute();
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+    db_delete('quiz_short_answer_user_answers')
+      ->condition('question_nid', $this->question->nid)
+      ->condition('question_vid', $this->question->vid)
+      ->condition('result_id', $this->result_id)
+      ->execute();
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    // Manual scoring means we go with what is in the DB.
+    if ($this->question->correct_answer_evaluation == ShortAnswerQuestion::ANSWER_MANUAL) {
+      $score = db_query('SELECT score FROM {quiz_short_answer_user_answers} WHERE result_id = :result_id AND question_vid = :question_vid', array(':result_id' => $this->result_id, ':question_vid' => $this->question->vid))->fetchField();
+      if (!$score) {
+        $score = 0;
+      }
+    }
+    // Otherwise, we run the scoring automatically.
+    else {
+      $shortAnswer = new ShortAnswerQuestion($this->question);
+      $score = $shortAnswer->evaluateAnswer($this->getResponse());
+    }
+    return $score;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer;
+  }
+
+  /**
+   * Implementation of getReportFormResponse
+   */
+  public function getReportFormResponse() {
+    $data = array();
+
+    $data[] = array(
+        'choice'            => '',
+        'attempt'           => $this->answer,
+        'correct'           => !$this->evaluated ? t('This answer has not yet been scored.') : '',
+        'score'             => $this->getScore(),
+        'answer_feedback'   => check_markup($this->answer_feedback, $this->answer_feedback_format),
+        'question_feedback' => '',
+        'solution'          => '',
+    );
+
+    return $data;
+  }
+
+  public function getReportFormAnswerFeedback() {
+    return array(
+        '#title'         => t('Enter feedback'),
+        '#type'          => 'text_format',
+        '#default_value' => $this->answer_feedback,
+        '#format'        => isset($this->answer_feedback_format) ? $this->answer_feedback_format : filter_default_format(),
+        '#attributes'    => array('class' => array('quiz-report-score')),
+    );
+  }
+
+  /**
+   * Implementation of getReportFormSubmit
+   *
+   * @see QuizQuestionResponse#getReportFormSubmit()
+   */
+  public function getReportFormSubmit() {
+    return 'short_answer_report_submit';
+  }
+
+  /**
+   * Implements QuizQuestionResponse::getReportFormValidate().
+   */
+  public function getReportFormValidate(&$element, &$form_state) {
+    // Check to make sure that entered score is not higher than max allowed score.
+    if ($element['score']['#value'] > $this->question->max_score) {
+      form_error($element['score'], t('The score needs to be a number between 0 and @max', array('@max' => $this->question->max_score)));
+    }
+  }
+
+}
diff --git a/modules/question_types/short_answer/short_answer.info b/modules/question_types/short_answer/short_answer.info
new file mode 100644
index 0000000..60c0b0d
--- /dev/null
+++ b/modules/question_types/short_answer/short_answer.info
@@ -0,0 +1,9 @@
+name = Short Answer
+package = Quiz Question
+description = Short answer question type for Quiz.
+core = 7.x
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = short_answer.classes.inc
+files[] = tests/ShortAnswerTestCase.test
diff --git a/modules/question_types/short_answer/short_answer.install b/modules/question_types/short_answer/short_answer.install
new file mode 100644
index 0000000..8e2a6c0
--- /dev/null
+++ b/modules/question_types/short_answer/short_answer.install
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * The installer file for short_answer.
+ * @file
+ */
+
+/**
+ * Implements hook_install().
+ */
+function short_answer_install() {
+  // Add body field to short answer node
+  quiz_question_add_body_field('short_answer');
+  variable_set('node_options_short_answer', array('status'));
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function short_answer_uninstall() {
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_uninstall_schema('short_answer')
+}
+
+/**
+ * Implements hook_schema().
+ */
+function short_answer_schema() {
+  // Properties for a question nodes go in here:
+  $schema['quiz_short_answer_node_properties'] = array(
+    'fields' => array(
+      'nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'maximum_score' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+      // One of (0) textfield and (1) textare
+      'text_entry_type' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0
+      ),
+      // One of exact (0), case insensitive (1), regex (2), manual (3)
+      'correct_answer_evaluation' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0
+      ),
+      // Correct answer
+      'correct_answer' => array(
+        'type' => 'text'
+      ),
+      // Feedback if answer is correct
+      'feedback_correct'  => array(
+        'type' => 'text'
+      ),
+      // Feedback if answer is incorrect
+      'feedback_incorrect'  => array(
+        'type' => 'text'
+      ),
+    ),
+    'primary key' => array('nid', 'vid'),
+  );
+
+  // User answers go in here.
+  $schema['quiz_short_answer_user_answers'] = array(
+    'fields' => array(
+      'answer_id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'score' => array(
+        'type' => 'float',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      // This may be set to false if manual scoring is used.
+      'is_evaluated' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'size' => 'tiny',
+        'default' => 0,
+        'not null' => TRUE,
+      ),
+      'answer' => array(
+        'type' => 'text'
+      ),
+      'answer_feedback' => array(
+        'type' => 'text'
+      ),
+      'answer_feedback_format' => array(
+        'type' => 'varchar',
+        'length' => 255,
+      ),
+    ),
+    'primary key' => array('answer_id'),
+    'unique keys' => array(
+      'ids' => array('result_id', 'question_nid', 'question_vid'),
+    ),
+  );
+  return $schema;
+}
+
+
+/**
+ * Adding feedback field to table {quiz_short_answer_user_answers}
+ */
+function short_answer_update_7401() {
+  if (!db_field_exists('quiz_short_answer_user_answers', 'answer_feedback')) {
+    $spec = array(
+      'type' => 'text',
+      'not null' => FALSE,
+    );
+    db_add_field('quiz_short_answer_user_answers', 'answer_feedback', $spec);
+  }
+}
+
+/**
+ * Adding feedback format field to table {quiz_short_answer_user_answers}
+ */
+function short_answer_update_7402() {
+  if (!db_field_exists('quiz_short_answer_user_answers', 'answer_feedback_format')) {
+    $spec = array(
+        'type' => 'varchar',
+        'length' => 255,
+    );
+    db_add_field('quiz_short_answer_user_answers', 'answer_feedback_format', $spec);
+  }
+}
diff --git a/modules/question_types/short_answer/short_answer.module b/modules/question_types/short_answer/short_answer.module
new file mode 100644
index 0000000..39a8b31
--- /dev/null
+++ b/modules/question_types/short_answer/short_answer.module
@@ -0,0 +1,252 @@
+<?php
+
+/**
+ * The main file for short_answer.
+ *
+ * Short answer is structurally similar to long answer. However, the module
+ * mechanism makes it very difficult for these two modules (either one of
+ * which may be disabled) to effectively share code.
+ * @file
+ */
+
+/**
+ * Implements hook_help().
+ */
+function short_answer_help($path, $args) {
+  if ($path == 'admin/help#short_answer') {
+    return t('This module provides a short answer question type for Quiz.');
+  }
+}
+
+/**
+ * Implements hook_permission().
+ */
+function short_answer_permission() {
+  return array(
+    'use regex for short answer' => array(
+      'title' => t('use regex for short answer'),
+      'description' => t('Use PHP "regular expressions" the advanced option for automated response evaluation.'),
+      'restrict access' => TRUE,
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function short_answer_menu() {
+  $items['admin/quiz/reports/score-short-answer'] = array(
+    'title' => 'Score short answer questions',
+    'description' => 'Score the answers from quizzes that use short answer questions.',
+    'page callback' => 'short_answer_view_unscored',
+    'access arguments' => array('score any quiz', 'score own quiz', 'score taken quiz answer'),
+    'access callback' => 'quiz_access_multi_or',
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'short_answer.admin.inc',
+  );
+  // Pass vid and rid to this path.
+  $items['admin/quiz/reports/score-short-answer/%/%'] = array(
+    'title' => 'Score short answer response',
+    'description' => 'Score a response to a short answer question.',
+    'page callback' => 'short_answer_edit_score',
+    'page arguments' => array(4, 5),
+    'type' => MENU_NORMAL_ITEM,
+    'access arguments' => array(4, 5),
+    'access callback' => 'quiz_question_access_to_score',
+    'file' => 'short_answer.admin.inc'
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function short_answer_quiz_question_info() {
+  return array(
+    'short_answer' => array(
+      'name' => t('Short answer question'),
+      'description' => t('Quiz questions that allow a user to enter a line of text.'),
+      'question provider' => 'ShortAnswerQuestion',
+      'response provider' => 'ShortAnswerResponse',
+      'module' => 'quiz_question', // All wrapper functions are in that module.
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function short_answer_quiz_question_config() {
+  $form['short_answer_default_max_score'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Default max score'),
+    '#description' => t('Choose the default maximum score for a short answer question.'),
+    '#default_value' => variable_get('short_answer_default_max_score', 5),
+  );
+  $form['#validate'][] = 'short_answer_config_validate';
+  return $form;
+}
+
+/**
+ * Validate the long_answer config form values
+ */
+function short_answer_config_validate($form, $form_state) {
+  if ($form_state['values']['short_answer_default_max_score'] <= 0) {
+    form_set_error('short_answer_default_max_score', t('The default max score must be greater than 0'));
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function short_answer_theme($existing, $type, $theme, $path) {
+  $module_path = drupal_get_path('module', 'short_answer');
+  return array(
+    'short_answer_view_unscored' => array(
+      'variables' => array('unscored' => array()),
+      'path' => $module_path . '/theme',
+      'file' => 'short_answer.theme.inc',
+    ),
+    'short_answer_response_form' => array(
+      'render element' => 'form',
+      'path' => $module_path . '/theme',
+      'file' => 'short_answer.theme.inc',
+    ),
+    'short_answer_user_answer' => array(
+      'variables' => array('answer' => NULL, 'correct' => NULL),
+      'path' => $module_path . '/theme',
+      'file' => 'short_answer.theme.inc',
+    ),
+    'short_answer_answering_form' => array(
+      'render element' => 'form',
+      'path' => $module_path . '/theme',
+      'template' => 'short-answer-answering-form',
+    ),
+  );
+}
+
+/**
+ * Set a score for a short answer question.
+ *
+ * This stores a score for a short answer question and marks that question as having been evaluated.
+ * The function updates all of the necessary data sources so that the individual answer results should be
+ * reflected in the total scoring table.
+ *
+ * @param $quiz
+ *  Quiz entity.
+ * @param $nid
+ *  Node ID of question.
+ * @param $vid
+ *  Version ID of question.
+ * @param $result_id
+ *  Result ID for the quiz results.
+ * @param $score
+ *  The numeric score to assign the result.
+ * @param $update_total
+ *  Shall the total score for a quiz be updated?
+ *
+ * @return int
+ *  Number of scores adjusted. If a change was made, this should be 1.
+ */
+function short_answer_score_an_answer($values, $update_total = TRUE) {
+  extract($values);
+  // When we set the score we make sure that the max score in the quiz the question belongs to is considered
+  $question_max_score = db_query('SELECT max_score FROM {quiz_question_properties} WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->FetchField();
+
+  $quiz_max_score = db_query('SELECT max_score FROM {quiz_relationship} WHERE quiz_vid = :pvid AND question_vid = :cvid', array(':pvid' => $quiz->vid, ':cvid' => $vid))->fetchField();
+
+  $changed = db_update('quiz_short_answer_user_answers')
+    ->fields(array(
+      'score' => $score * $question_max_score / $quiz_max_score,
+      'is_evaluated' => 1,
+      'answer_feedback' => empty($answer_feedback['value']) ? '' : $answer_feedback['value'],
+      'answer_feedback_format' => empty($answer_feedback['format']) ? '' : $answer_feedback['format'],
+    ))
+    ->condition('question_nid', $nid)
+    ->condition('question_vid', $vid)
+    ->condition('result_id', $result_id)
+    ->execute();
+  // Now the short answer user data has been updated. We also need to update the data in the quiz tables
+  if ($changed > 0) {
+    $max = db_query('SELECT max_score FROM {quiz_question_properties} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
+    if ($max <= 0) {
+      $is_correct = 0;
+      $points_awarded = 0;
+    }
+    else {
+      $is_correct = ($score / $max > 0.5) ? 1 : 0;
+      $points_awarded = $score;
+    }
+
+    db_update('quiz_results_answers')
+      ->fields(array(
+        'points_awarded' => $points_awarded,
+        'is_correct' => $is_correct,
+      ))
+      ->condition('question_vid', $vid)
+      ->condition('result_id', $result_id)
+      ->execute();
+
+    // Third, we update the main quiz results table
+    if ($update_total) {
+      quiz()->getQuizHelper()->getResultHelper()->updateTotalScore($quiz, $result_id);
+    }
+  }
+  return $changed;
+}
+
+/**
+ * Submit the result report for short answer
+ */
+function short_answer_report_submit($values) {
+  short_answer_score_an_answer($values, FALSE);
+}
+
+/**
+ * Get the answer for a question.
+ *
+ * This stores a score for a short answer question and marks that question as having been evaluated.
+ * @param $question_nid
+ *  Node ID of question.
+ * @param $question_vid
+ *  Version ID of question.
+ * @param $result_id
+ *  Result ID for the quiz results.
+ *
+ * @return Assoc array
+ *  An array if successful, or FALSE if no result could be found. The array contains the following properties:
+ *  <code>
+ *  answer_id; // The answer ID
+ *  answer; // The full text of the answer
+ *  is_evaluated; // 0 if the question has not been evaluated, 1 if it has
+ *  score; // The score the evaluator gave the user; this should be 0 if is_evaluated is 0.
+ *  question_vid
+ *  question_nid
+ *  result_id
+ *  </code>
+ */
+function short_answer_get_answer($question_nid, $question_vid, $result_id) {
+  $results = db_query('SELECT sa.answer_id, sa.answer, sa.is_evaluated, sa.score, sa.question_vid, sa.question_nid, sa.result_id, sa.answer_feedback, sa.answer_feedback_format, rel.max_score AS rel_max_score
+    FROM {quiz_short_answer_user_answers} sa
+    JOIN {quiz_results} qnr ON (sa.result_id = qnr.result_id)
+    JOIN {quiz_relationship} rel ON (qnr.quiz_vid = rel.quiz_vid AND rel.question_vid = sa.question_vid)
+    WHERE sa.question_nid = :qnid AND sa.question_vid = :qvid AND sa.result_id = :rid', array(':qnid' => $question_nid, ':qvid' => $question_vid, ':rid' => $result_id))->fetchAssoc();
+  return $results ? $results : FALSE;
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function short_answer_field_extra_fields() {
+  $extra['node']['short_answer'] = array(
+    'form' => array(
+      'answer' => array(
+        'label' => t('Answer'),
+        'description' => t('Provide the answer and the method by which the answer will be evaluated.'),
+        'weight' => -4,
+      ),
+    ),
+  );
+  return $extra;
+}
diff --git a/modules/question_types/short_answer/tests/ShortAnswerTestCase.test b/modules/question_types/short_answer/tests/ShortAnswerTestCase.test
new file mode 100644
index 0000000..21e3aea
--- /dev/null
+++ b/modules/question_types/short_answer/tests/ShortAnswerTestCase.test
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * Test class for short answer.
+ */
+class ShortAnswerTestCase extends QuizQuestionTestCase {
+
+  var $question_node_type = 'short_answer';
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Short answer'),
+        'description' => t('Unit test for Short answers question type.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('short_answer'), array('score any quiz'));
+  }
+
+  /**
+   * Test case insensitive graded questions.
+   */
+  public function testGradeAnswerInsensitive() {
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    $question_node = $this->testCreateQuizQuestion(array(
+        'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_INSENSITIVE_MATCH,
+        'correct_answer'            => 'the Zero One Infinity rule',
+    ));
+    $quiz = $this->linkQuestionToQuiz($question_node);
+
+    // Test incorrect.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'This is an incorrect answer.'), t('Finish'));
+    $this->assertText('Your score: 0%');
+
+    // Test correct.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'the Zero One Infinity rule'), t('Finish'));
+    $this->assertText('Your score: 100%');
+
+    // Test correct.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'the zero one Infinity rule'), t('Finish'));
+    $this->assertText('Your score: 100%');
+  }
+
+  /**
+   * Test case sensitive graded questions.
+   */
+  public function testGradeAnswerSensitive() {
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    $quiz = $this->drupalCreateQuiz(array(
+        'review_options' => array('end' => array('score' => 'score')),
+    ));
+
+    $question_node = $this->testCreateQuizQuestion(array(
+        'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_MATCH,
+        'correct_answer'            => 'the Zero One Infinity rule',
+    ));
+    $this->linkQuestionToQuiz($question_node, $quiz);
+
+    // Test incorrect.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'the zero one Infinity rule'), t('Finish'));
+    $this->assertText('Your score: 0%');
+
+    // Test correct.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'the Zero One Infinity rule'), t('Finish'));
+    $this->assertText('Your score: 100%');
+  }
+
+  /**
+   * Test regex graded questions.
+   */
+  public function testGradeAnswerRegex() {
+    $this->drupalLogin($this->admin);
+
+    $quiz = $this->drupalCreateQuiz();
+
+    $question_node = $this->testCreateQuizQuestion(array(
+        'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_REGEX,
+        'correct_answer'            => '/Zero One Infinity/i',
+    ));
+    $this->linkQuestionToQuiz($question_node, $quiz);
+
+    // Test incorrect.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'um some rule, I forget'), t('Finish'));
+    $this->assertText('Your score: 0%');
+
+    // Test correct.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 'the answer is the zero one infinity rule'), t('Finish'));
+    $this->assertText('Your score: 100%');
+  }
+
+  /**
+   * Test manually graded questions.
+   *
+   * Note: we use two questions here to make sure the grading form is handled
+   * correctly.
+   */
+  public function testGradeAnswerManualFeedback() {
+    $this->drupalLogin($this->admin);
+
+    $quiz = $this->drupalCreateQuiz();
+
+    $question_node1 = $this->testCreateQuizQuestion(array(
+        'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_MANUAL,
+        'correct_answer'            => 'the Zero One Infinity rule',
+    ));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+
+    $question_node2 = $this->testCreateQuizQuestion(array(
+        'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_MANUAL,
+        'correct_answer'            => 'The number two is ridiculous and cannot exist',
+    ));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+
+    // Test correct.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 'the answer is the zero one infinity rule'), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 'the number two really is ridiculous'), t('Finish'));
+    $this->assertText('Your score: 0%');
+    // Strange behavior - extra spaces in HTML.
+    //$this->assertText('Score ? of 10');
+    $this->assertText('This answer has not yet been scored.');
+    $this->assertNoFieldByName('0[score]');
+    $this->assertNoFieldByName('1[score]');
+    $this->assertNoFieldByName('0[answer_feedback][value]');
+    $this->assertNoFieldByName('1[answer_feedback][value]');
+    $this->assertNoRaw(t('Save score'));
+    $url_of_result = $this->getUrl();
+
+    // Test grading the question.
+    $this->drupalLogin($this->admin);
+    $this->drupalGet($url_of_result);
+    $this->assertFieldByName('0[score]');
+    $this->assertFieldByName('1[score]');
+    // Admin has to provide feedback.
+    $this->drupalLogin($this->admin);
+    $this->drupalGet($url_of_result);
+    $this->drupalPost(NULL, array(
+        "0[score]"                   => 2,
+        "1[score]"                   => 3,
+        "0[answer_feedback][value]"  => 'Feedback for answer 1.',
+        "1[answer_feedback][value]"  => 'Feedback for answer 2.',
+        "0[answer_feedback][format]" => 'filtered_html',
+        "1[answer_feedback][format]" => 'filtered_html',
+      ), t('Save score'));
+    $this->assertText('The scoring data you provided has been saved.');
+
+    // Test the score is visible to the user.
+    $this->drupalLogin($this->user);
+    $this->drupalGet($url_of_result);
+    $this->assertText('You got 5 of 10 possible points.');
+    $this->assertText('Your score: 50%');
+    // Strange behavior - extra spaces in HTML.
+    //$this->assertText('Score 2 of 5');
+    //$this->assertText('Score 3 of 5');
+    $this->assertText('Feedback for answer 1.');
+    $this->assertText('Feedback for answer 2.');
+  }
+
+  /**
+   * Test adding and taking a truefalse question.
+   */
+  function testCreateQuizQuestion($settings = NULL) {
+    if (!$settings) {
+      $settings = array(
+          'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_INSENSITIVE_MATCH,
+          'correct_answer'            => 'SA 1 correct answer.',
+      );
+    }
+
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    $question_node = $this->drupalCreateNode(array(
+        'type'  => $this->question_node_type,
+        'title' => 'SA 1 title',
+        'body'  => array(LANGUAGE_NONE => array(array('value' => 'SA 1 body text.'))),
+      ) + $settings);
+
+    return $question_node;
+  }
+
+}
diff --git a/modules/question_types/short_answer/theme/short-answer-answering-form.tpl.php b/modules/question_types/short_answer/theme/short-answer-answering-form.tpl.php
new file mode 100644
index 0000000..4a54b09
--- /dev/null
+++ b/modules/question_types/short_answer/theme/short-answer-answering-form.tpl.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Handles the layout of the short_answer answering form.
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render($form);
+
+?>
\ No newline at end of file
diff --git a/modules/question_types/short_answer/theme/short_answer.theme.inc b/modules/question_types/short_answer/theme/short_answer.theme.inc
new file mode 100644
index 0000000..35b8d92
--- /dev/null
+++ b/modules/question_types/short_answer/theme/short_answer.theme.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Theme functions for short_answer.
+ */
+
+/**
+ * Theme the list of unscored short answer questions.
+ *
+ * @param $unscored
+ *  An array of objects with information about the unscored questions
+ */
+function theme_short_answer_view_unscored($variables) {
+  $unscored = $variables['unscored'];
+  $output = '';
+  $header = array(
+    t('Question'),
+    t('Time Finished'),
+    t('Action')
+  );
+  $rows = array();
+
+  foreach ($unscored as $item) {
+    if ($item->time_end > 0) {
+      $rows[] = array(
+        $item->title,
+        date('Y-m-d H:i', $item->time_end),
+        l(t('score this response'), 'admin/quiz/reports/score-short-answer/' . $item->question_vid . '/' . $item->result_id),
+      );
+    }
+  }
+
+  if (!empty($rows)) {
+    $output .= theme('table', array('header' => $header, 'rows' => $rows));
+  }
+  else {
+    $output .= t('There are no unscored essays.');
+  }
+  return $output;
+}
+
+/**
+ * @todo Please document this function.
+ * @see http://drupal.org/node/1354
+ */
+function theme_short_answer_user_answer($variables) {
+  $answer = $variables['answer'];
+  $correct = $variables['correct'];
+  $header = array(t('Correct Answer'), t('User Answer'));
+  $row = array(array($correct, $answer));
+  return theme('table', array('header' => $header, 'rows' => $row));
+}
+
+/**
+ * Theme the short_answer response form
+ *
+ * @param $form
+ *  The response form
+ */
+function theme_short_answer_response_form($variables) {
+  $form = $variables['form'];
+  return drupal_render_children($form);
+}
diff --git a/modules/question_types/truefalse/tests/TrueFalseUnitTest.test b/modules/question_types/truefalse/tests/TrueFalseUnitTest.test
new file mode 100644
index 0000000..506402d
--- /dev/null
+++ b/modules/question_types/truefalse/tests/TrueFalseUnitTest.test
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Test class for true false questions.
+ */
+class TrueFalseTestCase extends QuizQuestionTestCase {
+
+  var $question_node_type = 'truefalse';
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('True/False'),
+        'description' => t('Unit test for true or false question type.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('truefalse'));
+  }
+
+  /**
+   * Test adding and taking a truefalse question.
+   */
+  function testCreateQuizQuestion() {
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    $question_node = $this->drupalCreateNode(array(
+        'type'           => 'truefalse',
+        'title'          => 'TF 1 title',
+        'correct_answer' => 1,
+        'body'           => array(LANGUAGE_NONE => array(array('value' => 'TF 1 body text'))),
+    ));
+
+    return $question_node;
+  }
+
+  function testTakeQuestion() {
+    $question_node = $this->testCreateQuizQuestion();
+
+    // Create the quiz and question.
+    $quiz = $this->drupalCreateQuiz();
+
+    // Link the question.
+    $question_node->quiz_qid = $quiz->qid;
+    $question_node->quiz_vid = $quiz->vid;
+    $qq = _quiz_question_get_instance($question_node);
+    $qq->saveRelationships();
+
+    // Test that question appears in lists.
+    $this->drupalGet("quiz/$quiz->qid/questions");
+    $this->assertText('TF 1 title');
+
+    // Login as non-admin.
+    $this->drupalLogin($this->user);
+
+    // Take the quiz.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->assertNoText('TF 1 title');
+    $this->assertText('TF 1 body text');
+    $this->assertText('True');
+    $this->assertText('False');
+
+    // Test validation.
+    $this->drupalPost(NULL, array(), t('Finish'));
+    $this->assertText('You must provide an answer.');
+
+    // Test correct question.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 1), t('Finish'));
+    $this->assertText('You got 1 of 1 possible points.');
+
+    // Test incorrect question.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 0), t('Finish'));
+    $this->assertText('You got 0 of 1 possible points.');
+  }
+
+  function testQuestionFeedback() {
+    // Login as our privileged user.
+    $this->drupalLogin($this->admin);
+
+    // Create the quiz and question.
+    $question_node = $this->testCreateQuizQuestion();
+
+    // Link the question.
+    $quiz = $this->linkQuestionToQuiz($question_node);
+
+    // Test incorrect question with all feedbacks on.
+    // Login as non-admin.
+    $this->drupalLogin($this->user);
+    // Take the quiz.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 1), t('Finish'));
+    $this->assertRaw('quiz-score-icon correct');
+    $this->assertRaw('quiz-score-icon should');
+
+    // Take the quiz.
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 0), t('Finish'));
+    $this->assertRaw('quiz-score-icon incorrect');
+    $this->assertRaw('quiz-score-icon should');
+  }
+
+}
diff --git a/modules/question_types/truefalse/theme/truefalse-answering-form.tpl.php b/modules/question_types/truefalse/theme/truefalse-answering-form.tpl.php
new file mode 100644
index 0000000..4d26eac
--- /dev/null
+++ b/modules/question_types/truefalse/theme/truefalse-answering-form.tpl.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Handles the layout of the truefalse answering form. 
+ *
+ *
+ * Variables available:
+ * - $form
+ */
+print drupal_render($form);
+
+?>
\ No newline at end of file
diff --git a/modules/question_types/truefalse/truefalse.classes.inc b/modules/question_types/truefalse/truefalse.classes.inc
new file mode 100644
index 0000000..1755d49
--- /dev/null
+++ b/modules/question_types/truefalse/truefalse.classes.inc
@@ -0,0 +1,314 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * Defines the classes necessary for a True/False quiz.
+ *
+ * @file
+ */
+
+/**
+ * Extension of QuizQuestion.
+ */
+class TrueFalseQuestion extends QuizQuestion {
+
+  /**
+   * Implementation of saveNodeProperties
+   *
+   * @see QuizQuestion#saveNodeProperties($is_new)
+   */
+  public function saveNodeProperties($is_new = FALSE) {
+    if ($is_new || $this->node->revision == 1) {
+      $id = db_insert('quiz_truefalse_node')
+        ->fields(array(
+            'nid'            => $this->node->nid,
+            'vid'            => $this->node->vid,
+            'correct_answer' => $this->node->correct_answer,
+        ))
+        ->execute();
+    }
+    else {
+      db_update('quiz_truefalse_node')
+        ->fields(array(
+            'correct_answer' => (int) $this->node->correct_answer
+        ))
+        ->condition('nid', $this->node->nid)
+        ->condition('vid', $this->node->vid)
+        ->execute();
+    }
+  }
+
+  /**
+   * Implementation of validateNode
+   *
+   * @see QuizQuestion#validateNode($form)
+   */
+  public function validateNode(array &$form) {
+    // This space intentionally left blank. :)
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestion#delete($only_this_version)
+   */
+  public function delete($only_this_version = FALSE) {
+    parent::delete($only_this_version);
+
+    $delete_ans = db_delete('quiz_truefalse_user_answers');
+    $delete_ans->condition('question_nid', $this->node->nid);
+
+    $delete_node = db_delete('quiz_truefalse_node');
+    $delete_node->condition('nid', $this->node->nid);
+
+    if ($only_this_version) {
+      $delete_ans->condition('question_vid', $this->node->vid);
+      $delete_node->condition('vid', $this->node->vid);
+    }
+
+    $delete_ans->execute();
+    $delete_node->execute();
+  }
+
+  /**
+   * Implementation of getNodeProperties
+   *
+   * @see QuizQuestion#getNodeProperties()
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props = parent::getNodeProperties();
+
+    $res_a = db_query('SELECT correct_answer '
+      . ' FROM {quiz_truefalse_node} '
+      . ' WHERE nid = :nid AND vid = :vid', array(
+        ':nid' => $this->node->nid,
+        ':vid' => $this->node->vid))->fetchAssoc();
+
+    if (is_array($res_a)) {
+      $props = array_merge($props, $res_a);
+    }
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Implementation of getNodeView
+   *
+   * @see QuizQuestion#getNodeView()
+   */
+  public function getNodeView() {
+    $content = parent::getNodeView();
+    if ($this->viewCanRevealCorrect()) {
+      $answer = !empty($this->node->correct_answer) ? t('True') : t('False');
+      $content['answers']['#markup'] = '<div class="quiz-solution">' . $answer . '</div>';
+      $content['answers']['#weight'] = 2;
+    }
+    else {
+      $content['answers'] = array(
+          '#markup' => '<div class="quiz-answer-hidden">' . t('Answer hidden') . '</div>',
+          '#weight' => 2,
+      );
+    }
+    return $content;
+  }
+
+  /**
+   * Implementation of getAnsweringForm
+   *
+   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    $element = parent::getAnsweringForm($form_state, $result_id);
+    //$form['#theme'] = 'truefalse_answering_form';
+    // 'tries' is unfortunately required by quiz.module
+    $element += array(
+        '#type'          => 'radios',
+        '#title'         => t('Choose one'),
+        '#options'       => array(1 => t('True'), 0 => t('False')),
+        '#default_value' => NULL, // prevent default value set to NULL
+    );
+
+    if (isset($result_id)) {
+      $response = new TrueFalseResponse($result_id, $this->node);
+      $default = $response->getResponse();
+      $element['#default_value'] = is_null($default) ? NULL : $default;
+    }
+    return $element;
+  }
+
+  /**
+   * Question response validator.
+   */
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+    if (is_null($form_state['values']['question'][$this->node->nid])) {
+      form_set_error('', t('You must provide an answer.'));
+    }
+  }
+
+  /**
+   * Implementation of getBodyFieldTitle
+   *
+   * @see QuizQuestion#getBodyFieldTitle()
+   */
+  public function getBodyFieldTitle() {
+    return t('True/false statement');
+  }
+
+  /**
+   * Implementation of getCreationForm
+   *
+   * @see QuizQuestion#getCreationForm($form_state)
+   */
+  public function getCreationForm(array &$form_state = NULL) {
+    $form['correct_answer'] = array(
+        '#type'          => 'radios',
+        '#title'         => t('Correct answer'),
+        '#options'       => array(
+            1 => t('True'),
+            0 => t('False'),
+        ),
+        '#default_value' => isset($this->node->correct_answer) ? $this->node->correct_answer : 1,
+        '#required'      => TRUE,
+        '#weight'        => -4,
+        '#description'   => t('Choose if the correct answer for this question is "true" or "false".')
+    );
+    return $form;
+  }
+
+  /**
+   * Implementation of getMaximumScore
+   *
+   * @see QuizQuestion#getMaximumScore()
+   */
+  public function getMaximumScore() {
+    return 1;
+  }
+
+  /**
+   * Get the answer to this question.
+   *
+   * This is a utility function. It is not defined in the interface.
+   */
+  public function getCorrectAnswer() {
+    return db_query('SELECT correct_answer FROM {quiz_truefalse_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchField();
+  }
+
+}
+
+/**
+ * Extension of QuizQuestionResponse
+ */
+class TrueFalseResponse extends QuizQuestionResponse {
+
+  /**
+   * Constructor
+   */
+  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
+    parent::__construct($result_id, $question_node, $answer);
+    if (!isset($answer)) {
+      if ($response = $this->getUserAnswer()) {
+        $this->answer = $response->answer;
+        $this->score = $response->score;
+      }
+    }
+    else {
+      // Load from input
+      $this->answer = $answer;
+    }
+  }
+
+  /**
+   * Implementation of save
+   *
+   * @see QuizQuestionResponse#save()
+   */
+  public function save() {
+    db_insert('quiz_truefalse_user_answers')
+      ->fields(array(
+          'question_nid' => $this->question->nid,
+          'question_vid' => $this->question->vid,
+          'result_id'    => $this->result_id,
+          'answer'       => (int) $this->answer,
+          'score'        => (int) $this->getScore(),
+      ))
+      ->execute();
+  }
+
+  /**
+   * Implementation of delete
+   *
+   * @see QuizQuestionResponse#delete()
+   */
+  public function delete() {
+    db_delete('quiz_truefalse_user_answers')
+      ->condition('question_nid', $this->question->nid)
+      ->condition('question_vid', $this->question->vid)
+      ->condition('result_id', $this->result_id)
+      ->execute();
+  }
+
+  /**
+   * Implementation of score
+   *
+   * @see QuizQuestionResponse#score()
+   */
+  public function score() {
+    $tfQuestion = new TrueFalseQuestion($this->question);
+    return ($this->getResponse() == $tfQuestion->getCorrectAnswer()) ? 1 : 0;
+  }
+
+  /**
+   * Implementation of getResponse
+   *
+   * @see QuizQuestionResponse#getResponse()
+   */
+  public function getResponse() {
+    return $this->answer;
+  }
+
+  /**
+   * Implementation of getCorrectAnswer
+   */
+  public function getUserAnswer() {
+    return db_query('SELECT answer, score '
+        . ' FROM {quiz_truefalse_user_answers} '
+        . ' WHERE question_vid = :qvid AND result_id = :rid', array(
+          ':qvid' => $this->question->vid,
+          ':rid'  => $this->result_id
+      ))->fetch();
+  }
+
+  /**
+   * Implementation of getReportFormResponse
+   */
+  public function getReportFormResponse() {
+    $answer = $this->question->answers[0]['answer'];
+    $correct_answer = !empty($this->question->correct_answer);
+
+    return array(
+        array(
+            'choice'          => 'True',
+            'attempt'         => $answer ? quiz_icon('selected') : '',
+            'correct'         => $answer == 1 ? quiz_icon($correct_answer ? 'correct' : 'incorrect') : '',
+            'score'           => intval($correct_answer == 1 && $answer == 1),
+            'answer_feedback' => '',
+            'solution'        => $correct_answer == 1 ? quiz_icon('should') : '',
+            'quiz_feedback'   => t('@quiz feedback', array('@quiz' => QUIZ_NAME)),
+        ),
+        array(
+            'choice'          => 'False',
+            'attempt'         => !$answer ? quiz_icon('selected') : '',
+            'correct'         => $answer == 0 ? (quiz_icon(!$correct_answer ? 'correct' : 'incorrect')) : '',
+            'score'           => intval($correct_answer == 0 && $answer == 0),
+            'answer_feedback' => '',
+            'solution'        => $correct_answer == 0 ? quiz_icon('should') : '',
+        )
+    );
+  }
+
+}
diff --git a/modules/question_types/truefalse/truefalse.info b/modules/question_types/truefalse/truefalse.info
new file mode 100644
index 0000000..b01c620
--- /dev/null
+++ b/modules/question_types/truefalse/truefalse.info
@@ -0,0 +1,9 @@
+name = True False
+package = Quiz Question
+description = Module to create True or False Kind of Questions
+core = 7.x
+dependencies[] = quiz
+dependencies[] = quiz_question
+
+files[] = truefalse.classes.inc
+files[] = tests/TrueFalseUnitTest.test
diff --git a/modules/question_types/truefalse/truefalse.install b/modules/question_types/truefalse/truefalse.install
new file mode 100644
index 0000000..d1c5c38
--- /dev/null
+++ b/modules/question_types/truefalse/truefalse.install
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * The installer file for truefalse question type. *
+ */
+
+/**
+ * Implements hook_install().
+ */
+function truefalse_install() {
+  // Add body field to true false node
+  quiz_question_add_body_field('truefalse');
+
+  if (!db_table_exists('quiz_truefalse_node')) {
+    // TODO The drupal_(un)install_schema functions are called automatically in D7.
+    // drupal_install_schema('truefalse')
+  }
+  else {
+    // TODO Please review the conversion of this statement to the D7 database API syntax.
+    /* db_query("UPDATE {system} SET name = 'truefalse' WHERE name = 'true_false'") */
+    db_update('system')
+      ->fields(array(
+        'name' => 'truefalse',
+      ))
+      ->condition('name', 'true_false')
+      ->execute();
+    // TODO Please review the conversion of this statement to the D7 database API syntax.
+    /* db_query("UPDATE {node} SET type = 'truefalse' WHERE type = 'true_false'") */
+    db_update('node')
+      ->fields(array(
+        'type' => 'truefalse',
+      ))
+      ->condition('type', 'true_false')
+      ->execute();
+    // TODO Please review the conversion of this statement to the D7 database API syntax.
+    /* db_query("UPDATE {system} SET type = 'truefalse' WHERE type = 'true_false'") */
+    db_update('system')
+      ->fields(array(
+        'type' => 'truefalse',
+      ))
+      ->condition('type', 'true_false')
+      ->execute();
+    // TODO Please review the conversion of this statement to the D7 database API syntax.
+    /* db_query("UPDATE {taxonomy_vocabulary_node_type} SET type = 'truefalse' WHERE type = 'true_false'") */
+    /*
+      db_update('taxonomy_vocabulary_node_type')
+      ->fields(array(
+      'type' =>  'truefalse',
+      ))
+      ->condition('type', 'true_false')
+      ->execute();
+     */
+  }
+  variable_set('node_options_truefalse', array('status'));
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function truefalse_uninstall() {
+  // TODO The drupal_(un)install_schema functions are called automatically in D7.
+  // drupal_uninstall_schema('truefalse')
+}
+
+/**
+ * Implements hook_schema().
+ */
+function truefalse_schema() {
+
+  //////////////////////
+  // TRUE/FALSE tables.
+  //////////////////////
+  // Extensions to nodes for T/F
+  $schema['quiz_truefalse_node'] = array(
+    'fields'      => array(
+      'nid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'vid'            => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'correct_answer' => array(
+        'description' => 'The correct answer for the user to give. 1 for true.',
+        'type'        => 'int',
+        'unsigned'    => TRUE,
+        'size'        => 'tiny',
+        'default'     => 0,
+      ),
+    ),
+    'primary key' => array(
+      'vid', 'nid'
+    ),
+  );
+
+  // Place to store user answers for T/F questions
+  $schema['quiz_truefalse_user_answers'] = array(
+    'fields'      => array(
+      'question_nid' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'question_vid' => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'result_id'    => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'answer'       => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'size'     => 'tiny',
+        'default'  => 0,
+      ),
+      'score'        => array(
+        'type'     => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default'  => 0,
+      ),
+    ),
+    'primary key' => array(
+      'result_id', 'question_nid', 'question_vid',
+    ),
+  );
+
+  return $schema;
+}
diff --git a/modules/question_types/truefalse/truefalse.module b/modules/question_types/truefalse/truefalse.module
new file mode 100644
index 0000000..30aeb5f
--- /dev/null
+++ b/modules/question_types/truefalse/truefalse.module
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * TrueFalse question type for quiz module
+ *
+ * Allows the creation of "True or False" questions
+ */
+
+/**
+ * Implements hook_help().
+ */
+function truefalse_help($path, $args) {
+  switch ($path) {
+    case 'admin/modules#description':
+      return t('TrueFalse question type for the quiz module.');
+    case 'node/add#truefalse':
+    case 'admin/help#truefalse':
+      return t('A question type for the quiz module. A simplified version of multichoice where "true" and "false" are the alternatives.');
+    default:
+      break;
+  }
+}
+
+/**
+ * Implements hook_quiz_question_info().
+ */
+function truefalse_quiz_question_info() {
+  return array(
+    'truefalse' => array(
+      'name'              => t('True/false question'),
+      'description'       => t('Quiz questions that allow a user to select "true" or "false" as his response to a statement.'),
+      'question provider' => 'TrueFalseQuestion',
+      'response provider' => 'TrueFalseResponse',
+      'module'            => 'quiz_question', // All wrapper functions are in that module.
+    ),
+  );
+}
+
+/**
+ * Implements hook_quiz_question_config().
+ */
+function truefalse_quiz_question_config() {
+  return FALSE;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function truefalse_theme() {
+  return array(
+    'truefalse_answering_form' => array(
+      'render element' => 'form',
+      'path'           => drupal_get_path('module', 'truefalse') . '/theme',
+      'template'       => 'truefalse-answering-form',
+    ),
+    'truefalse_response'       => array(
+      'variables' => array(
+        'metadata' => array(),
+        'data'     => array(),
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme the response part of the response report
+ *
+ * @param $metadata
+ *  Can be used as a table header
+ * @param $data
+ *  Can be used as table rows
+ */
+function theme_truefalse_response($variables) {
+  $metadata = $variables['metadata'];
+  $data = $variables['data'];
+  return theme('table', array('header' => $metadata, 'rows' => $data));
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function truefalse_field_extra_fields() {
+  $extra['node']['truefalse'] = array(
+    'form' => array(
+      'correct_answer' => array(
+        'label'       => t('Correct answer'),
+        'description' => t('The answer for this question.'),
+        'weight'      => -4,
+      ),
+    ),
+  );
+  return $extra;
+}
diff --git a/modules/quiz_question/quiz_question.hooks.inc b/modules/quiz_question/quiz_question.hooks.inc
new file mode 100644
index 0000000..5d6b12f
--- /dev/null
+++ b/modules/quiz_question/quiz_question.hooks.inc
@@ -0,0 +1,387 @@
+<?php
+
+/**
+ * Implements hook_help().
+ */
+function quiz_question_help($path, $args) {
+  if ($path == 'admin/help#quiz_quesion') {
+    return t('Support for Quiz question types.');
+  }
+}
+
+/**
+ * Implements hook_entity_info()
+ */
+function quiz_question_entity_info() {
+  return array(
+      'quiz_question' => array(
+          'label'                  => t('Quiz question properties'),
+          'controller class'       => 'EntityAPIController',
+          'base table'             => 'quiz_question_properties',
+          'entity keys'            => array(
+              'id' => 'qqp_id',
+          ),
+          'views controller class' => 'EntityDefaultViewsController',
+      ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function quiz_question_menu() {
+  $items = array();
+  $items['quiz_question/%/%/revision_actions'] = array(
+      'title'            => 'Revision actions',
+      'page callback'    => 'drupal_get_form',
+      'page arguments'   => array('quiz_question_revision_actions', 1, 2),
+      'access arguments' => array('manual quiz revisioning'),
+      'file'             => 'quiz_question.pages.inc',
+      'type'             => MENU_NORMAL_ITEM,
+  );
+  // Menu items for admin view of each question type.
+  $items['admin/quiz/settings/questions_settings'] = array(
+      'title'            => 'Question configuration',
+      'description'      => 'Configure the question types.',
+      'page callback'    => 'drupal_get_form',
+      'page arguments'   => array('quiz_question_config'),
+      'access arguments' => array('administer quiz configuration'),
+      'type'             => MENU_NORMAL_ITEM,
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function quiz_question_theme() {
+  $hooks['quiz_question_feedback'] = array(
+      'variables' => NULL,
+      'pattern'   => 'quiz_question_feedback__',
+  );
+
+  return $hooks;
+}
+
+/**
+ * Implements hook_node_info().
+ */
+function quiz_question_node_info() {
+  $node_info = array();
+  foreach (_quiz_question_get_implementations(NULL, TRUE) as $type => $definition) {
+    $node_info[$type] = array(
+        'name'        => $definition['name'],
+        'base'        => 'quiz_question',
+        'description' => $definition['description']
+    );
+    //$info[$type] = $node_info + $defaults;
+  }
+  return $node_info;
+}
+
+/**
+ * Implements hook_node_presave().
+ */
+function quiz_question_node_presave($node) {
+  $q_types = _quiz_question_get_implementations();
+  foreach ($q_types as $q_type => $info) {
+    if ($node->type == $q_type) {
+      if (drupal_strlen($node->title) == 0 || !user_access('edit question titles')) {
+        $body = field_get_items('node', $node, 'body');
+        $markup = strip_tags(check_markup($body[0]['value'], $body[0]['format']));
+        if (drupal_strlen($markup) > variable_get('quiz_autotitle_length', 50)) {
+          $node->title = drupal_substr($markup, 0, variable_get('quiz_autotitle_length', 50) - 3) . '...';
+        }
+        else {
+          $node->title = $markup;
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_node_insert().
+ */
+function quiz_question_node_insert($node) {
+  // Make sure the latest quizzes table is maintained when a quiz changes
+  if ($node->type == 'quiz') {
+    quiz_question_refresh_latest_quizzes($node->nid);
+  }
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function quiz_question_node_update($node) {
+  // Make sure the latest quizzes table is maintained when a quiz changes
+  if ($node->type == 'quiz') {
+    quiz_question_refresh_latest_quizzes($node->nid);
+  }
+}
+
+/**
+ * Implements hook_node_revision_delete().
+ */
+function quiz_question_node_revision_delete($node) {
+  $q_types = _quiz_question_get_implementations();
+  foreach ($q_types as $q_type => $info) {
+    if ($node->type == $q_type) {
+      _quiz_delete_question($node, TRUE); // true for only this version
+    }
+  }
+}
+
+/**
+ * Implements hook_node_access_records().
+ */
+function quiz_question_node_access_records($node) {
+  $grants = array();
+  // Restricting view access to question nodes outside quizzes.
+  $question_types = _quiz_question_get_implementations();
+  $question_types = array_keys($question_types);
+  if (in_array($node->type, $question_types)) {
+    // This grant is for users having 'view quiz question outside of a quiz'
+    // permission. We set a priority of 2 because OG has a 1 priority and we
+    // want to get around it.
+    $grants[] = array(
+        'realm'        => 'quiz_question',
+        'gid'          => 1,
+        'grant_view'   => 1,
+        'grant_update' => 0,
+        'grant_delete' => 0,
+        'priority'     => 2,
+    );
+  }
+  return $grants;
+}
+
+/**
+ * Implements hook_node_grants().
+ */
+function quiz_question_node_grants($account, $op) {
+  $grants = array();
+  if ($op == 'view') {
+    if (user_access('view quiz question outside of a quiz')) {
+      // Granting view access
+      $grants['quiz_question'][] = 1;
+    }
+  }
+  return $grants;
+}
+
+/**
+ * Implements hook_view().
+ */
+function quiz_question_view($node, $view_mode) {
+  if ($view_mode == 'search_index' && !variable_get('quiz_index_questions', 1)) {
+    $node->body = '';
+    $node->content = array();
+    $node->title = '';
+    $node->taxonomy = array();
+    return $node;
+  }
+  $content = '';
+
+  if ($view_mode == 'teaser') {
+    $node->content['question_teaser'] = _quiz_question_teaser_content($node);
+  }
+  else {
+    // normal node view
+    //$question = _quiz_question_get_instance($node, TRUE);
+    $content = _quiz_question_get_instance($node, TRUE)->getNodeView();
+  }
+
+  // put it into the node->content
+  if (!empty($content)) {
+    $node->content = (isset($node->content)) ? $node->content + $content : $content;
+  }
+  return $node;
+}
+
+/**
+ * Implements hook_update().
+ */
+function quiz_question_update($node) {
+  _quiz_question_get_instance($node)->save();
+}
+
+/**
+ * Implements hook_delete().
+ */
+function quiz_question_delete(&$node) {
+  _quiz_delete_question($node, FALSE);
+}
+
+/**
+ * Implements hook_load().
+ */
+function quiz_question_load($nodes) {
+  foreach ($nodes as $nid => &$node) {
+    $node_additions = _quiz_question_get_instance($node, TRUE)->getNodeProperties();
+    foreach ($node_additions as $property => &$value) {
+      $node->$property = $value;
+    }
+  }
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+function quiz_question_node_delete($node) {
+  // Make sure the latest quizzes table is maintained when a quiz changes
+  if ($node->type == 'quiz') {
+    quiz_question_remove_latest_quizzes($node->nid);
+  }
+}
+
+/**
+ * Implements hook_validate().
+ */
+function quiz_question_validate($node, &$form) {
+  // Check to make sure that there is a question.
+  if (empty($node->body)) {
+    form_set_error('body', t('Question text is empty.'));
+  }
+  _quiz_question_get_instance($node)->validateNode($form);
+}
+
+/**
+ * Implements hook_insert().
+ */
+function quiz_question_insert(stdClass $question) {
+  _quiz_question_get_instance($question)->save(TRUE);
+  if (isset($question->quiz_qid) && $question->quiz_qid > 0) {
+    quiz_question_refresh_latest_quizzes($question->quiz_qid);
+  }
+}
+
+/**
+ * Implements hook_form().
+ */
+function quiz_question_form(&$node, &$form_state) {
+  $question = _quiz_question_get_instance($node);
+  $form = $question->getNodeForm($form_state);
+  return $form;
+}
+
+// QUIZ HOOK IMPLEMENTATIONS
+
+/**
+ * Implementation of hook_get_report().
+ *
+ * @return
+ *  Node containing all of the items from the question plus the user's answer.
+ */
+function quiz_question_get_report($nid, $vid, $result_id) {
+  $response_instance = _quiz_question_response_get_instance($result_id, NULL, NULL, $nid, $vid);
+  if (!$response_instance) {
+    drupal_set_message(t('Unable to load question with nid %nid and vid %vid', array('%nid' => $nid, '%vid' => $vid)), 'error');
+    return FALSE;
+  }
+  $result = $response_instance->getReport();
+  $response_instance->question->answers[$result['answer_id']] = $result;
+  $response_instance->question->correct = $result['is_correct'];
+  return $response_instance->question;
+}
+
+/**
+ * Implements hook_quiz_question_score().
+ */
+function quiz_question_quiz_question_score($quiz, $question_nid, $question_vid = NULL, $result_id = NULL) {
+  if (!isset($quiz) && !isset($result_id)) {
+    return quiz_question_get_max_score($question_nid, $question_vid);
+  }
+
+  // We avoid using node_load to increase performance...
+  $dummy_node = new stdClass();
+  $dummy_node->nid = $question_nid;
+  $dummy_node->vid = $question_vid;
+
+  if (!$question = _quiz_question_get_instance($dummy_node, TRUE)) {
+    return FALSE;
+  }
+
+  $score = new stdClass();
+  $score->possible = $question->getMaximumScore();
+  $score->question_nid = $question->node->nid;
+  $score->question_vid = $question->node->vid;
+
+  if (isset($result_id)) {
+    $response = _quiz_question_response_get_instance($result_id, $question->node);
+    $score->attained = $score->possible > 0 ? $response->getScore() : 0;
+    $score->possible = $response->getMaxScore();
+    $score->is_evaluated = $response->isEvaluated();
+  }
+  return $score;
+}
+
+/**
+ * Implements hook_delete_result().
+ *
+ * @param int $result_id
+ *  Result id
+ * @param int $question_nid
+ *  Question node id
+ * @param int $question_vid
+ *  Question node version id
+ */
+function quiz_question_delete_result($result_id, $question_nid, $question_vid) {
+  $response = _quiz_question_response_get_instance($result_id, NULL, NULL, $question_nid, $question_vid);
+  if ($response) {
+    $response->delete();
+  }
+  else {
+    drupal_set_message(t('Unable to delete result. A constructor could not be found for the question-type'), 'error');
+  }
+}
+
+// QUIZ HOOK IMPLEMENTATIONS
+// To be removed
+
+/**
+ * Delete the question node from the db, and mark its identifiers in the quiz
+ * linking table as "NEVER".  This is safer than deleting them
+ * and allows for same tracing of what's happened if a question was deleted unintentionally.
+ *
+ * @param $node the question node
+ * @param $only_this_version whether to delete only the specific revision of the question
+ */
+function _quiz_delete_question(&$node, $only_this_version) {
+  // let each question class delete its own stuff
+  _quiz_question_get_instance($node, TRUE)->delete($only_this_version);
+
+  // FIXME QuizQuestion class makes these relationships, so it should handle their 'deletion' too
+  // FIXME alternately, move the relationship handling out of QuizQuestion class
+  // @todo reconsider this QUESTION_NEVER status, since the node is actually gone
+  // then remove it from {quiz_relationship} linking table
+  //$base_sql = "UPDATE {quiz_relationship} SET question_status = " . QUESTION_NEVER;
+  $select_sql = 'SELECT quiz_vid FROM {quiz_relationship}';
+  if ($only_this_version) {
+    $select_sql .= ' WHERE question_nid = :question_nid AND question_vid = :question_vid';
+    $filter_arg = array(':question_nid' => $node->nid, ':question_vid' => $node->vid);
+  }
+  else {
+    $select_sql .= ' WHERE question_nid = :question_nid';
+    $filter_arg = array(':question_nid' => $node->nid);
+  }
+  //$res = db_query($select_sql . $filter_sql, $node->nid, $node->vid);
+  $res = db_query($select_sql, $filter_arg);
+
+  //db_query($base_sql . $filter_sql, $node->nid, $node->vid);
+  $update = db_update('quiz_relationship')
+    ->fields(array('question_status' => QUESTION_NEVER))
+    ->condition('question_nid', $node->nid);
+  if ($only_this_version) {
+    $update = $update->condition('question_vid', $node->vid);
+  }
+  $update->execute();
+
+  $quizzes_to_update = array();
+  while ($quizzes_to_update[] = $res->fetchField()) {
+
+  }
+  quiz_update_max_score_properties($quizzes_to_update);
+}
+
+// END To be removed
diff --git a/modules/quiz_question/quiz_question.info b/modules/quiz_question/quiz_question.info
new file mode 100644
index 0000000..825572d
--- /dev/null
+++ b/modules/quiz_question/quiz_question.info
@@ -0,0 +1,7 @@
+name = Quiz Question
+package = Quiz Core
+description = Helper module for all quiz question types
+core = 7.x
+dependencies[] = quiz
+
+files[] = tests/QuizQuestionTestCase.test
diff --git a/modules/quiz_question/quiz_question.install b/modules/quiz_question/quiz_question.install
new file mode 100644
index 0000000..71ebbe0
--- /dev/null
+++ b/modules/quiz_question/quiz_question.install
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * The installer file for quiz_question.
+ * @file
+ */
+
+/**
+ * Implements hook_install().
+ */
+function quiz_question_install() {
+
+}
+
+/**
+ * Implements hook_schema().
+ */
+function quiz_question_schema() {
+  $schema = array();
+  $schema['quiz_question_properties'] = array(
+      'description' => 'Properties common for all quizzes',
+      'fields'      => array(
+          'qqp_id'          => array(
+              'type' => 'serial',
+          ),
+          'nid'             => array(
+              'type'     => 'int',
+              'unsigned' => TRUE,
+              'not null' => TRUE,
+          ),
+          'vid'             => array(
+              'type'     => 'int',
+              'unsigned' => TRUE,
+              'not null' => TRUE,
+          ),
+          'max_score'       => array(
+              'type'     => 'int',
+              'unsigned' => TRUE,
+              'default'  => 0,
+          ),
+          'feedback'        => array(
+              'type' => 'text',
+          ),
+          'feedback_format' => array(
+              'type'     => 'varchar',
+              'length'   => 255,
+              'not null' => FALSE,
+          ),
+      ),
+      'primary key' => array('qqp_id'),
+      'indexes'     => array('question_id' => array('vid', 'nid')),
+  );
+
+  $schema['quiz_question_latest_quizzes'] = array(
+      'description' => 'Stores the latest quizzes each user has been involved in',
+      'fields'      => array(
+          'id'       => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
+          'quiz_qid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'uid'      => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+      ),
+      'primary key' => array('id'),
+  );
+  return $schema;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function quiz_question_uninstall() {
+
+}
+
+/**
+ * Implements hook_update_N()
+ */
+
+/**
+ * Make the question body visible by default for the question view mode
+ */
+function quiz_question_update_7401() {
+  $question_types = array_keys(_quiz_question_get_implementations());
+
+  foreach ($question_types as $question_type) {
+    $instance = field_read_instance('node', 'body', $question_type);
+
+    $instance['display']['question'] = array(
+        'label'    => 'hidden',
+        'type'     => 'text_default',
+        'weight'   => 1,
+        'settings' => array(),
+        'module'   => 'text',
+    );
+
+    field_update_instance($instance);
+  }
+}
+
+/**
+ * Add fields for general question feedback.
+ */
+function quiz_question_update_7500() {
+  db_drop_primary_key('quiz_question_properties');
+  db_add_field('quiz_question_properties', 'qqp_id', array('type' => 'serial'), array('primary key' => array('qqp_id')));
+  db_add_field('quiz_question_properties', 'feedback', array('type' => 'text'));
+  db_add_field('quiz_question_properties', 'feedback_format', array('type' => 'varchar', 'length' => 255, 'not null' => FALSE));
+
+  // Migrate the truefalse feedback, since we have generic feedback now.
+  $filter = filter_default_format();
+  db_query("UPDATE {quiz_question_properties} qqp
+    INNER JOIN {quiz_truefalse_node} qtn ON (qtn.vid = qqp.vid)
+    SET qqp.feedback = qtn.feedback, qqp.feedback_format = :format", array(':format' => $filter));
+
+  // Drop old field.
+  db_drop_field('quiz_truefalse_node', 'feedback');
+
+  return t('Added global quiz question feedback fields and migrated true/false feedback.');
+}
+
+/**
+ * Rename quiz_question_latest_quizzes.quiz_nid to quiz_question_latest_quizzes.quiz_qid
+ */
+function quiz_question_update_7501() {
+  $specs = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE); # @TODO: Missing specs
+  db_change_field('quiz_question_latest_quizzes', 'quiz_nid', 'quiz_qid', $specs);
+}
diff --git a/modules/quiz_question/quiz_question.module b/modules/quiz_question/quiz_question.module
new file mode 100644
index 0000000..739d48d
--- /dev/null
+++ b/modules/quiz_question/quiz_question.module
@@ -0,0 +1,446 @@
+<?php
+
+use Drupal\quiz_question\QuizQuestion;
+use Drupal\quiz_question\QuizQuestionResponse;
+
+/**
+ * Quiz Question module.
+ * This module provides the basic facilities for adding quiz question types to a quiz.
+ * @file
+ */
+/*
+ * The system remembers what quizzes a user has been involved in lately. This constant determines
+ * how many quizzes the system will remember for each user
+ */
+define('QUIZ_QUESTION_NUM_LATEST', 10);
+
+/**
+ * Include hook implementations.
+ */
+require_once dirname(__FILE__) . '/quiz_question.hooks.inc';
+
+/**
+ * Retrieve list of question types.
+ *
+ * @return
+ *  Array of question types.
+ */
+function quiz_get_question_types() {
+  static $quiz_questions = array();
+
+  // We vastly improves performance by statically caching the question types.
+  if (!empty($quiz_questions)) {
+    return $quiz_questions;
+  }
+
+  // Get question types from the modules that defines them.
+  if ($quiz_questions = module_invoke_all('quiz_question_info')) {
+    return $quiz_questions;
+  }
+
+  drupal_set_message(t('You need to install and enable at least one question type(multichoice for instance) to use quiz.'), 'warning', FALSE);
+  return array();
+}
+
+/**
+ * Figure out if a user has access to score a certain result
+ *
+ * @param $vid
+ *  Question version id
+ * @param $result_id
+ *  Result id
+ * @return
+ *  True if the user has access to score the result
+ */
+function quiz_question_access_to_score($vid, $result_id) {
+  global $user;
+  $sql = 'SELECT * FROM {quiz_results_answers} WHERE result_id = :result_id AND question_vid = :question_vid';
+  $answer = db_query($sql, array(':result_id' => $result_id, ':question_vid' => $vid))->fetch();
+  if (!$answer) {
+    return FALSE;
+  }
+  if (user_access('score any quiz')) {
+    return TRUE;
+  }
+  if (user_access('score taken quiz answer')) {
+    $uid = db_query('SELECT uid from {quiz_results} qnr WHERE qnr.result_id = :result_id', array(':result_id' => $result_id))->fetchField();
+    if ($uid == $user->uid) {
+      return TRUE;
+    }
+  }
+  if (user_access('score own quiz')) {
+    return db_query('SELECT r.uid FROM {node_revision} r
+            JOIN {quiz_results} qnr ON (r.nid = qnr.nid)
+            WHERE qnr.result_id = :result_id
+            ', array(':result_id' => $result_id))->fetchField() == $user->uid;
+  }
+}
+
+/**
+ * Form for teaser display
+ *
+ * @param $node
+ *  The question node
+ * @return
+ *  Content array
+ */
+function _quiz_question_teaser_content($node) {
+  $content['question_type'] = array(
+      '#markup' => '<div class="question_type_name">' . node_type_get_type($node)->name . '</div>',
+      '#weight' => -100,
+  );
+  return $content;
+}
+
+/**
+ * Check if a question has already been answered by anyone.
+ *
+ * This is to see if a new revision of a question should be made when saving.
+ *
+ * @return bool
+ *  TRUE if a user has submitted an answer for this question.
+ */
+function quiz_question_has_been_answered($question) {
+  if ($question_instance = _quiz_question_get_instance($question, true)) {
+    return $question_instance->hasBeenAnswered();
+  }
+}
+
+/**
+ * Get the configuration form for all enabled question types.
+ */
+function quiz_question_config($form, $context) {
+  $form['#validate'] = array();
+
+  // Go through all question types and merge their config forms
+  foreach (_quiz_question_get_implementations() as $type => $values) {
+    $function = $type . '_quiz_question_config';
+    if ($admin_form = $function()) {
+      $form[$type] = $admin_form;
+      $form[$type]['#type'] = 'fieldset';
+      $form[$type]['#title'] = $values['name'];
+      $form[$type]['#collapsible'] = TRUE;
+      $form[$type]['#collapsed'] = TRUE;
+      if (isset($admin_form['#validate']) && is_array($admin_form['#validate'])) {
+        $form['#validate'] = array_merge($form['#validate'], $admin_form['#validate']);
+        unset($form[$type]['#validate']);
+      }
+    }
+  }
+
+  return system_settings_form($form);
+}
+
+/**
+ * Get an instance of a quiz question.
+ *
+ * Get information about the class and use it to construct a new
+ * object of the appropriate type.
+ *
+ * @param $node
+ *  Question node
+ * @param $use_cached
+ *  Can we use a cached version of the node?
+ * @return
+ *  The appropriate QuizQuestion extension instance
+ */
+function _quiz_question_get_instance(&$node, $use_cached = FALSE) {
+  // We use static caching to improve performance
+  static $question_instances = array();
+
+  $vid = is_object($node) ? (isset($node->vid) ? $node->vid : 0) : $node['vid'];
+  if ($use_cached && isset($question_instances[$vid])) {
+    return $question_instances[$vid];
+  }
+
+  $using_dummy_node = FALSE;
+  if (is_object($node)) {
+    // If $node don't have a type it is a dummy node
+    if (!isset($node->type)) {
+      // To substanitally improve performance(especially on the result page) we avoid node_load()...
+      // @todo why is this? consider getting rid of this as nodes are better
+      // cached/retrieved in d7. adding the UID here to get rid of a notice.
+      $sql = 'SELECT n.type, r.nid, r.vid, r.title, p.max_score, n.uid
+              FROM {node_revision} r
+              JOIN {node} n ON r.nid = n.nid
+              JOIN {quiz_question_properties} p ON r.vid = p.vid
+              WHERE r.vid = :vid';
+      $node = db_query($sql, array(':vid' => $node->vid))->fetch();
+      $using_dummy_node = TRUE;
+    }
+    $name = $node->type;
+  }
+  elseif (is_array($node)) {
+    $name = $node['type'];
+  }
+
+  // No cached instance of QuizQuestion has been returned. We construct a new instance
+  $info = _quiz_question_get_implementations();
+  $constructor = $info[$name]['question provider'];
+  if (empty($constructor)) {
+    return FALSE;
+  }
+
+  // We create a new instance of QuizQuestion
+  $to_return = new $constructor($node);
+  if (!($to_return instanceof QuizQuestion)) {
+    // Make sure the constructor is creating an extension of QuizQuestion
+    drupal_set_message(t('The question-type %name isn\'t a QuizQuestion. It needs to extend the QuizQuestion class.', array('%name' => $name)), 'error', FALSE);
+  }
+
+  // If we're using a dummy node we have to run getNodeProperties, and populate the node with those properties
+  if ($using_dummy_node) {
+    $props = $to_return->getNodeProperties();
+    foreach ($props as $key => $value) {
+      $to_return->node->{$key} = $value;
+    }
+  }
+
+  // Cache the node
+  $question_instances[$vid] = $to_return;
+
+  return $to_return;
+}
+
+/**
+ * Get an instance of a quiz question responce.
+ *
+ * Get information about the class and use it to construct a new
+ * object of the appropriate type.
+ *
+ * @param $result_id
+ *  Result id
+ * @param $question
+ *  The question node(not a QuizQuestion instance)
+ * @param $answer
+ *  Resonce to the answering form.
+ * @param $nid
+ *  Question node id
+ * @param $vid
+ *  Question node version id
+ * @return
+ *  The appropriate QuizQuestionResponce extension instance
+ */
+function _quiz_question_response_get_instance($result_id, $question, $answer = NULL, $nid = NULL, $vid = NULL) {
+  // We cache responses to improve performance
+  static $quiz_responses = array();
+  if (is_object($question) && isset($quiz_responses[$result_id][$question->vid])) {
+    // We refresh the question node in case it has been changed since we cached the response
+    $quiz_responses[$result_id][$question->vid]->refreshQuestionNode($question);
+    if ($quiz_responses[$result_id][$question->vid]->is_skipped !== FALSE) {
+      return $quiz_responses[$result_id][$question->vid];
+    }
+  }
+  elseif (isset($quiz_responses[$result_id][$vid])) {
+    if ($quiz_responses[$result_id][$vid]->is_skipped !== FALSE) {
+      return $quiz_responses[$result_id][$vid];
+    }
+  }
+  if (!isset($quiz_responses[$result_id])) {
+    // Prepare to cache responses for this result id
+    $quiz_responses[$result_id] = array();
+  }
+  // If the question node isn't set we fetch it from the QuizQuestion instance this responce belongs to
+  if (!isset($question)) {
+    $question_node = node_load($nid, $vid);
+    $question = _quiz_question_get_instance($question_node, TRUE)->node;
+  }
+  if (!$question) {
+    return FALSE;
+  }
+  $info = _quiz_question_get_implementations();
+  $constructor = $info[$question->type]['response provider'];
+  $to_return = new $constructor($result_id, $question, $answer);
+  // All responce classes must extend QuizQuestionResponse
+  if (!($to_return instanceof QuizQuestionResponse)) {
+    drupal_set_message(t('The question-response isn\'t a QuizQuestionResponse. It needs to extend the QuizQuestionResponse interface, or extend the abstractQuizQuestionResponse class.'), 'error', FALSE);
+  }
+  // Cache the responce instance
+  $quiz_responses[$result_id][$question->vid] = $to_return;
+
+  return $to_return;
+}
+
+/**
+ * Get the information about various implementations of quiz questions.
+ *
+ * @param $reset
+ *  If this is true, the cache will be reset.
+ * @return
+ *  An array of information about quiz question implementations.
+ * @see quiz_question_quiz_question_info() for an example of a quiz question info hook.
+ */
+function _quiz_question_get_implementations($name = NULL, $reset = FALSE) {
+  static $info = array();
+  if (empty($info) || $reset) {
+    $qtypes = module_invoke_all('quiz_question_info');
+    foreach ($qtypes as $type => $definition) {
+      // We only want the ones with classes.
+      if (!empty($definition['question provider'])) {
+        // Cache the info
+        $info[$type] = $definition;
+      }
+    }
+    drupal_alter('quiz_question_info', $info);
+  }
+  return $info;
+}
+
+/**
+ * Refreshes the quiz_question_latest_quizzes table when a user has modified a new quiz.
+ *
+ * The latest quizzes table is used to know what quizzes the user has been using lately.
+ *
+ * @param int $qid
+ *   ID of the last quiz the current user modified
+ */
+function quiz_question_refresh_latest_quizzes($qid) {
+  global $user;
+
+  // Delete entry if it allready exists
+  db_delete('quiz_question_latest_quizzes')
+    ->condition('uid', $user->uid)
+    ->condition('quiz_qid', $qid)
+    ->execute();
+
+  // Inserts as new entry to get new id. Latest quizzes are ordered by id(descending)
+  db_insert('quiz_question_latest_quizzes')
+    ->fields(array('uid' => $user->uid, 'quiz_nid' => $qid))
+    ->execute();
+
+  // If we have to many entries for current user, delete the oldest entries...
+  $min_id = db_select('quiz_question_latest_quizzes', 'lq')
+    ->fields('lq', array('id'))
+    ->condition('uid', $user->uid)
+    ->orderBy('id', 'DESC')
+    ->range(QUIZ_QUESTION_NUM_LATEST - 1, 1)
+    ->execute()
+    ->fetchField();
+
+  // Delete all table entries older than the nth row, if nth row was found.
+  if ($min_id) {
+    db_delete('quiz_question_latest_quizzes')
+      ->condition('id', $min_id, '<')
+      ->condition('uid', $user->uid)
+      ->execute();
+  }
+}
+
+/**
+ * Removes a quiz from the quiz_question_latest_quizzes table.
+ *
+ * @param $qid
+ *   ID of a quiz entity that shall be removed
+ */
+function quiz_question_remove_latest_quizzes($qid) {
+  db_delete('quiz_question_latest_quizzes')->condition('quiz_qid', $qid)->execute();
+}
+
+/**
+ * Get the max score for a question
+ *
+ * @param $nid
+ *  Question node id
+ * @param $vid
+ *  Question node version id
+ * @return
+ *  Max score(int)
+ */
+function quiz_question_get_max_score($nid, $vid) {
+  return db_query('SELECT max_score
+          FROM {quiz_question_properties}
+          WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->fetchField();
+}
+
+/**
+ * Returns a result report for a question response.
+ *
+ * The retaurned value is a form array because in some contexts the scores in the form
+ * is editable
+ *
+ * @param $question
+ *  The question node
+ * @return
+ *  FAPI form array
+ */
+function quiz_question_report_form($question) {
+  $answer = $question->answers[0];
+  $response_instance = _quiz_question_response_get_instance($answer['result_id'], $question, $answer);
+
+  // If need to specify the score weight if it isn't already specified.
+  if (!isset($response_instance->question->score_weight)) {
+    $vid = db_query('SELECT quiz_vid FROM {quiz_results} WHERE result_id = :rid', array(
+        ':rid' => $answer['result_id']
+      ))->fetchField();
+
+    $qr_max_score = db_query('SELECT qnr.max_score
+      FROM {quiz_relationship} qnr
+      WHERE qnr.question_vid = :question_vid AND qnr.quiz_vid = :quiz_vid', array(
+        ':question_vid' => $question->vid,
+        ':quiz_vid'     => $vid
+      ))->fetchField();
+
+    if ($qr_max_score === FALSE) {
+      $qr_max_score = db_query('SELECT qt.max_score FROM {quiz_results} qnr
+         JOIN {quiz_results_answers} qnra ON (qnr.result_id = qnra.result_id)
+         JOIN {quiz_terms} qt ON (qt.vid = qnr.quiz_vid AND qt.tid = qnra.tid)
+         WHERE qnr.result_id = :rid AND qnra.question_nid = :qnid AND qnra.question_vid = :qvid', array(
+          ':rid'  => $answer['result_id'],
+          ':qnid' => $question->nid,
+          ':qvid' => $question->vid
+        ))->fetchField();
+    }
+
+    $response_instance->question->score_weight = 0;
+    if (($qr_max_score != 0 && $response_instance->question->max_score != 0)) {
+      $response_instance->question->score_weight = $qr_max_score / $response_instance->question->max_score;
+    }
+  }
+
+  return $response_instance->getReportForm();
+}
+
+/**
+ * Add body field to quiz_question nodes.
+ */
+function quiz_question_add_body_field($type) {
+  node_types_rebuild();
+  $node_type = node_type_get_type($type);
+  if (!$node_type) {
+    watchdog('quiz', 'Attempt to add body field was failed as question content type %type is not defined.', array('%type' => $type), WATCHDOG_ERROR);
+    watchdog('quiz', '<pre>' . print_r(node_type_get_types(), 1), array(), WATCHDOG_ERROR);
+    return;
+  }
+  node_add_body_field($node_type, 'Question');
+  // Override default weight to make body field appear first
+  $instance = field_read_instance('node', 'body', $type);
+  $instance['widget']['weight'] = -10;
+  $instance['widget']['settings']['rows'] = 6;
+
+  // Make the question body visible by default for the question view mode
+  $instance['display']['question'] = array(
+      'label'    => 'hidden',
+      'type'     => 'text_default',
+      'weight'   => 1,
+      'settings' => array(),
+      'module'   => 'text',
+  );
+
+  field_update_instance($instance);
+}
+
+/**
+ * Theme the feedback for any question type.
+ */
+function theme_quiz_question_feedback($variables) {
+  $rows = $variables['data'];
+  $headers = array_intersect_key($variables['labels'], $rows[0]);
+  return theme('table', array('header' => $headers, 'rows' => $rows));
+}
+
+/**
+ * Helper function to faciliate icon display, like "correct" or "selected".
+ */
+function quiz_icon($icon) {
+  return theme('quiz_answer_result', array('type' => $icon));
+}
diff --git a/modules/quiz_question/quiz_question.pages.inc b/modules/quiz_question/quiz_question.pages.inc
new file mode 100644
index 0000000..0eeddb9
--- /dev/null
+++ b/modules/quiz_question/quiz_question.pages.inc
@@ -0,0 +1,267 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the quiz_question module.
+ */
+
+/**
+ * Create the form for the revision actions page
+ *
+ * Form for deciding what to do with the quizzes a question is member of when the question is
+ * revised
+ *
+ * @param $form_state
+ * @param $nid
+ *  Question node id
+ * @param $vid
+ *  Question node version id
+ * @return
+ *  FAPI form array
+ */
+function quiz_question_revision_actions($form, $form_state, $nid, $vid) {
+  // If no questions were kept we shouldn't get here...
+  if (!isset($_SESSION['quiz_question_kept'])) {
+    drupal_goto('node/' . $nid);
+  }
+
+  $form = array();
+  $form['q_nid'] = array('#type' => 'value', '#value' => intval($nid));
+  $form['q_vid'] = array('#type' => 'value', '#value' => intval($vid));
+
+  // Fetch data for all the quizzes that was kept
+  $quiz_qids = array();
+  $quiz_vids = array();
+  foreach ($_SESSION['quiz_question_kept'] as $nid_vid) {
+    $temp = explode('-', $nid_vid);
+    if (_quiz_is_int($temp[0], 0) && _quiz_is_int($temp[1], 0)) {
+      $quiz_qids[] = $temp[0];
+      $quiz_vids[] = $temp[1];
+    }
+  }
+  $quizzes = array();
+  $sql = 'SELECT nr.nid, nr.vid, nr.title, n.status FROM {node_revision} nr
+          JOIN {node} n ON n.nid = nr.nid
+          WHERE nr.vid IN (:vids)';
+  $res = db_query($sql, array(':vids' => $quiz_vids));
+  //while ($res_o = db_fetch_object($res)) {
+  foreach ($res as $res_o) {
+    $res_o->answered = quiz_has_been_answered($res_o);
+    $quizzes[] = $res_o;
+  }
+
+  $text = t('You have created a new revision of a question that belongs to %num quizzes. Choose what you want to do with the different quizzes.', array('%num' => count($quizzes)));
+  $form['intro'] = array('#markup' => $text);
+  $form['quizzes'] = array();
+  // Create a form element for each quiz
+  foreach ($quizzes as $quiz) {
+    $published = $quiz->status == 1 ? t('published') : t('unpublished');
+    $answered = $quiz->answered ? t('answered') : t('unanswered');
+    $quiz_id = $quiz->qid;
+
+    // We fetch the revision options from a helper function
+    $options = _quiz_question_revision_options($quiz->status == 1, $quiz->answered);
+
+    $form['quizzes'][$quiz_id . '-' . $quiz->vid . '-' . $quiz->status . '-' . ($quiz->answered ? '1' : '0')] = array(
+        '#type'          => 'radios',
+        '#title'         => check_plain($quiz->title) . ' (' . $published . ' ' . t('and') . ' ' . $answered . ')',
+        '#default_value' => $options['default'],
+        '#options'       => $options['options'],
+    );
+    $form['quizzes'][$quiz->vid]['#quiz_title'] = $quiz->title;
+  }
+
+  $form['submit'] = array(
+      '#type'  => 'submit',
+      '#value' => t('Submit'),
+  );
+  $form['#submit'] = array('quiz_question_revision_actions_submit');
+
+  // Help texts
+  $form['update_expl'] = array(
+      '#type'  => 'item',
+      '#title' => t('Update'),
+      '#value' => t('Replace the old revision of the question with the new revision.'),
+  );
+  $form['revise_expl'] = array(
+      '#type'        => 'item',
+      '#title'       => t('Revise'),
+      '#value'       => t('If a quiz has been answered you should make a new revision to ensure that existing answer statistics and reports remain correct.'),
+      '#description' => t('If the new revision of the question only correct spelling errors etc. you don\'t need to revise.'),
+  );
+  return $form;
+}
+
+/**
+ * Get revision options for the revision actions page
+ *
+ * Returns revision options and default option depending on the published and answered status
+ * for a quiz
+ *
+ * @param $published
+ *  Publish status for a quiz
+ * @param $answered
+ *  Has the quiz been answered?
+ * @return
+ *  Array with values for the #options and #default_value part of a form item
+ */
+function _quiz_question_revision_options($published, $answered) {
+  // We create a data structure holding the different options for the different quiz states
+  $struct = array(
+      'published'   => array(
+          'answered'   => array(
+              'options' => array(
+                  // The key is in the form [update][revise][publish]
+                  '110' => t('Update, revise and unpublish'),
+                  '111' => t('Update and revise'),
+                  '101' => t('Update'),
+                  '001' => t('Do nothing'),
+              ),
+              'default' => '111',
+          ),
+          'unanswered' => array(
+              'options' => array(
+                  '100' => t('Update and Unpublish'),
+                  '101' => t('Update'),
+                  '001' => t('Do nothing'),
+              ),
+              'default' => '101',
+          ),
+      ),
+      'unpublished' => array(
+          'answered'   => array(
+              'options' => array(
+                  '111' => t('Update, revise and publish'),
+                  '110' => t('Update and revise'),
+                  '101' => t('Update and publish'),
+                  '100' => t('Update'),
+                  '000' => t('Do nothing'),
+              ),
+              'default' => '110',
+          ),
+          'unanswered' => array(
+              'options' => array(
+                  '101' => t('Update and publish'),
+                  '100' => t('Update'),
+                  '000' => t('Do nothing'),
+              ),
+              'default' => '100',
+          ),
+      ),
+  );
+  $published = $published ? 'published' : 'unpublished';
+  $answered = $answered ? 'answered' : 'unanswered';
+  return $struct[$published][$answered];
+}
+
+/**
+ * Submit callback for the revision actions page
+ */
+function quiz_question_revision_actions_submit($form, &$form_state) {
+  // TODO: Add date check here?
+  // We should't be able to revisit the revision actions page after this.
+  unset($_SESSION['quiz_question_kept']);
+
+  foreach ($form_state['values'] as $key => $value) {
+    if (!preg_match('/^[0-9]+-[0-9]+-[0,1]-[0,1]$/', $key)) {
+      continue;
+    }
+    list($update, $revise, $publish) = str_split($value); //FORMAT: Update[0,1], revise[0,1] and publish[0,1]
+    list($qid, $vid, $published, $answered) = explode('-', $key); //FORMAT: nid(int), vid(int), published[0,1] and answered[0,1]
+    //
+    // If we are to revise the quiz we need to do that first...
+    if ($revise == '1') {
+      $quiz = quiz_entity_single_load((int) $qid, (int) $vid);
+      $quiz->is_new_revision = 1;
+    }
+
+    if (!isset($quiz) && $publish != $published) {
+      $quiz = quiz_entity_single_load((int) $qid, (int) $vid);
+    }
+
+    // If the quiz entity is to be revised and/or (un)published we save it now
+    if (isset($quiz)) {
+      $quiz->auto_created = TRUE;
+      $quiz->status = $publish;
+      node_save($quiz);
+      $quiz_qid = $quiz->qid;
+      $quiz_vid = $quiz->vid;
+    }
+    else {
+      $quiz_qid = (int) $qid;
+      $quiz_vid = (int) $vid;
+    }
+
+    if ($update == '1') {
+      $res_relationship = db_query(
+        'SELECT max_score, auto_update_max_score
+            FROM {quiz_relationship}
+            WHERE quiz_vid = :quiz_vid
+              AND question_nid = :question_nid', array(
+          ':quiz_vid'     => $quiz_vid,
+          ':question_nid' => $form_state['values']['q_nid']
+        ))->fetch();
+
+      $auto_update_max_score = isset($res_relationship->auto_update_max_score) ? $res_relationship->auto_update_max_score : 0;
+
+      if ($res_relationship) {
+        $max_score = $res_relationship->max_score;
+      }
+
+      if (!$res_relationship || $auto_update_max_score) {
+        $res = db_query(
+          'SELECT max_score
+            FROM {quiz_question_properties}
+            WHERE vid = :vid', array(':vid' => $form_state['values']['q_vid']));
+        $max_score = $res->fetchField();
+      }
+
+      $res = db_query(
+        'SELECT weight, question_status
+         FROM {quiz_relationship}
+            WHERE quiz_qid = :quiz_qid
+                AND quiz_vid = :quiz_vid
+                AND question_nid = :question_nid', array(
+          ':quiz_qid'     => $quiz_qid,
+          ':quiz_vid'     => $quiz_vid,
+          ':question_nid' => $form_state['values']['q_nid']
+      ));
+
+      if ($res_o = $res->fetch()) {
+        // Remove old revsions of the question from the quiz
+        db_delete('quiz_relationship')
+          ->condition('quiz_qid', $quiz_qid)
+          ->condition('quiz_vid', $quiz_vid)
+          ->condition('question_nid', $form_state['values']['q_nid'])
+          ->execute();
+        $weight = $res_o->weight;
+        $question_status = $res_o->question_status;
+      }
+      else {
+        $weight = 1 + db_query(
+            "SELECT MAX(weight)
+              FROM {quiz_relationship}
+              WHERE quiz_vid = :quiz_vid", array(':quiz_vid' => $quiz_vid))->fetchField();
+
+        $quiz_randomization = db_query(
+          "SELECT randomization
+            FROM {quiz_entity_revision}
+            WHERE qid = :qid AND vid = :vid", array(':qid' => $quiz_qid, ':vid' => $quiz_vid))->fetchField();
+
+        $question_status = $quiz_randomization == 2 ? QUESTION_RANDOM : QUESTION_ALWAYS;
+      }
+      // Insert the newest revision of the question into the quiz
+      $entity = (object) array(
+            'quiz_qid'              => $quiz_qid,
+            'quiz_vid'              => $quiz_vid,
+            'question_nid'          => $form_state['values']['q_nid'],
+            'question_vid'          => $form_state['values']['q_vid'],
+            'max_score'             => $max_score,
+            'weight'                => $weight,
+            'question_status'       => $question_status,
+            'auto_update_max_score' => $auto_update_max_score,
+      );
+      entity_save('quiz_question_relationship', $entity);
+    }
+  }
+}
diff --git a/modules/quiz_question/src/QuizQuestion.php b/modules/quiz_question/src/QuizQuestion.php
new file mode 100644
index 0000000..31cd44a
--- /dev/null
+++ b/modules/quiz_question/src/QuizQuestion.php
@@ -0,0 +1,495 @@
+<?php
+
+namespace Drupal\quiz_question;
+
+use Drupal\quiz\Controller\QuizQuestionFeedbackController;
+use Drupal\quiz\Entity\QuizEntity;
+use stdClass;
+
+/**
+ * Classes used in the Quiz Question module.
+ *
+ * The core of the Quiz Question module is a set of abstract classes that
+ * can be used to quickly and efficiently create new question types.
+ *
+ * Why OO?
+ * Drupal has a long history of avoiding many of the traditional OO structures
+ * and metaphors. However, with PHP 5, there are many good reasons to use OO
+ * principles more broadly.
+ *
+ * The case for Quiz question types is that question types all share common
+ * structure and logic. Using the standard hook-only Drupal metaphor, we are
+ * forced to copy and paste large amounts of repetitive code from question
+ * type to question type. By using OO principles and construction, we can
+ * easily encapsulate much of that logic, while still making it easy to
+ * extend the existing content.
+ *
+ * Where do I start?
+ * To create a new question type, check out the multichoice question type for instance.
+ *
+ * @file
+ */
+
+/**
+ * A base implementation of a quiz_question, adding a layer of abstraction between the
+ * node API, quiz API and the question types.
+ *
+ * It is required that Question types extend this abstract class.
+ *
+ * This class has default behaviour that all question types must have. It also handles the node API, but
+ * gives the question types oppurtunity to save, delete and provide data specific to the question types.
+ *
+ * This abstract class also declares several abstract functions forcing question-types to implement required
+ * methods.
+ */
+abstract class QuizQuestion {
+  /*
+   * QUESTION IMPLEMENTATION FUNCTIONS
+   *
+   * This part acts as a contract(/interface) between the question-types and the rest of the system.
+   *
+   * Question types are made by extending these generic methods and abstract methods.
+   */
+
+  /**
+   * The current node for this question.
+   */
+  public $node = NULL;
+
+  /**
+   * Extra node properties
+   */
+  public $nodeProperties = NULL;
+
+  /**
+   * QuizQuestion constructor stores the node object.
+   *
+   * @param $node
+   *   The node object
+   */
+  public function __construct(stdClass &$node) {
+    $this->node = $node;
+  }
+
+  /**
+   * Allow question types to override the body field title
+   *
+   * @return
+   *  The title for the body field
+   */
+  public function getBodyFieldTitle() {
+    return t('Question');
+  }
+
+  /**
+   * Returns a node form to quiz_question_form
+   *
+   * Adds default form elements, and fetches question type specific elements from their
+   * implementation of getCreationForm
+   *
+   * @param array $form_state
+   * @return unknown_type
+   */
+  public function getNodeForm(array &$form_state = NULL) {
+    global $user;
+    $form = array();
+
+    // mark this form to be processed by quiz_form_alter. quiz_form_alter will among other things
+    // hide the revion fieldset if the user don't have permission to controll the revisioning manually.
+    $form['#quiz_check_revision_access'] = TRUE;
+
+    // Allow user to set title?
+    if (user_access('edit question titles')) {
+      $this->includeAutoTitleScript();
+
+      $form['title'] = array(
+          '#type'          => 'textfield',
+          '#title'         => t('Title'),
+          '#maxlength'     => 255,
+          '#default_value' => $this->node->title,
+          '#required'      => FALSE,
+          '#description'   => t('Add a title that will help distinguish this question from other questions. This will not be seen during the @quiz.', array('@quiz' => QUIZ_NAME)),
+      );
+    }
+    else {
+      $form['title'] = array('#type' => 'value', '#value' => $this->node->title);
+    }
+
+    // Store quiz id in the form
+    $form['quiz_qid'] = array('#type' => 'hidden');
+    $form['quiz_vid'] = array('#type' => 'hidden');
+
+    if (isset($_GET['quiz_qid']) && isset($_GET['quiz_vid'])) {
+      $form['quiz_qid']['#value'] = intval($_GET['quiz_qid']);
+      $form['quiz_vid']['#value'] = intval($_GET['quiz_vid']);
+    }
+
+    // Identify this node as a quiz question type so that it can be recognized by other modules effectively.
+    $form['is_quiz_question'] = array(
+        '#type'  => 'value',
+        '#value' => TRUE
+    );
+
+    if (!empty($this->node->nid)) {
+      if ($properties = entity_load('quiz_question', FALSE, array('nid' => $this->node->nid, 'vid' => $this->node->vid))) {
+        $quiz_question = reset($properties);
+      }
+    }
+
+    $form['feedback'] = array(
+        '#type'          => 'text_format',
+        '#title'         => t('Question feedback'),
+        '#default_value' => !empty($quiz_question->feedback) ? $quiz_question->feedback : '',
+        '#format'        => !empty($quiz_question->feedback_format) ? $quiz_question->feedback_format : filter_default_format(),
+        '#description'   => t('This feedback will show when configured and the user answers a question, regardless of correctness.'),
+    );
+
+    // Add question type specific content
+    $form = array_merge($form, $this->getCreationForm($form_state));
+
+    if ($this->hasBeenAnswered()) {
+      $log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
+      $this->node->revision = 1;
+      $this->node->log = $log;
+    }
+    return $form;
+  }
+
+  /**
+   * Adds inline js to automatically set the question's node title.
+   */
+  private function includeAutoTitleScript() {
+    $max_length = variable_get('quiz_autotitle_length', 50);
+    drupal_add_js(array('quiz_max_length' => $max_length), array('type' => 'setting'));
+    drupal_add_js(drupal_get_path('module', 'quiz') . '/misc/js/quiz.auto-title.js');
+  }
+
+  /**
+   * Retrieve information relevant for viewing the node.
+   *
+   * (This data is generally added to the node's extra field.)
+   *
+   * @return
+   *  Content array
+   */
+  public function getNodeView() {
+    $type = node_type_get_type($this->node);
+    $content['question_type'] = array(
+        '#markup' => '<div class="question_type_name">' . $type->name . '</div>',
+        '#weight' => -2,
+    );
+    return $content;
+  }
+
+  /**
+   * Getter function returning properties to be loaded when the node is loaded.
+   *
+   * @see load hook in quiz_question.module (quiz_question_load)
+   *
+   * @return array
+   */
+  public function getNodeProperties() {
+    if (isset($this->nodeProperties)) {
+      return $this->nodeProperties;
+    }
+    $props['max_score'] = db_query('SELECT max_score
+            FROM {quiz_question_properties}
+            WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchField();
+    $props['is_quiz_question'] = TRUE;
+    $this->nodeProperties = $props;
+    return $props;
+  }
+
+  /**
+   * Responsible for handling insert/update of question-specific data.
+   * This is typically called from within the Node API, so there is no need
+   * to save the node.
+   *
+   * The $is_new flag is set to TRUE whenever the node is being initially
+   * created.
+   *
+   * A save function is required to handle the following three situations:
+   * - A new node is created ($is_new is TRUE)
+   * - A new node *revision* is created ($is_new is NOT set, because the
+   *   node itself is not new).
+   * - An existing node revision is modified.
+   *
+   * @see hook_update and hook_insert in quiz_question.module
+   *
+   * @param $is_new
+   *  TRUE when the node is initially created.
+   */
+  public function save($is_new = FALSE) {
+    // We call the abstract function saveNodeProperties to save type specific data
+    $this->saveNodeProperties($is_new);
+
+    db_merge('quiz_question_properties')
+      ->key(array(
+          'nid' => $this->node->nid,
+          'vid' => $this->node->vid,
+      ))
+      ->fields(array(
+          'nid'             => $this->node->nid,
+          'vid'             => $this->node->vid,
+          'max_score'       => $this->getMaximumScore(),
+          'feedback'        => !empty($this->node->feedback['value']) ? $this->node->feedback['value'] : '',
+          'feedback_format' => !empty($this->node->feedback['format']) ? $this->node->feedback['format'] : filter_default_format(),
+      ))
+      ->execute();
+
+    // Save what quizzes this question belongs to.
+    $quizzes_kept = $this->saveRelationships();
+    if ($quizzes_kept && $this->node->revision) {
+      if (user_access('manual quiz revisioning') && !variable_get('quiz_auto_revisioning', 1)) {
+        unset($_GET['destination']);
+        unset($_REQUEST['edit']['destination']);
+        drupal_goto('quiz_question/' . $this->node->nid . '/' . $this->node->vid . '/revision_actions');
+      }
+      // For users without the 'manual quiz revisioning' permission we submit the revision_actions form
+      // silently with its default values set.
+      else {
+        $form_state = array();
+        $form_state['values']['op'] = t('Submit');
+        require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'quiz_question') . '/quiz_question.pages.inc';
+        drupal_form_submit('quiz_question_revision_actions', $form_state, $this->node->nid, $this->node->vid);
+      }
+    }
+  }
+
+  /**
+   * Delete question data from the database.
+   *
+   * Called by quiz_question_delete (hook_delete).
+   * Child classes must call super
+   *
+   * @param $only_this_version
+   *  If the $only_this_version flag is TRUE, then only the particular
+   *  nid/vid combo should be deleted. Otherwise, all questions with the
+   *  current nid can be deleted.
+   */
+  public function delete($only_this_version = FALSE) {
+    // Delete answeres & properties
+    $remove_answer = db_delete('quiz_results_answers')->condition('question_nid', $this->node->nid);
+    $remove_properties = db_delete('quiz_question_properties')->condition('nid', $this->node->nid);
+    if ($only_this_version) {
+      $remove_answer->condition('question_vid', $this->node->vid);
+      $remove_properties->condition('vid', $this->node->vid);
+    }
+    $remove_answer->execute();
+    $remove_properties->execute();
+  }
+
+  /**
+   * Provides validation for question before it is created.
+   *
+   * When a new question is created and initially submited, this is
+   * called to validate that the settings are acceptible.
+   *
+   * @param $form
+   *  The processed form.
+   */
+  abstract public function validateNode(array &$form);
+
+  /**
+   * Get the form through which the user will answer the question.
+   *
+   * @param $form_state
+   *  The FAPI form_state array
+   * @param $result_id
+   *  The result id.
+   * @return
+   *  Must return a FAPI array.
+   */
+  public function getAnsweringForm(array $form_state = NULL, $result_id) {
+    return array(
+        '#element_validate' => array(
+            'quiz_question_element_validate'
+        )
+    );
+  }
+
+  /**
+   * Element validator (for repeat until correct).
+   */
+  public static function elementValidate(&$element, &$form_state) {
+    $quiz = quiz_entity_single_load(__quiz_get_context_id());
+
+    $question_nid = $element['#array_parents'][1];
+    $answer = $form_state['values']['question'][$question_nid];
+    $current_question = node_load($question_nid);
+
+    // There was an answer submitted.
+    $response = _quiz_question_response_get_instance($_SESSION['quiz'][$quiz->qid]['result_id'], $current_question, $answer);
+    if ($quiz->repeat_until_correct && !$response->isCorrect()) {
+      form_set_error('', t('The answer was incorrect. Please try again.'));
+
+      $result = $form_state['build_info']['args'][3];
+      $controller = new QuizQuestionFeedbackController($quiz, $result);
+      $feedback = $controller->buildRenderArray($current_question);
+      $element['feedback'] = array(
+          '#weight' => 100,
+          '#markup' => drupal_render($feedback),
+      );
+    }
+  }
+
+  /**
+   * Get the form used to create a new question.
+   *
+   * @param
+   *  FAPI form state
+   * @return
+   *  Must return a FAPI array.
+   */
+  abstract public function getCreationForm(array &$form_state = NULL);
+
+  /**
+   * Get the maximum possible score for this question.
+   */
+  abstract public function getMaximumScore();
+
+  /**
+   * Save question type specific node properties
+   */
+  abstract public function saveNodeProperties($is_new = FALSE);
+
+  /**
+   * Save this Question to the specified Quiz.
+   */
+  function saveRelationships() {
+    if (!empty($this->node->quiz_qid) && !empty($this->node->quiz_vid)) {
+      /* @var $quiz QuizEntity */
+      $quiz = quiz_entity_single_load($this->node->quiz_qid, $this->node->quiz_vid);
+      $quiz_id = $quiz->qid;
+      $ids[0] = $quiz_id;
+      $ids[1] = $quiz->vid;
+
+      if (quiz_has_been_answered($quiz)) {
+        // We need to revise the quiz if it has been answered
+        $quiz->is_new_revision = 1;
+        entity_save('quiz_entity', $quiz);
+
+        $ids[0] = $quiz_id;
+        $ids[1] = $quiz->vid;
+        drupal_set_message(t('New revision has been created for the @quiz %n', array('%n' => $quiz->title, '@quiz' => QUIZ_NAME)));
+      }
+
+      $nid = $this->node->nid;
+
+      $insert_values[$nid]['quiz_qid'] = $quiz_id;
+      $insert_values[$nid]['quiz_vid'] = $quiz->vid;
+      $insert_values[$nid]['question_nid'] = $this->node->nid;
+      $insert_values[$nid]['question_vid'] = $this->node->vid;
+      $insert_values[$nid]['max_score'] = $this->getMaximumScore();
+      $insert_values[$nid]['auto_update_max_score'] = $this->autoUpdateMaxScore() ? 1 : 0;
+      $insert_values[$nid]['weight'] = 1 + db_query('SELECT MAX(weight) FROM {quiz_relationship} WHERE quiz_vid = :vid', array(':vid' => $ids[1]))->fetchField();
+      $randomization = db_query('SELECT randomization '
+        . ' FROM {quiz_entity_revision} '
+        . ' WHERE qid = :qid AND vid = :vid', array(':qid' => $ids[0], ':vid' => $ids[1]))->fetchField();
+      $insert_values[$nid]['question_status'] = $randomization == 2 ? QUESTION_RANDOM : QUESTION_ALWAYS;
+
+      $insert_qnr = db_insert('quiz_relationship');
+      $insert_qnr->fields(array('quiz_qid', 'quiz_vid', 'question_nid', 'question_vid', 'max_score', 'weight', 'question_status', 'auto_update_max_score'));
+      foreach ($insert_values as $insert_value) {
+        $insert_qnr->values($insert_value);
+      }
+      $insert_qnr->execute();
+
+      // Update max_score for relationships if auto update max score is enabled
+      // for question
+      $quizzes_to_update = array();
+      $result = db_query(
+        'SELECT quiz_vid as vid from {quiz_relationship} where question_nid = :nid and question_vid = :vid and auto_update_max_score=1', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
+      foreach ($result as $record) {
+        $quizzes_to_update[] = $record->vid;
+      }
+
+      db_update('quiz_relationship')
+        ->fields(array('max_score' => $this->getMaximumScore()))
+        ->condition('question_nid', $this->node->nid)
+        ->condition('question_vid', $this->node->vid)
+        ->condition('auto_update_max_score', 1)
+        ->execute();
+
+      quiz_update_max_score_properties($quizzes_to_update);
+      quiz_update_max_score_properties(array($quiz->vid));
+    }
+  }
+
+  /**
+   * Finds out if a question has been answered or not
+   *
+   * This function also returns TRUE if a quiz that this question belongs to have been answered.
+   * Even if the question itself haven't been answered. This is because the question might have
+   * been rendered and a user is about to answer it...
+   *
+   * @return
+   *   true if question has been answered or is about to be answered...
+   */
+  public function hasBeenAnswered() {
+    if (!isset($this->node->vid)) {
+      return FALSE;
+    }
+
+    $answered = db_query_range('SELECT 1 '
+      . ' FROM {quiz_results} qnres '
+      . ' JOIN {quiz_relationship} qrel ON (qnres.quiz_vid = qrel.quiz_vid) '
+      . ' WHERE qrel.question_vid = :question_vid', 0, 1, array(':question_vid' => $this->node->vid))->fetch();
+
+    return $answered ? TRUE : FALSE;
+  }
+
+  /**
+   * Determines if the user can view the correct answers
+   *
+   * @todo grabbing the node context here probably isn't a great idea
+   *
+   * @return boolean
+   *   true iff the view may include the correct answers to the question
+   */
+  public function viewCanRevealCorrect() {
+    global $user;
+
+    $reveal_correct[] = user_access('view any quiz question correct response');
+    $reveal_correct[] = ($user->uid == $this->node->uid);
+    if (array_filter($reveal_correct)) {
+      return TRUE;
+    }
+  }
+
+  /**
+   * Utility function that returns the format of the node body
+   */
+  protected function getFormat() {
+    $node = isset($this->node) ? $this->node : $this->question;
+    $body = field_get_items('node', $node, 'body');
+    return isset($body[0]['format']) ? $body[0]['format'] : NULL;
+  }
+
+  /**
+   * This may be overridden in subclasses. If it returns true,
+   * it means the max_score is updated for all occurrences of
+   * this question in quizzes.
+   */
+  protected function autoUpdateMaxScore() {
+    return false;
+  }
+
+  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
+
+  }
+
+  /**
+   * Is this question graded?
+   *
+   * Questions like Quiz Directions, Quiz Page, and Scale are not.
+   *
+   * By default, questions are expected to be gradeable
+   *
+   * @return bool
+   */
+  public function isGraded() {
+    return TRUE;
+  }
+
+}
diff --git a/modules/quiz_question/src/QuizQuestionResponse.php b/modules/quiz_question/src/QuizQuestionResponse.php
new file mode 100644
index 0000000..2f5a312
--- /dev/null
+++ b/modules/quiz_question/src/QuizQuestionResponse.php
@@ -0,0 +1,437 @@
+<?php
+
+namespace Drupal\quiz_question;
+
+use Drupal\quiz_question\QuizQuestion;
+use stdClass;
+
+/**
+ * Each question type must store its own response data and be able to calculate a score for
+ * that data.
+ */
+abstract class QuizQuestionResponse {
+
+  /** @var \Drupal\quiz\Entity\Result */
+  protected $result;
+  protected $result_id = 0;
+  protected $is_correct = FALSE;
+  protected $evaluated = TRUE;
+  // The question node(not a quiz question instance)
+  public $question = NULL;
+  public $quizQuestion = NULL;
+  protected $answer = NULL;
+  protected $score;
+  public $is_skipped;
+  public $is_doubtful;
+
+  /**
+   * Create a new user response.
+   *
+   * @param $result_id
+   *  The result ID for the user's result set. There is one result ID per time
+   *  the user takes a quiz.
+   * @param $question_node
+   *  The question node.
+   * @param $answer
+   *  The answer (dependent on question type).
+   */
+  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
+    $this->result_id = $result_id;
+    $this->result = quiz_result_load($result_id);
+    $this->question = $question_node;
+    $this->quizQuestion = _quiz_question_get_instance($question_node);
+    $this->answer = $answer;
+    $result = db_query('SELECT is_skipped, is_doubtful '
+      . ' FROM {quiz_results_answers} '
+      . ' WHERE result_id = :result_id '
+      . '   AND question_nid = :question_nid '
+      . '   AND question_vid = :question_vid', array(
+        ':result_id'    => $result_id,
+        ':question_nid' => $question_node->nid,
+        ':question_vid' => $question_node->vid
+      ))->fetch();
+    if (is_object($result)) {
+      $this->is_doubtful = $result->is_doubtful;
+      $this->is_skipped = $result->is_skipped;
+    }
+  }
+
+  /**
+   *
+   * @return QuizQuestion
+   */
+  function getQuizQuestion() {
+    return $this->quizQuestion;
+  }
+
+  /**
+   * Used to refresh this instances question node in case drupal has changed it.
+   *
+   * @param $newNode
+   *  Question node
+   */
+  public function refreshQuestionNode($newNode) {
+    $this->question = $newNode;
+  }
+
+  /**
+   * Indicate whether the response has been evaluated (scored) yet.
+   * Questions that require human scoring (e.g. essays) may need to manually
+   * toggle this.
+   */
+  public function isEvaluated() {
+    return (bool) $this->evaluated;
+  }
+
+  /**
+   * Check to see if the answer is marked as correct.
+   *
+   * This default version returns TRUE iff the score is equal to the maximum possible score.
+   */
+  function isCorrect() {
+    return ($this->getMaxScore() == $this->getScore());
+  }
+
+  /**
+   * Returns stored score if it exists, if not the score is calculated and returned.
+   *
+   * @param $weight_adjusted
+   *  If the returned score shall be adjusted according to the max_score the question has in a quiz
+   * @return
+   *  Score(int)
+   */
+  function getScore($weight_adjusted = TRUE) {
+    if ($this->is_skipped) {
+      return 0;
+    }
+    if (!isset($this->score)) {
+      $this->score = $this->score();
+    }
+    if (isset($this->question->score_weight) && $weight_adjusted) {
+      return round($this->score * $this->question->score_weight);
+    }
+    return $this->score;
+  }
+
+  /**
+   * Returns stored max score if it exists, if not the max score is calculated and returned.
+   *
+   * @param $weight_adjusted
+   *  If the returned max score shall be adjusted according to the max_score the question has in a quiz
+   * @return
+   *  Max score(int)
+   */
+  public function getMaxScore($weight_adjusted = TRUE) {
+    if (!isset($this->question->max_score)) {
+      $this->question->max_score = $this->question->getMaximumScore();
+    }
+    if (isset($this->question->score_weight) && $weight_adjusted) {
+      return round($this->question->max_score * $this->question->score_weight);
+    }
+    return $this->question->max_score;
+  }
+
+  /**
+   * Represent the response as a stdClass object.
+   *
+   * Convert data to an object that has the following properties:
+   * - $score
+   * - $result_id
+   * - $nid
+   * - $vid
+   * - $is_correct
+   */
+  function toBareObject() {
+    $result = new stdClass();
+    $result->score = $this->getScore(); // This can be 0 for unscored.
+    $result->quiz_qid = $this->question->nid;
+    $result->quiz_vid = $this->question->vid;
+    $result->result_id = $this->result_id;
+    $result->is_correct = (int) $this->isCorrect();
+    $result->is_evaluated = $this->isEvaluated();
+    $result->is_skipped = 0;
+    $result->is_doubtful = isset($_POST['is_doubtful']) ? (int) ($_POST['is_doubtful']) : 0;
+    $result->is_valid = $this->isValid();
+    return $result;
+  }
+
+  /**
+   * Validates response from a quiz taker. If the response isn't valid the quiz taker won't be allowed to proceed.
+   *
+   * @return
+   *  True if the response is valid.
+   *  False otherwise
+   */
+  public function isValid() {
+    return TRUE;
+  }
+
+  /**
+   * Get data suitable for reporting a user's score on the question.
+   * This expects an object with the following attributes:
+   *
+   *  answer_id; // The answer ID
+   *  answer; // The full text of the answer
+   *  is_evaluated; // 0 if the question has not been evaluated, 1 if it has
+   *  score; // The score the evaluator gave the user; this should be 0 if is_evaluated is 0.
+   *  question_vid
+   *  question_nid
+   *  result_id
+   */
+  public function getReport() {
+    // Basically, we encode internal information in a
+    // legacy array format for Quiz.
+    $report = array(
+        'answer_id'    => 0, // <-- Stupid vestige of multichoice.
+        'answer'       => $this->answer,
+        'is_evaluated' => $this->isEvaluated(),
+        'is_correct'   => $this->isCorrect(),
+        'score'        => $this->getScore(),
+        'question_vid' => $this->question->vid,
+        'question_nid' => $this->question->nid,
+        'result_id'    => $this->result_id,
+    );
+    return $report;
+  }
+
+  /**
+   * Creates the report form for the admin pages, and for when a user gets feedback after answering questions.
+   *
+   * The report is a form to allow editing scores and the likes while viewing the report form
+   *
+   * @return $form
+   *  Drupal form array
+   */
+  public function getReportForm() {
+    global $user;
+
+    // Add general data, and data from the question type implementation
+    $form = array();
+    $form['nid'] = array(
+        '#type'  => 'value',
+        '#value' => $this->question->nid,
+    );
+    $form['vid'] = array(
+        '#type'  => 'value',
+        '#value' => $this->question->vid,
+    );
+    $form['result_id'] = array(
+        '#type'  => 'value',
+        '#value' => $this->result_id,
+    );
+
+    if ($this->result->canAccessOwnScore($user) && ($submit = $this->getReportFormSubmit())) {
+      $form['submit'] = array('#type' => 'value', '#value' => $submit);
+    }
+    $form['question'] = $this->getReportFormQuestion();
+
+    if ($this->result->canAccessOwnScore($user)) {
+      $form['answer_feedback'] = $this->getReportFormAnswerFeedback();
+    }
+
+    $form['max_score'] = array(
+        '#type'  => 'value',
+        '#value' => ($this->canReview('score')) ? $this->getMaxScore() : '?',
+    );
+
+    $rows = array();
+
+    $labels = array(
+        'attempt'         => t('Your answer'),
+        'choice'          => t('Choice'),
+        'correct'         => t('Correct?'),
+        'score'           => t('Score'),
+        'answer_feedback' => t('Feedback'),
+        'solution'        => t('Correct answer'),
+    );
+    drupal_alter('quiz_feedback_labels', $labels);
+
+    foreach ($this->getReportFormResponse() as $idx => $row) {
+      foreach (array_keys($labels) as $reviewType) {
+        if (('choice' === $reviewType) || (isset($row[$reviewType]) && $this->canReview($reviewType))) {
+          $rows[$idx][$reviewType] = $row[$reviewType];
+        }
+      }
+    }
+
+    if ($this->isEvaluated()) {
+      $score = $this->getScore();
+      if ($this->isCorrect()) {
+        $class = 'q-correct';
+      }
+      else {
+        $class = 'q-wrong';
+      }
+    }
+    else {
+      $score = t('?');
+      $class = 'q-waiting';
+    }
+
+    if (quiz()->getQuizHelper()->getAccessHelper()->canAccessQuizScore($user) && $submit) {
+      $form['score'] = $this->getReportFormScore();
+    }
+
+    if ($this->canReview('score') || quiz()->getQuizHelper()->getAccessHelper()->canAccessQuizScore($user)) {
+      $form['score_display']['#markup'] = theme('quiz_question_score', array('score' => $score, 'max_score' => $this->getMaxScore(), 'class' => $class));
+    }
+
+    $headers = array_intersect_key($labels, $rows[0]);
+    $type = $this->getQuizQuestion()->node->type;
+    $form['response']['#markup'] = theme('quiz_question_feedback__' . $type, array('labels' => $headers, 'data' => $rows));
+
+    if ($this->canReview('question_feedback')) {
+      if ($properties = entity_load('quiz_question', FALSE, array('nid' => $this->quizQuestion->node->nid, 'vid' => $this->quizQuestion->node->vid))) {
+        $quiz_question = reset($properties);
+        $form['question_feedback']['#markup'] = check_markup($quiz_question->feedback, $quiz_question->feedback_format);
+      }
+    }
+
+    $form['#theme'] = $this->getReportFormTheme();
+    return $form;
+  }
+
+  /**
+   * get the question part of the reportForm
+   *
+   * @return
+   *  FAPI form array holding the question
+   */
+  public function getReportFormQuestion() {
+    $node = node_load($this->question->nid);
+    $node->no_answer_form = TRUE;
+    node_build_content($node, 'question');
+    return $node->content;
+  }
+
+  /**
+   * Get the response part of the report form
+
+   * @return
+   *  Array of choices
+   */
+  public function getReportFormResponse() {
+    $data = array();
+
+    $data[] = array(
+        'choice'            => 'True',
+        'attempt'           => 'Did the user choose this?',
+        'correct'           => 'Was their answer correct?',
+        'score'             => 'Points earned for this answer',
+        'answer_feedback'   => 'Feedback specific to the answer',
+        'question_feedback' => 'General question feedback for any answer',
+        'solution'          => 'Is this choice the correct solution?',
+        'quiz_feedback'     => 'Quiz feedback at this time',
+    );
+
+    return $data;
+  }
+
+  public function getReportFormAnswerFeedback() {
+    return FALSE;
+  }
+
+  /**
+   * Get the submit function for the reportForm
+   *
+   * @return
+   *  Submit function as a string, or FALSE if no submit function
+   */
+  public function getReportFormSubmit() {
+    return FALSE;
+  }
+
+  /**
+   * Get the validate function for the reportForm
+   *
+   * @return
+   *  Validate function as a string, or FALSE if no validate function
+   */
+  public function getReportFormValidate(&$element, &$form_state) {
+    return FALSE;
+  }
+
+  /**
+   * Get the theme key for the reportForm
+   *
+   * @return
+   *  Theme key as a string, or FALSE if no submit function
+   */
+  public function getReportFormTheme() {
+    return FALSE;
+  }
+
+  /**
+   * Saves the quiz result. This should only be called when an answer is
+   * provided.
+   */
+  public function saveResult() {
+    $this->save();
+  }
+
+  /**
+   * Utility function that returns the format of the node body
+   */
+  protected function getFormat() {
+    $body = field_get_items('node', $this->question, 'body');
+    return ($body ? $body[0]['format'] : NULL);
+  }
+
+  /**
+   * Save the current response.
+   */
+  abstract public function save();
+
+  /**
+   * Delete the response.
+   */
+  abstract public function delete();
+
+  /**
+   * Calculate the score for the response.
+   */
+  abstract public function score();
+
+  /**
+   * Get the user's response.
+   */
+  abstract public function getResponse();
+
+  /**
+   * Can the quiz taker view the requested review?
+   */
+  public function canReview($option) {
+    $result = quiz_result_load($this->result_id);
+    return quiz()->getQuizHelper()->getFeedbackHelper()->canReview($option, $result);
+  }
+
+  /**
+   * Implementation of getReportFormScore
+   *
+   * @see QuizQuestionResponse#getReportFormScore()
+   */
+  public function getReportFormScore() {
+    $score = ($this->isEvaluated()) ? $this->getScore() : '';
+    return array(
+        '#title'            => 'Enter score',
+        '#type'             => 'textfield',
+        '#default_value'    => $score,
+        '#size'             => 3,
+        '#maxlength'        => 3,
+        '#attributes'       => array('class' => array('quiz-report-score')),
+        '#element_validate' => array('element_validate_integer'),
+        '#required'         => TRUE,
+        '#field_suffix'     => '/ ' . $this->getMaxScore(),
+    );
+  }
+
+  /**
+   * Set the target result ID for this Question response.
+   *
+   * Useful for cloning entire result sets.
+   */
+  public function setResultId($result_id) {
+    $this->result_id = $result_id;
+  }
+
+}
diff --git a/modules/quiz_question/tests/QuizQuestionTestCase.test b/modules/quiz_question/tests/QuizQuestionTestCase.test
new file mode 100644
index 0000000..29c47bf
--- /dev/null
+++ b/modules/quiz_question/tests/QuizQuestionTestCase.test
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Base test class for Quiz questions.
+ */
+abstract class QuizQuestionTestCase extends QuizTestCase {
+
+  var $question_node_type = 'quiz_question';
+
+  /**
+   * Set up a question test case.
+   *
+   * @param array $modules
+   *   Array of modules to enable.
+   * @param array $admin_permissions
+   *   Array of admin permissions to add.
+   * @param array $user_permissions
+   *   Array of user permissions to add.
+   */
+  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
+    $modules[] = 'quiz_question';
+    $admin_permissions[] = "create {$this->question_node_type} content";
+    $admin_permissions[] = "edit any {$this->question_node_type} content";
+    $user_permissions[] = 'access quiz';
+    parent::setUp($modules, $admin_permissions, $user_permissions);
+  }
+
+  /**
+   * Test the subclass's quiz question implementation.
+   */
+  public function testQuizQuestionImplementation() {
+    $question_info = _quiz_question_get_implementations();
+    $this->assertTrue(isset($question_info[$this->question_node_type]), t('Check that the question implementation is correct.'));
+  }
+
+}
diff --git a/modules/quiz_stats/quiz_stats.admin.inc b/modules/quiz_stats/quiz_stats.admin.inc
index 8e715a9..90f22db 100644
--- a/modules/quiz_stats/quiz_stats.admin.inc
+++ b/modules/quiz_stats/quiz_stats.admin.inc
@@ -7,249 +7,31 @@
  */
 
 /**
- * Page for selecting between several quiz revisions
- *
- * @param $nid
- *  Quiz node id
- * @return
- *  Themed html
- */
-function quiz_stats_revision_selector_page($quiz) {
-  $res = db_query('SELECT vid FROM {node_revision} WHERE nid = :nid ORDER BY vid', array(':nid' => $quiz->nid));
-  $count = 0; //uses this variable to slightly increase performance
-  $vids = array();
-  while ($res_o = $res->fetch()) {
-    $vids[] = $res_o->vid;
-    $count++;
-  }
-  if ($count < 1) {
-    return t('Something went wrong. Please try again');
-  }
-  elseif ($count == 1) {
-    // If there is only one revision we jump directly to that revision
-    drupal_goto("node/{$quiz->nid}/quiz/statistics/{$vids[0]}");
-  }
-  else {
-    $content = array();
-    $content['explanation'] = t('There are !num revisions of this quiz that have been taken.
-    Different revisions may have different scoring, difficulity and other differences affecting its statistics.
-    Because of this you have to choose the revision you want to see statistics from.', array('!num' => $count));
-    $content['links'] = array();
-    foreach ($vids as $key => $value) {
-      $content['links'][] = 'node/' . $quiz->nid . '/quiz/statistics/' . $value;
-    }
-    return theme('quiz_stats_revision_selector', array('content' => $content));
-  }
-}
-
-/**
  * Returns statistics for all available quizzes
  *
- * @param $uid
+ * @param int $uid
  *   User id
  * @return
  *   HTML table
  */
 function quiz_stats_get_basic_stats($uid = 0) {
-  $results = _quiz_get_quizzes($uid);
+  $results = quiz()->getQuizHelper()->getQuizzesByUserId($uid);
   return empty($results) ? t('No Statistics found.') : theme('quiz_stats_get_basic_stats', array('results' => $results));
 }
 
 /**
- * Get stats for a single quiz. Maybe also for a single user.
- *
- * @param $vid
- *   quiz revision id
- *
- * @param $uid
- *   User id
- *
- * @return
- *   HTML page with charts/graphs
- */
-function quiz_stats_get_adv_stats($vid, $uid = 0) {
-  $charts = array();
-  drupal_add_css(drupal_get_path('module', 'quiz_stats') . '/quiz_stats.css', array('type' => 'file', 'weight' => CSS_THEME));
-  $charts['takeup'] = _get_date_vs_takeup_count_chart($vid, $uid);
-  // line chart/graph showing quiz takeup date along x-axis and count along y-axis
-  $charts['status'] = _get_quiz_status_chart($vid, $uid);
-  // 3D pie chart showing percentage of pass, fail, incomplete quiz status
-  $charts['top_scorers'] = _get_quiz_top_scorers_chart($vid, $uid);
-  // Bar chart displaying top scorers
-  $charts['grade_range'] = _get_quiz_grade_range_chart($vid, $uid);
-  return theme('quiz_stats_charts', array('charts' => $charts));
-}
-
-/**
- * Generates grade range chart
- *
- * @param $uid
- *   User id
- * @param $vid
- *   revision id of quiz node
- * @return
- *   array with chart and metadata
- */
-function _get_quiz_grade_range_chart($vid, $uid = 0) {
-  // @todo: make the ranges configurable
-  $sql = 'SELECT SUM(score >= 0 && score < 20) AS zero_to_twenty,
-    SUM(score >= 20 && score < 40) AS twenty_to_fourty,
-    SUM(score >= 40 && score < 60) AS fourty_to_sixty,
-    SUM(score >= 60 && score < 80) AS sixty_to_eighty,
-    SUM(score >= 80 && score <= 100) AS eighty_to_hundred
-    FROM {quiz_node_results}
-    WHERE vid = :vid';
-  $arg[':vid'] = $vid;
-  if ($uid != 0) {
-    $sql .= ' AND uid = :uid';
-    $arg[':uid'] = $uid;
-  }
-  $range = db_query($sql, $arg)->fetch();
-  $count = $range->zero_to_twenty + $range->twenty_to_fourty + $range->fourty_to_sixty + $range->sixty_to_eighty + $range->eighty_to_hundred;
-  if ($count < 2) {
-    return FALSE;
-  }
-  // Get the charts
-  $chart = '<div id="quiz_top_scorers" class="quiz-stats-chart-space">';
-  $chart .= theme('quiz_grade_range', array('range' => $range));
-  $chart .= '</div>';
-  // Return the chart with some meta data
-  return array(
-    'chart' => $chart,
-    'title' => t('Distribution'),
-    'explanation' => t('This chart shows the distribution of the scores on this quiz.'),
-  );
-}
-
-/**
- * Generates the top scorers chart
- *
- * @param $vid
- *   revision id of quiz node
- *
- * @param $uid
- *   User id
- *
- * @return
- *   array with chart and metadata
- */
-function _get_quiz_top_scorers_chart($vid, $uid = 0) {
-  $top_scorers = array();
-  $sql = 'SELECT name, score FROM {quiz_node_results} qnr
-    LEFT JOIN {users} u ON (u.uid = qnr.uid)
-    WHERE vid = :vid';
-  $arg[':vid'] = $vid;
-  if ($uid != 0) {
-    $sql .= ' AND qnr.uid = :uid';
-    $arg[':uid'] = $uid;
-  }
-  $sql .= ' ORDER by score DESC LIMIT 10';
-  $results = db_query($sql, $arg);
-  while ($result = $results->fetchAssoc()) {
-    $key = $result['name'] . '-' . $result['score'];
-    $top_scorers[$key] = $result;
-  }
-  if (count($top_scorers) == 0) {
-    return FALSE;
-  }
-  $chart = '<div id="quiz_top_scorers" class="quiz-stats-chart-space">';
-  $chart .= theme('quiz_top_scorers', array('scorer' => $top_scorers));
-  $chart .= '</div>';
-  return array(
-    'chart' => $chart,
-    'title' => t('Top scorers'),
-    'explanation' => t('This chart shows which quiz takers have the highest scores.'),
-  );
-}
-
-/**
- * Generates a chart showing the status for all registered responses to a quiz
- *
- * @param $vid
- *   revision id of quiz node
- *
- * @param $uid
- *   User id
- *
- * @return
- *   HTML to render to chart/graph
- */
-function _get_quiz_status_chart($vid, $uid = 0) {
-  // get the pass rate of the given quiz by vid
-  $pass_rate = db_query("SELECT pass_rate FROM {quiz_node_properties} WHERE vid = :vid", array(':vid' => $vid))->fetchField();
-  if (!$pass_rate) {
-    return;
-  }
-  // get the count value of results row above and below pass rate
-  $quiz = db_query("SELECT SUM(score >= $pass_rate) AS no_pass, SUM(score < $pass_rate) AS no_fail, SUM(is_evaluated = 0) AS no_incomplete FROM {quiz_node_results} WHERE vid = :vid", array(':vid' => $vid))
-    ->fetchAssoc();
-  if (($quiz['no_pass'] + $quiz['no_fail'] + $quiz['no_incomplete']) < 1) {
-    return FALSE; // no sufficient data
-  }
-  // generates quiz status chart 3D pie chart
-  $chart = '<div id="get_quiz_status_chart" class="quiz-stats-chart-space">';
-  $chart .= theme('get_quiz_status_chart', array('quiz' => $quiz));
-  $chart .= '</div>';
-  return array(
-    'chart' => $chart,
-    'title' => t('Status'),
-    'explanation' => t('This chart shows the status for all attempts made to answer this revision of the quiz.'),
-  );
-}
-
-/**
- * Generates chart showing how often the quiz has been taken the last days
- *
- * @param $uid
- *   user id
- * @param $vid
- *   revision id of quiz
- * @return
- *   HTML to display chart
- */
-function _get_date_vs_takeup_count_chart($vid, $uid = 0) {
-  $start = 0;
-  $end = 30;
-  $takeup = array();
-  $sql = "SELECT COUNT(result_id) AS count,
-            DATE_FORMAT(FROM_UNIXTIME(time_start), '%Y-%m-%e') AS date
-            FROM {quiz_node_results}
-            WHERE vid = :vid AND time_start > (UNIX_TIMESTAMP(NOW()) - (86400*$end))";
-  $sql_args[':vid'] = $vid;
-  if ($uid != 0) {
-    $sql .= " AND uid = :uid";
-    $sql_args[':uid'] = $uid;
-  }
-  $sql .= " GROUP BY date ORDER BY time_start ASC";
-  $results = db_query($sql, $sql_args);
-  $res_o = $results->fetchAll();
-  if ($res_o) {
-    $chart = '<div id="date_vs_takeup_count" class="quiz-stats-chart-space">';
-    // wrapping the chart output with div for custom theming.
-    $chart .= theme('date_vs_takeup_count', array('takeup' => $res_o));
-    // generate date vs takeup count line chart
-    $chart .= '</div>';
-    return array(
-      'chart' => $chart,
-      'title' => t('Activity'),
-      'explanation' => t('This chart shows how many times the quiz has been taken each day over the last !days days.', array('!days' => $end)),
-    );
-  }
-}
-
-/**
  * Computes quiz attempt count
  *
- * @param $vid
- *   quiz node id
- * @param $uid
+ * @param int $vid
+ *   quiz id
+ * @param int $uid
  *   user id for filtering
  * @return
  *   Integer containing number of times that a quiz has
  *   been attended, can be filtered by uid.
  */
 function _quiz_get_attempt_count($nid, $uid = 0) {
-  $sql = 'SELECT COUNT(result_id) FROM {quiz_node_results} WHERE nid = :nid';
+  $sql = 'SELECT COUNT(result_id) FROM {quiz_results} WHERE nid = :nid';
   $arg[':nid'] = $nid;
   if ($uid != 0) {
     $sql .= ' AND uid = :uid';
@@ -276,36 +58,36 @@ function theme_quiz_grade_range($variables) {
   $count = array_sum((array) $range);
 
   $chart = array(
-    '#type' => 'chart',
-    '#chart_type' => 'column',
-    '#title' => t('Score distribution'),
+      '#type'       => 'chart',
+      '#chart_type' => 'column',
+      '#title'      => t('Score distribution'),
   );
   $chart['range'] = array(
-    '#type' => 'chart_data',
-    '#title' => t('% of quiz takers'),
-    '#data' => array(
-      round(($range->zero_to_twenty / $count) * 100),
-      round(($range->twenty_to_fourty / $count) * 100),
-      round(($range->fourty_to_sixty / $count) * 100),
-      round(($range->sixty_to_eighty / $count) * 100),
-      round(($range->eighty_to_hundred / $count) * 100),
-    ),
+      '#type'  => 'chart_data',
+      '#title' => t('% of quiz takers'),
+      '#data'  => array(
+          round(($range->zero_to_twenty / $count) * 100),
+          round(($range->twenty_to_fourty / $count) * 100),
+          round(($range->fourty_to_sixty / $count) * 100),
+          round(($range->sixty_to_eighty / $count) * 100),
+          round(($range->eighty_to_hundred / $count) * 100),
+      ),
   );
   $chart['xaxis'] = array(
-    '#title' => t('Score'),
-    '#type' => 'chart_xaxis',
-    '#labels' => array(
-      '0-20%',
-      '20-40%',
-      '40-60%',
-      '60-80%',
-      '80-100%',
-    ),
+      '#title'  => t('Score'),
+      '#type'   => 'chart_xaxis',
+      '#labels' => array(
+          '0-20%',
+          '20-40%',
+          '40-60%',
+          '60-80%',
+          '80-100%',
+      ),
   );
   $chart['yaxis'] = array(
-    '#title' => t('Grade istribution'),
-    '#type' => 'chart_yaxis',
-    '#max' => 100,
+      '#title' => t('Grade istribution'),
+      '#type'  => 'chart_yaxis',
+      '#max'   => 100,
   );
   return drupal_render($chart);
 }
@@ -321,30 +103,29 @@ function theme_quiz_grade_range($variables) {
  */
 function theme_quiz_top_scorers($variables) {
   $attendees = array_reverse($variables['scorer']);
+
   $chart = array(
-    '#type' => 'chart',
-    '#title' => t('Quiz Top Scorers'),
-    '#chart_type' => 'column',
+      '#type'       => 'chart',
+      '#title'      => t('Quiz Top Scorers'),
+      '#chart_type' => 'column',
   );
   $chart['bar_data'] = array(
-    '#type' => 'chart_data',
-    '#title' => 'Result',
+      '#type'  => 'chart_data',
+      '#title' => 'Result',
   );
   foreach ($attendees as $attendee) {
     $chart['bar_data']['#data'][] = (float) $attendee['score'];
     $labels[] = $attendee['name'] . ' ' . $attendee['score'] . '%';
-
   }
   $chart['yaxis'] = array(
-    '#title' => t('Score') . ' (%)',
-    '#type' => 'chart_yaxis',
-    '#max' => 100,
+      '#title' => t('Score') . ' (%)',
+      '#type'  => 'chart_yaxis',
+      '#max'   => 100,
   );
   $chart['xaxis'] = array(
-    '#title' => 'Quiz Attendees',
-    '#type' => 'chart_xaxis',
-    '#labels' => $labels,
-
+      '#title'  => 'Quiz Attendees',
+      '#type'   => 'chart_xaxis',
+      '#labels' => $labels,
   );
   return drupal_render($chart);
 }
@@ -363,20 +144,20 @@ function theme_get_quiz_status_chart($variables) {
   $quiz['total'] = $quiz['no_pass'] + $quiz['no_fail'] + $quiz['no_incomplete'];
 
   $chart = array(
-    '#type' => 'chart',
-    '#title' => t('Quiz results'),
-    '#chart_type' => 'pie',
+      '#type'       => 'chart',
+      '#title'      => t('Quiz results'),
+      '#chart_type' => 'pie',
   );
 
   $chart['pie_data'] = array(
-    '#type' => 'chart_data',
-    '#title' => t('Result'),
-    '#labels' => array('Pass', 'Incomplete', 'Fail'),
-    '#data' => array(
-      (int) $quiz['no_pass'],
-      (int) $quiz['no_incomplete'],
-      (int) $quiz['no_fail']
-    ),
+      '#type'   => 'chart_data',
+      '#title'  => t('Result'),
+      '#labels' => array('Pass', 'Incomplete', 'Fail'),
+      '#data'   => array(
+          (int) $quiz['no_pass'],
+          (int) $quiz['no_incomplete'],
+          (int) $quiz['no_fail']
+      ),
   );
   return drupal_render($chart);
 }
@@ -391,15 +172,15 @@ function theme_get_quiz_status_chart($variables) {
  */
 function theme_date_vs_takeup_count($variables) {
   $chart = array(
-    '#type' => 'chart',
-    '#title' => t('Activity by date'),
-    '#chart_type' => 'line',
+      '#type'       => 'chart',
+      '#title'      => t('Activity by date'),
+      '#chart_type' => 'line',
   );
 
   $chart['line_data'] = array(
-    '#type' => 'chart_data',
-    '#title' => t('Result'),
-    '#labels' => array('Something'),
+      '#type'   => 'chart_data',
+      '#title'  => t('Result'),
+      '#labels' => array('Something'),
   );
 
   $chart['line_data']['#data'] = array();
@@ -410,12 +191,12 @@ function theme_date_vs_takeup_count($variables) {
   }
 
   $chart['xaxis'] = array(
-    '#type' => 'chart_xaxis',
-    '#labels' => $labels,
+      '#type'   => 'chart_xaxis',
+      '#labels' => $labels,
   );
   $chart['yaxis'] = array(
-    '#title' => 'Participants',
-    '#type' => 'chart_yaxis',
+      '#title' => 'Participants',
+      '#type'  => 'chart_yaxis',
   );
   return drupal_render($chart);
 }
@@ -433,11 +214,11 @@ function theme_quiz_stats_get_basic_stats($variables) {
   if (empty($results)) {
     return '<p>' . t('No questions were found.') . '</p>';
   }
-  $header = $rows = array();
+  $rows = array();
   $header = array(
-    'title' => t('Quiz'),
-    'creator' => t('Author'),
-    'created' => t('Created'),
+      'title'   => t('Quiz'),
+      'creator' => t('Author'),
+      'created' => t('Created'),
   );
   if (user_access('access author stats')) {
     $header['questions_count'] = t('Questions');
@@ -448,12 +229,12 @@ function theme_quiz_stats_get_basic_stats($variables) {
   foreach ($results as $result) {
     $title_link = user_access('access author stats') ? 'node/' . $result['nid'] . '/quiz/statistics' : 'user/' . arg(1) . '/stats/';
     $row = array(
-      'title' => l($chart_icon . ' ' . $result['title'], $title_link, array('html' => TRUE)),
-      'creator' => l($result['name'], 'user/' . $result['uid']),
-      'created' => format_date($result['created'], 'short'),
+        'title'   => l($chart_icon . ' ' . $result['title'], $title_link, array('html' => TRUE)),
+        'creator' => l($result['name'], 'user/' . $result['uid']),
+        'created' => format_date($result['created'], 'short'),
     );
     if (user_access('access author stats')) {
-      $row['questions_count'] = quiz_get_number_of_questions($result['vid']);
+      $row['questions_count'] = quiz()->getQuizHelper()->countQuestion($result['vid']);
       $row['attempt_count'] = _quiz_get_attempt_count($result['nid']);
     }
     $rows[] = $row;
diff --git a/modules/quiz_stats/quiz_stats.module b/modules/quiz_stats/quiz_stats.module
index 4a6123a..ff2916b 100644
--- a/modules/quiz_stats/quiz_stats.module
+++ b/modules/quiz_stats/quiz_stats.module
@@ -1,5 +1,7 @@
 <?php
 
+use Drupal\quiz\Entity\QuizEntity;
+
 /**
  * @file
  * Quiz stats
@@ -21,12 +23,8 @@ function quiz_stats_help($path, $arg) {
  */
 function quiz_stats_permission() {
   $permission = array(
-    'access user stats' => array(
-      'title' => t('access user stats'),
-    ),
-    'access author stats' => array(
-      'title' => t('access author stats'),
-    ),
+    'access user stats'   => array('title' => t('access user stats')),
+    'access author stats' => array('title' => t('access author stats')),
   );
   return $permission;
 }
@@ -36,51 +34,51 @@ function quiz_stats_permission() {
  */
 function quiz_stats_menu() {
   $items['admin/quiz/reports/stats/creator'] = array(
-    'title' => 'Quiz Statistics',
-    'description' => 'Generates a report on quiz results for quiz creators.',
-    'file' => 'quiz_stats.admin.inc',
-    'page callback' => 'quiz_stats_get_basic_stats',
+    'title'            => 'Quiz Statistics',
+    'description'      => 'Generates a report on quiz results for quiz creators.',
+    'file'             => 'quiz_stats.admin.inc',
+    'page callback'    => 'quiz_stats_get_basic_stats',
     'access arguments' => array('access author stats'),
-    'type' => MENU_NORMAL_ITEM,
+    'type'             => MENU_NORMAL_ITEM,
   );
-  $items['node/%node/quiz/statistics'] = array(
-    'title' => 'Statistics',
-    'description' => 'Generates a report on quiz results for quiz creators.',
-    'file' => 'quiz_stats.admin.inc',
-    'page callback' => 'quiz_stats_revision_selector_page',
-    'page arguments' => array(1),
-    'access callback' => 'quiz_type_confirm',
+  $items['quiz/%quiz_entity_single/statistics'] = array(
+    'title'            => 'Statistics',
+    'description'      => 'Generates a report on quiz results for quiz creators.',
+    'file'             => 'quiz_stats.admin.inc',
+    'page callback'    => 'Drupal\quiz_stats\Controller\RevisionListingController::staticCallback',
+    'page arguments'   => array(1),
+    'access callback'  => 'quiz_type_confirm',
     'access arguments' => array(1, 'access user stats'),
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 4,
+    'type'             => MENU_LOCAL_TASK,
+    'weight'           => 4,
   );
-  $items['node/%node/quiz/statistics/%'] = array(
-    'title' => 'Statistics',
-    'description' => 'Generates a report on quiz results for quiz creators.',
-    'file' => 'quiz_stats.admin.inc',
-    'page callback' => 'quiz_stats_get_adv_stats',
-    'page arguments' => array(4),
-    'access callback' => 'quiz_stats_validate_vid',
-    'access arguments' => array(1, 4),
-    'type' => MENU_CALLBACK,
-    'weight' => 4,
+  $items['quiz/%quiz_entity_single/statistics/%'] = array(
+    'title'            => 'Statistics',
+    'description'      => 'Generates a report on quiz results for quiz creators.',
+    'file'             => 'quiz_stats.admin.inc',
+    'page callback'    => 'Drupal\quiz_stats\Controller\RevisionStatsController::staticCallback',
+    'page arguments'   => array(3),
+    'access callback'  => 'quiz_stats_validate_vid',
+    'access arguments' => array(1, 3),
+    'type'             => MENU_CALLBACK,
+    'weight'           => 4,
   );
   $items['user/%/stats'] = array(
-    'title' => 'Result Statistics',
-    'description' => 'Generates a report on quiz results for quiz creators.',
-    'file' => 'quiz_stats.admin.inc',
-    'page callback' => 'quiz_stats_get_basic_stats',
-    'page arguments' => array(1),
+    'title'            => 'Result Statistics',
+    'description'      => 'Generates a report on quiz results for quiz creators.',
+    'file'             => 'quiz_stats.admin.inc',
+    'page callback'    => 'quiz_stats_get_basic_stats',
+    'page arguments'   => array(1),
     'access arguments' => array('access user stats'),
-    'type' => MENU_LOCAL_TASK,
+    'type'             => MENU_LOCAL_TASK,
   );
   $items['user/%/stats/%/view'] = array(
-    'title' => 'Result Statistics',
-    'file' => 'quiz_stats.admin.inc',
-    'page callback' => 'quiz_stats_get_adv_stats',
-    'page arguments' => array(3, 1),
+    'title'            => 'Result Statistics',
+    'file'             => 'quiz_stats.admin.inc',
+    'page callback'    => 'quiz_stats_get_adv_stats',
+    'page arguments'   => array(3, 1),
     'access arguments' => array('access user stats'),
-    'type' => MENU_CALLBACK,
+    'type'             => MENU_CALLBACK,
   );
   return $items;
 }
@@ -91,55 +89,56 @@ function quiz_stats_menu() {
 function quiz_stats_theme() {
   $path = drupal_get_path('module', 'quiz_stats') . '/theme';
   return array(
-    'quiz_stats_get_basic_stats' => array(
+    'quiz_stats_get_basic_stats'   => array(
       'variables' => array('results' => NULL),
-      'file' => 'quiz_stats.admin.inc',
+      'file'      => 'quiz_stats.admin.inc',
     ),
-    'date_vs_takeup_count' => array(
+    'date_vs_takeup_count'         => array(
       'variables' => array('takeup' => NULL),
-      'file' => 'quiz_stats.admin.inc',
+      'file'      => 'quiz_stats.admin.inc',
     ),
-    'get_quiz_status_chart' => array(
+    'get_quiz_status_chart'        => array(
       'variables' => array('quiz' => NULL),
-      'file' => 'quiz_stats.admin.inc',
+      'file'      => 'quiz_stats.admin.inc',
     ),
-    'quiz_top_scorers' => array(
+    'quiz_top_scorers'             => array(
       'variables' => array('scorer' => NULL),
-      'file' => 'quiz_stats.admin.inc',
+      'file'      => 'quiz_stats.admin.inc',
     ),
-    'quiz_grade_range' => array(
+    'quiz_grade_range'             => array(
       'variables' => array('range' => NULL),
-      'file' => 'quiz_stats.admin.inc',
+      'file'      => 'quiz_stats.admin.inc',
     ),
     'quiz_stats_revision_selector' => array(
       'variables' => array('content' => NULL),
-      'path' => $path,
-      'template' => 'quiz_stats_revision_selector',
+      'path'      => $path,
+      'template'  => 'quiz_stats_revision_selector',
     ),
-    'quiz_stats_charts' => array(
+    'quiz_stats_charts'            => array(
       'variables' => array('charts' => NULL),
-      'path' => $path,
-      'template' => 'quiz_stats_charts',
+      'path'      => $path,
+      'template'  => 'quiz_stats_charts',
     ),
   );
 }
 
 /**
- * Validate that a node is of type quiz, and that the user has access to it, and that the vid is a vid of that quiz
+ * Validate that a node is of type quiz, and that the user has access to it, and
+ * that the vid is a vid of that quiz.
  *
- * @param $quiz
- *  The quiz node
- * @param $vid
- *  The version id
- * @return
+ * @param QuizEntity $quiz
+ * @param int $quiz_vid
+ * @return bool
  *  TRUE if user has access
  */
-function quiz_stats_validate_vid($quiz, $vid) {
-  if ($quiz->type != 'quiz') {
+function quiz_stats_validate_vid(QuizEntity $quiz, $quiz_vid) {
+  if (!user_access('access author stats')) {
     return FALSE;
   }
-  if (!user_access('access author stats')) {
+
+  if (!$revision = quiz_entity_single_load(NULL, $quiz_vid)) {
     return FALSE;
   }
-  return $quiz->nid == db_query('SELECT n.nid FROM {node} n INNER JOIN {node_revision} nr ON (n.nid = nr.nid) WHERE nr.vid = :vid', array(':vid' => $vid))->fetchField();
+
+  return $quiz->qid == $revision->qid;
 }
diff --git a/modules/quiz_stats/src/Controller/RevisionListingController.php b/modules/quiz_stats/src/Controller/RevisionListingController.php
new file mode 100644
index 0000000..a62b83a
--- /dev/null
+++ b/modules/quiz_stats/src/Controller/RevisionListingController.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\quiz_stats\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class RevisionListingController {
+
+  private $quiz;
+
+  public function __construct(QuizEntity $quiz) {
+    $this->quiz = $quiz;
+  }
+
+  /**
+   * Page for selecting between several quiz revisions
+   *
+   * @return string
+   */
+  public static function staticCallback(QuizEntity $quiz) {
+    $obj = new self($quiz);
+    return $obj->render();
+  }
+
+  public function render() {
+    $vids = db_query('SELECT qr.vid'
+      . ' FROM {quiz_entity_revision} qr'
+      . ' WHERE qr.qid = :qid'
+      . ' ORDER BY qr.vid DESC', array(':qid' => $this->quiz->qid))->fetchCol();
+
+    if (!$count = count($vids)) {
+      return t('Something went wrong. Please try again');
+    }
+
+    // If there is only one revision we jump directly to that revision
+    if ($count == 1) {
+      drupal_goto("quiz/{$this->quiz->qid}/statistics/{$vids[0]}");
+    }
+
+    $content = array();
+    $content['explanation'] = t('There are !num revisions of this quiz that have been taken.
+    Different revisions may have different scoring, difficulity and other differences affecting its statistics.
+    Because of this you have to choose the revision you want to see statistics from.', array('!num' => $count));
+    $content['links'] = array();
+    foreach ($vids as $vid) {
+      $content['links'][] = 'quiz/' . $this->quiz->qid . '/statistics/' . $vid;
+    }
+    return theme('quiz_stats_revision_selector', array('content' => $content));
+  }
+
+}
diff --git a/modules/quiz_stats/src/Controller/RevisionStatsController.php b/modules/quiz_stats/src/Controller/RevisionStatsController.php
new file mode 100644
index 0000000..6184486
--- /dev/null
+++ b/modules/quiz_stats/src/Controller/RevisionStatsController.php
@@ -0,0 +1,201 @@
+<?php
+
+namespace Drupal\quiz_stats\Controller;
+
+use stdClass;
+
+class RevisionStatsController {
+
+  private $vid;
+  private $uid;
+
+  /**
+   * @param int $vid
+   *   quiz revision id
+   * @param int $uid
+   *   User id
+   */
+  public function __construct($vid, $uid = 0) {
+    $this->vid = $vid;
+    $this->uid = $uid;
+  }
+
+  public static function staticCallback($vid, $uid = 0) {
+    $obj = new static($vid, $uid);
+    return $obj->render($vid, $uid);
+  }
+
+  /**
+   * Get stats for a single quiz. Maybe also for a single user.
+   *
+   * @return
+   *   HTML page with charts/graphs
+   */
+  public function render() {
+    return array(
+        '#theme'    => 'quiz_stats_charts',
+        '#chart'    => $this->buildChartStructure(),
+        '#attached' => array(
+            'css' => array(drupal_get_path('module', 'quiz_stats') . '/quiz_stats.css')
+        )
+    );
+  }
+
+  private function buildChartStructure() {
+    return array(
+        'takeup'      => $this->getDateVSTakeupCountChart(),
+        // line chart/graph showing quiz takeup date along x-axis and count along y-axis
+        'status'      => $this->getQuizStatusChart($this->vid, $this->uid),
+        // 3D pie chart showing percentage of pass, fail, incomplete quiz status
+        'top_scorers' => $this->getQuizTopScorersChart(),
+        // Bar chart displaying top scorers
+        'grade_range' => $this->getQuizGradeRangeChart(),
+    );
+  }
+
+  /**
+   * Generates chart showing how often the quiz has been taken the last days
+   *
+   * @return
+   *   HTML to display chart
+   */
+  private function getDateVSTakeupCountChart() {
+    $end = 30;
+    $sql = "SELECT COUNT(result_id) AS count,
+            DATE_FORMAT(FROM_UNIXTIME(time_start), '%Y-%m-%e') AS date
+            FROM {quiz_results}
+            WHERE vid = :vid AND time_start > (UNIX_TIMESTAMP(NOW()) - (86400*$end))";
+    $sql_args[':vid'] = $this->vid;
+    if ($this->uid) {
+      $sql .= " AND uid = :uid";
+      $sql_args[':uid'] = $this->uid;
+    }
+    $sql .= " GROUP BY date ORDER BY time_start ASC";
+    $results = db_query($sql, $sql_args);
+    if ($res_o = $results->fetchAll()) {
+      $chart = '<div id="date_vs_takeup_count" class="quiz-stats-chart-space">';
+      // wrapping the chart output with div for custom theming.
+      $chart .= theme('date_vs_takeup_count', array('takeup' => $res_o));
+      // generate date vs takeup count line chart
+      $chart .= '</div>';
+      return array(
+          'chart'       => $chart,
+          'title'       => t('Activity'),
+          'explanation' => t('This chart shows how many times the quiz has been taken the last !days days.', array('!days' => $end)),
+      );
+    }
+  }
+
+  /**
+   * Generates a chart showing the status for all registered responses to a quiz
+   *
+   * @return
+   *   HTML to render to chart/graph
+   */
+  private function getQuizStatusChart() {
+    // get the pass rate of the given quiz by vid
+    $pass_rate = db_query("SELECT pass_rate "
+      . " FROM {quiz_entity_revision} "
+      . " WHERE vid = :vid", array(':vid' => $this->vid))->fetchField();
+    if (!$pass_rate) {
+      return;
+    }
+
+    // get the count value of results row above and below pass rate
+    $quiz = db_query(
+      "SELECT "
+      . "   SUM(score >= $pass_rate) AS no_pass, "
+      . "   SUM(score < $pass_rate) AS no_fail, "
+      . "   SUM(is_evaluated = 0) AS no_incomplete "
+      . " FROM {quiz_results} "
+      . " WHERE quiz_vid = :vid", array(':vid' => $this->vid))->fetchAssoc();
+
+    if (($quiz['no_pass'] + $quiz['no_fail'] + $quiz['no_incomplete']) < 1) {
+      return FALSE; // no sufficient data
+    }
+
+    // generates quiz status chart 3D pie chart
+    $chart = '<div id="get_quiz_status_chart" class="quiz-stats-chart-space">';
+    $chart .= theme('get_quiz_status_chart', array('quiz' => $quiz));
+    $chart .= '</div>';
+    return array(
+        'chart'       => $chart,
+        'title'       => t('Status'),
+        'explanation' => t('This chart shows the status for all attempts made to answer this revision of the quiz.'),
+    );
+  }
+
+  /**
+   * Generates the top scorers chart
+   *
+   * @return
+   *   array with chart and metadata
+   */
+  private function getQuizTopScorersChart() {
+    $top_scorers = array();
+    $sql = 'SELECT name, score
+      FROM {quiz_results} qnr
+      LEFT JOIN {users} u ON (u.uid = qnr.uid)
+      WHERE quiz_vid = :vid';
+    $arg[':vid'] = $this->vid;
+    if ($this->uid) {
+      $sql .= ' AND qnr.uid = :uid';
+      $arg[':uid'] = $this->uid;
+    }
+    $sql .= ' ORDER by score DESC LIMIT 10';
+    $results = db_query($sql, $arg);
+    while ($result = $results->fetchAssoc()) {
+      $key = $result['name'] . '-' . $result['score'];
+      $top_scorers[$key] = $result;
+    }
+
+    if (!count($top_scorers)) {
+      return FALSE;
+    }
+
+    $chart = theme('quiz_top_scorers', array('scorer' => $top_scorers));
+    return array(
+        'chart'       => '<div id="quiz_top_scorers" class="quiz-stats-chart-space">' . $chart . '</div>',
+        'title'       => t('Top scorers'),
+        'explanation' => t('This chart shows which quiz takers have the highest scores.'),
+    );
+  }
+
+  /**
+   * Generates grade range chart
+   *
+   * @return
+   *   array with chart and metadata
+   */
+  private function getQuizGradeRangeChart() {
+    // @todo: make the ranges configurable
+    $sql = 'SELECT SUM(score >= 0 && score < 20) AS zero_to_twenty,
+              SUM(score >= 20 && score < 40) AS twenty_to_fourty,
+              SUM(score >= 40 && score < 60) AS fourty_to_sixty,
+              SUM(score >= 60 && score < 80) AS sixty_to_eighty,
+              SUM(score >= 80 && score <= 100) AS eighty_to_hundred
+            FROM {quiz_results}
+            WHERE quiz_vid = :vid';
+    $arg[':vid'] = $this->vid;
+    if ($this->uid) {
+      $sql .= ' AND uid = :uid';
+      $arg[':uid'] = $this->uid;
+    }
+    $range = db_query($sql, $arg)->fetch();
+    $count = $range->zero_to_twenty + $range->twenty_to_fourty + $range->fourty_to_sixty + $range->sixty_to_eighty + $range->eighty_to_hundred;
+    if ($count < 2) {
+      return FALSE;
+    }
+
+    // Get the charts
+    $chart = theme('quiz_grade_range', array('range' => $range));
+
+    // Return the chart with some meta data
+    return array(
+        'chart'       => '<div id="quiz_top_scorers" class="quiz-stats-chart-space">' . $chart . '</div>',
+        'title'       => t('Distribution'),
+        'explanation' => t('This chart shows the distribution of the scores on this quiz.'),
+    );
+  }
+
+}
diff --git a/modules/quiz_stats/theme/quiz_stats_charts.tpl.php b/modules/quiz_stats/theme/quiz_stats_charts.tpl.php
index 4aad472..929ba9a 100644
--- a/modules/quiz_stats/theme/quiz_stats_charts.tpl.php
+++ b/modules/quiz_stats/theme/quiz_stats_charts.tpl.php
@@ -19,16 +19,20 @@
  * $charts['top_scorers']['explanation'] (string)
  */
 $chart_found = FALSE;
+
 if (!function_exists('_quiz_stats_print_chart')) {
+
   function _quiz_stats_print_chart(&$chart) {
     if (is_array($chart)) {
       echo '<h2 class="quiz-charts-title">' . $chart['title'] . '</h2>' . "\n"
-         . $chart['chart']. "\n"
-         . $chart['explanation'] . "\n";
+      . $chart['chart'] . "\n"
+      . $chart['explanation'] . "\n";
       $chart_found = TRUE;
     }
   }
+
 }
+
 _quiz_stats_print_chart($charts['takeup']);
 _quiz_stats_print_chart($charts['top_scorers']);
 _quiz_stats_print_chart($charts['status']);
@@ -36,4 +40,3 @@ _quiz_stats_print_chart($charts['grade_range']);
 if (!$chart_found) {
   echo '<p>' . t('If there are no statistics for this quiz (or quiz revision), this is probably because nobody has yet run this quiz (or quiz revision). If the quiz has multiple revisions, it is possible that the other revisions do have statistics. If this is the last revision, taking the quiz should generate some statistics.') . '</p>';
 }
-?>
diff --git a/plugins/content_types/quiztake_pane.inc b/plugins/content_types/quiztake_pane.inc
index c52ed0d..9c40def 100644
--- a/plugins/content_types/quiztake_pane.inc
+++ b/plugins/content_types/quiztake_pane.inc
@@ -1,49 +1,51 @@
 <?php
+
+use Drupal\quiz\Controller\QuizTakeController;
+
 /**
-* This plugin array is more or less self documenting
-*/
+ * This plugin array is more or less self documenting
+ */
 $plugin = array(
-  // the title in the admin
-  'title' => t('@quiz take pane', array('@quiz' => QUIZ_NAME)),
-  'single' => TRUE,
-  'render callback' => 'quiz_take_pane_content_type_render',
-  'category' => array(t('Quiz'), -9),
-//  'edit form' => 'quiz_take_pane_content_type_edit_form',
-  'description' => t('@quiz take pane description.', array('@quiz' => QUIZ_NAME)),
-  'required context' => new ctools_context_required(t('Node'), 'node'),
+    # the title in the admin
+    'title'            => t('@quiz take pane', array('@quiz' => QUIZ_NAME)),
+    'single'           => TRUE,
+    'render callback'  => 'quiz_take_pane_content_type_render',
+    'category'         => array(t('Quiz'), -9),
+    # 'edit form' => 'quiz_take_pane_content_type_edit_form',
+    'description'      => t('@quiz take pane description.', array('@quiz' => QUIZ_NAME)),
+    'required context' => new ctools_context_required(t('Node'), 'node'),
 );
 
-
 /**
-* Run-time rendering of the body of the block (content type)
-* See ctools_plugin_examples for more advanced info
-*/
+ * Run-time rendering of the body of the block (content type)
+ * See ctools_plugin_examples for more advanced info
+ */
 function quiz_take_pane_content_type_render($subtype, $conf, $panel_args, $context = NULL) {
   if (!empty($context) && empty($context->data)) {
     return;
   }
-  $node = isset($context->data) ? clone($context->data) : NULL;
+  $quiz = isset($context->data) ? clone($context->data) : NULL;
   $block = new stdClass();
 
-  if (empty($node)) {
-    $block->delta   = 'placeholder';
+  if (empty($quiz)) {
+    $block->delta = 'placeholder';
     $block->title = t('Placeholder title.');
     $block->content = t('Placeholder content goes here.');
     return $block;
   }
 
-  if (!quiz_take_access($node)) {
+  if (!quiz_take_access($quiz)) {
     return;
   }
 
   $block->module = 'node';
-  $block->delta = $node->nid;
+  $block->delta = $quiz->qid;
 
-  //drupal_alter('quiz_take', $node);
-  if (isset($node->rendered_content)) {
-    $block->content = $node->rendered_content;
-  } else {
-    $block->content = quiz_take_quiz($node);
+  if (isset($quiz->rendered_content)) {
+    $block->content = $quiz->rendered_content;
+  }
+  else {
+    $block->content = QuizTakeController::staticCallback($quiz);
   }
 
   return $block;
diff --git a/plugins/page_manager/tasks/quiztake.inc b/plugins/page_manager/tasks/quiztake.inc
index 6cc6d3f..898499e 100644
--- a/plugins/page_manager/tasks/quiztake.inc
+++ b/plugins/page_manager/tasks/quiztake.inc
@@ -7,26 +7,22 @@
 function quiz_quiztake_page_manager_tasks() {
 
   return array(
-    // This is a 'page' task and will fall under the page admin UI
-    'task type' => 'page',
-
-    'title' => t('Take @quiz', array('@quiz' => QUIZ_NAME)),
-    'admin title' => t('The take page for @quiz.', array('@quiz' => QUIZ_NAME)),
-    'admin description' => t('When enabled, this overrides the default view at node/%node/take'),
-    'admin path' => 'node/%node/take',
-
-    // Menu hooks so that we can alter the node/%node menu entry to point to us.
-    'hook menu alter' => 'quiz_quiztake_menu_alter',
-
-    // This is task uses 'context' handlers and must implement these to give the
-    // handler data it needs.
-    'handler type' => 'context',
-    'get arguments' => 'quiz_quiztake_get_arguments',
-    'get context placeholders' => 'quiz_quiztake_get_contexts',
-
-    // Allow this to be enabled or disabled:
-    'disabled' => variable_get('quiz_quiztake_disabled', TRUE),
-    'enable callback' => 'quiz_quiztake_enable',
+      // This is a 'page' task and will fall under the page admin UI
+      'task type'                => 'page',
+      'title'                    => t('Take @quiz', array('@quiz' => QUIZ_NAME)),
+      'admin title'              => t('The take page for @quiz.', array('@quiz' => QUIZ_NAME)),
+      'admin description'        => t('When enabled, this overrides the default view at quiz/%quiz_entity_single/take'),
+      'admin path'               => 'node/%node/take',
+      // Menu hooks so that we can alter the node/%node menu entry to point to us.
+      'hook menu alter'          => 'quiz_quiztake_menu_alter',
+      // This is task uses 'context' handlers and must implement these to give the
+      // handler data it needs.
+      'handler type'             => 'context',
+      'get arguments'            => 'quiz_quiztake_get_arguments',
+      'get context placeholders' => 'quiz_quiztake_get_contexts',
+      // Allow this to be enabled or disabled:
+      'disabled'                 => variable_get('quiz_quiztake_disabled', TRUE),
+      'enable callback'          => 'quiz_quiztake_enable',
   );
 }
 
@@ -111,13 +107,13 @@ function quiz_quiztake_enable($cache, $status) {
  */
 function quiz_quiztake_get_arguments($task, $subtask_id) {
   return array(
-    array(
-      'keyword' => 'node',
-      'identifier' => t('Node being taken'),
-      'id' => 1,
-      'name' => 'entity_id:node',
-      'settings' => array(),
-    ),
+      array(
+          'keyword'    => 'node',
+          'identifier' => t('Node being taken'),
+          'id'         => 1,
+          'name'       => 'entity_id:node',
+          'settings'   => array(),
+      ),
   );
 }
 
diff --git a/question_types/long_answer/long_answer.admin.inc b/question_types/long_answer/long_answer.admin.inc
deleted file mode 100644
index 26e042e..0000000
--- a/question_types/long_answer/long_answer.admin.inc
+++ /dev/null
@@ -1,184 +0,0 @@
-<?php
-
-/**
- * Administration pages for the long answer questions module.
- * @file
- */
-
-/**
- * Generate a view of all unscored answer questions.
- *
- * @see theme_long_answer_view_unscored()
- */
-function long_answer_view_unscored() {
-  $unscored = LongAnswerResponse::fetchAllUnscoredAnswers();
-  return theme('long_answer_view_unscored', array('unscored' => $unscored));
-}
-
-/**
- * Page handler for displaying a scoring form.
- * This function is called directly from the menu router. It generates a form for
- * scoring a quiz.
- *
- * @param $vid
- *  The VID of the question and answer to load.
- * @param $result_id
- *  The result ID of the answer to load.
- * @return
- *  Text to display.
- */
-function long_answer_edit_score($vid, $result_id) {
-
-  // We have to do the vid -> nid lookup ourselves because node_load uses only node.vid,
-  // and we need to be able to access old nodes in node_revision.vid.
-  $nid = db_query('SELECT nid FROM {node_revision} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
-  if (!$nid) {
-    drupal_not_found();
-    return;
-  }
-
-  $node = node_load($nid, $vid);
-  if (!$node || $node->type != 'long_answer') {
-    drupal_not_found();
-    return;
-  }
-
-  $answer = (object) long_answer_get_answer($node->nid, $node->vid, $result_id);
-  if (!$answer) {
-    drupal_not_found();
-    return;
-  }
-  if ($node->max_score > 0) {
-    $answer->rel_score = round($answer->score * $answer->rel_max_score / $node->max_score);
-  }
-  else {
-    $answer->rel_score = 0;
-  }
-  drupal_set_title(t('Score answer to "@title"', array('@title' => $node->title)), PASS_THROUGH);
-  return drupal_get_form('long_answer_score_form', $node, $answer);
-}
-
-/**
- * Build a form for scoring long-answer questions.
- *
- * @param $node
- *  The question node.
- * @param $answer
- *  An object containing an answer to the question. This form is for scoring that answer.
- * @return
- *  The form (as a FAPI array).
- */
-function long_answer_score_form($form, $form_state, $node, $answer) {
-  if (!$node || $node->type != 'long_answer' || !$answer) {
-    drupal_not_found();
-    return;
-  }
-  // Set up the form
-  $form['question'] = array(
-    '#type' => 'item',
-    '#title' => t('Question'),
-    '#markup' => check_markup($node->body['und'][0]['value'], $node->body['und'][0]['format']),
-  );
-  $form['rubric'] = array(
-    '#type' => 'item',
-    '#title' => t('Rubric'),
-    '#markup' => check_markup($node->rubric),
-  );
-  $form['show_max_score'] = array(
-    '#type' => 'item',
-    '#title' => t('Maximum Score'),
-    '#markup' => (int) $answer->rel_max_score,
-  );
-  $form['score_answer'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Score answer'),
-  );
-  $form['score_answer']['answer'] = array(
-    '#type' => 'item',
-    '#title' => t('Answer'),
-    '#markup' => check_markup($answer->answer),
-  );
-  $form['score_answer']['score'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Score'),
-    '#description' => t('The score for this essay, between 0 and @max', array('@max' => $answer->rel_max_score)),
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#default_value' => (int) $answer->rel_score,
-    '#required' => TRUE,
-  );
-  $form['score_answer']['answer_feedback'] = array(
-    '#title' => t('Feedback'),
-    '#type' => 'text_format',
-    '#description' => t('The text entered here would be shown to attendees'),
-    '#default_value' => $answer->answer_feedback,
-    '#format' => isset($answer->answer_feedback_format) ? $answer->answer_feedback_format : NULL
-  );
-
-  $form['score_answer']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save this score'),
-  );
-
-  // Save some work by keeping these.
-  $form['max_score'] = array(
-    '#type' => 'value',
-    '#value' => $node->max_score,
-  );
-  $form['rel_max_score'] = array(
-    '#type' => 'value',
-    '#value' => $answer->rel_max_score,
-  );
-  $form['question_nid'] = array(
-    '#type' => 'value',
-    '#value' => $answer->question_nid,
-  );
-  $form['question_vid'] = array(
-    '#type' => 'value',
-    '#value' => $answer->question_vid,
-  );
-  $form['result_id'] = array(
-    '#type' => 'value',
-    '#value' => $answer->result_id,
-  );
-
-  return $form;
-}
-
-/**
- * Validates the long answer score form
- */
-function long_answer_score_form_validate($form, $form_state) {
-  // Check to make sure that entered score is not higher than max allowed score.
-  $max = (int) $form_state['values']['rel_max_score'];
-  $given = $form_state['values']['score'];
-  if (!_quiz_is_int($given, 0, $max)) {
-    $args = array('@score' => $given, '@max' => $max);
-    form_set_error('score', t('The given score (@score) must be an integer between 0 and @max', $args));
-  }
-}
-
-/**
- * Submit handler for the long answer score form
- */
-function long_answer_score_form_submit($form, &$form_state) {
-  $result = db_query('SELECT nid, vid FROM {quiz_node_results} WHERE result_id = :result_id', array(':result_id' => $form_state['values']['result_id']))->fetch();
-
-  $result = long_answer_score_an_answer(array(
-    'quiz' => node_load($result->nid, $result->vid),
-    'nid' => $form_state['values']['question_nid'],
-    'vid' => $form_state['values']['question_vid'],
-    'result_id' => $form_state['values']['result_id'],
-    'score' => $form_state['values']['score'],
-    'max_score' => $form_state['values']['rel_max_score'],
-    'answer_feedback' => $form_state['values']['answer_feedback']
-  ));
-
-  if ($result == 1) {
-    drupal_set_message(t('The score has been saved.'));
-    $form_state['redirect'] = 'admin/quiz/reports/score-long-answer';
-  }
-  else {
-    drupal_set_message(t('Error saving the score. The selected answer was not scored.'), 'error');
-  }
-}
diff --git a/question_types/long_answer/long_answer.classes.inc b/question_types/long_answer/long_answer.classes.inc
deleted file mode 100644
index 4fd4268..0000000
--- a/question_types/long_answer/long_answer.classes.inc
+++ /dev/null
@@ -1,383 +0,0 @@
-<?php
-
-/**
- * Long answer classes.
- *
- * @file
- *  Classes modelling the long answer question and the long answer question response
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class LongAnswerQuestion extends QuizQuestion {
-
-  /**
-   * Implementation of saveNodeProperties
-   * @see QuizQuestion#saveNodeProperties($is_new)
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    if (!isset($this->node->feedback)) {
-      $this->node->feedback = '';
-    }
-    if ($is_new || $this->node->revision == 1) {
-      $id = db_insert('quiz_long_answer_node_properties')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'rubric' => $this->node->rubric,
-        ))
-        ->execute();
-    }
-    else {
-      db_update('quiz_long_answer_node_properties')
-        ->fields(array('rubric' => $this->node->rubric))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-  }
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validateNode($form)
-   */
-  public function validateNode(array &$form) {
-
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete($only_this_version)
-   */
-  public function delete($only_this_version = FALSE) {
-    if ($only_this_version) {
-      db_delete('quiz_long_answer_user_answers')
-        ->condition('question_nid', $this->node->nid)
-        ->condition('question_vid', $this->node->vid)
-        ->execute();
-      db_delete('quiz_long_answer_node_properties')
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-    else {
-      db_delete('quiz_long_answer_node_properties')
-        ->condition('nid', $this->node->nid)
-        ->execute();
-      db_delete('quiz_long_answer_user_answers')
-        ->condition('question_nid', $this->node->nid)
-        ->execute();
-    }
-    parent::delete($only_this_version);
-  }
-
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-
-    $res_a = db_query('SELECT rubric FROM {quiz_long_answer_node_properties}
-      WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
-
-    if (is_array($res_a)) {
-      $props = array_merge($props, $res_a);
-    }
-
-    $this->nodeProperties = $props;
-    return $props;
-  }
-
-  /**
-   * Implementation of getNodeView
-   *
-   * @see QuizQuestion#getNodeView()
-   */
-  public function getNodeView() {
-    $content = parent::getNodeView();
-    if ($this->viewCanRevealCorrect()) {
-      $content['answers'] = array(
-        '#type' => 'item',
-        '#title' => t('Rubric'),
-        '#markup' => '<div class="quiz-solution">' . check_markup($this->node->rubric, $this->getFormat()) . '</div>',
-        '#weight' => 1,
-      );
-    }
-    else {
-      $content['answers'] = array(
-        '#markup' => '<div class="quiz-answer-hidden">Answer hidden</div>',
-        '#weight' => 1,
-      );
-    }
-    return $content;
-  }
-
-  /**
-   * Implementation of getAnweringForm
-   *
-   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $element = parent::getAnsweringForm($form_state, $result_id);
-
-    $element += array(
-      '#type' => 'textarea',
-      '#title' => t('Answer'),
-      '#description' => t('Enter your answer here. If you need more space, click on the grey bar at the bottom of this area and drag it down.'),
-      '#rows' => 15,
-      '#cols' => 60,
-      //'#theme' =>'long_answer_answering_form',
-    );
-    if (isset($result_id)) {
-      $response = new LongAnswerResponse($result_id, $this->node);
-      $element['#default_value'] = $response->getResponse();
-    }
-    return $element;
-  }
-
-  /**
-   * Question response validator.
-   */
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-    if ($form_state['values']['question'][$this->node->nid] == '') {
-      form_set_error('', t('You must provide an answer.'));
-    }
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm($form_state)
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    $form['rubric'] = array(
-      '#type' => 'textarea',
-      '#title' => t('Rubric'),
-      '#description' => t('Specify the criteria for grading the response.'),
-      '#default_value' => isset($this->node->rubric) ? $this->node->rubric : '',
-      '#size' => 60,
-      '#maxlength' => 2048,
-      '#required' => FALSE,
-    );
-    return $form;
-  }
-
-  /**
-   * Implementation of getMaximumScore
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    return variable_get('long_answer_default_max_score', 10);
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class LongAnswerResponse extends QuizQuestionResponse {
-
-  /**
-   * Get all scores that have not yet been evaluated.
-   *
-   * @param $count
-   *  Number of items to return (default: 50).
-   * @param $offset
-   *  Where in the results we should start (default: 0).
-   *
-   * @return
-   *  Array of objects describing unanswered questions. Each object will have result_id, question_nid, and question_vid.
-   */
-  public static function fetchAllUnscoredAnswers($count = 50, $offset = 0) {
-    global $user;
-    $query = db_select('quiz_long_answer_user_answers', 'a');
-    $query->fields('a', array('result_id', 'question_nid', 'question_vid', 'answer_feedback', 'answer_feedback_format'));
-    $query->fields('r', array('title'));
-    $query->fields('qnr', array('time_end', 'time_start', 'uid'));
-    $query->join('node_revision', 'r', 'a.question_vid = r.vid');
-    $query->join('quiz_node_results', 'qnr', 'a.result_id = qnr.result_id');
-    $query->join('node', 'n', 'qnr.nid = n.nid');
-    $query->condition('a.is_evaluated', 0);
-    if (user_access('score own quiz') && user_access('score taken quiz answer')) {
-      $query->condition(db_or()->condition('n.uid', $user->uid)->condition('qnr.uid', $user->uid));
-    }
-    else if (user_access('score own quiz')) {
-      $query->condition('n.uid', $user->uid);
-    }
-    else if (user_access('score taken quiz answer')) {
-      $query->condition('qnr.uid', $user->uid);
-    }
-    $results = $query->execute();
-    $unscored = array();
-    foreach ($results as $row) {
-      $unscored[] = $row;
-    }
-    return $unscored;
-  }
-
-  /**
-   * Given a quiz, return a list of all of the unscored answers.
-   *
-   * @param $nid
-   *  Node ID for the quiz to check.
-   * @param $vid
-   *  Version ID for the quiz to check.
-   * @param $count
-   *  Number of items to return (default: 50).
-   * @param $offset
-   *  Where in the results we should start (default: 0).
-   *
-   * @return
-   *  Indexed array of result IDs that need to be scored.
-   */
-  public static function fetchUnscoredAnswersByQuestion($nid, $vid, $count = 50, $offset = 0) {
-    $results = db_query('SELECT result_id FROM {quiz_long_answer_user_answers}
-      WHERE is_evaluated = :is_evaluated
-      AND question_nid = :question_nid
-      AND question_vid = :question_vid', array(':is_evaluated' => 0, ':question_nid' => $nid, ':question_vid' => $vid));
-    $unscored = array();
-    foreach ($results as $row) {
-      $unscored[] = $row->result_id;
-    }
-    return $unscored;
-  }
-
-  /**
-   * ID of the answer.
-   */
-  protected $answer_id = 0;
-
-  /**
-   * Constructor
-   */
-  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
-    parent::__construct($result_id, $question_node, $answer);
-
-    if (!isset($answer)) {
-      // Question has been answered allready. We fetch the answer data from the database.
-      $r = db_query('SELECT answer_id, answer, is_evaluated, score, question_vid, question_nid, result_id, answer_feedback, answer_feedback_format
-        FROM {quiz_long_answer_user_answers}
-        WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(':qnid' => $question_node->nid, ':qvid' => $question_node->vid, ':rid' => $result_id))->fetch();
-
-      if (!empty($r)) {
-        $this->answer = $r->answer;
-        $this->score = $r->score;
-        $this->evaluated = $r->is_evaluated;
-        $this->answer_id = $r->answer_id;
-        $this->answer_feedback = $r->answer_feedback;
-        $this->answer_feedback_format = $r->answer_feedback_format;
-      }
-    }
-    else {
-      $this->answer = $answer;
-      $this->evaluated = FALSE;
-    }
-  }
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-    $this->answer_id = db_insert('quiz_long_answer_user_answers')
-      ->fields(array(
-        'answer' => $this->answer,
-        'question_nid' => $this->question->nid,
-        'question_vid' => $this->question->vid,
-        'result_id' => $this->result_id,
-      ))
-      ->execute();
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-    db_delete('quiz_long_answer_user_answers')
-      ->condition('question_nid', $this->question->nid)
-      ->condition('question_vid', $this->question->vid)
-      ->condition('result_id', $this->result_id)
-      ->execute();
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    return (int) db_query('SELECT score FROM {quiz_long_answer_user_answers}
-      WHERE result_id = :result_id AND question_vid = :question_vid', array(':result_id' => $this->result_id, ':question_vid' => $this->question->vid))->fetchField();
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer;
-  }
-
-  /**
-   * Implementation of getReportFormResponse
-   */
-  public function getReportFormResponse() {
-    $data = array();
-
-    $data[] = array(
-      'choice' => '',
-      'attempt' => $this->answer,
-      'correct' => !$this->evaluated ? t('This answer has not yet been scored.') : '',
-      'score' => $this->getScore(),
-      'answer_feedback' => check_markup($this->answer_feedback, $this->answer_feedback_format),
-      'question_feedback' => '',
-      'solution' => $this->question->rubric,
-    );
-
-    return $data;
-  }
-
-  public function getReportFormAnswerFeedback() {
-    return array(
-      '#title' => t('Enter feedback'),
-      '#type' => 'text_format',
-      '#default_value' => isset($this->answer_feedback) ? check_markup($this->answer_feedback, $this->answer_feedback_format) : '',
-      '#format' => isset($this->answer_feedback_format) ? $this->answer_feedback_format : filter_default_format(),
-      '#attributes' => array('class' => array('quiz-report-score')),
-    );
-  }
-
-  /**
-   * Implementation of getReportFormSubmit
-   *
-   * @see QuizQuestionResponse#getReportFormSubmit()
-   */
-  public function getReportFormSubmit() {
-    return 'long_answer_report_submit';
-  }
-
-  /**
-   * Implements QuizQuestionResponse::getReportFormValidate().
-   */
-  public function getReportFormValidate(&$element, &$form_state) {
-    $max = $this->question->max_score;
-    // Check to make sure that entered score is not higher than max allowed score.
-    if ($element['score']['#value'] > $max) {
-      form_error($element['score'], t('The score needs to be a number between @min and @max', array('@min' => 0, '@max' => $max)));
-    }
-  }
-
-}
diff --git a/question_types/long_answer/long_answer.info b/question_types/long_answer/long_answer.info
deleted file mode 100644
index 2a0c68c..0000000
--- a/question_types/long_answer/long_answer.info
+++ /dev/null
@@ -1,9 +0,0 @@
-name = Long Answer
-package = Quiz Question
-description = This provides long answer (essay, multi-paragraph) question types for use by the Quiz module.
-dependencies[] = quiz
-dependencies[] = quiz_question
-core = 7.x
-
-files[] = long_answer.classes.inc
-files[] = long_answer.test
diff --git a/question_types/long_answer/long_answer.install b/question_types/long_answer/long_answer.install
deleted file mode 100644
index 4d8a89f..0000000
--- a/question_types/long_answer/long_answer.install
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-
-/**
- * Long answer questions.
- * @file
- */
-
-/**
- * Implements hook_install().
- */
-function long_answer_install() {
-  // Add body field to long answer node
-  quiz_question_add_body_field('long_answer');
-
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_install_schema('long_answer')
-  variable_set('node_options_long_answer', array('status'));
-}
-
-/**
- * Implements hook_uninstall().
- */
-function long_answer_uninstall() {
-
-  // Delete tables
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_uninstall_schema('long_answer')
-
-  // Delete data from other tables
-
-  // Clear the cache.
-  cache_clear_all('variables', 'cache');
-  drupal_set_message(t('The Long Answer module has been uninstalled and related data has been deleted.'));
-
-}
-
-/**
- * Implements hook_schema().
- */
-function long_answer_schema() {
-  // Properties for a question nodes go in here:
-  $schema['quiz_long_answer_node_properties'] = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'rubric' => array(
-        'type' => 'text',
-      ),
-    ),
-    'primary key' => array('nid', 'vid'),
-  );
-
-  // User answers go in here.
-  $schema['quiz_long_answer_user_answers'] = array(
-    'fields' => array(
-      'answer_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'score' => array(
-        'type' => 'float',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'is_evaluated' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'size' => 'tiny',
-        'default' => 0,
-        'not null' => TRUE,
-      ),
-      'answer' => array(
-        'type' => 'text'
-      ),
-      'answer_feedback' => array(
-        'type' => 'text'
-      ),
-      'answer_feedback_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-      ),
-    ),
-    'primary key' => array('answer_id'),
-    'unique keys' => array(
-      'ids' => array('result_id', 'question_nid', 'question_vid'),
-    ),
-  );
-
-  return $schema;
-}
-
-/**
- * Adding feedback field to table {quiz_long_answer_user_answers}
- */
-function long_answer_update_7401() {
-  $spec = array(
-    'type' => 'text',
-    'not null' => FALSE,
-  );
-  db_add_field('quiz_long_answer_user_answers', 'answer_feedback', $spec);
-}
-
-/**
- * Adding feedback format field to table {quiz_long_answer_user_answers}
- */
-function long_answer_update_7402() {
-  if (!db_field_exists('quiz_long_answer_user_answers', 'answer_feedback_format')) {
-    $spec = array(
-        'type' => 'varchar',
-        'length' => 255,
-    );
-    db_add_field('quiz_long_answer_user_answers', 'answer_feedback_format', $spec);
-  }
-}
\ No newline at end of file
diff --git a/question_types/long_answer/long_answer.module b/question_types/long_answer/long_answer.module
deleted file mode 100644
index 3eb690a..0000000
--- a/question_types/long_answer/long_answer.module
+++ /dev/null
@@ -1,307 +0,0 @@
-<?php
-
-/**
- * This module defines a long answer question type for quizzes.
- *
- * Long answer questions make use of the quiz question framework (quiz_question.module).
- * The functions in this file are largely used for grading long answer questions. Most
- * of the real logic is in long_answer.classes.inc.
- *
- * @file
- */
-
-/**
- * Implements hook_help().
- */
-function long_answer_help($path, $arg) {
-  if ($path == 'admin/help#long_answer') {
-    return '<p>' . t('This module provides long-answer (essay, multi-paragraph) questions to the quiz module.') .
-      '</p><p>' . t('A long-answer question is designed to provide the quiz taker a lengthy area to expand on ideas.
-      Common forms of long-answer questions include essays, single paragraph responses, hypothesis design problems,
-      outlines and summaries, and lengthier math problems
-      where the focus is on showing work rather than simply getting the correct answer.') . '</p>';
-  }
-}
-
-/**
- * Implements hook_menu().
- */
-function long_answer_menu() {
-  $items['admin/quiz/reports/score-long-answer'] = array(
-    'title' => 'Score long answer questions',
-    'description' => 'Score the answers from quizzes that use long answer questions.',
-    'page callback' => 'long_answer_view_unscored',
-    'access arguments' => array('score any quiz', 'score own quiz', 'score taken quiz answer'),
-    'access callback' => 'quiz_access_multi_or',
-    'type' => MENU_NORMAL_ITEM,
-    'file' => 'long_answer.admin.inc',
-  );
-  // Pass vid and rid to this path.
-  $items['admin/quiz/reports/score-long-answer/%/%'] = array(
-    'title' => 'Score long answer response',
-    'description' => 'Score a response to a long answer question.',
-    'page callback' => 'long_answer_edit_score',
-    'page arguments' => array(4, 5),
-    'type' => MENU_NORMAL_ITEM,
-    'access arguments' => array(4, 5),
-    'access callback' => 'quiz_question_access_to_score',
-    'file' => 'long_answer.admin.inc'
-  );
-
-  return $items;
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function long_answer_quiz_question_info() {
-  return array(
-    'long_answer' => array(
-      'name' => t('Long answer question'),
-      'description' => t('Quiz questions that allow a user to enter multiple paragraphs of text.'),
-      'question provider' => 'LongAnswerQuestion',
-      'response provider' => 'LongAnswerResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function long_answer_quiz_question_config() {
-  $form['long_answer_default_max_score'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default max score'),
-    '#description' => t('Choose the default maximum score for a long answer question.'),
-    '#default_value' => variable_get('long_answer_default_max_score', 10),
-  );
-  $form['#validate'][] = 'long_answer_config_validate';
-  return $form;
-}
-
-/**
- * Validate the long_answer config form values
- */
-function long_answer_config_validate($form, $form_state) {
-  if ($form_state['values']['long_answer_default_max_score'] <= 0) {
-    form_set_error('long_answer_default_max_score', t('The default max score must be greater than 0'));
-  }
-}
-
-/**
- * Implements hook_theme().
- */
-function long_answer_theme() {
-  return array(
-    'long_answer_view_unscored' => array(
-      'variables' => array('unscored' => array()),
-      'path' => drupal_get_path('module', 'long_answer') . '/theme',
-      'file' => 'long_answer.theme.inc',
-    ),
-    'long_answer_response_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'long_answer') . '/theme',
-      'file' => 'long_answer.theme.inc',
-    ),
-    'long_answer_answering_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'long_answer') . '/theme',
-      'template' => 'long-answer-answering-form',
-    ),
-  );
-}
-
-/**
- * Set a score for a long answer question.
- *
- * This stores a score for a long answer question and marks that question as having been evaluated.
- * The function updates all of the necessary data sources so that the individual answer results should be
- * reflected in the total scoring table.
- *
- * @param $quiz
- *  Quiz node.
- * @param $nid
- *  Node ID of question.
- * @param $vid
- *  Version ID of question.
- * @param $result_id
- *  Result ID for the quiz results.
- * @param $score
- *  The numeric score to assign the result.
- * @param $update_total
- *  Shall the total score be updated?
- *
- * @return int
- *  Number of scores adjusted. If a change was made, this should be 1.
- */
-function long_answer_score_an_answer($values, $update_total = TRUE) {
-  extract($values);
-  // Quiz scoring information is spread out across three tables:
-  // 1. The module should retain its own scoring information in any case where scoring is non-trivial.
-  // 2. The Quiz moduleretains a limited amount of scoring information.
-  // 3. The Quiz module retains an overall score for a quiz. This is the percentage score for the combination of all
-  //    questions on the quiz.
-  //
-  // We update all three.
-  // First, we update the long answer table
-  $question_max_score = db_query('SELECT max_score FROM {quiz_question_properties} WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->FetchField();
-
-  $quiz_max_score = db_query('SELECT max_score FROM {quiz_node_relationship} WHERE parent_vid = :pvid AND child_vid = :cvid', array(':pvid' => $quiz->vid, ':cvid' => $vid))->fetchField();
-
-  $changed = db_update('quiz_long_answer_user_answers')
-    ->fields(array(
-      'score' => $score * $question_max_score / $quiz_max_score,
-      'is_evaluated' => 1,
-      'answer_feedback' => isset($answer_feedback['value']) ? $answer_feedback['value'] : '',
-      'answer_feedback_format' => empty($answer_feedback['format']) ? '' : $answer_feedback['format'],
-    ))
-    ->condition('question_nid', $nid)
-    ->condition('question_vid', $vid)
-    ->condition('result_id', $result_id)
-    ->execute();
-  if ($changed > 0) {
-    // Second, we update the main quiz answers table
-    // What do we do about the quiz_node_results_answers table? It assumes strict
-    // bivalence (is_correct). I guess we consider any essay with over 50% to be correct?
-    $max = db_query('SELECT max_score FROM {quiz_question_properties} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
-    if ($max <= 0) {
-      $is_correct = 0;
-      $points_awarded = 0;
-    }
-    else {
-      $is_correct = $score == $max ? 1 : 0;
-      $points_awarded = $score;
-    }
-
-    db_update('quiz_node_results_answers')
-      ->fields(array(
-        'points_awarded' => $points_awarded,
-        'is_correct' => $is_correct,
-      ))
-      ->condition('question_vid', $vid)
-      ->condition('result_id', $result_id)
-      ->execute();
-
-    // Third, we update the main quiz results table
-    if ($update_total) {
-      quiz_update_total_score($quiz, $result_id);
-    }
-  }
-
-  return $changed;
-}
-
-/**
- * Get the answer for a question.
- *
- * This stores a score for a long answer question and marks that question as having been evaluated.
- * @param $nid
- *  Node ID of question.
- * @param $vid
- *  Version ID of question.
- * @param $result_id
- *  Result ID for the quiz results.
- *
- * @return Assoc array
- *  An array if successful, or FALSE if no result could be found. The array contains the following properties:
- *  <code>
- *  answer_id; // The answer ID
- *  answer; // The full text of the answer
- *  is_evaluated; // 0 if the question has not been evaluated, 1 if it has
- *  score; // The score the evaluator gave the user; this should be 0 if is_evaluated is 0.
- *  question_vid
- *  question_nid
- *  result_id
- *  </code>
- */
-function long_answer_get_answer($question_nid, $question_vid, $result_id) {
-  $result = db_query('SELECT answer_id, answer, la.is_evaluated, la.score, question_vid, question_nid, la.result_id, la.answer_feedback, la.answer_feedback_format, rel.max_score AS rel_max_score FROM {quiz_long_answer_user_answers} la
-    JOIN {quiz_node_results} qnr ON (la.result_id = qnr.result_id)
-    JOIN {quiz_node_relationship} rel ON (qnr.vid = rel.parent_vid AND rel.child_vid = la.question_vid)
-    WHERE question_nid = :qnid AND question_vid = :qvid AND la.result_id = :rid', array(':qnid' => $question_nid, ':qvid' => $question_vid, ':rid' => $result_id))->fetchAssoc();
-  return $result ? $result : FALSE;
-}
-
-/**
- * Given a quiz, return a list of all the unscored answers.
- *
- * @param $nid
- *  Node ID for the quiz to check.
- * @param $vid
- *  Version ID for the quiz to check.
- * @param $count
- *  Number of items to return (default: 50).
- * @param $offset
- *  Where in the results we should start (default: 0).
- *
- * @return
- *  Indexed array of result IDs that need to be scored.
- */
-function long_answer_get_unscored_answers_by_question($nid, $vid, $count = 50, $offset = 0) {
-  $results = db_query('SELECT result_id FROM {quiz_long_answer_user_answers}
-    WHERE is_evaluated = :is_evaluated AND question_nid = :question_nid AND question_vid = :question_vid', array(':is_evaluated' => 0, ':question_nid' => $nid, ':question_vid' => $vid));
-
-  $unscored = array();
-  foreach ($results as $result) {
-    $unscored[] = $result->result_id;
-  }
-  return $unscored;
-}
-
-/**
- * Get all quiz scores that have not yet been evaluated.
- *
- * @param $count
- *  Number of items to return (default: 50).
- * @param $offset
- *  Where in the results we should start (default: 0).
- *
- * @return
- *  Array of objects describing unanswered questions. Each object will have result_id, question_nid, and question_vid.
- */
-function long_answer_get_all_unscored_answers($count = 50, $offset = 0) {
-  $unscored = LongAnswerResponse::fetchAllUnscoredAnswers();
-  return $unscored;
-}
-
-/**
- * Generate a list of long answer questions
- *
- * @return
- *  array containing nid as key and title as value
- */
-function long_answer_questions_list() {
-  $questions = array();
-  $results = db_query('SELECT nid, title FROM {node} WHERE type = :type', array(':type' => 'long_answer'));
-  foreach ($results as $result) {
-    $questions[$result->nid] = check_plain($result->title);
-  }
-  return $questions;
-}
-
-/**
- * Submit function for the report form
- *
- * @param $values
- *   The FAPI $form_state['values']
- */
-function long_answer_report_submit($values) {
-  long_answer_score_an_answer($values, FALSE);
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function long_answer_field_extra_fields() {
-  $extra['node']['long_answer'] = array(
-    'form' => array(
-      'rubric' => array(
-        'label' => t('Rubric'),
-        'description' => t('Specify the criteria for grading the response'),
-        'weight' => -4,
-      ),
-    ),
-  );
-  return $extra;
-}
diff --git a/question_types/long_answer/long_answer.test b/question_types/long_answer/long_answer.test
deleted file mode 100644
index 7886c4d..0000000
--- a/question_types/long_answer/long_answer.test
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the long_answer Module.
- */
-
-/**
- * Test class for long answer.
- */
-class LongAnswerTestCase extends QuizQuestionTestCase {
-
-  var $question_node_type = 'long_answer';
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Long answer'),
-      'description' => t('Unit test for Long answers question type.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  function setUp() {
-    parent::setUp(array('long_answer'), array('score any quiz'));
-  }
-
-  /**
-   * Test manually graded questions. Also test feedback here instead of its own
-   * test case.
-   *
-   * Note: we use two questions here to make sure the grading form is handled
-   * correctly.
-   */
-  public function testGradeAnswerManualFeedback() {
-    $this->drupalLogin($this->admin);
-
-    $question_node1 = $this->testCreateQuizQuestion();
-    $quiz_node = $this->linkQuestionToQuiz($question_node1);
-
-    $question_node2 = $this->testCreateQuizQuestion();
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-
-    // Test correct.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 'the answer is the zero one infinity rule',
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 'the number two really is ridiculous',
-      ), t('Finish'));
-    $this->assertText('Your score: 0%');
-    // Strange behavior - extra spacing in the HTML.
-    //$this->assertText('Score ? of 10');
-    $this->assertText('This answer has not yet been scored.');
-    $this->assertNoFieldByName('0[score]');
-    $this->assertNoFieldByName('1[score]');
-    $url_of_result = $this->getUrl();
-
-    // Test grading the question.
-    $this->drupalLogin($this->admin);
-    $this->drupalGet($url_of_result);
-    $this->drupalPost(NULL, array(
-      "0[score]" => 3,
-      "1[score]" => 7,
-      "0[answer_feedback][value]" => 'Feedback for answer 1.',
-      "1[answer_feedback][value]" => 'Feedback for answer 2.',
-      "0[answer_feedback][format]" => 'filtered_html',
-      "1[answer_feedback][format]" => 'filtered_html',
-      ), t('Save score'));
-    $this->assertText('The scoring data you provided has been saved.');
-
-    // Test the score and feedback are visible to the user.
-    $this->drupalLogin($this->user);
-    $this->drupalGet($url_of_result);
-    $this->assertText('You got 10 of 20 possible points.');
-    $this->assertText('Your score: 50%');
-    // Strange behavior - extra spacing in the HTML.
-    //$this->assertText('Score 3 of 10');
-    //$this->assertText('Score 7 of 10');
-    $this->assertText('Feedback for answer 1.');
-    $this->assertText('Feedback for answer 2.');
-  }
-
-  /**
-   * Test adding and taking a truefalse question.
-   */
-  function testCreateQuizQuestion($settings = NULL) {
-    if (!$settings) {
-      $settings = array(
-        'rubric' => 'LA 1 rubric.',
-      );
-    }
-
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    $question_node = $this->drupalCreateNode(array(
-      'type' => $this->question_node_type,
-      'title' => 'LA 1 title',
-      'body' => array(LANGUAGE_NONE => array(array('value' => 'LA 1 body text.'))),
-      ) + $settings);
-
-    return $question_node;
-  }
-
-}
diff --git a/question_types/long_answer/theme/long-answer-answering-form.tpl.php b/question_types/long_answer/theme/long-answer-answering-form.tpl.php
deleted file mode 100644
index b487a9c..0000000
--- a/question_types/long_answer/theme/long-answer-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the long_answer answering form.
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render_children($form);
-
-?>
diff --git a/question_types/long_answer/theme/long_answer.theme.inc b/question_types/long_answer/theme/long_answer.theme.inc
deleted file mode 100644
index f245a76..0000000
--- a/question_types/long_answer/theme/long_answer.theme.inc
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-/**
- * @file
- * Theme functions for long_answer.
- */
-
-
-/**
- * Theme the list of unscored long answer questions.
- *
- * @param $unscored
- *  An array of objects, each with the question_nid, question_vid, and result_id of an unscored question.
- */
-function theme_long_answer_view_unscored($variables) {
-  $unscored = $variables['unscored'];
-  $output = '';
-  $header = array(
-    t('Question'),
-    t('Finished'),
-    t('Action')
-  );
-  $rows = array();
-
-  foreach ($unscored as $item) {
-    if ($item->time_end > 0) {
-      $rows[] = array(
-        check_plain($item->title),
-        date('Y-m-d H:i', $item->time_end),
-        l(t('Score this response'), 'admin/quiz/reports/score-long-answer/' . $item->question_vid . '/' . $item->result_id),
-      );
-    }
-  }
-  $output .= !empty($rows) ? theme('table', array('header' => $header, 'rows' => $rows)) : t('There are no unscored essays.');
-  return $output;
-}
-
-/**
- * Theme the long_answer response form
- *
- * @param $form
- *  The response form
- */
-function theme_long_answer_response_form($variables) {
-  $form = $variables['form'];
-  return drupal_render_children($form);
-}
diff --git a/question_types/matching/matching.classes.inc b/question_types/matching/matching.classes.inc
deleted file mode 100644
index c8309f1..0000000
--- a/question_types/matching/matching.classes.inc
+++ /dev/null
@@ -1,557 +0,0 @@
-<?php
-
-/**
- * matching.classes
- *
- * This module uses the question interface to define the Matching question type.
- *
- * A Matching node defines a series of questions and answers and requires the user
- * to associate each answer with a question.
- *
- * @file
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class MatchingQuestion extends QuizQuestion {
-
-  /**
-   * Constructor
-   *
-   * @param $node
-   *  matching node
-   */
-  public function __construct(stdClass $node) {
-    parent::__construct($node);
-    if (empty($this->node->match)) {
-      $this->node->match = array();
-    }
-  }
-
-  /**
-   * Implementation of saveNodeProperties
-   *
-   * @see QuizQuestion#saveNodeProperties($is_new)
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    // Update or insert the question properties
-    if ($is_new) {
-      db_insert('quiz_matching_properties')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'choice_penalty' => $this->node->choice_penalty,
-        ))
-        ->execute();
-    }
-    else {
-      db_update('quiz_matching_properties')
-        ->fields(array(
-          'choice_penalty' => $this->node->choice_penalty,
-        ))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-
-    // Loop through each question-answer combination
-    foreach ($this->node->match as $match) {
-      $match['feedback'] = (isset($match['feedback'])) ? $match['feedback'] : '';
-      // match_id is not so it is a new question.
-      if ($is_new || empty($match['match_id']) || $this->node->revision || isset($this->node->node_export_drupal_version)) {
-        if (!empty($match['question']) && !empty($match['answer'])) {
-          $id = db_insert('quiz_matching_node')
-            ->fields(array(
-              'nid' => $this->node->nid,
-              'vid' => $this->node->vid,
-              'question' => $match['question'],
-              'answer' => $match['answer'],
-              'feedback' => $match['feedback'],
-            ))
-            ->execute();
-        }
-      }
-      // match_id is set, user may remove or update existing question.
-      else {
-        if (empty($match['question']) && empty($match['answer'])) {
-          // remove sub question.
-          db_delete('quiz_matching_node')
-            ->condition('match_id', $match['match_id'])
-            ->execute();
-        }
-        else {
-          // update sub question.
-          db_update('quiz_matching_node')
-            ->fields(array(
-              'question' => $match['question'],
-              'answer' => $match['answer'],
-              'feedback' => $match['feedback'],
-            ))
-            ->condition('match_id', $match['match_id'])
-            ->execute();
-        }
-      }
-    }
-  }
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validateNode($form)
-   */
-  public function validateNode(array &$form) {
-    // No validation is required
-    // The first two pairs are required in the form, if there are other errors we forgive them
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete($only_this_version)
-   */
-  public function delete($only_this_version = FALSE) {
-    parent::delete($only_this_version);
-
-    $delete_properties = db_delete('quiz_matching_properties')->condition('nid', $this->node->nid);
-
-    if ($only_this_version) {
-      $delete_properties->condition('vid', $this->node->vid);
-
-      $match_id = db_query('SELECT match_id FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchCol();
-      db_delete('quiz_matching_user_answers')
-        ->condition('match_id', is_array($match_id) ? $match_id : array(0), 'IN')
-        ->execute();
-
-      db_delete('quiz_matching_node')
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-    // Delete all versions of this question.
-    else {
-      $match_id = db_query('SELECT match_id FROM {quiz_matching_node} WHERE nid = :nid', array(':nid' => $this->node->nid))->fetchCol();
-      if (!empty($match_id)) {
-        db_delete('quiz_matching_user_answers')
-          ->condition('match_id', $match_id, 'IN')
-          ->execute();
-      }
-
-      db_delete('quiz_matching_node')
-        ->condition('nid', $this->node->nid)
-        ->execute();
-    }
-
-    $delete_properties->execute();
-  }
-
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-
-    $res_a = db_query('SELECT choice_penalty FROM {quiz_matching_properties}
-            WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
-
-    if (is_array($res_a)) {
-      $props = array_merge($props, $res_a);
-    }
-
-    //$sql = "SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = %d AND vid = %d";
-    $query = db_query('SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-    while ($result = $query->fetch()) {
-      $props['match'][] = array(
-        'match_id' => $result->match_id,
-        'question' => $result->question,
-        'answer' => $result->answer,
-        'feedback' => $result->feedback,
-      );
-    }
-    $this->nodeProperties = $props;
-    return $props;
-  }
-
-  /**
-   * Implementation of getNodeView
-   *
-   * @see QuizQuestion#getNodeView()
-   */
-  public function getNodeView() {
-    $content = parent::getNodeView();
-
-    list($matches, $select_option) = $this->getSubquestions();
-    $subquestions = array();
-    if ($this->viewCanRevealCorrect()) {
-      foreach ($matches as $match) {
-        $subquestions[] = array(
-          'question' => $match['question'],
-          'correct' => $match['answer'],
-          'feedback' => $match['feedback']
-        );
-      }
-    }
-    else {
-      // shuffle the answer column
-      foreach ($matches as $match) {
-        $sub_qs[] = $match['question'];
-        $sub_as[] = $match['answer'];
-      }
-      shuffle($sub_as);
-      foreach ($sub_qs as $i => $sub_q) {
-        $subquestions[] = array(
-          'question' => $sub_q,
-          'random' => $sub_as[$i],
-        );
-      }
-    }
-    $content['answers'] = array(
-      '#markup' => theme('matching_match_node_view', array('subquestions' => $subquestions)),
-      '#weight' => 2,
-    );
-    return $content;
-  }
-
-  /**
-   * Implementation of getAnsweringForm
-   *
-   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $form = parent::getAnsweringForm($form_state, $result_id);
-
-    if (isset($result_id)) {
-      // The question has already been answered. We load the answers
-      $response = new MatchingResponse($result_id, $this->node);
-      $responses = $response->getResponse();
-    }
-    list($matches, $select_option) = $this->getSubquestions();
-    //$form['#theme'] = 'matching_subquestion_form';
-    foreach ($matches as $match) {
-      $form[$match['match_id']] = array(
-        '#title' => $match['question'],
-        '#type' => 'select',
-        '#options' => $this->customShuffle($select_option),
-      );
-      if ($responses) {
-        // If this question already has been answered
-        $form[$match['match_id']]['#default_value'] = $responses[$match['match_id']];
-      }
-    }
-    if (variable_get('quiz_matching_shuffle_options', TRUE)) {
-      $form = $this->customShuffle($form);
-    }
-    $form['scoring_info'] = array(
-      '#markup' => '<p><em>' . t('You lose points by selecting incorrect options. You may leave an option blank to avoid losing points.') . '</em></p>',
-    );
-    return $form;
-  }
-
-  /**
-   * Question response validator.
-   */
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-    $question_answer = $form_state['values']['question'][$this->node->nid];
-    foreach ($question_answer as $value) {
-      if ($value != 'def') {
-        return TRUE;
-      }
-    }
-    form_set_error('', t('You need to match at least one of the items.'));
-  }
-
-  /**
-   * Shuffles an array, but keeps the keys, and makes sure the first element is the default element
-   *
-   * @param $array
-   *  Array to be shuffled
-   * @return
-   *  A shuffled version of the array with $array['def'] = '' as the first element
-   */
-  private function customShuffle(array $array = array()) {
-    $new_array = array();
-    $new_array['def'] = '';
-    while (count($array)) {
-      $element = array_rand($array);
-      $new_array[$element] = $array[$element];
-      unset($array[$element]);
-    }
-    return $new_array;
-  }
-
-  /**
-   * Helper function to fetch subquestions
-   *
-   * @return
-   *  Array with two arrays, matches and selected options
-   */
-  public function getSubquestions() {
-    $matches = $select_option = array();
-    //$sql = "SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = %d AND vid = %d";
-    $query = db_query('SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-    while ($result = $query->fetch()) {
-      $matches[] = array(
-        'match_id' => $result->match_id,
-        'question' => $result->question,
-        'answer' => $result->answer,
-        'feedback' => $result->feedback,
-      );
-      $select_option[$result->match_id] = $result->answer;
-    }
-    return array($matches, $select_option);
-  }
-
-  /**
-   * Implementation of getBodyFieldTitle
-   *
-   * @see QuizQuestion#getBodyFieldTitle()
-   */
-  public function getBodyFieldTitle() {
-    return t('Instruction');
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm($form_state)
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    // Get the nodes settings, users settings or default settings
-    $default_settings = $this->getDefaultAltSettings();
-
-    $form['settings'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Settings'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-    );
-    $form['settings']['choice_penalty'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Penalty for guessing'),
-      '#description' => t('Subtract 1 point from the users score for each incorrect match. Scores cannot go below 0.'),
-      '#default_value' => $default_settings['choice_penalty'],
-      '#parents' => array('choice_penalty'),
-    );
-
-    $form['match'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Answer'),
-      '#weight' => -4,
-      '#tree' => TRUE,
-      '#theme' => 'matching_question_form',
-      '#description' => t('Write your pairs in the question and answer columns. For the user the question will be fixed and the answers will be shown as alternatives in a dropdown box.'),
-    );
-    for ($i = 1; $i <= variable_get('quiz_matching_form_size', 5); ++$i) {
-      $form['match'][$i] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Question ' . $i),
-      );
-      $form['match'][$i]['match_id'] = array(
-        '#type' => 'value',
-        '#default_value' => isset($this->node->match[$i - 1]['match_id']) ? $this->node->match[$i - 1]['match_id'] : ''
-      );
-      $form['match'][$i]['question'] = array(
-        '#type' => 'textarea',
-        '#rows' => 2,
-        '#default_value' => isset($this->node->match[$i - 1]['question']) ? $this->node->match[$i - 1]['question'] : '',
-        '#required' => $i < 3,
-      );
-      $form['match'][$i]['answer'] = array(
-        '#type' => 'textarea',
-        '#rows' => 2,
-        '#default_value' => isset($this->node->match[$i - 1]['answer']) ? $this->node->match[$i - 1]['answer'] : '',
-        '#required' => $i < 3,
-      );
-
-      $form['match'][$i]['feedback'] = array(
-        '#type' => 'textarea',
-        '#rows' => 2,
-        '#default_value' => isset($this->node->match[$i - 1]['feedback']) ? $this->node->match[$i - 1]['feedback'] : ''
-      );
-    }
-    return $form;
-  }
-
-  /**
-   * Helper function provding the default settings for the creation form.
-   *
-   * @return
-   *  Array with the default settings
-   */
-  private function getDefaultAltSettings() {
-    // If the node is being updated the default settings are those stored in the node
-    if (isset($this->node->nid)) {
-      $settings['choice_penalty'] = $this->node->choice_penalty;
-    }
-    // The user is creating his first matching node
-    else {
-      $settings['choice_penalty'] = 0;
-    }
-    return $settings;
-  }
-
-  /**
-   * Implementation of getMaximumScore
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    $to_return = 0;
-    foreach ($this->node->match as $match) {
-      if (empty($match['question']) || empty($match['answer'])) {
-        continue;
-      }
-      $to_return++;
-    }
-    // The maximum score = the number of sub-questions
-    return $to_return;
-  }
-
-  /**
-   * Get the correct answers for this question
-   *
-   * @return
-   *  Array of correct answers
-   */
-  public function getCorrectAnswer() {
-    $correct_answers = array();
-    $query = db_query('SELECT match_id, question, answer, feedback FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-    while ($result = $query->fetch()) {
-      $correct_answers[$result->match_id] = array(
-        'match_id' => $result->match_id,
-        'question' => $result->question,
-        'answer' => $result->answer,
-        'feedback' => $result->feedback,
-      );
-    }
-    return $correct_answers;
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class MatchingResponse extends QuizQuestionResponse {
-
-  /**
-   * Constructor
-   */
-  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
-    parent::__construct($result_id, $question_node, $answer);
-    if (!isset($answer)) {
-      $res = db_query('SELECT ua.answer, score, ua.match_id FROM {quiz_matching_user_answers} ua
-              JOIN {quiz_matching_node} n ON n.match_id = ua.match_id
-              WHERE n.nid = :nid AND n.vid = :vid AND ua.result_id = :result_id', array(':nid' => $question_node->nid, ':vid' => $question_node->vid, ':result_id' => $result_id));
-      $this->answer = array();
-      while ($obj = $res->fetch()) {
-        $this->answer[$obj->match_id] = $obj->answer;
-      }
-    }
-    $this->is_correct = $this->isCorrect();
-  }
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-    if (!isset($this->answer) || !is_array($this->answer)) {
-      return;
-    }
-    $insert = db_insert('quiz_matching_user_answers')->fields(array('match_id', 'result_id', 'answer', 'score'));
-    foreach ($this->answer as $key => $value) {
-      $insert->values(array(
-        'match_id' => $key,
-        'result_id' => $this->result_id,
-        'answer' => (int) $value,
-        'score' => ($key == $value) ? 1 : 0,
-      ));
-    }
-    $insert->execute();
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-    $match_id = db_query('SELECT match_id FROM {quiz_matching_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->question->nid, ':vid' => $this->question->vid))->fetchCol();
-    db_delete('quiz_matching_user_answers')
-      ->condition('match_id', is_array($match_id) ? $match_id : array(0), 'IN')
-      ->condition('result_id', $this->result_id)
-      ->execute();
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    $wrong_answer = 0;
-    $correct_answer = 0;
-    $user_answers = isset($this->answer['answer']) ? $this->answer['answer'] : $this->answer;
-    $MatchingQuestion = new MatchingQuestion($this->question);
-    $correct_answers = $MatchingQuestion->getCorrectAnswer();
-    foreach ((array) $user_answers as $key => $value) {
-      if ($value != 0 && $correct_answers[$key]['answer'] == $correct_answers[$value]['answer']) {
-        $correct_answer++;
-      }
-      elseif ($value == 0 || $value == 'def') {
-
-      }
-      else {
-        $wrong_answer++;
-      }
-    }
-
-    $score = $correct_answer;
-    if ($this->question->choice_penalty) {
-      $score -= $wrong_answer;
-    }
-
-    return $score < 0 ? 0 : $score;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer;
-  }
-
-  /**
-   * Implementation of getReportFormResponse
-   */
-  public function getReportFormResponse() {
-    $data = array();
-    $answers = $this->question->answers[0]['answer'];
-    $solution = $this->quizQuestion->getSubquestions();
-    foreach ($this->question->match as $match) {
-
-      $data[] = array(
-        'choice' => $match['question'],
-        'attempt' => !empty($answers[$match['match_id']]) ? $solution[1][$answers[$match['match_id']]] : '',
-        'correct' => $answers[$match['match_id']] == $match['match_id'] ? theme('quiz_answer_result', array('type' => 'correct')) : theme('quiz_answer_result', array('type' => 'incorrect')),
-        'score' => $answers[$match['match_id']] == $match['match_id'] ? 1 : 0,
-        'answer_feedback' => $match['feedback'],
-        'question_feedback' => 'Question feedback',
-        'solution' => $solution[1][$match['match_id']],
-        'quiz_feedback' => "Quiz feedback",
-      );
-    }
-
-    return $data;
-  }
-
-}
diff --git a/question_types/matching/matching.info b/question_types/matching/matching.info
deleted file mode 100644
index 95edb04..0000000
--- a/question_types/matching/matching.info
+++ /dev/null
@@ -1,9 +0,0 @@
-name = Matching question
-package = Quiz Question
-description = Provide a way to create matching type of questions.
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-
-files[] = matching.classes.inc
-files[] = matching.test
diff --git a/question_types/matching/matching.install b/question_types/matching/matching.install
deleted file mode 100644
index 9273b5c..0000000
--- a/question_types/matching/matching.install
+++ /dev/null
@@ -1,185 +0,0 @@
-<?php
-
-/**
- * @file
- * matching install file.
- */
-
-/**
- * Implements hook_install().
- */
-function matching_install() {
-  // Add body field to matching node
-  quiz_question_add_body_field('matching');
-
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_install_schema('matching')
-  variable_set('node_options_matching', array('status'));
-}
-
-/**
- * Implements hook_uninstall().
- */
-function matching_uninstall() {
-
-  // Delete tables
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_uninstall_schema('matching')
-
-  // Delete data from other tables
-
-  // Clear the cache.
-  cache_clear_all('variables', 'cache');
-  drupal_set_message(t('The Matching module has been uninstalled and related data has been deleted.'));
-}
-
-/**
- * Implements hook_schema().
- */
-function matching_schema() {
-  // Properties for a question nodes go in here:
-  $schema['quiz_matching_properties'] = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'choice_penalty' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'nid', 'vid'
-    ),
-  );
-
-  $schema['quiz_matching_node'] = array(
-    'fields' => array(
-      'match_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question' => array(
-        'type' => 'text',
-        'not null' => TRUE,
-      ),
-      'answer' => array(
-        'type' => 'text',
-        'not null' => TRUE,
-      ),
-      'feedback' => array(
-        'type' => 'text',
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array('match_id'),
-    'indexes' => array(
-      'question_id' => array('nid', 'vid'),
-    ),
-  );
-
-  // User answers go in here.
-  $schema['quiz_matching_user_answers'] = array(
-    'fields' => array(
-      'answer_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-
-      'match_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'score' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'answer' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array('answer_id'),
-  );
-
-  return $schema;
-}
-
-/**
- * Implements hook_update_N()
- */
-
-/**
- * Add a table to store properties for the matching questions
- */
-function matching_update_7400() {
-  $schema = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'choice_penalty' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'nid', 'vid'
-    ),
-  );
-
-  db_create_table('quiz_matching_properties', $schema);
-
-  // Insert default properties for all of the existing matching questions
-  $result = db_query("SELECT nid, vid FROM {node} WHERE type='matching'");
-
-  foreach ($result as $question) {
-    db_insert('quiz_matching_properties')
-          ->fields(array(
-            'nid' => $question->nid,
-            'vid' => $question->vid,
-            'choice_penalty' => 0,
-          ))
-          ->execute();
-  }
-}
diff --git a/question_types/matching/matching.module b/question_types/matching/matching.module
deleted file mode 100644
index 55593bc..0000000
--- a/question_types/matching/matching.module
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-/**
- * @file
- * Matching question type for quiz module
- *
- * Allows the creation of matching questions, which associate one term
- * with another
- */
-
-/**
- * Implements hook_help().
- */
-function matching_help($path, $args) {
-  switch ($path) {
-    case 'admin/modules#description':
-      return t('Matching question type for quiz module.');
-    case 'node/add#matching':
-    case 'admin/help#matching':
-      return t('A question type for the quiz module: allows you to create matching type questions, which connect terms with one another.');
-    default:
-      break;
-  }
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function matching_quiz_question_info() {
-  return array(
-    'matching' => array(
-      'name' => t('Matching'),
-      'description' => t('Matching question type for quiz module. A question type for the quiz module: allows you to create matching type questions, which connect terms with one another.'),
-      'question provider' => 'MatchingQuestion',
-      'response provider' => 'MatchingResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- *
- * @return FAPI array
- */
-function matching_quiz_question_config() {
-  $form['quiz_matching_form_size'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Match Question Size'),
-    '#description' => t('Number of questions allowed to wrap under a matching type question.'),
-    '#default_value' => variable_get('quiz_matching_form_size', 5),
-  );
-  $form['quiz_matching_shuffle_options'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Shuffle Matching Questions'),
-    '#default_value' => variable_get('quiz_matching_shuffle_options', TRUE),
-    '#description' => t('If checked matching questions will be shuffled'),
-  );
-  $form['#validate'][] = 'matching_config_validate';
-  return $form;
-}
-
-/**
- * Validate the matching config form values
- */
-function matching_config_validate($form, $form_state) {
-  if (!_quiz_is_int($form_state['values']['quiz_matching_form_size'], 2, 50)) {
-    form_set_error('quiz_matching_form_size', t('The number of questions must be between 2 and 50'));
-  }
-}
-
-/**
- * Implements hook_theme().
- */
-function matching_theme() {
-  return array(
-    'matching_question_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'matching') . '/theme',
-      'file' => 'matching.theme.inc',
-    ),
-    'matching_response' => array(
-      'variables' => array('metadata' => NULL, 'data' => NULL),
-      'path' => drupal_get_path('module', 'matching') . '/theme',
-      'file' => 'matching.theme.inc',
-    ),
-    'matching_subquestion_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'matching') . '/theme',
-      'file' => 'matching.theme.inc',
-    ),
-    'matching_match_node_view' => array(
-      'variables' => array('subquestions' => NULL),
-      'path' => drupal_get_path('module', 'matching') . '/theme',
-      'file' => 'matching.theme.inc',
-    ),
-    'matching_answering_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'matching') . '/theme',
-      'template' => 'matching-answering-form',
-    ),
-
-  );
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function matching_field_extra_fields() {
-  $extra['node']['matching'] = array(
-    'form' => array(
-      'settings' => array(
-        'label' => t('Settings'),
-        'description' => t('The settings for the matching question.'),
-        'weight' => -5,
-      ),
-      'match' => array(
-        'label' => t('Answer'),
-        'description' => t('The sets of matches for this question.'),
-        'weight' => -4,
-      ),
-    ),
-  );
-  return $extra;
-}
diff --git a/question_types/matching/matching.test b/question_types/matching/matching.test
deleted file mode 100644
index b19f505..0000000
--- a/question_types/matching/matching.test
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the matching Module.
- */
-
-/**
- * Test class for matching questions.
- */
-class MatchingTestCase extends QuizQuestionTestCase {
-
-  // initializing attributes
-  var $question_node_type = 'matching';
-  var $title = '';
-  var $body = '';
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Matching'),
-      'description' => t('Unit test for matching question type.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  function setUp() {
-    parent::setUp(array('matching'));
-  }
-
-  /**
-   * function to create true false type question node for testing.
-   */
-  public function testCreateQuizQuestion() {
-    // matching question node attributes
-    $this->title = $this->randomName();
-    $this->body = $this->randomName();
-
-    // array of node attributes to create a test node
-    $settings = array(
-      'type' => $this->question_node_type,
-      'title' => $this->title,
-      'body' => array(LANGUAGE_NONE => array(array('value' => $this->body))),
-      'revisions' => TRUE,
-      'choice_penalty' => 0,
-    );
-
-    $this->match = array();
-    $form_size = variable_get('quiz_matching_form_size', 5);
-    // generate rand match question, answer and feedback
-    for ($i = 1; $i < $form_size; $i++) {
-      $settings['match'][$i]['question'] = $this->match[$i]['question'] = $this->randomName();
-      $setting['match'][$i]['answer'] = $this->match[$i]['answer'] = $this->randomName();
-      $settings['match'][$i]['feedback'] = $this->match[$i]['feedback'] = $this->randomName();
-    }
-
-    // create drupal node
-    $node = $this->drupalCreateNode($settings);
-    $load = node_load($node->nid);
-
-    // @todo verify save
-
-    return $load;
-  }
-
-}
diff --git a/question_types/matching/theme/matching-answering-form.tpl.php b/question_types/matching/theme/matching-answering-form.tpl.php
deleted file mode 100644
index 5410a07..0000000
--- a/question_types/matching/theme/matching-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the matching answering form.
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render($form);
-
-?>
\ No newline at end of file
diff --git a/question_types/matching/theme/matching.theme.inc b/question_types/matching/theme/matching.theme.inc
deleted file mode 100644
index 24c329e..0000000
--- a/question_types/matching/theme/matching.theme.inc
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-
-
-/**
- * @file
- *   Themes for the matching module.
- */
-
-/**
- * Theme the matching question form.
- *
- * @param $form
- *   drupal form array
- */
-function theme_matching_question_form($variables) {
-  $form = $variables['form'];
-  $rows = array();
-  $header = array(
-    'question' => t('Question'),
-    'answer' => t('Correct answer'),
-    'feedback' => t('Feedback'),
-  );
-  foreach (element_children($form) as $key) {
-    $rows[] = array(
-      'question' => drupal_render($form[$key]['question']),
-      'answer' => drupal_render($form[$key]['answer']),
-      'feedback' => drupal_render($form[$key]['feedback']),
-    );
-  }
-  // Theme output and display to screen.
-  return theme('table', array('header' => $header, 'rows' => $rows));
-}
-
-/**
- * Theme the answering form
- */
-function theme_matching_subquestion_form($variables) {
-  $form = $variables['form'];
-  $out = '<table class = "matching-tbl">';
-  foreach ($form as $key => $value) {
-    if (is_int($key)) {
-      $out .= '<tr><td class = "matching-question">' . $value['#question'];
-      $out .= '</td><td class = "matching-select">' . drupal_render_children($value) . '</td></tr>';
-    }
-  }
-  $out .= '</table>';
-  return $out;
-}
-
-/**
- * Theme the answer part of the node view
- *
- * @param $subquestions
- *  Array with all the data in the subquestions.
- */
-function theme_matching_match_node_view($variables) {
-  $subquestions = $variables['subquestions'];
-  // We know the correct answer for each subquestion
-  if (isset($subquestions[0]['correct'])) {
-    $header = array(t('Subquestion'), t('Correct match'), t('Feedback'));
-  }
-  // We do not know the correct answer for each subquestion
-  else {
-    $header = array(t('Subquestion'), t('Possible match'));
-  }
-  return theme('table', array('header' => $header, 'rows' => $subquestions));
-}
-
-/**
- * Theme the contents of the matching response form
- *
- * @param $metadata
- *  Can be used as a table header
- * @param $data
- *  Can be used as table rows
- */
-function theme_matching_response($variables) {
-  $metadata = $variables['metadata'];
-  $data = $variables['data'];
-  if (isset($data[0]['is_correct'])) {
-    foreach ($data as $id => $match_data) {
-      if ($match_data['is_correct'] == 0) {
-        $theme = 'quiz_score_incorrect';
-      }
-      if ($match_data['is_correct'] == 1) {
-        $theme = 'quiz_score_correct';
-      }
-      if ($match_data['is_correct'] == 2) {
-        $theme = 'quiz_score_unknown';
-      }
-      // TODO Please change this theme call to use an associative array for the $variables parameter.
-      $data[$id]['is_correct'] = array(
-        'data' => theme($theme),
-        'class' => 'quiz-summary-qcell',
-      );
-    }
-  }
-  return theme('table', array('header' => $metadata, 'rows' => $data));
-}
diff --git a/question_types/multichoice/README.txt b/question_types/multichoice/README.txt
deleted file mode 100644
index 256dd47..0000000
--- a/question_types/multichoice/README.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-
-/**
- * @file
- * README file for Quiz Multichoice.
- */
-
-Quiz Multichoice
-Allows quiz creators to make a multiple choice question type.
-
-Sponsored by: Norwegian Centre for Telemedicine
-Code: falcon
-
-
-CONTENTS
---------
-
-1.  Introduction
-2.  Installation
-3.  Configuration
-
-----
-1. Introduction
-This module is an attempt to make it easy to multiple choice questions using the quiz module.
-
-The multichoice module lets the user create single answer questions and multiple answer questions.
-Advanced feedback and scoring options are also available.
-
-The multichoice module is based on the OO framework of the quiz project.
-
-----
-2. Installation
-
-To install, unpack the module to your modules folder, and simply enable the module at Admin > Build > Modules.
-
-Several database tables prefixed with quiz_multichoice are installed to have a separate storage for this module.
-
-----
-3.  Configuration
-Settings are to be found here: admin/quiz/questions_settings
-
-The only available setting is the number of default alternatives. This decides how many alternatives
-will be shown on the node-form by default. The question creator can add more by pushing the
-add more alternatives button, and he can always leave some alternatives blank if not all the shown
-alternatives are needed.
-
diff --git a/question_types/multichoice/multichoice.classes.inc b/question_types/multichoice/multichoice.classes.inc
deleted file mode 100644
index 608b0ea..0000000
--- a/question_types/multichoice/multichoice.classes.inc
+++ /dev/null
@@ -1,1022 +0,0 @@
-<?php
-
-/**
- * The main classes for the multichoice question type.
- *
- * These inherit or implement code found in quiz_question.classes.inc.
- *
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * Based on:
- * Other question types in the quiz framework.
- *
- *
- *
- * @file
- * Question type, enabling the creation of multiple choice and multiple answer questions.
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class MultichoiceQuestion extends QuizQuestion {
-
-  /**
-   * Forgive some possible logical flaws in the user input.
-   */
-  private function forgive() {
-    if ($this->node->choice_multi == 1) {
-      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-        $short = &$this->node->alternatives[$i];
-        // If the scoring data doesn't make sense, use the data from the "correct" checkbox to set the score data
-        if ($short['score_if_chosen'] == $short['score_if_not_chosen'] || !is_numeric($short['score_if_chosen']) || !is_numeric($short['score_if_not_chosen'])) {
-          if (!empty($short['correct'])) {
-            $short['score_if_chosen'] = 1;
-            $short['score_if_not_chosen'] = 0;
-          }
-          else {
-            if (variable_get('multichoice_def_scoring', 0) == 0) {
-              $short['score_if_chosen'] = -1;
-              $short['score_if_not_chosen'] = 0;
-            }
-            elseif (variable_get('multichoice_def_scoring', 0) == 1) {
-              $short['score_if_chosen'] = 0;
-              $short['score_if_not_chosen'] = 1;
-            }
-          }
-        }
-      }
-    }
-    else {
-      // For questions with one, and only one, correct answer, there will be no points awarded for alternatives
-      // not chosen.
-      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-        $short = &$this->node->alternatives[$i];
-        $short['score_if_not_chosen'] = 0;
-        if (isset($short['correct']) && $short['correct'] == 1 && !_quiz_is_int($short['score_if_chosen'], 1)) {
-          $short['score_if_chosen'] = 1;
-        }
-      }
-    }
-  }
-
-  /**
-   * Warn the user about possible user errors
-   */
-  private function warn() {
-    // Count the number of correct answers
-    $num_corrects = 0;
-    for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-      $alt = &$this->node->alternatives[$i];
-      if ($alt['score_if_chosen'] > $alt['score_if_not_chosen']) {
-        $num_corrects++;
-      }
-    }
-    if ($num_corrects == 1 && $this->node->choice_multi == 1 || $num_corrects > 1 && $this->node->choice_multi == 0) {
-      $link_options = array();
-      if (isset($_GET['destination'])) {
-        $link_options['query'] = array('destination' => $_GET['destination']);
-      }
-      $go_back = l(t('go back'), 'node/' . $this->node->nid . '/edit', $link_options);
-      if ($num_corrects == 1) {
-        drupal_set_message(
-          t('Your question allows multiple answers. Only one of the alternatives have been marked as correct. If this wasn\'t intended please !go_back and correct it.', array('!go_back' => $go_back)), 'warning');
-      }
-      else {
-        drupal_set_message(
-          t('Your question doesn\'t allow multiple answers. More than one of the alternatives have been marked as correct. If this wasn\'t intended please !go_back and correct it.', array('!go_back' => $go_back)), 'warning');
-      }
-    }
-  }
-
-  /**
-   * Run check_markup() on the field of the specified choice alternative
-   * @param $alternativeIndex
-   *  The index of the alternative in the alternatives array.
-   * @param $field
-   *  The name of the field we want to check markup on
-   * @param $check_user_access
-   *  Whether or not to check for user access to the filter we're trying to apply
-   * @return HTML markup
-   */
-  private function checkMarkup($alternativeIndex, $field, $check_user_access = FALSE) {
-    $alternative = $this->node->alternatives[$alternativeIndex];
-    return check_markup($alternative[$field]['value'], $alternative[$field]['format']);
-  }
-
-  /**
-   * Implementation of save
-   *
-   * Stores the question in the database.
-   *
-   * @param is_new if - if the node is a new node...
-   * (non-PHPdoc)
-   * @see sites/all/modules/quiz-HEAD/question_types/quiz_question/QuizQuestion#save()
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    $is_new = $is_new || $this->node->revision == 1;
-
-    // Before we save we forgive some possible user errors
-    $this->forgive();
-
-    // We also add warnings on other possible user errors
-    $this->warn();
-
-    if ($is_new) {
-      $id = db_insert('quiz_multichoice_properties')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'choice_multi' => $this->node->choice_multi,
-          'choice_random' => $this->node->choice_random,
-          'choice_boolean' => $this->node->choice_boolean,
-        ))
-        ->execute();
-
-      // TODO: utilize the benefit of multiple insert of DBTNG
-      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-        if (drupal_strlen($this->node->alternatives[$i]['answer']['value']) > 0) {
-          $this->insertAlternative($i);
-        }
-      }
-    }
-    else {
-      db_update('quiz_multichoice_properties')
-        ->fields(array(
-          'choice_multi' => $this->node->choice_multi,
-          'choice_random' => $this->node->choice_random,
-          'choice_boolean' => $this->node->choice_boolean,
-        ))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-
-      // We fetch ids for the existing answers belonging to this question
-      // We need to figure out if an existing alternative has been changed or deleted.
-      $res = db_query('SELECT id FROM {quiz_multichoice_answers}
-              WHERE question_nid = :nid AND question_vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-
-      // We start by assuming that all existing alternatives needs to be deleted
-      $ids_to_delete = array();
-      while ($res_o = $res->fetch()) {
-        $ids_to_delete[] = $res_o->id;
-      }
-
-      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-        $short = $this->node->alternatives[$i];
-        if (drupal_strlen($this->node->alternatives[$i]['answer']['value']) > 0) {
-          // If new alternative
-          if (!is_numeric($short['id'])) {
-            $this->insertAlternative($i);
-          }
-          // If existing alternative
-          else {
-            $this->updateAlternative($i);
-            // Make sure this alternative isn't deleted
-            $key = array_search($short['id'], $ids_to_delete);
-            $ids_to_delete[$key] = FALSE;
-          }
-        }
-      }
-      foreach ($ids_to_delete as $id_to_delete) {
-        if ($id_to_delete) {
-          db_delete('quiz_multichoice_answers')
-            ->condition('id', $id_to_delete)
-            ->execute();
-        }
-      }
-    }
-    $this->saveUserSettings();
-  }
-
-  function _normalizeAlternative($alternatives) {
-    $copy = $alternatives;
-    // answer
-    if (is_array($alternatives['answer']) && isset($alternatives['answer']['value'])) {
-      $copy['answer'] = $alternatives['answer']['value'];
-    }
-    // answer format
-    if (is_array($alternatives['answer']) && isset($alternatives['answer']['format'])) {
-      $copy['answer_format'] = $alternatives['answer']['format'];
-    }
-    // feedback if choosen
-    if (is_array($alternatives['feedback_if_chosen']) && isset($alternatives['feedback_if_chosen']['value'])) {
-      $copy['feedback_if_chosen'] = $alternatives['feedback_if_chosen']['value'];
-    }
-    // feedback if choosen foramt
-    if (is_array($alternatives['feedback_if_chosen']) && isset($alternatives['feedback_if_chosen']['format'])) {
-      $copy['feedback_if_chosen_format'] = $alternatives['feedback_if_chosen']['format'];
-    }
-    // feedback if not chosen
-    if (is_array($alternatives['feedback_if_not_chosen']) && isset($alternatives['feedback_if_not_chosen']['value'])) {
-      $copy['feedback_if_not_chosen'] = $alternatives['feedback_if_not_chosen']['value'];
-    }
-    // feedback if not chosen foramt
-    if (is_array($alternatives['feedback_if_not_chosen']) && isset($alternatives['feedback_if_not_chosen']['format'])) {
-      $copy['feedback_if_not_chosen_format'] = $alternatives['feedback_if_not_chosen']['format'];
-    }
-    return $copy;
-  }
-
-  /**
-   * Helper function. Saves new alternatives
-   *
-   * @param $i
-   *  The alternative index
-   */
-  private function insertAlternative($i) {
-    $alternatives = $this->_normalizeAlternative($this->node->alternatives[$i]);
-    db_insert('quiz_multichoice_answers')
-      ->fields(array(
-        'answer' => $alternatives['answer'],
-        'answer_format' => $alternatives['answer_format'],
-        'feedback_if_chosen' => $alternatives['feedback_if_chosen'],
-        'feedback_if_chosen_format' => $alternatives['feedback_if_chosen_format'],
-        'feedback_if_not_chosen' => $alternatives['feedback_if_not_chosen'],
-        'feedback_if_not_chosen_format' => $alternatives['feedback_if_not_chosen_format'],
-        'score_if_chosen' => $alternatives['score_if_chosen'],
-        'score_if_not_chosen' => $alternatives['score_if_not_chosen'],
-        'question_nid' => $this->node->nid,
-        'question_vid' => $this->node->vid
-      ))
-      ->execute();
-  }
-
-  /**
-   * Helper function. Updates existing alternatives
-   *
-   * @param $i
-   *  The alternative index
-   */
-  private function updateAlternative($i) {
-    $alternatives = $this->_normalizeAlternative($this->node->alternatives[$i]);
-    db_update('quiz_multichoice_answers')
-      ->fields(array(
-        'answer' => $alternatives['answer'],
-        'answer_format' => $alternatives['answer_format'],
-        'feedback_if_chosen' => $alternatives['feedback_if_chosen'],
-        'feedback_if_chosen_format' => $alternatives['feedback_if_chosen_format'],
-        'feedback_if_not_chosen' => $alternatives['feedback_if_not_chosen'],
-        'feedback_if_not_chosen_format' => $alternatives['feedback_if_not_chosen_format'],
-        'score_if_chosen' => $alternatives['score_if_chosen'],
-        'score_if_not_chosen' => $alternatives['score_if_not_chosen'],
-      ))
-      ->condition('id', $alternatives['id'])
-      ->condition('question_nid', $this->node->nid)
-      ->condition('question_vid', $this->node->vid)
-      ->execute();
-  }
-
-  /**
-   * Implementation of validate
-   *
-   * QuizQuestion#validate()
-   */
-  public function validateNode(array &$form) {
-    if ($this->node->choice_multi == 0) {
-      $found_one_correct = FALSE;
-      for ($i = 0; (isset($this->node->alternatives[$i]) && is_array($this->node->alternatives[$i])); $i++) {
-        $short = $this->node->alternatives[$i];
-        if (drupal_strlen($this->checkMarkup($i, 'answer')) < 1) {
-          continue;
-        }
-        if ($short['correct'] == 1) {
-          if ($found_one_correct) {
-            // We don't display an error message here since we allow alternatives to be partially correct
-          }
-          else {
-            $found_one_correct = TRUE;
-          }
-        }
-      }
-      if (!$found_one_correct) {
-        form_set_error('choice_multi', t('You have not marked any alternatives as correct. If there are no correct alternatives you should allow multiple answers.'));
-      }
-    }
-    else {
-      for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-        $short = $this->node->alternatives[$i];
-        if (strlen($this->checkMarkup($i, 'answer')) < 1) {
-          continue;
-        }
-        if ($short['score_if_chosen'] < $short['score_if_not_chosen'] && $short['correct']) {
-          form_set_error("alternatives][$i][score_if_not_chosen", t('The alternative is marked as correct, but gives more points if you don\'t select it.'));
-        }
-        elseif ($short['score_if_chosen'] > $short['score_if_not_chosen'] && !$short['correct']) {
-          form_set_error("alternatives][$i][score_if_chosen", t('The alternative is not marked as correct, but gives more points if you select it.'));
-        }
-      }
-    }
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete()
-   */
-  public function delete($only_this_version = FALSE) {
-    $delete_properties = db_delete('quiz_multichoice_properties')->condition('nid', $this->node->nid);
-    $delete_answers = db_delete('quiz_multichoice_answers')->condition('question_nid', $this->node->nid);
-    $delete_results = db_delete('quiz_multichoice_user_answers')->condition('question_nid', $this->node->nid);
-
-    if ($only_this_version) {
-      $delete_properties->condition('vid', $this->node->vid);
-      $delete_answers->condition('question_vid', $this->node->vid);
-      $delete_results->condition('question_vid', $this->node->vid);
-    }
-
-    // Delete from table quiz_multichoice_user_answer_multi
-    if ($only_this_version) {
-      $user_answer_id = db_query('SELECT id FROM {quiz_multichoice_user_answers} WHERE question_nid = :nid AND question_vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchCol();
-    }
-    else {
-      $user_answer_id = db_query('SELECT id FROM {quiz_multichoice_user_answers} WHERE question_nid = :nid', array(':nid' => $this->node->nid))->fetchCol();
-    }
-
-    if (!empty($user_answer_id)) {
-      db_delete('quiz_multichoice_user_answer_multi')
-        ->condition('user_answer_id', $user_answer_id, 'IN')
-        ->execute();
-    }
-    $delete_properties->execute();
-    $delete_answers->execute();
-    $delete_results->execute();
-    parent::delete($only_this_version);
-  }
-
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties) && !empty($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-
-    $res_a = db_query('SELECT choice_multi, choice_random, choice_boolean FROM {quiz_multichoice_properties}
-            WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
-
-    if (is_array($res_a)) {
-      $props = array_merge($props, $res_a);
-    }
-
-    // Load the answers
-    $res = db_query('SELECT id, answer, answer_format, feedback_if_chosen, feedback_if_chosen_format,
-            feedback_if_not_chosen, feedback_if_not_chosen_format, score_if_chosen, score_if_not_chosen
-            FROM {quiz_multichoice_answers}
-            WHERE question_nid = :question_nid AND question_vid = :question_vid
-            ORDER BY id', array(':question_nid' => $this->node->nid, ':question_vid' => $this->node->vid));
-    $props['alternatives'] = array(); // init array so it can be iterated even if empty
-    while ($res_arr = $res->fetchAssoc()) {
-      $props['alternatives'][] = array(
-        'id' => $res_arr['id'],
-        'answer' => array(
-          'value' => $res_arr['answer'],
-          'format' => $res_arr['answer_format'],
-        ),
-        'feedback_if_chosen' => array(
-          'value' => $res_arr['feedback_if_chosen'],
-          'format' => $res_arr['feedback_if_chosen_format'],
-        ),
-        'feedback_if_not_chosen' => array(
-          'value' => $res_arr['feedback_if_not_chosen'],
-          'format' => $res_arr['feedback_if_not_chosen_format'],
-        ),
-        'score_if_chosen' => $res_arr['score_if_chosen'],
-        'score_if_not_chosen' => $res_arr['score_if_not_chosen'],
-      );
-    }
-    $this->nodeProperties = $props;
-    return $props;
-  }
-
-  /**
-   * Implementation of getNodeView
-   *
-   * @see QuizQuestion#getNodeView()
-   */
-  public function getNodeView() {
-    $content = parent::getNodeView();
-    if ($this->node->choice_random) {
-      $this->shuffle($this->node->alternatives);
-    }
-    $content['answers'] = array(
-      '#markup' => theme('multichoice_answer_node_view', array('alternatives' => $this->node->alternatives, 'show_correct' => $this->viewCanRevealCorrect())),
-      '#weight' => 2,
-    );
-
-    return $content;
-  }
-
-  /**
-   * Generates the question element.
-   *
-   * This is called whenever a question is rendered, either
-   * to an administrator or to a quiz taker.
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $element = parent::getAnsweringForm($form_state, $result_id);
-    //$form['#theme'] = 'multichoice_answering_form';
-
-    /* We use an array looking key to be able to store multiple answers in tries.
-     * At the moment all user answers have to be stored in tries. This is something we plan
-     * to fix in quiz 5.x.
-     */
-    $element['#theme'] = 'multichoice_alternative';
-    if (isset($result_id)) {
-      // This question has already been answered. We load the answer.
-      $response = new MultichoiceResponse($result_id, $this->node);
-    }
-    for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-      $short = $this->node->alternatives[$i];
-      $answer_markup = check_markup($short['answer']['value'], $short['answer']['format']);
-      if (drupal_strlen($answer_markup) > 0) {
-        $element['user_answer']['#options'][$short['id']] = $answer_markup;
-      }
-    }
-    if ($this->node->choice_random) {
-      // We save the choice order so that the order will be the same in the answer report
-      $element['choice_order'] = array(
-        '#type' => 'hidden',
-        '#value' => implode(',', $this->shuffle($element['user_answer']['#options'])),
-      );
-    }
-    if ($this->node->choice_multi) {
-      $element['user_answer']['#type'] = 'checkboxes';
-      $element['user_answer']['#title'] = t('Choose all that apply');
-      if (isset($response)) {
-        if (is_array($response->getResponse())) {
-          $element['#default_value'] = $response->getResponse();
-        }
-      }
-    }
-    else {
-      $element['user_answer']['#type'] = 'radios';
-      $element['user_answer']['#title'] = t('Choose one');
-      if (isset($response)) {
-        $selection = $response->getResponse();
-        if (is_array($selection)) {
-          $element['user_answer']['#default_value'] = array_pop($selection);
-        }
-      }
-    }
-
-    return $element;
-  }
-
-  /**
-   * Custom shuffle function. It keeps the array key - value relationship intact
-   *
-   * @param array $array
-   * @return unknown_type
-   */
-  private function shuffle(array &$array) {
-    $newArray = array();
-    $toReturn = array_keys($array);
-    shuffle($toReturn);
-    foreach ($toReturn as $key) {
-      $newArray[$key] = $array[$key];
-    }
-    $array = $newArray;
-    return $toReturn;
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm()
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    $form = array();
-    $type = node_type_get_type($this->node);
-    // We add #action to the form because of the use of ajax
-    $options = array();
-    $get = $_GET;
-    unset($get['q']);
-    if (!empty($get)) {
-      $options['query'] = $get;
-    }
-
-    // TODO The second parameter to this function call should be an array.
-    $action = url('node/add/' . $type->type, $options);
-    if (isset($this->node->nid)) {
-      // TODO The second parameter to this function call should be an array.
-      $action = url('node/' . $this->node->nid . '/edit', $options);
-    }
-    $form['#action'] = $action;
-
-    $form['alternatives'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Answer'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#weight' => -4,
-      '#tree' => TRUE,
-    );
-
-    // Get the nodes settings, users settings or default settings
-    $default_settings = $this->getDefaultAltSettings();
-
-    $form['alternatives']['settings'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Settings'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#description' => t('Your settings will be remembered.'),
-    );
-    $form['alternatives']['settings']['choice_multi'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Multiple answers'),
-      '#description' => t('Allow any number of answers(checkboxes are used). If this box is not checked, one, and only one answer is allowed(radiobuttons are used).'),
-      '#default_value' => $default_settings['choice_multi'],
-      '#parents' => array('choice_multi'),
-    );
-    $form['alternatives']['settings']['choice_random'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Random order'),
-      '#description' => t('Present alternatives in random order when @quiz is being taken.', array('@quiz' => QUIZ_NAME)),
-      '#default_value' => $default_settings['choice_random'],
-      '#parents' => array('choice_random'),
-    );
-    $form['alternatives']['settings']['choice_boolean'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Simple scoring'),
-      '#description' => t('Give max score if everything is correct. Zero points otherwise.'),
-      '#default_value' => $default_settings['choice_boolean'],
-      '#parents' => array('choice_boolean'),
-    );
-
-    // Add helper tag where we will place the input selector for all the textareas.
-    $form['alternatives']['input_format_all'] = array(
-      '#markup' => '<DIV id="input-all-ph"></DIV>',
-    );
-
-    $form['alternatives']['#theme'][] = 'multichoice_creation_form';
-    $i = 0;
-
-    // choice_count might be stored in the form_state after an ajax callback
-    if (isset($form_state['choice_count'])) {
-      $choice_count = $form_state['choice_count'];
-    }
-    else {
-      $choice_count = max(variable_get('multichoice_def_num_of_alts', 2), isset($this->node->alternatives) ? count($this->node->alternatives) : 0);
-    }
-
-    for (; $i < $choice_count; $i++) {
-      $short = isset($this->node->alternatives[$i]) ? $this->node->alternatives[$i] : NULL;
-      $form['alternatives'][$i] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Alternative !i', array('!i' => ($i + 1))),
-        '#collapsible' => TRUE,
-        '#collapsed' => FALSE,
-        // - The two first alternatives won't be collapsed.
-        // - Populated alternatives won't be collapsed
-      );
-      $form['alternatives'][$i]['#theme'][] = 'multichoice_alternative_creation';
-
-      if (is_array($short)) {
-        if ($short['score_if_chosen'] == $short['score_if_not_chosen']) {
-          $correct_default = isset($short['correct']) ? $short['correct'] : FALSE;
-        }
-        else {
-          $correct_default = $short['score_if_chosen'] > $short['score_if_not_chosen'];
-        }
-      }
-      else {
-        $correct_default = FALSE;
-      }
-      $form['alternatives'][$i]['correct'] = array(
-        '#type' => 'checkbox',
-        '#title' => t('Correct'),
-        '#default_value' => $correct_default,
-        '#attributes' => array('onchange' => 'Multichoice.refreshScores(this, ' . variable_get('multichoice_def_scoring', 0) . ')'),
-      );
-      // We add id to be able to update the correct alternatives if the node is updated, without destroying
-      // existing answer reports
-      $form['alternatives'][$i]['id'] = array(
-        '#type' => 'value',
-        '#value' => $short['id'],
-      );
-
-      $form['alternatives'][$i]['answer'] = array(
-        '#type' => 'text_format',
-        '#base_type' => 'textarea',
-        '#title' => t('Alternative !i', array('!i' => ($i + 1))),
-        '#default_value' => $short['answer']['value'],
-        '#required' => $i < 2,
-        '#format' => isset($short['answer']['format']) ? $short['answer']['format'] : NULL,
-        '#rows' => 3,
-      );
-
-      $form['alternatives'][$i]['advanced'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Advanced options'),
-        '#collapsible' => TRUE,
-        '#collapsed' => TRUE,
-      );
-      $form['alternatives'][$i]['advanced']['feedback_if_chosen'] = array(
-        '#type' => 'text_format',
-        '#base_type' => 'textarea',
-        '#title' => t('Feedback if chosen'),
-        '#description' => t('This feedback is given to users who chooses this alternative.'),
-        '#parents' => array('alternatives', $i, 'feedback_if_chosen'),
-        '#default_value' => $short['feedback_if_chosen']['value'],
-        '#format' => isset($short['feedback_if_chosen']['format']) ? $short['feedback_if_chosen']['format'] : NULL,
-        '#rows' => 3,
-      );
-      // We add 'helper' to trick the current version of the wysiwyg module to add an editor to several
-      // textareas in the same fieldset
-      $form['alternatives'][$i]['advanced']['helper']['feedback_if_not_chosen'] = array(
-        '#type' => 'text_format',
-        '#base_type' => 'textarea',
-        '#title' => t('Feedback if not chosen'),
-        '#description' => t('This feedback is given to users who doesn\'t choose this alternative.'),
-        '#parents' => array('alternatives', $i, 'feedback_if_not_chosen'),
-        '#default_value' => $short['feedback_if_not_chosen']['value'],
-        '#format' => isset($short['feedback_if_not_chosen']['format']) ? $short['feedback_if_not_chosen']['format'] : NULL,
-        '#rows' => 3,
-      );
-      $default_value = isset($this->node->alternatives[$i]['score_if_chosen']) ? $this->node->alternatives[$i]['score_if_chosen'] : 0;
-      $form['alternatives'][$i]['advanced']['score_if_chosen'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Score if chosen'),
-        '#size' => 4,
-        '#maxlength' => 4,
-        '#default_value' => $default_value,
-        '#description' => t("This score is added to the user's total score if the user chooses this alternative."),
-        '#attributes' => array(
-          'onkeypress' => 'Multichoice.refreshCorrect(this)',
-          'onkeyup' => 'Multichoice.refreshCorrect(this)',
-          'onchange' => 'Multichoice.refreshCorrect(this)'
-        ),
-        '#parents' => array('alternatives', $i, 'score_if_chosen')
-      );
-
-      $default_value = $short['score_if_not_chosen'];
-      if (!isset($default_value)) {
-        $default_value = '0';
-      }
-      $form['alternatives'][$i]['advanced']['score_if_not_chosen'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Score if not chosen'),
-        '#size' => 4,
-        '#maxlength' => 4,
-        '#default_value' => $default_value,
-        '#description' => t("This score is added to the user's total score if the user doesn't choose this alternative. Only used if multiple answers are allowed."),
-        '#attributes' => array(
-          'onkeypress' => 'Multichoice.refreshCorrect(this)',
-          'onkeyup' => 'Multichoice.refreshCorrect(this)',
-          'onchange' => 'Multichoice.refreshCorrect(this)'
-        ),
-        '#parents' => array('alternatives', $i, 'score_if_not_chosen')
-      );
-    }
-    // ahah helper tag. New questions will be inserted before this tag
-    $form['alternatives']["placeholder"] = array(
-      '#markup' => '<div id="placeholder"></div>',
-    );
-
-    // We can't send the get values to the ahah callback the normal way, so we do it like this.
-    $form['get'] = array(
-      '#type' => 'value',
-      '#value' => $get,
-    );
-
-    $form['alternatives']['multichoice_add_alternative'] = array(
-      '#type' => 'submit',
-      '#value' => t('Add more alternatives'),
-      '#submit' => array('multichoice_more_choices_submit'), // If no javascript action.
-      '#limit_validation_errors' => array(),
-      '#ajax' => array(
-        'callback' => 'multichoice_add_alternative_ajax_callback',
-        'wrapper' => 'placeholder',
-        'effect' => 'slide',
-        'method' => 'before',
-      ),
-    );
-    $form['#attached']['js'] = array(
-      drupal_get_path('module', 'multichoice') . '/multichoice.js',
-    );
-
-    return $form;
-  }
-
-  /**
-   * Helper function provding the default settings for the creation form.
-   *
-   * @return
-   *  Array with the default settings
-   */
-  private function getDefaultAltSettings() {
-    // If the node is being updated the default settings are those stored in the node
-    if (isset($this->node->nid)) {
-      $settings['choice_multi'] = $this->node->choice_multi;
-      $settings['choice_random'] = $this->node->choice_random;
-      $settings['choice_boolean'] = $this->node->choice_boolean;
-    }
-    // We try to fetch the users settings
-    elseif ($settings = $this->getUserSettings()) {
-
-    }
-    // The user is creating his first multichoice node
-    else {
-      $settings['choice_multi'] = 0;
-      $settings['choice_random'] = 0;
-      $settings['choice_boolean'] = 0;
-    }
-    return $settings;
-  }
-
-  /**
-   * Fetches the users default settings for the creation form
-   *
-   * @return
-   *  The users default node settings
-   */
-  private function getUserSettings() {
-    global $user;
-    $res = db_query('SELECT choice_multi, choice_boolean, choice_random
-            FROM {quiz_multichoice_user_settings}
-            WHERE uid = :uid', array(':uid' => $user->uid))->fetchAssoc();
-    if ($res) {
-      return $res;
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  /**
-   * Fetches the users default settings from the creation form
-   */
-  private function saveUserSettings() {
-    global $user;
-    db_merge('quiz_multichoice_user_settings')
-      ->key(array('uid' => $user->uid))
-      ->fields(array(
-        'choice_random' => $this->node->choice_random,
-        'choice_multi' => $this->node->choice_multi,
-        'choice_boolean' => $this->node->choice_boolean,
-      ))
-      ->execute();
-  }
-
-  /**
-   * Implementation of getMaximumScore.
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    if ($this->node->choice_boolean) {
-      return 1;
-    }
-
-    $max = 0;
-    for ($i = 0; isset($this->node->alternatives[$i]); $i++) {
-      $short = $this->node->alternatives[$i];
-      if ($this->node->choice_multi) {
-        $max += max($short['score_if_chosen'], $short['score_if_not_chosen']);
-      }
-      else {
-        $max = max($max, $short['score_if_chosen'], $short['score_if_not_chosen']);
-      }
-    }
-    return max($max, 1);
-  }
-
-  /**
-   * Question response validator.
-   */
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-    if (!$this->node->choice_multi && is_null($form_state['values']['question'][$this->node->nid]['user_answer'])) {
-      form_set_error('', t('You must provide an answer.'));
-    }
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class MultichoiceResponse extends QuizQuestionResponse {
-
-  /**
-   * ID of the answers.
-   */
-  protected $user_answer_ids;
-  protected $choice_order;
-
-  /**
-   * Constructor.
-   *
-   * @param type $result_id
-   * @param stdClass $question_node
-   * @param type $values
-   *   Form state values.
-   */
-  public function __construct($result_id, stdClass $question_node, $values = NULL) {
-    parent::__construct($result_id, $question_node, $values['user_answer']);
-    $this->user_answer_ids = array();
-    // tries is the tries part of the post data
-    if (isset($values['user_answer'])) {
-      if (!is_array($values['user_answer'])) {
-        // Account for single-select
-        $values['user_answer'] = array($values['user_answer']);
-      }
-      if (isset($values['choice_order'])) {
-        $this->choice_order = $values['choice_order'];
-      }
-      unset($values['choice_order']);
-      if (isset($values['user_answer']) && is_array($values['user_answer'])) {
-        foreach ($values['user_answer'] as $answer_id) {
-          $this->user_answer_ids[] = $answer_id;
-          $this->answer = $this->user_answer_ids; //@todo: Stop using user_answer_ids and only use answer instead...
-        }
-      }
-      elseif (isset($values['user_answer'])) {
-        $this->user_answer_ids[] = $values['user_answer'];
-      }
-    }
-    // We load the answer from the database
-    else {
-      $res = db_query('SELECT answer_id FROM {quiz_multichoice_user_answers} ua
-              LEFT OUTER JOIN {quiz_multichoice_user_answer_multi} uam ON(uam.user_answer_id = ua.id)
-              WHERE ua.result_id = :result_id AND ua.question_nid = :question_nid AND ua.question_vid = :question_vid', array(':result_id' => $result_id, ':question_nid' => $this->question->nid, ':question_vid' => $this->question->vid));
-      while ($res_o = $res->fetch()) {
-        $this->user_answer_ids[] = $res_o->answer_id;
-      }
-    }
-  }
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-    $user_answer_id = db_insert('quiz_multichoice_user_answers')
-      ->fields(array(
-        'result_id' => $this->result_id,
-        'question_vid' => $this->question->vid,
-        'question_nid' => $this->question->nid,
-        'choice_order' => $this->choice_order
-      ))
-      ->execute();
-
-    $query = db_insert('quiz_multichoice_user_answer_multi')
-      ->fields(array('user_answer_id', 'answer_id'));
-    for ($i = 0; $i < count($this->user_answer_ids); $i++) {
-      if ($this->user_answer_ids[$i]) {
-        $query->values(array($user_answer_id, $this->user_answer_ids[$i]));
-      }
-    }
-    $query->execute();
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-    $user_answer_id = array();
-    $query = db_query('SELECT id FROM {quiz_multichoice_user_answers} WHERE question_nid = :nid AND question_vid = :vid AND result_id = :result_id', array(':nid' => $this->question->nid, ':vid' => $this->question->vid, ':result_id' => $this->result_id));
-    while ($user_answer = $query->fetch()) {
-      $user_answer_id[] = $user_answer->id;
-    }
-
-    if (!empty($user_answer_id)) {
-      db_delete('quiz_multichoice_user_answer_multi')
-        ->condition('user_answer_id', $user_answer_id, 'IN')
-        ->execute();
-    }
-
-    db_delete('quiz_multichoice_user_answers')
-      ->condition('result_id', $this->result_id)
-      ->condition('question_nid', $this->question->nid)
-      ->condition('question_vid', $this->question->vid)
-      ->execute();
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @return uint
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    if ($this->question->choice_boolean || $this->isAllWrong()) {
-      $score = $this->getMaxScore();
-      foreach ($this->question->alternatives as $key => $alt) {
-        if (in_array($alt['id'], $this->user_answer_ids)) {
-          if ($alt['score_if_chosen'] <= $alt['score_if_not_chosen']) {
-            $score = 0;
-          }
-        }
-        else {
-          if ($alt['score_if_chosen'] > $alt['score_if_not_chosen']) {
-            $score = 0;
-          }
-        }
-      }
-    }
-    else {
-      $score = 0;
-      foreach ($this->question->alternatives as $key => $alt) {
-        if (in_array($alt['id'], $this->user_answer_ids)) {
-          $score += $alt['score_if_chosen'];
-        }
-        else {
-          $score += $alt['score_if_not_chosen'];
-        }
-      }
-    }
-    return $score;
-  }
-
-  /**
-   * If all answers in a question is wrong
-   *
-   * @return boolean
-   *  TRUE if all answers are wrong. False otherwise.
-   */
-  public function isAllWrong() {
-    foreach ($this->question->alternatives as $key => $alt) {
-      if ($alt['score_if_chosen'] > 0 || $alt['score_if_not_chosen'] > 0) {
-        return FALSE;
-      }
-    }
-    return TRUE;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @return answer
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->user_answer_ids;
-  }
-
-  /**
-   * Implementation of getReportFormResponse
-   */
-  public function getReportFormResponse() {
-    $this->orderAlternatives($this->question->alternatives);
-    $simple_scoring = $this->question->choice_boolean;
-
-    $data = array();
-    foreach ($this->question->alternatives as $alternative) {
-      $chosen = in_array($alternative['id'], $this->user_answer_ids);
-      $not = $chosen ? '' : 'not_';
-
-      $data[] = array(
-        'choice' => check_markup($alternative['answer']['value'], $alternative['answer']['format']),
-        'attempt' => $chosen ? quiz_icon('selected') : '',
-        'correct' => $chosen ? $alternative['score_if_chosen'] > 0 ? quiz_icon('correct') : quiz_icon('incorrect') : '',
-        'score' => $alternative["score_if_{$not}chosen"],
-        'answer_feedback' => check_markup($alternative["feedback_if_{$not}chosen"]['value'], $alternative["feedback_if_{$not}chosen"]['format'], FALSE),
-        'question_feedback' => 'Question feedback',
-        'solution' => $alternative['score_if_chosen'] > 0 ? quiz_icon('should') : ($simple_scoring ? quiz_icon('should-not') : ''),
-        'quiz_feedback' => "Quiz feedback",
-      );
-    }
-
-    return $data;
-  }
-
-  /**
-   * Order the alternatives according to the choice order stored in the database
-   *
-   * @param array $alternatives
-   *  The alternatives to be ordered
-   */
-  protected function orderAlternatives(array &$alternatives) {
-    if (!$this->question->choice_random) {
-      return;
-    }
-    $result = db_query('SELECT choice_order FROM {quiz_multichoice_user_answers}
-            WHERE result_id = :result_id AND question_nid = :question_nid AND question_vid = :question_vid', array(':result_id' => $this->result_id, ':question_nid' => $this->question->nid, ':question_vid' => $this->question->vid))->fetchField();
-    if (!$result) {
-      return;
-    }
-    $order = explode(',', $result);
-    $newAlternatives = array();
-    foreach ($order as $value) {
-      foreach ($alternatives as $alternative) {
-        if ($alternative['id'] == $value) {
-          $newAlternatives[] = $alternative;
-          break;
-        }
-      }
-    }
-    $alternatives = $newAlternatives;
-  }
-
-}
diff --git a/question_types/multichoice/multichoice.info b/question_types/multichoice/multichoice.info
deleted file mode 100644
index e5fdf68..0000000
--- a/question_types/multichoice/multichoice.info
+++ /dev/null
@@ -1,8 +0,0 @@
-name = Quiz Multichoice
-package = Quiz Question
-description = Multiple choice question type for quiz.
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-files[] = multichoice.classes.inc
-files[] = multichoice.test
diff --git a/question_types/multichoice/multichoice.install b/question_types/multichoice/multichoice.install
deleted file mode 100644
index 25797fa..0000000
--- a/question_types/multichoice/multichoice.install
+++ /dev/null
@@ -1,233 +0,0 @@
-<?php
-
-/**
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * @file
- * multichoice Install (a quiz question type)
- */
-
-
-/**
- * Implements hook_install().
- */
-function multichoice_install() {
-  // Add body field to multichoice node
-  quiz_question_add_body_field('multichoice');
-
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_install_schema('multichoice')
-  variable_set('node_options_multichoice', array('status'));
-}
-
-/**
- * Implements hook_schema().
- */
-function multichoice_schema() {
-  // Stores the users answers to a question.
-  $schema['quiz_multichoice_user_answers'] = array(
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'choice_order' => array(
-        'type' => 'text',
-      ),
-    ),
-    'primary key' => array(
-      'id'
-    ),
-    'indexes' => array(
-      'answer_id' => array('result_id', 'question_nid', 'question_vid'),
-    ),
-  );
-
-  $schema['quiz_multichoice_user_answer_multi'] = array(
-    'fields' => array(
-      'user_answer_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'user_answer_id', 'answer_id'
-    ),
-    'indexes' => array(
-      'answer_id' => array('user_answer_id'),
-    ),
-  );
-
-  $schema['quiz_multichoice_properties'] = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'choice_multi' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'choice_random' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'choice_boolean' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'nid', 'vid'
-    ),
-  );
-
-  /*
-   * Holds each answer in the answer_collections.
-   */
-  $schema['quiz_multichoice_answers'] = array(
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer' => array(
-        'type' => 'text',
-      ),
-      'answer_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'feedback_if_chosen' => array(
-        'type' => 'text',
-      ),
-      'feedback_if_chosen_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'feedback_if_not_chosen' => array(
-        'type' => 'text',
-      ),
-      'feedback_if_not_chosen_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'score_if_chosen' => array(
-        'type' => 'int',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'score_if_not_chosen' => array(
-        'type' => 'int',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-
-    ),
-    'primary key' => array(
-      'id',
-    ),
-    'indexes' => array(
-      'quiz_id' => array('question_nid', 'question_vid'),
-    )
-  );
-
-  $schema['quiz_multichoice_user_settings'] = array(
-    'fields' => array(
-      'uid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'choice_multi' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'choice_random' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'choice_boolean' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'uid'
-    ),
-  );
-  return $schema;
-}
-
-/**
- * Implements hook_uninstall().
- */
-function multichoice_uninstall() {
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_uninstall_schema('multichoice')
-  $var = array(
-    'multichoice_def_scoring',
-    'multichoice_def_num_of_alts',
-  );
-  foreach ($var as $v) {
-    variable_del($v);
-  }
-  drupal_set_message(t("The multichoice module has been uninstalled. multichoice nodes may still exist, but they will not function properly."));
-}
diff --git a/question_types/multichoice/multichoice.js b/question_types/multichoice/multichoice.js
deleted file mode 100644
index b6b7f74..0000000
--- a/question_types/multichoice/multichoice.js
+++ /dev/null
@@ -1,89 +0,0 @@
-var Multichoice = Multichoice || {};
-(function($) {
-
-Multichoice.refreshScores = function(checkbox, scoring) {
-  var prefix = '#' + Multichoice.getCorrectIdPrefix(checkbox.id);
-  if (checkbox.checked) {
-    $(prefix + 'score-if-chosen').val('1');
-    $(prefix + 'score-if-not-chosen').val('0');
-  }
-  else {
-    if (scoring == 0) {
-      $(prefix + 'score-if-not-chosen').val('0');
-      if ($('#edit-choice-multi').attr('checked')) {
-        $(prefix + 'score-if-chosen').val('-1');
-      }
-      else {
-        $(prefix + 'score-if-chosen').val('0');
-      }
-    }
-    else if (scoring == 1) {
-	    $(prefix + 'score-if-chosen').val('0');
-	    if ($('#edit-choice-multi').attr('checked')) {
-	      $(prefix + 'score-if-not-chosen').val('1');
-	    }
-	    else {
-	      $(prefix + 'score-if-not-chosen').val('0');
-	    }
-    }
-  }
-}
-
-/**
- * Updates correct checkboxes according to changes of the score values for an alternative
- *
- * @param textfield
- *  The textfield(score) that is being updated
- */
-Multichoice.refreshCorrect = function(textfield) {
-  var prefix = '#' + Multichoice.getCorrectIdPrefix(textfield.id);
-  var chosenScore;
-  var notChosenScore;
-
-  // Fetch the score if chosen and score if not chosen values for the active alternative
-  if (Multichoice.isChosen(textfield.id)) {
-    chosenScore = new Number(textfield.value);
-    notChosenScore = new Number($(prefix + 'score-if-not-chosen').val());
-  }
-  else {
-    chosenScore = new Number($(prefix + 'score-if-chosen').val());
-    notChosenScore = new Number(textfield.value);
-  }
-
-  // Set the checked status for the checkbox in the active alternative
-  if(notChosenScore < chosenScore) {
-    $(prefix + 'correct').attr('checked', true);
-  }
-  else {
-    $(prefix + 'correct').attr('checked', false);
-  }
-}
-
-/**
- * Helper function fetching the id prefix for a html id attribute
- *
- * @param string
- *  Html id attribute
- * @return
- *  The common prefix for all the alternatives in this alternative fieldset
- */
-Multichoice.getCorrectIdPrefix = function(string) {
-  // TODO: Will the regExp below always work?
-  var pattern = new RegExp("^(edit-alternatives-[0-9]{1,2}-)(?:correct|score-if-(?:not-|)chosen)$");
-  pattern.exec(string);
-  return RegExp.lastParen;
-}
-
-/**
- * Checks if the id belongs to the score if chosen textfield
- *
- * @param string
- *  html id attribute of one of the score text fields
- * @return
- *  True if the string ends with "score-if-chosen", false otherwise.
- */
-Multichoice.isChosen = function(string) {
-  var pattern = new RegExp("score-if-chosen$");
-  return pattern.test(string);
-}
-})(jQuery);
\ No newline at end of file
diff --git a/question_types/multichoice/multichoice.module b/question_types/multichoice/multichoice.module
deleted file mode 100644
index c1d08a6..0000000
--- a/question_types/multichoice/multichoice.module
+++ /dev/null
@@ -1,195 +0,0 @@
-<?php
-
-/**
- * The main file for multichoice.
- *
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * @file
- * Multiplechoice question type for the Quiz module.
- *
- */
-
-/**
- * Implements hook_help().
- */
-function multichoice_help($path, $args) {
-  if ($path == 'admin/help#multichoice') {
-    return t("
-      <p>This module provides a multiple choice question type for Quiz.</p>
-
-      <p>The module has three settings.
-      <em>Multiple answers</em> allows the quiz taker to select more than one alternative
-      (it also allows for the possibility that none of the alternatives are correct).
-      Alternatives are selected using checkboxes instead of radio buttons.
-      <em>Random order</em> displays the alternatives in random order when quiz is beeing taken.
-      <em>Simple scoring</em> gives max score if everything is correct. Zero points otherwise.</p>
-
-      <p>The scoring system in multichoice is a bit complex. With multiple answers each alternative adds a given number of points to
-      the total score if it is chosen, and another number of points is added if the alternative isn't chosen. Both <em>score if chosen</em> and
-      <em>score if not chosen</em> may be edited for each alternative by the question creator.
-      If multiple answers isn't allowed the score will be set to the <em>score if chosen</em> of the alternative that has been chosen.
-      The question is considered correct if the quiz taker gets the maximum amount of points possible for the question.</p>
-    ");
-  }
-}
-/**
- * Implements hook_quiz_question_info().
- */
-function multichoice_quiz_question_info() {
-  return array(
-    'multichoice' => array(
-      'name' => t('Multiple choice question'),
-      'description' => t('This provides multiple choice questions for use by the Quiz module.'),
-      'question provider' => 'MultichoiceQuestion',
-      'response provider' => 'MultichoiceResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-/**
- * Implements hook_quiz_question_config().
- */
-function multichoice_quiz_question_config() {
-  $form['multichoice_def_num_of_alts'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default number of alternatives'),
-    '#default_value' => variable_get('multichoice_def_num_of_alts', 2),
-  );
-  $form['multichoice_def_scoring'] = array(
-    '#type' => 'radios',
-    '#title' => t('Default scoring method'),
-    '#description' => t('Choose the default scoring method for questions with multiple correct answers.'),
-    '#options' => array(
-      0 => t('Give minus one point for incorrect answers'),
-      1 => t("Give one point for each incorrect option that haven't been chosen"),
-    ),
-    '#default_value' => variable_get('multichoice_def_scoring', 0),
-  );
-  $form['#validate'][] = 'multichoice_config_validate';
-  return $form;
-}
-
-/**
- * Validate the multichoice config form values
- */
-function multichoice_config_validate($form, $form_state) {
-  if (!_quiz_is_int($form_state['values']['multichoice_def_num_of_alts'], 2, 50)) {
-    form_set_error('multichoice_def_num_of_alts', t('The default number of alternatives must be between 2 and 50'));
-  }
-}
-
-/**
- * Implements hook_theme().
- */
-function multichoice_theme($existing, $type, $theme, $path) {
-  $path = drupal_get_path('module', 'multichoice') . '/theme';
-  return array(
-    'multichoice_creation_form' => array(
-      'render element' => 'form',
-      'path' => $path,
-      'file' => 'multichoice.theme.inc',
-    ),
-    'multichoice_answer_node_view' => array(
-      'variables' => array(
-        'alternatives' => NULL,
-        'show_correct' => NULL,
-      ),
-      'path' => $path,
-      'file' => 'multichoice.theme.inc',
-    ),
-    'multichoice_response' => array(
-      'variables' => array(
-        'data' => array(),
-      ),
-      'path' => $path,
-      'file' => 'multichoice.theme.inc',
-    ),
-    'multichoice_alternative_creation' => array(
-      'render element' => 'form',
-      'path' => $path,
-      'template' => 'multichoice-alternative-creation',
-    ),
-    'multichoice_answering_form' => array(
-      'render element' => 'form',
-      'path' => $path,
-      'template' => 'multichoice-answering-form',
-    ),
-    'multichoice_alternative' => array(
-      'render element' => 'form',
-      'path' => $path,
-      'template' => 'multichoice-alternative',
-    ),
-  );
-}
-
-/**
- * ajax callback function used when adding alternatives to the node-form
- */
-function multichoice_add_alternative_ajax_callback($form, &$form_state) {
-  $i = 0;
-  while (isset($form['alternatives'][$i])) {
-    $i++;
-  }
-  return $form['alternatives'][$i - 1];
-}
-
-/**
- * Submit handler used when adding more alternatives to the node-form
- */
-function multichoice_more_choices_submit($form, &$form_state) {
-  // Set the form to rebuild and run submit handlers.
-  if (!empty($form['node']->nid)) {
-    node_form_submit_build_node($form, $form_state);
-  }
-
-  // Count the existing alternatives
-  $exists = 0;
-  while (isset($form['alternatives'][$exists])) {
-    $exists++;
-  }
-
-  // Make the changes we want to the form state.
-  if ($form_state['values']['alternatives']['multichoice_add_alternative']) {
-    // We add 3 if js is disabled. 1 if the adding is done using ahah
-    $n = $_GET['q'] == 'system/ajax' ? 1 : 3;
-    $form_state['choice_count'] = $exists + $n;
-  }
-  $form_state['rebuild'] = TRUE;
-}
-
-/**
- * Implements hook_user_cancel().
- */
-function multichoice_user_cancel($edit, $account, $method) {
-  db_delete('quiz_multichoice_user_settings')
-  ->condition('uid', $account->uid)
-  ->execute();
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function multichoice_field_extra_fields() {
-  $extra['node']['multichoice'] = array(
-    'form' => array(
-      'alternatives' => array(
-        'label' => t('Alternatives'),
-        'description' => t('Alternatives for multichoice'),
-        'weight' => -4,
-      ),
-    ),
-  );
-  return $extra;
-}
-
-/**
- * Implements hook_views_api().
- */
-function multichoice_views_api() {
-  return array(
-    'api' => 3,
-    'path' => drupal_get_path('module', 'multichoice'),
-  );
-}
diff --git a/question_types/multichoice/multichoice.test b/question_types/multichoice/multichoice.test
deleted file mode 100644
index dcc91fa..0000000
--- a/question_types/multichoice/multichoice.test
+++ /dev/null
@@ -1,184 +0,0 @@
-<?php
-
-/**
- * @file
- * Test suite for choice Questions type module.
- */
-
-/**
- * Test class for multichoice questions.
- */
-class MultichoiceTestCase extends QuizQuestionTestCase {
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Multichoice'),
-      'description' => t('Unit test for the multichoice question type.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  var $question_node_type = 'multichoice';
-
-  function setUp() {
-    parent::setUp(array('multichoice'));
-  }
-
-  /**
-   * Create a default MCQ with default settings.
-   */
-  public function testCreateQuizQuestion($settings = array()) {
-    $settings += array(
-      'title' => 'MCQ 1 Title',
-      'type' => 'multichoice',
-      'choice_multi' => 0,
-      'choice_random' => 0,
-      'choice_boolean' => 0,
-      'body' => array(LANGUAGE_NONE => array(array('value' => 'MCQ 1 body text'))),
-    );
-
-    // Set up some alternatives.
-    $settings['alternatives'][0]['answer']['value'] = 'A';
-    $settings['alternatives'][0]['answer']['format'] = 'filtered_html';
-    $settings['alternatives'][0]['feedback_if_chosen']['value'] = 'You chose A';
-    $settings['alternatives'][0]['feedback_if_chosen']['format'] = 'filtered_html';
-    $settings['alternatives'][0]['feedback_if_not_chosen']['value'] = 'You did not choose A';
-    $settings['alternatives'][0]['feedback_if_not_chosen']['format'] = 'filtered_html';
-    $settings['alternatives'][0]['score_if_chosen'] = 1;
-    $settings['alternatives'][0]['score_if_not_chosen'] = 0;
-
-    $settings['alternatives'][1]['answer']['value'] = 'B';
-    $settings['alternatives'][1]['answer']['format'] = 'filtered_html';
-    $settings['alternatives'][1]['feedback_if_chosen']['value'] = 'You chose B';
-    $settings['alternatives'][1]['feedback_if_chosen']['format'] = 'filtered_html';
-    $settings['alternatives'][1]['feedback_if_not_chosen']['value'] = 'You did not choose B';
-    $settings['alternatives'][1]['feedback_if_not_chosen']['format'] = 'filtered_html';
-    $settings['alternatives'][1]['score_if_chosen'] = 0;
-    $settings['alternatives'][1]['score_if_not_chosen'] = 0;
-
-    $settings['alternatives'][2]['answer']['value'] = 'C';
-    $settings['alternatives'][2]['answer']['format'] = 'filtered_html';
-    $settings['alternatives'][2]['feedback_if_chosen']['value'] = 'You chose C';
-    $settings['alternatives'][2]['feedback_if_chosen']['format'] = 'filtered_html';
-    $settings['alternatives'][2]['feedback_if_not_chosen']['value'] = 'You did not choose C';
-    $settings['alternatives'][2]['feedback_if_not_chosen']['format'] = 'filtered_html';
-    $settings['alternatives'][2]['score_if_chosen'] = 0;
-    $settings['alternatives'][2]['score_if_not_chosen'] = 0;
-
-    $node = $this->drupalCreateNode($settings);
-    $node = node_load($node->nid);
-
-    $this->assertNotNull($node->alternatives[0]['id'], 'Alternative A was created as part of question save.');
-    $this->assertNotNull($node->alternatives[1]['id'], 'Alternative B was created as part of question save.');
-    $this->assertNotNull($node->alternatives[2]['id'], 'Alternative C was created as part of question save.');
-
-    $this->assertEqual($node->alternatives[0]['answer']['value'], 'A', 'Alternative text A was saved and loaded correctly.');
-    $this->assertEqual($node->alternatives[1]['answer']['value'], 'B', 'Alternative text B was saved and loaded correctly.');
-    $this->assertEqual($node->alternatives[2]['answer']['value'], 'C', 'Alternative text C was saved and loaded correctly.');
-
-    $this->assertEqual($node->alternatives[0]['answer']['format'], 'filtered_html', 'Alternative format A was saved and loaded correctly.');
-    $this->assertEqual($node->alternatives[1]['answer']['format'], 'filtered_html', 'Alternative format B was saved and loaded correctly.');
-    $this->assertEqual($node->alternatives[1]['answer']['format'], 'filtered_html', 'Alternative format C was saved and loaded correctly.');
-
-    return $node;
-  }
-
-  public function testQuestionFeedback() {
-    $this->drupalLogin($this->admin);
-
-    $question_node = $this->testCreateQuizQuestion();
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    // Login as non-admin.
-    $this->drupalLogin($this->user);
-
-    // Test incorrect question. Feedback, answer.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid][user_answer]" => $question_node->alternatives[1]['id'],
-      ), t('Finish'));
-    $this->assertPattern('/quiz-score-icon selected/', 'Found selected answer.');
-    $this->assertPattern('/quiz-score-icon should/', 'Found should answer.');
-    $this->assertPattern('/quiz-score-icon incorrect/', 'Found incorrect answer.');
-    $this->assertText('You did not choose A');
-    $this->assertText('You chose B');
-    $this->assertText('You did not choose C');
-  }
-
-  /**
-   * Test multiple answers.
-   */
-  function testMultipleAnswers() {
-    $this->drupalLogin($this->admin);
-    $question_node = $this->testCreateQuizQuestion(array('choice_multi' => 1));
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid][user_answer][1]" => 1,
-      "question[$question_node->nid][user_answer][3]" => 3,
-      ), t('Finish'));
-    // 0 of 1, because user picked a correct answer and an incorrect answer.
-    $this->assertText('You got 0 of 1 possible points.');
-    $this->assertText('Your score: 0%');
-
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid][user_answer][1]" => 1,
-      ), t('Finish'));
-    // 1 of 1, because user picked a correct answer and not an incorrect answer.
-    $this->assertText('You got 1 of 1 possible points.');
-    $this->assertText('Your score: 100%');
-  }
-
-  /**
-   * Test random order of choices.
-   * @todo I don't know how we would test random questions.
-   */
-  function testRandomOrder() {
-    $this->drupalLogin($this->admin);
-    $question_node = $this->testCreateQuizQuestion(array('choice_random' => 1));
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    $this->drupalLogin($this->user);
-
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid][user_answer]" => $question_node->alternatives[0]['id'],
-      ), t('Finish'));
-  }
-
-  /**
-   * Test simple scoring.
-   */
-  function testSimpleScoring() {
-    $this->drupalLogin($this->admin);
-    $settings = array();
-    $settings['alternatives'][1]['score_if_chosen'] = 0;
-    $settings['alternatives'][1]['score_if_not_chosen'] = 0;
-    $settings['choice_multi'] = 1;
-    $settings['choice_boolean'] = 1;
-
-    $question_node = $this->testCreateQuizQuestion($settings);
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    $this->drupalLogin($this->user);
-
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid][user_answer][1]" => 1,
-      "question[$question_node->nid][user_answer][3]" => 3,
-      ), t('Finish'));
-    $this->assertText('You got 0 of 1 possible points.');
-    $this->assertText('Your score: 0%');
-
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid][user_answer][1]" => 1,
-      ), t('Finish'));
-    $this->assertText('You got 1 of 1 possible points.');
-    $this->assertText('Your score: 100%');
-  }
-
-}
diff --git a/question_types/multichoice/multichoice.views.inc b/question_types/multichoice/multichoice.views.inc
deleted file mode 100644
index 23cf1de..0000000
--- a/question_types/multichoice/multichoice.views.inc
+++ /dev/null
@@ -1,244 +0,0 @@
-<?php
-
-/**
- * Implementation of hook_views_data().
- */
-function multichoice_views_data() {
-  $data = array();
-
-  $data['quiz_multichoice_answers'] = array(
-    // Table Definition
-    'table' => array(
-      'group' => 'Quiz Multichoice Answers',
-      'join' => array(
-        'quiz_node_properties' => array(
-          'left_table' => 'quiz_node_relationship',
-          'left_field' => 'child_nid',
-          'field' => 'question_nid',
-        ),
-      ),
-    ),
-    // Field descriptions:
-    'id' => array(
-      'title' => t('Answer ID'),
-      'help' => t('The ID identifying an individual answer.'),
-      'field' => array(
-        'handler' => 'views_handler_field_numeric',
-        'click sortable' => TRUE,
-      ),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'result_id',
-      ),
-      'filter' => array(
-        'handler' => 'views_handler_filter_numeric',
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-    ),
-    'question_nid' => array(
-      'title' => t('Answers Question Node NID'),
-      'help' => t('The ID of the Question Node.'),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        //'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'nid',
-      ),
-      'filter' => array(
-        'handler' => 'quiz_views_handler_filter_numeric',
-        'help' => t('Filter results to a particular subset of question versions.'),
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-      'relationship' => array(
-        'handler' => 'views_handler_relationship',
-        'base' => 'node',
-        'field' => 'nid',
-        'label' => t('node'),
-      ),
-    ),
-    'question_vid' => array(
-      'title' => t('Answers Question Node VID'),
-      'help' => t('The Version ID of the Question Node.'),
-      'field' => array(
-        'handler' => 'views_handler_field',
-        'click sortable' => TRUE,
-      ),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        //'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'vid',
-      ),
-      // Related to NODE
-      'relationship' => array(
-        'handler' => 'views_handler_relationship',
-        'base' => 'node_revision',
-        'field' => 'vid',
-        'label' => t('node revision'),
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-    ),
-    'answer' => array(
-      'title' => t('Answer'),
-      'help' => t('Answer text'),
-      'field' => array(
-        'handler' => 'views_handler_field',
-        'click sortable' => TRUE,
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-    ),
-  ); // End quiz_multichoice_answers
-
-
-  $data['quiz_multichoice_user_answers'] = array(
-    // Table Definition
-    'table' => array(
-      'group' => 'Quiz Multichoice Answers',
-      'join' => array(
-        'quiz_node_properties' => array(
-          'left_table' => 'quiz_node_relationship',
-          'left_field' => 'child_nid',
-          'field' => 'question_nid',
-        ),
-      ),
-    ),
-    // Field descriptions:
-    'id' => array(
-      'title' => t('User answer set ID'),
-      'help' => t('The ID identifying an answer set for a user.'),
-      'field' => array(
-        'handler' => 'views_handler_field_numeric',
-        'click sortable' => TRUE,
-      ),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'result_id',
-      ),
-      'filter' => array(
-        'handler' => 'views_handler_filter_numeric',
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-    ),
-    'result_id' => array(
-      'title' => t('Result ID'),
-      'help' => t('The ID identifying a particular set of quiz results. This ID is unique with respect to the quiz nid and vid of the quiz, uid of the quizee, and the take of the quiz.'),
-      'field' => array(
-        'handler' => 'views_handler_field_numeric',
-        'click sortable' => TRUE,
-      ),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'result_id',
-      ),
-      'filter' => array(
-        'handler' => 'views_handler_filter_numeric',
-        'help' => t('Filter results to a particular result set'),
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-      'relationship' => array(
-        'handler' => 'views_handler_relationship',
-        'base' => 'quiz_node_results',
-        'field' => 'result_id',
-        'base field' => 'result_id',
-        'label' => t('Result'),
-      ),
-    ),
-    'question_nid' => array(
-      'title' => t('Result Question Node NID'),
-      'help' => t('The ID of the Question Node.'),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        //'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'nid',
-      ),
-      'filter' => array(
-        'handler' => 'quiz_views_handler_filter_numeric',
-        'help' => t('Filter results to a particular subset of question versions.'),
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-      'relationship' => array(
-        'handler' => 'views_handler_relationship',
-        'base' => 'node',
-        'field' => 'nid',
-        'label' => t('node'),
-      ),
-    ),
-    'question_vid' => array(
-      'title' => t('Result Question Node VID'),
-      'help' => t('The Version ID of the Question Node.'),
-      'field' => array(
-        'handler' => 'views_handler_field',
-        'click sortable' => TRUE,
-      ),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        //'name_field' => 'title',
-        'numeric' => TRUE,
-        'validate type' => 'vid',
-      ),
-      // Related to NODE
-      'relationship' => array(
-        'handler' => 'views_handler_relationship',
-        'base' => 'node_revision',
-        'field' => 'vid',
-        'label' => t('node revision'),
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-    ),
-  ); // End quiz_multichoice_user_answers
-
-  $data['quiz_multichoice_user_answer_multi'] = array(
-    'table' => array(
-      'group' => t('Quiz Multichoice Answers'),
-      'join' => array(
-        'quiz_node_properties' => array(
-          'left_table' => 'quiz_multichoice_user_answers',
-          'left_field' => 'id',
-          'field' => 'user_answer_id',
-        ),
-      ),
-    ),
-    // Field descriptions:
-    'user_answer_id' => array(
-      'title' => t('User answer set id'),
-      'help' => t("The ID of a user's answer set."),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        'numeric' => TRUE,
-      ),
-      'filter' => array(
-        'handler' => 'views_handler_filter_numeric',
-        'help' => t("Filter results to a particular subset of a user's answer set."),
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-    ),
-    'answer_id' => array(
-      'title' => t('Answer id'),
-      'help' => t("The ID of a user's answer."),
-      'argument' => array(
-        'handler' => 'views_handler_argument_numeric',
-        'numeric' => TRUE,
-      ),
-      'filter' => array(
-        'handler' => 'views_handler_filter_numeric',
-      ),
-      'sort' => array('handler' => 'views_handler_sort'),
-      'relationship' => array(
-        'handler' => 'views_handler_relationship',
-        'base' => 'quiz_multichoice_answers',
-        'field' => 'answer_id',
-        'base field' => 'id',
-        'label' => t('answer'),
-      ),
-    ),
-  ); // End quiz_multichoice_user_answer_multi
-
-  return $data;
-}
diff --git a/question_types/multichoice/multichoice_update_6400.inc b/question_types/multichoice/multichoice_update_6400.inc
deleted file mode 100644
index 3b1757d..0000000
--- a/question_types/multichoice/multichoice_update_6400.inc
+++ /dev/null
@@ -1,404 +0,0 @@
-<?php
-/*
- * @file
- * Update for choice Questions type module.
- *
- */
-
-/**
- * Helper function for multichoice_update_6400
- *
- * Converts the database from the old to the new multichoice module
- *
- * @param $sandbox
- *  Array to store progress in
- * @return
- *  Array with progress data
- */
-function _multichoice_update_6400(&$sandbox = NULL) {
-  $to_return = array();
-  $sandbox['current_step'] = isset($sandbox['current_step']) ? $sandbox['current_step'] : 0;
-
-  // Create datastructure for organizing the update process
-  $steps = array();
-  $steps[] = array('alter_user_answers1', 'next_step');
-  $steps[] = array('move_old_answers');
-  $steps[] = array('alter_user_answers2', 'create_properties', 'alter_answers', 'next_step');
-  $steps[] = array('check_answers');
-  $steps[] = array('create_user_settings', 'next_step');
-
-  // Do all the updates in the cureent step
-  foreach ($steps[$sandbox['current_step']] as $value) {
-    $function = '_multichoice_' . $value;
-    $function($to_return, $sandbox);
-  }
-
-  // Update progress so that the batch_api can update the status bar, and knows when the update is finished
-  $to_return['#finished'] = $sandbox['current_step'] / count($steps);
-
-  return $to_return;
-}
-
-/**
- * Alter and create tables to store user answers
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_alter_user_answers1(&$to_return, &$sandbox = NULL) {
-  db_drop_primary_key('quiz_multichoice_user_answers');
-  db_add_field('quiz_multichoice_user_answers', 'id', array(
-    'type' => 'serial',
-    'unsignet' => TRUE,
-    'not_null' => TRUE,
-  ), array('primary key' => array('id')));
-  db_add_field('quiz_multichoice_user_answers', 'choice_order', array('type' => 'text'));
-  db_create_table('quiz_multichoice_user_answer_multi', array(
-    'fields' => array(
-      'user_answer_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'user_answer_id', 'answer_id'
-    ),
-  ));
-}
-
-/**
- * Move/convert user answer data from the old data model to the new
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_move_old_answers(&$to_return, &$sandbox = NULL) {
-  // Store persistent data in the sandbox variable
-  if (!isset($sandbox['step_progress'])) {
-    $sandbox['step_progress'] = 0;
-    $sandbox['last_rid'] = -1;
-    $sandbox['last_nid'] = -1;
-    $sandbox['last_vid'] = -1;
-    $sandbox['last_id'] = -1;
-    $sandbox['step_max'] = db_query('SELECT COUNT(DISTINCT id) FROM {quiz_multichoice_user_answers}')->fetchField();
-  }
-  // Fetch old user ans
-  $sql = 'SELECT id, answer_id, question_nid, question_vid, result_id
-          FROM {quiz_multichoice_user_answers}
-          WHERE result_id >= %d
-          ORDER BY result_id, question_nid, question_vid, answer_id';
-  $res = db_query_range('SELECT id, answer_id, question_nid, question_vid, result_id
-          FROM {quiz_multichoice_user_answers}
-          WHERE result_id >= :result_id
-          ORDER BY result_id, question_nid, question_vid, answer_id', array(':result_id' => $sandbox['last_rid']));
-  $progress_to_add = 0;
-  while ($res_o = db_fetch_object($res)) {
-    if ($res_o->question_nid == $sandbox['last_nid']
-     && $res_o->question_vid == $sandbox['last_vid']
-     && $res_o->result_id    == $sandbox['last_rid']) {
-      // We only keep one row for each question result in quiz_multichoice_user_answers
-      if ($res_o->id > $sandbox['last_id']) {
-        $sql2 = 'DELETE FROM {quiz_multichoice_user_answers}
-                 WHERE id = %d';
-        // TODO Please review the conversion of this statement to the D7 database API syntax.
-        /* db_query($sql2, $res_o->id) */
-        db_delete('quiz_multichoice_user_answers')
-  ->condition('id', $res_o->id)
-  ->execute();
-        if ($progress_to_add + $sandbox['step_progress'] + 1 >= $sandbox['step_max']) {
-          $sandbox['step_progress'] = $sandbox['step_max'];
-        }
-      }
-    }
-    else {
-      // This is the first row for this question result
-      $sandbox['last_nid'] = $res_o->question_nid;
-      $sandbox['last_vid'] = $res_o->question_vid;
-      $sandbox['last_rid'] = $res_o->result_id;
-      $sandbox['last_id'] = $res_o->id;
-      $sandbox['step_progress'] += $progress_to_add;
-      $progress_to_add = 0;
-    }
-    // We insert ther answers in quiz_multichoice_user_answer_multi
-    $sql = 'INSERT IGNORE INTO {quiz_multichoice_user_answer_multi}
-            (user_answer_id, answer_id)
-            VALUES(%d, %d)';
-    // TODO Please convert this statement to the D7 database API syntax.
-    /* db_query($sql, $sandbox['last_id'], $res_o->answer_id) */
-    NULL;
-    $progress_to_add++;
-  }
-  // We move on if all user answers have been moved
-  if ($sandbox['step_progress'] >= $sandbox['step_max']) {
-    _multichoice_next_step($to_return, $sandbox);
-  }
-}
-
-/**
- * We delete fields not needed after the user_answer data was moved from the old data model
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_alter_user_answers2(&$to_return, &$sandbox = NULL) {
-  db_drop_field('quiz_multichoice_user_answers', 'answer_id');
-}
-
-/**
- * We create a new table to store multichoice properties in
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_create_properties(&$to_return, &$sandbox = NULL) {
-  db_create_table('quiz_multichoice_properties', array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'choice_multi' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'choice_random' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'choice_boolean' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'nid', 'vid'
-    ),
-  ));
-}
-
-/**
- * We alter the quiz_multichoice_answers table to fit the new data model
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_alter_answers(&$to_return, &$sandbox = NULL) {
-  //db_drop_primary_key($to_return, 'quiz_multichoice_answers');
-  db_change_field('quiz_multichoice_answers', 'answer_id', 'id', array(
-    'type' => 'serial',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-  ));
-  db_change_field('quiz_multichoice_answers', 'nid', 'question_nid', array(
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-  ));
-  db_change_field('quiz_multichoice_answers', 'vid', 'question_vid', array(
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-  ));
-  db_change_field('quiz_multichoice_answers', 'feedback', 'feedback_if_chosen', array(
-    'type' => 'text',
-  ));
-  db_add_field('quiz_multichoice_answers', 'feedback_if_chosen_format', array(
-    'type' => 'int',
-  ));
-  db_add_field('quiz_multichoice_answers', 'feedback_if_not_chosen', array(
-    'type' => 'text',
-  ));
-  db_add_field('quiz_multichoice_answers', 'feedback_if_not_chosen_format', array(
-    'type' => 'int',
-  ));
-  db_add_field('quiz_multichoice_answers', 'answer_format', array(
-    'type' => 'int',
-  ));
-  db_change_field('quiz_multichoice_answers', 'is_correct', 'score_if_chosen', array(
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not_null' => TRUE,
-    'default' => 0,
-  ));
-  db_add_field('quiz_multichoice_answers', 'score_if_not_chosen', array(
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not_null' => TRUE,
-    'default' => 0,
-  ));
-  db_drop_field('quiz_multichoice_answers', 'result_option');
-}
-
-/**
- * We go through the alternatives and update question properties and field formats
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_check_answers(&$to_return, &$sandbox) {
-  // Init persistent variables
-  if (!isset($sandbox['multichoice_check_answers'])) {
-    $sandbox['step_progress'] = 0;
-    $sandbox['multichoice_check_answers'] = TRUE;
-    $sandbox['last_nid'] = 0;
-    $sandbox['last_vid'] = 0;
-    $sandbox['step_max'] = db_query('SELECT COUNT(DISTINCT id) FROM {quiz_multichoice_answers}')->fetchField();
-  }
-  $sandbox['num_corrects'] = 0;
-  $sql = 'SELECT id, question_nid, question_vid, score_if_chosen, score_if_not_chosen
-          FROM {quiz_multichoice_answers}
-          WHERE question_nid >= %d AND question_vid >= %d
-          ORDER BY question_nid, question_vid';
-  $res = db_query_range('SELECT id, question_nid, question_vid, score_if_chosen, score_if_not_chosen
-          FROM {quiz_multichoice_answers}
-          WHERE question_nid >= :question_nid AND question_vid >= :question_vid
-          ORDER BY question_nid, question_vid', array(':question_nid' => $sandbox['last_nid'], ':question_vid' => $sandbox['last_vid']));
-  $progress_to_add = 0;
-  while ($res_o = db_fetch_object($res)) {
-    if ($res_o->question_nid == $sandbox['last_nid']
-     && $res_o->question_vid == $sandbox['last_vid']) {
-      // This is the same node as we processed in the last loop
-      if ($progress_to_add + $sandbox['step_progress'] + 1 >= $sandbox['step_max']) {
-        $sandbox['step_progress'] = $sandbox['step_max'];
-      }
-    }
-    else {
-      // New node
-      $sandbox['step_progress'] += $progress_to_add;
-      $progress_to_add = 0;
-      $sandbox['last_nid'] = $res_o->question_nid;
-      $sandbox['last_vid'] = $res_o->question_vid;
-      $sandbox['num_corrects'] = 0;
-
-      // Store the format for this node
-      $sql = 'SELECT format
-              FROM {node_revision}
-              WHERE vid = %d';
-      $res2 = db_query('SELECT format
-              FROM {node_revision}
-              WHERE vid = :vid', array(':vid' => $res_o->question_vid));
-      $sandbox['last_format'] = $res2->fetchField();
-      $sql = 'INSERT INTO {quiz_multichoice_properties}
-              (nid, vid, choice_boolean)
-              VALUES(%d, %d, 1)';
-      // TODO Please convert this statement to the D7 database API syntax.
-      /* db_query($sql, $res_o->question_nid, $res_o->question_vid) */
-      NULL;
-    }
-
-    if ($res_o->score_if_chosen == 1) {
-      $sandbox['num_corrects']++;
-      if ($sandbox['num_corrects'] == 2) {
-        // If more than one answer is correct we set the choice_multi property to one.
-        $sql = 'UPDATE {quiz_multichoice_properties}
-                SET choice_multi = 1
-                WHERE nid = %d AND vid = %d';
-        // TODO Please review the conversion of this statement to the D7 database API syntax.
-        /* db_query($sql, $res_o->question_nid, $res_o->question_vid) */
-        db_update('quiz_multichoice_properties')
-  ->fields(array(
-    'choice_multi' =>  1,
-  ))
-  ->condition('nid', $res_o->question_nid)
-  ->condition('vid', $res_o->question_vid)
-  ->execute();
-      }
-    }
-    // Update all the formats
-    $sql = 'UPDATE {quiz_multichoice_answers}
-            SET answer_format = %d, feedback_if_chosen_format = %d, feedback_if_not_chosen_format = %d
-            WHERE question_vid = %d'; // This table haven't been indexed yet, making this query very slow... :/
-    $lf = $sandbox['last_format'];
-    // TODO Please review the conversion of this statement to the D7 database API syntax.
-    /* db_query($sql, $lf, $lf, $lf, $res_o->question_vid) */
-    db_update('quiz_multichoice_answers')
-  ->fields(array(
-    'answer_format' => $lf,
-    'feedback_if_chosen_format' => $lf,
-    'feedback_if_not_chosen_format' => $lf,
-  ))
-  ->condition('question_vid', $res_o->question_vid)
-  ->execute();
-    $progress_to_add++;
-  }
-  $sandbox['step_progress'] += $progress_to_add;
-  // Check if we are finished, and jump to the next step if we are
-  if ($sandbox['step_progress'] >= $sandbox['step_max']) {
-    _multichoice_next_step($to_return, $sandbox);
-  }
-}
-
-/**
- * We create the user settings table
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_create_user_settings(&$to_return, &$sandbox = NULL) {
-  db_create_table('quiz_multichoice_user_settings', array(
-    'fields' => array(
-      'uid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'last_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'last_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'uid'
-    ),
-  ));
-}
-
-/**
- * Helper function to switch steps in the update process
- *
- * @param $to_return
- *  Array where progress can be reported
- * @param $sandbox
- *  Array where persistent data can be stored, and progress can be found
- */
-function _multichoice_next_step(&$to_return, &$sandbox) {
-  $sandbox['current_step']++;
-}
diff --git a/question_types/multichoice/theme/multichoice-alternative-creation.js b/question_types/multichoice/theme/multichoice-alternative-creation.js
deleted file mode 100644
index ce1b9c9..0000000
--- a/question_types/multichoice/theme/multichoice-alternative-creation.js
+++ /dev/null
@@ -1,34 +0,0 @@
-(function($) {
-/**
- * @file
- * JS enabling one filter fieldset to controll filter formats for all textareas in alternatives.
- */
-Drupal.behaviors.multichoiceBehavior = {
-  attach: function (context, settings) {
-    // When the top input filter selector is clicked change the rest of the selectors to the same value
-    $('.quiz-filter:first :radio').click(function(){
-      var myValue = $(this).val();
-      $('.quiz-filter:not(:first) :radio[value='+myValue+']').click();
-      $('.quiz-filter:not(:first) :radio[value='+myValue+']').change();
-    });
-
-    // Change all format selectors to have the same value as the first
-    var defaultValue = $('.quiz-filter:first :radio[checked=1]').val();
-    $('.quiz-filter:not(:first):not(.multichoiceBehavior-processed) :radio[value='+defaultValue+']').click().change().addClass('multichoiceBehavior-processed');
-
-    // Hide all format selectors except the first
-    $('.quiz-filter:not(:first)').hide().addClass('multichoiceStayHidden');
-
-    // Move the first input selector to the input-all-ph helper tag
-    $('.quiz-filter:first').insertAfter('#input-all-ph');
-
-    // Make sure the format selectors stay hidden when a fieldset is unfolded
-    var oldToggle = Drupal.toggleFieldset;
-    Drupal.toggleFieldset = function(context) {
-      oldToggle(context);
-      $('.multichoiceStayHidden').hide();
-    };
-  }
-};
-
-})(jQuery);
diff --git a/question_types/multichoice/theme/multichoice-alternative-creation.tpl.php b/question_types/multichoice/theme/multichoice-alternative-creation.tpl.php
deleted file mode 100644
index 6ed64bc..0000000
--- a/question_types/multichoice/theme/multichoice-alternative-creation.tpl.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the choice creation form.
- *
- *
- * Variables available:
- * - $form
- */
-
-?>
-<?php
-$p = drupal_get_path('module', 'multichoice');
-drupal_add_js($p .'/theme/multichoice-alternative-creation.js', 'module');
-
-// Get the title from the checkbox, and then unset it. We will place it as a table header
-$title_correct = check_plain($form['correct']['#title']);
-unset($form['correct']['#title']);
-
-// We have to add the required symbol manually
-$suf = $form['answer']['#required'] ? '<SPAN CLASS="form-required"> *</SPAN>' : '';
-
-// We store the title for the answer section as well
-$title_answer = check_plain($form['answer']['#title']).$suf;
-$form['answer']['#title'] = '';
-
-// Now we can render the table
-$row[] = drupal_render($form['correct']);
-$row[] = drupal_render($form['answer']);
-$rows[] = $row;
-$header[] = array('data' => $title_correct);
-$header[] = array('data' => $title_answer);
-print theme('table', array('header' => $header, 'rows' => $rows));
-
-//These lines make things look alot beter if user only has one input format available:
-_quiz_format_mod($form['format']);
-_quiz_format_mod($form['advanced']['format']);
-_quiz_format_mod($form['advanced']['helper']['format']);
-
-print drupal_render($form['format']);
-print drupal_render($form['advanced']);
-?>
diff --git a/question_types/multichoice/theme/multichoice-alternative.tpl.php b/question_types/multichoice/theme/multichoice-alternative.tpl.php
deleted file mode 100644
index de3f920..0000000
--- a/question_types/multichoice/theme/multichoice-alternative.tpl.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the multichoice answering form
- *
- *
- * Variables available:
- * - $form
- */
-
-?>
-<?php
-$p = drupal_get_path('module', 'multichoice');
-
-// Add script for using the entire alternative row as a button
-drupal_add_js(
-"( function($) {
-  Drupal.behaviors.multichoiceAlternativeBehavior = {
-    attach: function(context, settings) {
-      $('.multichoice_row')
-      .once()
-      .filter(':has(:checkbox:checked)')
-      .addClass('selected')
-      .end()
-      .click(function(event) {
-        $(this).toggleClass('selected');
-        if (event.target.type !== 'checkbox') {
-          $(':checkbox', this).attr('checked', function() {
-            return !this.checked;
-          });
-          $(':radio', this).attr('checked', true);
-          if ($(':radio', this).html() != null) {
-            $('.multichoice_row').removeClass('selected');
-              $(this).addClass('selected');
-          }
-        }
-      });
-    }
-  };
-})(jQuery);", 'inline');
-
-// We want to have the checkbox in one table cell, and the title in the next. We store the checkbox and the titles
-$options = $form['user_answer']['#options'];
-$fullOptions = array();
-$titles = array();
-foreach ($options as $key => $value) {
-  $fullOptions[$key] = $form['user_answer'][$key];
-  $titles[$key] = $form['user_answer'][$key]['#title'];
-  $fullOptions[$key]['#title'] = '';
-  unset($form['user_answer'][$key]);
-}
-unset($form['user_answer']['#options']);
-print drupal_render_children($form);
-
-// We use the stored checkboxes and titles to generate a table for the alternatives
-foreach ($titles as $key => $value) {
-  $row = array();
-  $row[] = array('data' => drupal_render($fullOptions[$key]), 'width' => 35, 'class' => 'selector-td');
-  $row[] = $value;
-  $rows[] = array('data' => $row, 'class' => array('multichoice_row'));
-}
-print theme('table', array('header' => array(), 'rows' => $rows));
-?>
diff --git a/question_types/multichoice/theme/multichoice-answering-form.tpl.php b/question_types/multichoice/theme/multichoice-answering-form.tpl.php
deleted file mode 100644
index 6f45513..0000000
--- a/question_types/multichoice/theme/multichoice-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the multichoice answering form.
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render($form);
-
-?>
\ No newline at end of file
diff --git a/question_types/multichoice/theme/multichoice.theme.inc b/question_types/multichoice/theme/multichoice.theme.inc
deleted file mode 100644
index 96db52a..0000000
--- a/question_types/multichoice/theme/multichoice.theme.inc
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-/**
- * The theme file for multichoice.
- *
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * @file
- * Theming functions for the multichoice question type.
- */
-
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function theme_multichoice_creation_form($variables) {
-  $form = $variables['form'];
-  $path = drupal_get_path('module', 'multichoice') . '/multichoice.js';
-  drupal_add_js($path);
-
-  // We open the settings fieldset if there is errors involving the choice_multi setting
-  $errors = form_get_errors();
-  if (isset($errors['choice_multi'])) {
-    $form['settings']['#collapsed'] = FALSE;
-  }
-
-  // We open the alternative fieldsets if errors have been reported
-  if ($errors) {
-    for ($i = 0; isset($form[$i]) && is_array($form[$i]); $i++) {
-      if (drupal_strlen(strip_tags($_POST['alternatives'][$i]['answer']['value'])) > 0) {
-        $form[$i]['#collapsed'] = FALSE;
-      }
-    }
-  }
-  return drupal_render_children($form);
-}
-
-/**
- * Theme the answer part of the node view
- *
- * @param $alternatives
- *  Array of alternatives. Each alternative is also an array with all the
- *  data for each alternative.
- * @param $show_correct
- *  True if the user is allowed to view the solution
- */
-function theme_multichoice_answer_node_view($variables) {
-  $alternatives = $variables['alternatives'];
-  $show_correct = $variables['show_correct'];
-  $header = array('', '');
-
-  $p = drupal_get_path('module', 'multichoice');
-  drupal_add_css($p .'/theme/multichoice.css');
-
-  foreach ($alternatives as $i => $short) {
-    $answer_markup = check_markup($short['answer']['value'], $short['answer']['format']);
-    // Find the is_correct status
-    $is_correct = ($short['score_if_chosen'] > $short['score_if_not_chosen']);
-    $image = $is_correct ? 'correct' : 'wrong';
-    if (!$show_correct) {
-      $image = 'unknown';
-    }
-
-    $rows[] = array(
-        array(
-          'data' => array(
-            '#theme' => 'html_tag',
-            '#tag' => 'span',
-            '#attributes' => array(
-              'class' => array(
-                'multichoice-icon',
-                $image,
-              ),
-              'title' => $show_correct ?
-                t('Score if chosen: @sc Score if not chosen: @nc', array(
-                  '@sc' => $short['score_if_chosen'],
-                  '@nc' => $short['score_if_not_chosen'])
-                ) :
-                t('You are not allowed to view the solution for this question'),
-            ),
-          ),
-          'class' => array('multichoice-icon-cell'),
-        ),
-        $answer_markup
-      );
-  }
-  return theme('table', array('header' => $header, 'rows' => $rows));
-}
-
-/**
- * Theme function for the multichoice report
- *
- * @param $data
- *  Array of data to be presented in the report
- */
-function theme_multichoice_response($variables) {
-  static $css_added;
-  if (!$css_added) {
-    drupal_add_css(drupal_get_path('module', 'multichoice') . '/theme/multichoice.css');
-    $css_added = TRUE;
-  }
-
-  $rows = array();
-  foreach ($variables['data'] as &$alternative) {
-    if ($alternative['is_chosen']) {
-      switch ($alternative['is_correct']) {
-        case 0:
-          $icon = '<span class="multichoice-icon wrong" title="' . t('This option is wrong.') . '"></span>';
-          break;
-        case 1:
-          $icon = '<span class="multichoice-icon almost" title="' . t('This option is correct, but there is another that gives a higher score.') . '"></span>';
-          break;
-        case 2:
-          $icon = '<span class="multichoice-icon correct" title="' . t('This option is correct.') . '"></span>';
-          break;
-        case 3:
-          // The answer is unknown.
-          $icon = '<span class="multichoice-icon unknown" title="' . t('This option is unknown.') . '"></span>';
-          break;
-      }
-    }
-    else {
-      $icon = $alternative['is_correct'] == 2 ? '<span class="multichoice-icon should" title="' . t("This option is correct, but you didn't select it.") . '"></span>' : '';
-    }
-    $rowspan = $alternative['feedback'] ? 2 : 1;
-    $rows[] = array(
-      array(
-        'data' => $icon,
-        'rowspan' => $rowspan,
-        'class' => 'selector-td multichoice-icon-cell',
-      ),
-      $alternative['answer']
-    );
-    if ($rowspan == 2) {
-      $rows[] = array('<div class="multichoice-label">' . t('Feedback') . ':</div><div class="multichoice-feedback">' . $alternative['feedback'] . '</div>');
-    }
-  }
-  return theme('table', array('header' => NULL, 'rows' => $rows));
-}
diff --git a/question_types/quiz_ddlines/js/colorwheel.js b/question_types/quiz_ddlines/js/colorwheel.js
deleted file mode 100644
index 6562f53..0000000
--- a/question_types/quiz_ddlines/js/colorwheel.js
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
-* Colorwheel
-* Copyright (c) 2010 John Weir (http://famedriver.com)
-* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
-*
-* requires jQuery & Raphael
-*   http://jquery.com http://raphaeljs.com
-*
-* see http://jweir.github.com/colorwheel for Usage
-*
-*/
-(function ($) {
-Raphael.colorwheel = function(target, color_wheel_size, no_segments){
-  var canvas,
-      current_color,
-      size,
-      segments = no_segments || 60,
-      bs_square = {},
-      hue_ring = {},
-      tri_size,
-      cursor = {},
-      drag_target,
-      input_target,
-      center,
-      parent,
-      change_callback,
-      drag_callbacks = [function(){}, function(){}],
-      offset,
-      padding = 2,
-      sdim; // holds the dimensions for the saturation square
-
-  function point(x, y){ return {x:x, y:y};}
-  function radians(a){ return a * (Math.PI/180);}
-
-  function angle(x,y){
-    var q = x > 0 ? 0 : 180;
-    return q+Math.atan((0 - y)/(0 - x))*180/(Math.PI);
-  }
-
-  function create(target, color_wheel_size){
-    size     = color_wheel_size;
-    tri_size = size/20;
-    center   = size/2;
-    parent   = $(target);
-    canvas   = Raphael(parent[0],size, size);
-    canvas.safari();
-
-    create_bs_square();
-    create_hue_ring();
-    hue_ring.cursor = cursor_create(tri_size);
-    bs_square.cursor = cursor_create(tri_size*0.5);
-    events_setup();
-    parent.css({height:size+"px", width:size+"px"});
-    disable_select(parent);
-    return public_methods();
-  }
-
-  function disable_select(target){
-    $(target).css({"unselectable": "on","-moz-user-select": "none","-webkit-user-select": "none"});
-  }
-
-  function public_methods(){
-    return {
-      input: input,
-      onchange: onchange,
-      ondrag : ondrag,
-      color : public_set_color
-    };
-  }
-
-  // Sets a textfield for user input of hex color values
-  // TODO don't clear the change callback
-  // TODO allow a null target to unbind the input
-  function input(target){
-    change_callback = null;
-    input_target = target;
-    $(target).keyup(function(){
-      if(this.value.match(/^#([0-9A-F]){3}$|^#([0-9A-F]){6}$/img)){
-        set_color(this.value);
-        update_color(true);
-    run_onchange_event();
-      }
-    });
-    set_color(target.value);
-    update_color(true);
-
-    return public_methods();
-  }
-
-  function onchange(callback){
-    change_callback = callback;
-    update_color(false);
-    return public_methods();
-  }
-
-  function ondrag(start_callback, end_callback){
-    drag_callbacks = [start_callback || function(){}, end_callback || function(){}];
-    return public_methods();
-  }
-
-  function drag(e){
-    var x = e.pageX - (parent.offset().left + center),
-        y = e.pageY - (parent.offset().top + center);
-
-    if(drag_target == hue_ring){
-      set_hue_cursor(x,y);
-      update_color();
-    run_onchange_event();
-      return true;
-    }
-    if(drag_target == bs_square){
-      set_bs_cursor(x,y);
-      update_color();
-    run_onchange_event();
-      return true;
-    }
-  }
-
-  function start_drag(event, target){
-    $(document).mouseup(stop_drag);
-    $(document).mousemove(drag);
-    drag_target = target;
-    drag(event);
-    drag_callbacks[0](current_color);
-  }
-
-  function stop_drag(){
-    $(document).unbind("mouseup",stop_drag);
-    $(document).unbind("mousemove",drag);
-    drag_callbacks[1](current_color);
-  run_onchange_event();
-  }
-
-  function event_drag_stop(event,o){
-    o.mousemove = null;
-    drag_target=null;
-  }
-
-  function events_setup(){
-    $([hue_ring.event.node,hue_ring.cursor[0].node]).mousedown(function(e){start_drag(e,hue_ring);});
-    $([bs_square.b.node, bs_square.cursor[0].node]).mousedown(function(e){start_drag(e,bs_square);});
-  }
-
-  function cursor_create(size){
-    var set = canvas.set().push(
-        canvas.circle(0, 0, size).attr({"stroke-width":4, stroke:"#333"}),
-        canvas.circle(0, 0, size+2).attr({"stroke-width":1, stroke:"#FFF", opacity:0.5})
-    );
-
-    set[0].node.style.cursor = "crosshair";
-
-    return set;
-  }
-
-  function set_bs_cursor(x,y){
-    x = x+center;
-    y = y+center;
-    if(x < sdim.x) x = sdim.x;
-    if(x > sdim.x+sdim.l) x = sdim.x+sdim.l;
-    if(y < sdim.y) y = sdim.y;
-    if(y > sdim.y+sdim.l) y = sdim.y + sdim.l;
-
-    bs_square.cursor.attr({cx:x, cy:y}).transform("t0,0");
-  }
-
-
-  function set_hue(color){
-    var hex = Raphael.getRGB(color).hex;
-    bs_square.h.attr("fill", hex);
-  }
-
-  function hue(){
-    return Raphael.rgb2hsb(bs_square.h.attr("fill")).h;
-  }
-
-  function public_set_color(value){
-    var ret = set_color(value);
-    update_color(false);
-    return ret;
-  }
-
-  function set_color(value){
-    if(value === undefined){ return current_color; }
-
-    var temp = canvas.rect(1,1,1,1).attr({fill:value}),
-        hsb = canvas.raphael.rgb2hsb(temp.attr("fill"));
-
-    set_bs_cursor(
-      (0-sdim.l/2) + (sdim.l*hsb.s),
-      sdim.l/2 - (sdim.l*hsb.b));
-    set_hue_cursor((360*(hsb.h))-90);
-    temp.remove();
-    return public_methods();
-  }
-
-  // Could optimize this method
-  function update_color(dont_replace_input_value){
-    var x = bs_square.cursor.items[0].attr("cx"),
-        y = bs_square.cursor.items[0].attr("cy"),
-        hsb = {
-          b: 1-(y-sdim.y)/sdim.l,
-          s: (x-sdim.x)/sdim.l,
-          h: hue()
-        };
-
-    current_color = Raphael.hsb2rgb(hsb.h, hsb.s,hsb.b);
-
-    if(input_target){
-      var c = current_color.hex;
-      if(dont_replace_input_value != true) { input_target.value = c;}
-       if(hsb.b < 0.5){
-        $(input_target).css("color", "#FFF");
-      } else {
-        $(input_target).css("color", "#000");
-      }
-      input_target.style.background = c;
-    }
-
-  }
-
-  // accepts either x,y or d (degrees)
-  function set_hue_cursor(mixed_args){
-    var d;
-    if(arguments.length == 2){
-      d = angle(arguments[0],arguments[1]);
-    } else {
-      d = arguments[0];
-    }
-
-    var x = Math.cos(radians(d)) * (center-tri_size-padding);
-    var y = Math.sin(radians(d)) * (center-tri_size-padding);
-    hue_ring.cursor.attr({cx:x+center, cy:y+center}).transform("t0,0");
-    set_hue("hsb("+(d+90)/360+",1,1)");
-  }
-
-  function bs_square_dim(){
-    if(sdim){ return sdim;}
-    var s = size - (tri_size * 4);
-    sdim = {
-      x:(s/6)+tri_size*2+padding,
-      y:(s/6)+tri_size*2+padding,
-      l:(s * 2/3)-padding*2
-    };
-    return sdim;
-  }
-
-  function create_bs_square(){
-    bs_square_dim();
-    box = [sdim.x, sdim.y, sdim.l, sdim.l];
-
-    bs_square.h = canvas.rect.apply(canvas, box).attr({
-      stroke:"#EEE", gradient: "0-#FFF-#000", opacity:1});
-    bs_square.s = canvas.rect.apply(canvas, box).attr({
-      stroke:null, gradient: "0-#FFF-#FFF", opacity:0});
-    bs_square.b = canvas.rect.apply(canvas, box).attr({
-      stroke:null, gradient: "90-#000-#FFF", opacity:0});
-    bs_square.b.node.style.cursor = "crosshair";
-  }
-
-  function hue_segement_shape(){
-    var path = "M -@W 0 L @W 0 L @W @H L -@W @H z";
-    return path.replace(/@H/img, tri_size*2).replace(/@W/img,tri_size);
-  }
-
-  function copy_segment(r, d, k){
-    var n = r.clone();
-    var hue = d*(255/k);
-
-    var s = size/2,
-      t = tri_size,
-      p = padding;
-
-    n.transform("t"+s+","+(s-t)+"r"+(360/k)*d+"t0,-"+(s-t-p)+"");
-
-    n.attr({"stroke-width":0, fill:"hsb("+d*(1/k)+", 1, 0.85)"});
-    hue_ring.hues.push(n);
-  }
-
-  function create_hue_ring(){
-    var s = hue_segement_shape(),
-        tri = canvas.path(s).attr({stroke:"rgba(0,0,0,0)"}).transform("t"+(size/2)+","+padding),
-        k = segments; // # of segments to use to generate the hues
-
-    hue_ring.hues = canvas.set();
-
-    for(n=0; n<k; n++){ copy_segment(tri, n, k); }
-
-    // IE needs a slight opacity to assign events
-    hue_ring.event = canvas.circle(
-      center,
-      center,
-      center-tri_size-padding).attr({"stroke-width":tri_size*2, opacity:0.01});
-
-    hue_ring.outline = canvas.circle(
-      center,
-      center,
-      center-tri_size-padding).attr({"stroke":"#000", "stroke-width":(tri_size*2)+3, opacity:0.1});
-    hue_ring.outline.toBack();
-    hue_ring.event.node.style.cursor = "crosshair";
-  }
-
-  function run_onchange_event(){
-  if (change_callback != undefined){
-      change_callback(current_color);
-    }
-  }
-
-  return create(target, color_wheel_size);
-};
-}(jQuery));
\ No newline at end of file
diff --git a/question_types/quiz_ddlines/js/quiz_ddlines.js b/question_types/quiz_ddlines/js/quiz_ddlines.js
deleted file mode 100644
index 646eb20..0000000
--- a/question_types/quiz_ddlines/js/quiz_ddlines.js
+++ /dev/null
@@ -1,1650 +0,0 @@
-/**
- * Sponsored by: Senter for IKT i utdanningen
- * Code: paalj
- *
- * @file
- * Drag and drop with lines quiz question type implemented using the Raphaël 
- * javascript library (http://raphaeljs.com/)
- * 
- */
-
-/**
- * Implementation of the connection between a hotspot and the label
- */
-Raphael.fn.connection = function (obj1, obj2, line, bg) {
-    if (obj1.line && obj1.from && obj1.to && obj1.bg) {
-        line = obj1;
-        obj1 = line.from;
-        obj2 = line.to;
-    }
-    
-    var bb1 = obj1.getBBox(),
-        bb2 = obj2.getBBox(),
-        p = [{x: bb1.x + bb1.width / 2, y: bb1.y - 1},
-        {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + 1},
-        {x: bb1.x - 1, y: bb1.y + bb1.height / 2},
-        {x: bb1.x + bb1.width + 1, y: bb1.y + bb1.height / 2},
-        {x: bb2.x + bb2.width / 2, y: bb2.y - 1},
-        {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + 1},
-        {x: bb2.x - 1, y: bb2.y + bb2.height / 2},
-        {x: bb2.x + bb2.width + 1, y: bb2.y + bb2.height / 2}],
-        d = {}, dis = [];
-    for (var i = 0; i < 4; i++) {
-        for (var j = 4; j < 8; j++) {
-            var dx = Math.abs(p[i].x - p[j].x),
-                dy = Math.abs(p[i].y - p[j].y);
-            if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
-                dis.push(dx + dy);
-                d[dis[dis.length - 1]] = [i, j];
-            }
-        }
-    }
-    if (dis.length == 0) {
-        var res = [0, 4];
-    } else {
-        res = d[Math.min.apply(Math, dis)];
-    }
-    var x1 = p[res[0]].x,
-        y1 = p[res[0]].y,
-        x4 = p[res[1]].x,
-        y4 = p[res[1]].y;
-    dx = Math.max(Math.abs(x1 - x4) / 2, 10);
-    dy = Math.max(Math.abs(y1 - y4) / 2, 10);
-    var x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
-        y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
-        x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
-        y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
-    var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
-    if (line && line.line) {
-        line.bg && line.bg.attr({path: path});
-        line.line.attr({path: path});
-    } else {
-        var color = typeof line == "string" ? line : "#000";
-        return {
-            bg: bg && bg.split && this.path(path).attr({stroke: bg.split("|")[0], fill: "none", "stroke-width": bg.split("|")[1] || 3})/*.toBack()*/,
-            line: this.path(path).attr({stroke: color, fill: "none", "stroke-width":3}),
-            from: obj1,
-            to: obj2
-        };
-    }
-};
-
-(function ($) {
-var QuizUtil = {
-    // The minimum width of the label
-    LABEL_MIN_WIDTH: 40,
-    
-    CANVAS_PADDING: 10,
-  
-    // The glow implementation should be moved into a class, 
-    // but will do for now 
-    current_glow: null,
-    
-    current_editor: null,
-    
-    drag_mode: false,
-    
-    imageChanged: false,
-
-    defined: function (a) {
-      return (typeof a !== 'undefined');
-    },
-    
-    bboxUnion: function(bbox1, bbox2) {
-      bbox1.x = bbox1.x < bbox2.x ? bbox1.x : bbox2.x; 
-      bbox1.y = bbox1.y < bbox2.y ? bbox1.y : bbox2.y;
-      bbox1.x2 = bbox1.x2 > bbox2.x2 ? bbox1.x2 : bbox2.x2;
-      bbox1.y2 = bbox1.y2 > bbox2.y2 ? bbox1.y2 : bbox2.y2;
-      
-      return bbox1;
-    }
-};
-/**
- * A list of quiz ddlines elements.
- * 
- * Responsible for: 
- * - Holding the elements in-memory 
- * - Creating/Parsing the jsondata 
- * - Making the form input field being in-sync
- */
-var QuizElementList = {
-  elements: [],
-  engine: null,
-  add: function(element) {
-    this.elements.push(element);
-    this.updateForm();
-  },
-  remove: function(element) {
-    // Remove from list:
-    for(var i=0; i<this.elements.length; i++) {
-      if(element == this.elements[i]) {
-        this.elements.splice(i,1);
-      }
-    }   
-    // Update form cache:
-    this.updateForm();
-  },
-  clear: function() {
-    this.elements = [];
-    
-    // Update form cache:
-    this.updateForm();
-  },
-  updateForm: function() {
-    if(this.engine.isEditMode()) {
-      // Set the value of the hidden form field (ddlines_elements)
-      $('input[name=ddlines_elements]').val(this.toJson());
-    }
-  },
-  load: function(engine, display_answers, id) {
-    
-    QuizElementList.engine = engine;
-    
-    var obj=null;
-    if(engine.isResultMode()) {
-      obj = display_answers ? engine.conf["answers-"+id] : engine.conf["correct-"+id];
-    }
-    else {
-      json = $('input[name=ddlines_elements]').val();
-      
-      if(!json || json.length == 0) {
-        return;
-      }
-      
-      obj = $.parseJSON(json);
-    }
-    
-    // Set canvas size:
-    engine.resizePaper(obj.canvas.width, obj.canvas.height);
-    
-    // Set image position:
-    if(!QuizUtil.imageChanged) {
-      engine.image.setBounds(obj.image.left, obj.image.top, obj.image.width, obj.image.height);
-    }
-    
-    // Add elements:
-    if(QuizUtil.defined(obj.elements)) {
-      for (var i = 0; i < obj.elements.length; i++) {
-        this.add(QuizElement.fromJson(obj.elements[i], engine));
-      }
-    }
-    
-    // Update connecting lines between each pair if hotspot 
-    // and label: 
-    engine.redrawConnections();
-  },
-  toJson: function() {
-    // Get image location:
-    var elements='';
-    var json = '{' + this.engine.image.toJson() + ',' + this.engine.toJson();
-    for (var i = 0; i < this.elements.length; i++) {      
-      elements += this.elements[i].toJson() + (i<this.elements.length-1 ? ',' : '');
-    }
-    
-    if(this.elements.length>0) {
-      json += ',"elements":['+elements+']';
-    }
-    
-    return json+'}';
-  },
-  size: function() {
-    return this.elements.length;
-  },
-  updateHotspotRadius: function(radius) {
-    radius = parseInt(radius);
-    
-    this.engine.conf.hotspot.radius = radius;
-    
-    // Update radius for all elements:
-    for(var i=0; i<this.elements.length; i++) {
-      this.elements[i].uiHotspot.updateHotspotRadius(radius);
-      this.elements[i].redrawConnection();
-    }   
-  },
-  adjustCanvasSize: function() {
-    
-    var moveLeft = moveUp = 0;
-    var bbox = this.engine.image.getBBox();
-    var maxElementWidth = 0;
-    
-    /* Auto adjust canvas size */
-    for (var i = 0; i < this.elements.length; i++) {
-      bbox2 = this.elements[i].getBBox();
-      maxElementWidth = maxElementWidth < bbox2.width ? bbox2.width : maxElementWidth; 
-      
-      bbox = QuizUtil.bboxUnion(bbox, bbox2);
-    }
-    
-    var horizontalPadding = this.engine.executionModeLines() ? QuizUtil.CANVAS_PADDING : maxElementWidth * 0.6;    
-    horizontalPadding = horizontalPadding > QuizUtil.CANVAS_PADDING ? horizontalPadding : QuizUtil.CANVAS_PADDING; 
-    
-    moveLeft = -bbox.x + horizontalPadding;
-    moveUp = -bbox.y + QuizUtil.CANVAS_PADDING;
-    
-    /* Update all elements */
-    for (var i = 0; i < this.elements.length; i++) {
-      this.elements[i].move(moveLeft, moveUp);      
-    }
-    
-    // Update image:
-    this.engine.image.move(moveLeft, moveUp);
-    
-    // Update canvas-size
-    this.engine.setSize(bbox.x2-bbox.x+(horizontalPadding*2),bbox.y2-bbox.y+(QuizUtil.CANVAS_PADDING*2));
-    
-    this.updateForm();
-  }
-};
-
-/*
- * A user's answers
- */
-var Answers = {
-  elements: [],  
-  setAnswer: function(label_id, hotspot_id) {
-    this.elements[label_id] = hotspot_id ;
-    this.updateForm();
-  },
-  removeAnswer: function(label_id) {
-    delete this.elements[label_id];
-    this.updateForm();
-  },
-  toJson: function() {
-    var json = '';
-    var count=0;
-    for (var key in this.elements) {    
-      json += (count>0 ? ',' : '') + '{"label_id":'+key+',"hotspot_id":'+this.elements[key]+'}';
-      count++;
-    }
-    
-    if(count===0) {
-      return '';
-    }
-    else {
-      return '['+json+']';
-    }
-  },
-  updateForm: function() {
-    $('input[name=tries]').val(this.toJson());
-  }
-};
-
-Drupal.behaviors.quiz_ddlines = {
-  initialized: false,
-  attach: function(context) {
-    /* Auto adjust canvas size */
-    $('.node-quiz_ddlines-form #edit-submit:not(.canvas-adjusted)').click(function() {
-      QuizElementList.adjustCanvasSize();
-      $(this).addClass('canvas-adjusted');
-    });
-    
-    // Make hotspot radius only accept numbers:
-    $('input#edit-hotspot-radius').keypress(function(e) {
-      var key_codes = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 8];
-    
-      if (!($.inArray(e.which, key_codes) >= 0)) {
-        e.preventDefault();
-      }
-    });
-    
-    // Initializing the result page; the correct answer part
-    $('.quiz-ddlines-correct-answers', context).each(function() {
-      // Id is the node id of the question node
-      // Will be overwritten in engine.init
-      var id = $(this).attr('id');
-
-      var engine = new Engine();
-      engine.init(this, id);
-        
-      // Load elements if they exists:
-      QuizElementList.load(engine, false, id);
-    });
-    
-    // Initializing the result page; the user answer part
-    $('.quiz-ddlines-user-answers', context).each(function() {
-      // Id is the node id of the question node
-      // Will be overwritten in engine.init
-      var id = $(this).attr('id');
-      
-      var engine = new Engine();
-      engine.init(this, id);
-        
-      // Load elements if they exists:
-      QuizElementList.load(engine, true, id);
-    });
-    
-    $('.image-preview', context).each(function() {
-      if(this.initialized) {
-        return;
-      }
-      var self = this;
-      this.initialized = true;
-      
-      // Initialize
-      var engine = new Engine();
-      
-      // Need to wait for IE to make image beeing displayed
-      // This is not a great solution, but it does work.
-      setTimeout( function() {
-        engine.init(self);
-        
-        // Load elements if they exists:
-        QuizElementList.load(engine);
-        
-        // Show helptext if no alternatives have been added:
-        if(engine.isNew()) {
-          engine.addHelpText();
-        }
-      }, ($.browser.msie ? 1000 : 0));
-    });           
-  }
-};
-
-// generic functions for dragging of sets:
-function set_drag_start(x, y, event, set) {
-  set = QuizUtil.defined(set) ? set : this.set;
-  
-  QuizUtil.drag_mode = false;
-  
-  set.ox = x;
-  set.oy = y;
-}
-function set_drag_move(dx, dy, x, y, event, set) {
-  QuizUtil.drag_mode = true;
-  
-  set = QuizUtil.defined(set) ? set : this.set;
-  
-  if(QuizElementList.engine.isEditMode()) {
-    var inside = true;
-    // Do not allow dragging outside canvas:
-    set.forEach(function(elm){
-      var elm_x,elm_y,elm_width,elm_height;
-      if(elm.type === "circle") {      
-        var radius = elm.attr("r");
-        elm_x = elm.attr("cx")+(x - elm.set.ox)-radius;
-        elm_y = elm.attr("cy")+(y - elm.set.oy)-radius;
-        elm_width = elm_height = radius*2;
-      }
-      else {
-        elm_x = elm.attr("x")+(x - elm.set.ox);
-        elm_y = elm.attr("y")+(y - elm.set.oy);
-        var elm_bbox = elm.getBBox();
-        elm_width = elm_bbox.width;
-        elm_height = elm_bbox.height;
-      }
-      
-      var going_left = (x - elm.set.ox) < 0;
-      var going_up = (y - elm.set.oy) < 0;
-      
-      if((elm_x<-10 && going_left)
-          || (elm_y<-10 && going_up) 
-          || ((elm_x + elm_width)>QuizElementList.engine.getCanvasWidth()+30 && !going_left)
-          || (elm_y + elm_height)>QuizElementList.engine.getCanvasHeight()+10 && !going_up) {
-        inside = false;
-        return false;
-      }
-    });
-    
-    
-    if(!inside) {
-      return false;
-    }
-  }
-  
-  set.forEach(function(elm){
-    if(elm.type === "circle") {
-      elm.attr({cx: elm.attr("cx")+(x - elm.set.ox),cy: elm.attr("cy")+(y - elm.set.oy)});
-    }
-    else {
-      elm.attr({x: elm.attr("x")+(x - elm.set.ox),y: elm.attr("y")+(y - elm.set.oy)});
-    }
-  });
-
-  set.ox = x;
-  set.oy = y;
-
-  if(QuizUtil.defined(set.parent)) {
-    set.parent.redrawConnection();
-  }
-  
-  return true;
-};
-function set_drag_finished(event) {
-  // Update form:
-  QuizElementList.updateForm();
-};
-
-
-/*
- * The main class. Representing the canvas.
- * */
-function Engine() {
-  var self = this;
-  this.id = null;
-  this.r = null;    
-  this.conf = null; 
-  this.node_id = null;
-  this.helptext = null;
-  
-  this.getSelector = function() {
-    return "#"+this.id;
-  };
-  
-  this.init = function (container_class, node_id) {
-  
-    if(!QuizUtil.defined(Engine.counter)) {
-      Engine.counter = 0;
-    }
-    Engine.counter++;
-    this.id = "ddlines-container-"+Engine.counter;
-    this.node_id = QuizUtil.defined(node_id) ? node_id : null;
-    
-    // Add a specific ID and class to div, so CSS may be set only
-    // for our case:
-    $(container_class).attr("id",this.id);
-    $(container_class).addClass("ddlines-container");
-    
-    this.conf = Drupal.settings.quiz_ddlines;
-    
-    if(this.isEditMode()) {
-      this.conf.feedback.enabled = $('#edit-feedback-enabled').is(':checked') ? "1" : "0";    
-      $('#edit-feedback-enabled').change(function(){
-        // Set the conf variable:
-        self.conf.feedback.enabled = $('#edit-feedback-enabled').is(':checked') ? "1" : "0";
-        $('#feedback-disabled').css({'display': (self.feedbackEnabled() ? 'none' : 'block')});
-      });
-      
-      // Listen to change events on hotspot radius input:
-      $('input#edit-hotspot-radius').change(function(e) {
-        QuizElementList.updateHotspotRadius($(this).val());
-      });
-      
-      // Add click-handler to add elements when clicking in canvas:
-      $(this.getSelector()).click(function(event) {
-        
-        var targetIsSVG = (event.target.nodeName === 'svg' || event.target.nodeName === 'DIV'); 
-        var targetIsImage = (event.target.nodeName === 'image' || event.target.nodeName === 'shape');
-        
-        if(!targetIsSVG && QuizUtil.drag_mode){
-          return;
-        }
-        
-        // Don't create new element when focus is on inputbox
-        if(QuizUtil.current_editor != null && (targetIsSVG || targetIsImage)) {
-          QuizUtil.current_editor.editingDone();
-          return;
-        }
-        
-        if(targetIsSVG || targetIsImage ||
-          (self.helptext != null && event.target.nodeName === 'tspan')) {
-          
-          // Remove help text:
-          if(self.helptext != null) {
-            self.helptext.remove();
-            self.helptext = null;
-          }
-          
-          // Add element:
-          self.createNewElement(event);
-        }
-      });
-      
-      $(this.getSelector()).resizable({
-         stop: function(event, ui) {
-           // Update size!
-           var height=$(self.getSelector()).height();
-           var width=$(self.getSelector()).width();
-           self.setSize(width,height);
-         }
-         /*minHeight: 300,
-         minWidth: 450*/
-      });
-      
-      // Add delete handler:
-      $(document).keydown(function(event) {
-        // Check if the deletion did not take place within an input field
-        // 46 is the delete button        
-        if(event.target.nodeName != 'INPUT' && event.which === 46) {
-          // The glow represents the current selected object
-          if(QuizUtil.current_glow != null) {
-            QuizUtil.current_glow.set.parent.remove();
-            QuizUtil.current_glow = null;
-            $("#quiz-ddlines-toolpane").fadeOut();
-          }
-        }
-      });
-      
-      // Add clearing of data when image is removed
-      $('form.node-quiz_ddlines-form #edit-field-image-und-0-remove-button').mousedown(function() {
-        QuizElementList.clear();
-        
-        QuizUtil.imageChanged = true;
-      });
-    }
-    
-    // Set size of canvas
-    $(this.getSelector()).height(this.getCanvasHeight()).width(this.getCanvasWidth());    
-    
-    // Create paper
-    this.r = Raphael(this.id, this.getCanvasWidth(), this.getCanvasHeight());
-
-    // Get image attributes:
-    var img_width = $(this.getSelector() + ' > img').attr("width");
-    var img_height = $(this.getSelector() + ' > img').attr("height");   
-    var img_uri=$(this.getSelector() + ' > img').attr("src");   
-    this.image = new QuizImage(this, img_uri, img_width, img_height);
-  };
-  
-  this.addHelpText = function() {
-    if(this.isEditMode()) {
-      // Add help text:
-      // Only if this
-      self.helptext = this.r.text(this.getCanvasWidth()/2,this.getCanvasHeight()/2,Drupal.t("Click anywhere in\nthe canvas to\nadd an alternative"));
-      self.helptext.attr({'font-size': 50, 'fill': '#FFF', stroke:'#000', 'font-weight': 'bold'});
-      
-      self.helptext.mousedown(function(){
-        QuizUtil.drag_mode = false;
-      });
-    }
-  };
-  
-  
-  this.getCanvasHeight = function() {
-    if(this.isResultMode()) {
-      return this.conf["correct-"+this.node_id].canvas.height;
-    }
-    else {
-      return parseInt(this.conf.canvas.height);
-    }
-  };
-  
-  this.getCanvasWidth = function() {
-    if(this.isResultMode()) {
-      return this.conf["correct-"+this.node_id].canvas.width;
-    }
-    else {      
-      return parseInt(this.conf.canvas.width);
-    }
-  };
-  
-  this.resizePaper = function(width, height) {
-    this.setSize(width,height);
-    $(this.getSelector()).height(height).width(width);
-  };
-  
-  this.setSize = function(width,height) {
-    if(this.isEditMode()) {
-      this.conf.canvas.width=width;
-      this.conf.canvas.height=height;
-    }
-    this.r.setSize(width,height);
-  };
-   
-  this.redrawConnections = function() {
-    for (var i = QuizElementList.elements.length; i--;) {
-      QuizElementList.elements[i].redrawConnection();
-    }
-    this.r.safari();
-  };
-    
-  this.createNewElement = function (event) {
-    // Create a new element    
-    var x = this.getRelativeX(event.pageX);
-    var y = this.getRelativeY(event.pageY);    
-    var element = new QuizElement(self, Raphael.getColor(0.9), "", undefined, x > 20 ? x-20 : x, y > 80 ? y-80 : y, undefined, x, y);
-    // Add to list
-    QuizElementList.add(element);
-    
-    element.gotoEditMode();
-  };
-  
-  this.toJson = function() {     
-    return '"canvas":{"width":'+this.conf.canvas.width+',"height":'+this.conf.canvas.height+'}';
-  };
-    
-  this.getPosition = function() {
-    return $(this.getSelector()).offset();
-  };
-    
-  this.getRelativeX = function(x) {
-    return (x-this.getPosition().left);
-  };
-  
-  this.getRelativeY = function(y) {
-    return (y-this.getPosition().top);
-  };
-  
-  this.isNew = function() {
-    return (this.conf.editmode === 'add');
-  };
-  
-  this.isEditMode = function() {
-    return (this.conf.mode === 'edit');
-  };
-  
-  this.executionModeLines = function() {
-    return (this.conf.execution_mode === '0');
-  };
-  
-  this.isResultMode = function() {
-    return (this.conf.mode === 'result');
-  };
-  
-  this.isTakeMode = function() {
-    return (this.conf.mode === 'take');
-  };
-  
-  this.feedbackEnabled = function() {
-    return (this.conf.feedback.enabled === '1');
-  };
-  
-  this.pointInsideCanvas = function(x,y) {
-    x = this.getRelativeX(x);
-    y = this.getRelativeY(y);
-    return (x>5 && y>5 && x<this.conf.canvas.width-5 && y<this.conf.canvas.height-5);
-  };
-  
-  this.showInfoPopup = function(text, border_color) {
-    if(text) {
-      // Show feedback:
-      // Create a div, since shapes in raphael need a value for height, width,
-      // and we don't know how much space we need at this point.
-      
-      // Calculate the displaytime for the message based on the length of the text:
-      var displaytime = 1800 + (text.length*30);
-      
-      var $feedback = $('<div id="quiz-ddlines-feedback">'+text+'</div>');
-      $feedback.css({'border-color': border_color, 'display': 'none'});
-      $(this.getSelector()).append($feedback);
-      $feedback.fadeIn();
-      setTimeout(function() { $feedback.fadeOut(); }, displaytime);
-    }
-  };
-};
-
-/**
- * The image class
- */
-function QuizImage(engine, uri, width, height) {
-  
-  /*var self = this;*/
-  
-  this.engine = engine;
-  this.image = this.engine.r.image(uri, 0, 0, width, height);
-  this.image.toFront();
-  
-  if(this.engine.isEditMode()) {
-    
-    // Center image when added:
-    if(this.engine.isNew()) {
-      this.image.attr({"x": (engine.getCanvasWidth()/2)-(width/2) ,"y": (engine.getCanvasHeight()/2)-(height/2)});
-    }
-    
-    /*
-    var bbox = this.image.getBBox();
-    this.image.resizer = this.engine.r.rect(bbox.x2-20,bbox.y2-20,20,20);
-    this.image.resizer.attr({color: "#00bb27", "fill": "#00bb27", "fill-opacity": 0.9, opacity:0.9, cursor: "se-resize"});
-    */
-    
-    this.image.attr({cursor:'move'});
-    /*
-    rstart = function () {
-      this.ox = this.attr("x");
-      this.oy = this.attr("y");
-      
-      self.image.ow = self.image.attr("width");
-      self.image.oh = self.image.attr("height");        
-    },
-    rmove = function (dx, dy) {
-        this.attr({x: this.ox + dx, y: this.oy + dy});
-        self.image.attr({width: self.image.ow + dx, height: self.image.oh + dy});
-    };
-    this.image.resizer.drag(rmove, rstart);*/
-    
-    // Setup image dragging handling
-    function image_drag_move(dx, dy, x, y, event) {
-      if(set_drag_move(dx, dy, x, y, event, this.set)) {
-        // Move all hotspots accordingly:
-        for (var i = QuizElementList.elements.length; i--;) {
-          set_drag_move(dx, dy, x, y, event, QuizElementList.elements[i].uiHotspot.set);
-        }
-      }
-    }
-
-    function image_drag_start(x, y, event) {
-      set_drag_start(x, y, event, this.set);
-      
-      // Make all hotspots readu to be moved:
-      for (var i = QuizElementList.elements.length; i--;) {
-        set_drag_start(x, y, event, QuizElementList.elements[i].uiHotspot.set);
-      }
-    }
-    
-    var set = this.engine.r.set();
-    set.push(this.image);
-    //set.push(this.image.resizer);
-    this.image.set = set;
-    //this.image.resizer.set = set;
-    set.drag(image_drag_move, image_drag_start, set_drag_finished);
-  }
-  
-  this.toJson = function() {    
-    return '"image":{"left":'+this.image.attr("x")+',"top":'+this.image.attr("y")+',"width":'+this.image.attr("width")+',"height":'+this.image.attr("height")+'}';
-  };
-  
-  this.setBounds = function(x,y,width,height) {
-    this.image.attr({"x":x, "y":y, "width":width, "height":height});
-  };
-  
-  this.getBBox = function() {
-    return this.image.getBBox();
-  };
-  
-  this.move = function(deltax, deltay) {
-    this.image.attr({"x":this.image.attr("x")+deltax, "y":this.image.attr("y")+deltay});
-  };
-}
-
-/**
- * The label UI element class
- */
-function QuizLabel(parent, color, id, text, x, y) {
-  
-  var self = this;
-  
-  this.parent = parent;
-  this.color = color;
-  this.text = text;
-  
-  this.setText = function(text) {
-    
-    self.label.text.hide();
-    
-    // Set the new text
-    self.label.text.attr({text: text});
-    
-    // Recalculate rectangle size.
-    var bbox = self.label.text.getBBox();
-    self.label.text.attr({
-      x: self.label.attr("x")+bbox.width/2+10,
-      y: self.label.attr("y")+10});
-    
-    // Set new width of parent rectangle:
-    var rect_width = bbox.width+20;
-    self.label.attr({width: rect_width < QuizUtil.LABEL_MIN_WIDTH ? QuizUtil.LABEL_MIN_WIDTH : rect_width});
-    self.label.fg.attr({width: rect_width < QuizUtil.LABEL_MIN_WIDTH ? QuizUtil.LABEL_MIN_WIDTH : rect_width});
-    
-    // Set size of glow if it exists
-    if(QuizUtil.defined(self.glow)) {
-      self.glow.attr({width: rect_width+8 < QuizUtil.LABEL_MIN_WIDTH+8 ? QuizUtil.LABEL_MIN_WIDTH+8 : rect_width+8});
-    }
-    
-    self.label.text.show();
-  };
-  
-  this.updateTextPosition = function() {
-    this.setText(this.text);
-    
-    // Update position of foreground object:
-    var bbox = self.label.getBBox();
-    self.label.fg.attr({x:bbox.x, y:bbox.y, width:bbox.width, height: bbox.height});
-    self.label.fg.show();
-  };
-  
-  // Create Raphael objects:
-  this.label = this.parent.engine.r.rect(x, y, 100, 20);
-  this.label.parent = this;
-  
-  // Use same id as previous if this is set
-  if(QuizUtil.defined(id)) {
-    this.label.id = id;
-  }
-  
-  this.label.text = this.parent.engine.r.text(-200,y+10,""); // initially hide the text;
-  this.label.fg = this.parent.engine.r.rect(x-5, y-5, 110, 30); // Foreground object
-  
-  // Set attributes:
-  this.label.attr({fill: "#fff", stroke: this.color, "fill-opacity": 1, "stroke-width": 3, cursor: (!this.parent.engine.isResultMode() ? "move" : "default")});
-  this.label.text.attr({"font-size": 12, cursor: (!this.parent.engine.isResultMode() ? "move" : "default")});
-  this.label.fg.attr({fill: "#fff", "fill-opacity": 0, "stroke-width": 0, "stroke-opacity":0, cursor: (!this.parent.engine.isResultMode() ? "move" : "default")});
-  this.label.fg.toFront();
-  
-  // Create a set so they can be treated as one:
-  this.set = this.parent.engine.r.set();
-  this.set.parent = parent;
-  this.set.push(this.label, this.label.text, this.label.fg);
-  
-  // Create backreference from elements to the set:
-  this.label.set = this.label.text.set = this.label.fg.set = this.set;
-  
-  // Set text if value set:
-  this.setText(text);
-  
-  // Move it to center of hotspot if resultpage
-  // and execution modes without lines:
-  if(!this.parent.engine.executionModeLines() && this.parent.engine.isResultMode() && QuizUtil.defined(this.parent.uiHotspot)) {
-    // Get hotspot:
-    var center = this.parent.uiHotspot.getCenter();
-    if (center != null) {
-      this.set.forEach(function(element) {
-        element.attr({x: center.x-(element.attr('width')/2), y:center.y-(element.attr('height')/2)});
-      });
-    }
-  }
-  
-  this.toJson = function(){
-    return '"label":{"id":'+this.label.id+',"x":'+this.label.attr("x")+',"y":'+this.label.attr("y")+',"text":"'+this.label.text.attr("text")+'"}';
-  };
-  
-  this.setColor = function(color) {
-    this.color = color;
-    this.label.attr({stroke: this.color});
-  };
-  
-  if(this.parent.engine.isEditMode()) {
-    this.set.drag(set_drag_move,set_drag_start,set_drag_finished);
-  }
-  
-  var edit_mode = function (event) {
-    // Add an inputbox as overlay:
-    // Get dimensions of rect
-    
-    if(QuizUtil.drag_mode) {
-      return false;
-    }
-    
-    if(event !== null) {
-      event.stopPropagation();
-    }
-    
-    if(QuizUtil.current_editor != null) {
-      QuizUtil.current_editor.editingDone();
-    }
-    
-    var me = QuizUtil.current_editor = this;
-    
-    var topleft=self.label.attr("x")-2;
-    var topright=self.label.attr("y")-2;
-    var bbox=self.label.getBBox();
-    
-    var $editButton = $('<input type="text" id="quiz-ddlines-edit" value="'+self.label.text.attr("text")+'"></input>');
-    $editButton.css({
-      'position': 'absolute',
-      'left': topleft+'px',
-      'top': topright+'px',
-      'z-index': 99999,
-      'border': '2px dashed '+self.color,
-      'height': (bbox.height+4)+'px',
-      'width': (bbox.width+4)+'px'
-    });
-    
-    
-    this.editingDone = function() {
-      // Save the text
-      self.parent.setText($editButton.val());
-      // Remove the input field
-      $editButton.remove();
-      self.parent.redrawConnection();
-      
-      QuizUtil.current_editor = null;
-    };
-    
-    // Listen on editing finished:    
-    $editButton.keypress(function(event) {
-      // 13 is enter
-      if(event.which === 13) {
-        me.editingDone();
-      }
-    });
-    $editButton.keyup(function(event) {
-      // Update toolpane:
-      $('#quiz-ddlines-toolpane label > em').html($editButton.val());
-    });
-    
-    $(self.parent.engine.getSelector()).append($editButton);
-    setTimeout( function() {
-      $editButton.focus();
-    }, 100);
-    
-    return false;
-  };
-  
-  var focus_mode = function () {
-    if($('#quiz-ddlines-toolpane').length != 0) {
-      $('#quiz-ddlines-toolpane').remove();
-    }
-    
-    if(QuizUtil.current_glow != null) {
-      if(QuizUtil.current_glow.set) {
-        QuizUtil.current_glow.set.exclude(QuizUtil.current_glow);
-      }
-      QuizUtil.current_glow.remove();
-      QuizUtil.current_glow = null;
-    }
-    
-    QuizUtil.current_glow = self.parent.engine.r.rect(self.label.attr('x')-4, self.label.attr('y')-4, self.label.attr('width')+8, self.label.attr('height')+8, 5);
-    QuizUtil.current_glow.attr({"fill-opacity": 0, "stroke": self.color, "stroke-width": 4, "stroke-opacity": 0.3});
-    QuizUtil.current_glow.parent_id = self.label.id;
-    
-    self.glow = QuizUtil.current_glow;
-    self.set.push(QuizUtil.current_glow);
-    QuizUtil.current_glow.set = self.set;
-    
-    /*var topleft=self.label.attr("x")-2;
-    var topright=self.label.attr("y")-2;
-    var bbox=self.label.getBBox();*/
-    var selected_text=self.label.text.attr("text");
-    
-    var $toolPane = $('<div id="quiz-ddlines-toolpane"></div>');
-    var $buttonPane = $('<div id="quiz-ddlines-buttonpane"></div>');
-    var $feedbackPane = $('<div id="quiz-ddlines-feedback-pane"></div>');
-    var $feedbackCorrect = $('<input type="text" class="form-text feedback correct" value="'+self.parent.feedback_correct+'"></input>');
-    var $feedbackWrong = $('<input type="text" class="form-text feedback wrong" value="'+self.parent.feedback_wrong+'"></input>');
-    var $feedbackLabel = $('<div class="form-item"><label id="settings-for">'+Drupal.t('Settings for')+' <em>'+selected_text+'</em></label></div>');
-    var $deleteButton = $('<input id="ddlines-delete-element" type="button" class="form-submit" tabindex="-1" value="'+Drupal.t('delete')+'">');
-    var $colorButton = $('<input id="ddlines-change-color" class="form-submit" type="button" value="'+Drupal.t('color')+'">');
-    var $closeButton = $('<input id="ddlines-close-panel" class="form-submit" type="button" value="'+Drupal.t('save')+'">');
-    
-    $toolPane.css({
-      'z-index': 10001,
-      'border-color': self.color
-    });
-    
-    $(self.parent.engine.getSelector()).append($toolPane);
-    $toolPane.append($feedbackPane);
-    $toolPane.append($buttonPane);
-    
-    $feedbackPane.append($feedbackLabel);
-    $feedbackPane.append($('<span id="feedback-correct">'+Drupal.t('Feedback when correct')+'</span>'));
-    $feedbackPane.append($('<span id="feedback-wrong">'+Drupal.t('Feedback when wrong')+'</span>'));
-    $feedbackPane.append($feedbackCorrect);
-    $feedbackPane.append($feedbackWrong);
-    
-    var updateFeedback = function(){
-      self.parent.setFeedback($feedbackCorrect.val(), $feedbackWrong.val());
-    };      
-    $feedbackCorrect.keyup(updateFeedback);
-    $feedbackWrong.keyup(updateFeedback);
-    
-    // Add feedback disabled:
-    var $feedbackDisabled = $('<div id="feedback-disabled"><p>'+Drupal.t('Feedback disabled')+'</p></div>');
-    $feedbackDisabled.css({'display': self.parent.engine.feedbackEnabled() ? 'none' : 'block'});
-    $feedbackPane.append($feedbackDisabled);
-    
-    $buttonPane.append($colorButton);
-    $buttonPane.append($closeButton);
-    $buttonPane.append($deleteButton);
-    
-    // Create container:
-    var $colorContainer = $('<div id="colorwheel-container"></div>').css({'width':'150px','height':'150px'});
-    $toolPane.append($colorContainer);
-    var cw = Raphael.colorwheel($('#colorwheel-container'), 150);
-    cw.color(self.color);
-    cw.onchange(function(color) {
-      $toolPane.css({'border-color': color.hex});
-      $colorContainer.css({'border-color': color.hex});
-      self.parent.setColor(color.hex);
-    });
-    
-    // Colorbutton clicked
-    $colorButton.click(function() {
-      
-      // If color container is visible, close it
-      if ( $colorContainer.is(":visible") ) {
-        $colorContainer.hide();
-      }
-      else {
-      
-        /*var position = $toolPane.position();
-        var pos_left = (position.left + 300) + "px";
-        var pos_top = (position.top - 150) + "px";*/
-        
-        // Set position of container:
-        $colorContainer.css({'border-color': self.color}).show();
-      }      
-    });
-        
-    $deleteButton.click(function() {      
-      $("#quiz-ddlines-toolpane").fadeOut();
-      if(QuizUtil.current_editor != null) {
-        QuizUtil.current_editor.editingDone();
-      }
-      self.parent.remove();
-    });    
-  
-    $('#ddlines-close-panel').click(function() {
-      // Hide colorwheel:
-      $colorContainer.hide();
-      
-      // Update form for saving
-      QuizElementList.updateForm();    
-      
-      $("#quiz-ddlines-toolpane").fadeOut();
-    });
-  };
-  
-  if(this.parent.engine.isEditMode()) {
-    this.set.click(edit_mode);
-    this.set.mousedown(focus_mode);
-  }
-  
-  this.gotoEditMode = function() {
-    focus_mode();
-    edit_mode(null);
-  };
-  
-  this.remove = function() {
-    this.set.remove();
-  };
-  
-  this.getBBox = function() {
-    return this.label.getBBox();
-  };
-  
-  this.move = function(deltax,deltay) {
-    this.label.attr({"x": this.label.attr("x") + deltax}); 
-    this.label.attr({"y": this.label.attr("y") + deltay});
-  };
-}
-
-function QuizLabelDrag(parent, label, color) {
-  var self = this;
-  
-  this.initial_x = label.attr('x');
-  this.initial_y = label.attr('y');
-  
-  this.label = label;
-  this.parent = parent;
-  this.color = color;
-  this.ox = this.oy = null;
-  
-  var drag_start = function(x,y) {
-    self.ox = x;
-    self.oy = y;
-    self.label.set.toFront();
-  };
-  
-  var drag_move = function(dx,dy,x,y) {
-    
-    /*if(!self.parent.engine.pointInsideCanvas(x,y)) {
-      return;
-    }*/
-    
-    self.label.set.forEach(function(elm){
-      elm.attr({x: elm.attr("x")+(x - self.ox), y: elm.attr("y")+(y - self.oy)});
-    });
-    
-    self.ox = x;
-    self.oy = y;
-    
-    // Hover on hits:
-    x = self.label.attr('x')+(self.label.attr('width')/2);
-    y = self.label.attr('y')+(self.label.attr('height')/2);
-    var hit_hotspot = false;
-    for (var i = QuizElementList.elements.length; i--;) {
-      // Check if x,y is within BBox:
-      var id = QuizElementList.elements[i].uiHotspot.hotspot.id;
-      if(QuizElementList.elements[i].uiHotspot.hotspot.margin.isPointInside(x,y)) {
-        
-        hit_hotspot = true;
-        
-        if(QuizUtil.current_glow === null || QuizUtil.current_glow.parent_id != id) {
-          if(QuizUtil.current_glow != null) {
-            QuizUtil.current_glow.remove();
-            QuizUtil.current_glow = null;
-          }
-          QuizUtil.current_glow = QuizElementList.elements[i].uiHotspot.hotspot.glow({"color": self.color, width: self.label.attr('width'), fill: true, opacity: 1.0});
-          QuizUtil.current_glow.parent_id = id;
-        }
-        
-        break;
-      }
-    }
-    
-    if(!hit_hotspot && QuizUtil.current_glow != null) {
-      QuizUtil.current_glow.remove();
-      QuizUtil.current_glow = null;
-    }
-  };
-  
-  var drag_finish = function(event) {
-    
-    if(QuizUtil.current_glow != null) {
-      QuizUtil.current_glow.remove();
-    }
-    
-    var x = self.label.attr("x") + (self.label.attr("width")/2);
-    var y = self.label.attr("y") + (self.label.attr("height")/2);
-    
-    // Inside any hotspot at all?
-    var targets = self.parent.engine.r.getElementsByPoint(x, y);
-    var selected_hotspot=null;
-    
-    targets.forEach(function(e){
-      if(e.isHotspotMargin) {
-        selected_hotspot = e.hotspot;
-        // Makes callback stop (like break if this was a for loop)
-        return false; 
-      }
-    });
-    
-    var correct_answer = selected_hotspot === null ? false : (self.parent.uiHotspot.hotspot === selected_hotspot);
-    var feedback_enabled = self.parent.engine.feedbackEnabled();
-    
-    if((!feedback_enabled && selected_hotspot !== null) || (feedback_enabled && correct_answer)) {
-      // Glue it to the center of the selected hotspot,
-      // and make it smaller:
-      self.label.text.hide();
-      self.label.fg.hide();
-      self.label.animate({
-        x: selected_hotspot.attr("cx")-(self.label.attr('width')/2), 
-        y: selected_hotspot.attr("cy")-(self.label.attr('height')/2),
-        "stroke-opacity": 0.3,
-        "fill-opacity": 0.95},
-        500, 
-        "bounce", function() {
-          self.label.parent.updateTextPosition();          
-        });        
-    }
-    
-    if(selected_hotspot === null || (feedback_enabled && !correct_answer)) {
-      // Animate back to source!      
-      // Do the animation of the label:
-      self.label.text.hide();
-      self.label.fg.hide();
-      self.label.animate({
-        "stroke-opacity": 1,
-        "fill-opacity": 1,
-        x: self.initial_x, 
-        y: self.initial_y}, 500, "bounce", function() {
-          self.label.parent.updateTextPosition();          
-        });
-      
-      if(selected_hotspot != null && feedback_enabled && !correct_answer){
-        self.parent.engine.showInfoPopup(self.parent.feedback_wrong, self.color);
-      }
-      
-      Answers.removeAnswer(self.label.id);
-    }
-    else if(feedback_enabled && correct_answer) {
-      self.parent.engine.showInfoPopup(self.parent.feedback_correct, self.color);     
-      // Save answer:
-      Answers.setAnswer(self.label.id, selected_hotspot.id);
-    }
-    else if(!feedback_enabled && selected_hotspot !== null) {
-      // Save answer:     
-      Answers.setAnswer(self.label.id, selected_hotspot.id);
-    }
-  };
-  
-  label.set.drag(drag_move, drag_start, drag_finish);
-}
-
-/**
- * The pointer class, the UI element dragged while taking
- * a test.
- * 
- */
-function QuizPointer(parent, label, color) {
-  var self = this;
-  
-  this.color = color;
-  this.parent = parent;
-  this.label = label;
-  this.radius = parseInt(parent.engine.conf.pointer.radius);
-  
-  this.connect = function() {
-    return this.ie_fix;
-  };
-  
-  this.createVisual = function(x,y) {
-    this.pointer = this.parent.engine.r.circle(x,y,this.radius);
-    this.pointer.attr({fill: this.color, stroke: this.color, "fill-opacity": 0.9, "stroke-opacity": 1, "stroke-width": 2, cursor: "move"});
-    this.border = this.parent.engine.r.circle(x,y,this.radius+2);
-    this.border.attr({stroke: "#fff", "stroke-width": 2, "stroke-opacity": 1});
-    this.ie_fix = this.parent.engine.r.rect(x-this.radius, y-this.radius, this.radius*2, this.radius*2);    
-    this.ie_fix.attr({opacity:0});
-    
-    this.set = this.parent.engine.r.set();
-    this.set.push(this.pointer, this.border);
-  };
-  
-  this.updateVisual = function(cx, cy, updatePointer) {
-    updatePointer = QuizUtil.defined(updatePointer) ? updatePointer : false;
-    
-    if(updatePointer) {
-      self.pointer.attr({cx: cx, cy: cy, r:self.radius});
-    }
-    else {
-      self.radius = parseInt(self.pointer.attr("r"));
-    }
-    
-    self.ie_fix.attr({x: cx-self.radius, y:cy-self.radius, width: self.radius*2, height: self.radius*2});
-    self.border.attr({cx: cx, cy: cy, r:(self.radius+2)});
-    
-    self.parent.redrawConnection();
-  };
-  
-  // Start dragging pointer 
-  this.pointer_drag_start = function(x,y) {
-    if(!self.pointer) {
-      self.createVisual(self.parent.engine.getRelativeX(x), self.parent.engine.getRelativeY(y));
-      
-      if(!self.parent.connection) {
-        self.parent.createConnection();
-      }
-  
-      self.pointer.drag(self.pointer_move, self.pointer_drag_start, self.pointer_up);
-    }
-  };  
-  
-  // Dragging of pointer
-  this.pointer_move = function(dx,dy,x,y) {
-    /*if(!self.parent.engine.pointInsideCanvas(x,y)) {
-      return;
-    }*/
-    x = self.parent.engine.getRelativeX(x);
-    y = self.parent.engine.getRelativeY(y);
-    
-    self.updateVisual(x,y,true);
-    
-    // Check if hotspot is hit, for hovering:
-    // Tried using onDragOver, but that did not work. See comment here:
-    // http://dashasalo.com/2011/08/15/raphaeljs-2-0-ondragover-limitation/
-    var hit_hotspot = false;
-    for (var i = QuizElementList.elements.length; i--;) {
-      // Check if x,y is within BBox:
-      var id = QuizElementList.elements[i].uiHotspot.hotspot.id;
-      if(QuizElementList.elements[i].uiHotspot.hotspot.isPointInside(x,y)) {
-        
-        hit_hotspot = true;
-        
-        if(QuizUtil.current_glow === null || QuizUtil.current_glow.parent_id !== id) {
-          if(QuizUtil.current_glow !== null) {
-            QuizUtil.current_glow.remove();
-            QuizUtil.current_glow = null;
-          }          
-          QuizUtil.current_glow = QuizElementList.elements[i].uiHotspot.hotspot.glow({"color": self.color, width: 20, fill: true, opacity: 1.0});
-          QuizUtil.current_glow.parent_id = id;
-        }
-        
-        break;
-      }
-    }
-    
-    if(!hit_hotspot && QuizUtil.current_glow !== null) {
-      QuizUtil.current_glow.remove();
-      QuizUtil.current_glow = null;
-    }
-  };
-      
-  this.pointer_up = function(event) {
-    
-    if(QuizUtil.current_glow !== null) {
-      QuizUtil.current_glow.remove();
-    }
-    
-    var x = self.pointer.attr("cx");
-    var y = self.pointer.attr("cy");
-    
-    // Inside any hotspot at all?
-    var targets = self.parent.engine.r.getElementsByPoint(x, y);
-    var selected_hotspot=null;
-    
-    targets.forEach(function(e){
-      if(e !== self.pointer && e.isHotspot) {
-        // Hit one
-        // Check if correct hotspot hit.
-        //correct_answer = (self.parent.uiHotspot.hotspot == e);
-        selected_hotspot = e;
-        
-        // Makes callback stop (like break if this was a for loop)
-        return false; 
-      }
-    });
-    
-    var correct_answer = selected_hotspot===null ? false : (self.parent.uiHotspot.hotspot == selected_hotspot);
-    var feedback_enabled = self.parent.engine.feedbackEnabled();
-    
-    if((!feedback_enabled && selected_hotspot !== null) || (feedback_enabled && correct_answer)) {
-      // Remove connecting line:
-      self.parent.hideConnection();
-      
-      // Glue it to the center of the selected hotspot:
-      self.set.animate({cx: selected_hotspot.attr("cx"), cy: selected_hotspot.attr("cy"), r: selected_hotspot.attr("r")}, 500, "bounce", function() {
-        self.updateVisual(self.pointer.attr("cx"), self.pointer.attr("cy"), false);        
-      });
-    }
-    
-    if(selected_hotspot === null || (feedback_enabled && !correct_answer)) {
-      // Animate back to source!      
-      // Remove connecting line:
-      self.parent.hideConnection();      
-      
-      // Do the animation of the pointer
-      var bbox = self.label.getBBox();
-      self.set.animate({cx: self.label.attr("x")+(bbox.width/2), cy: self.label.attr("y")+50, r: self.parent.engine.conf.pointer.radius}, 500, "bounce", function() {
-        self.updateVisual(self.pointer.attr("cx"), self.pointer.attr("cy"), false);
-      });
-      
-      if(selected_hotspot !== null && feedback_enabled && !correct_answer){
-        self.parent.engine.showInfoPopup(self.parent.feedback_wrong, self.color);
-      }
-      
-      Answers.removeAnswer(self.label.id);
-    }
-    else if(feedback_enabled && correct_answer) {
-      self.parent.engine.showInfoPopup(self.parent.feedback_correct, self.color);     
-      // Save answer:
-      Answers.setAnswer(self.label.id, selected_hotspot.id);
-    }
-    else if(!feedback_enabled && selected_hotspot !== null) {
-      // Save answer:     
-      Answers.setAnswer(self.label.id, selected_hotspot.id);
-    }
-  };
-  
-}
-
-/**
- * The hotspot UI element class
- */
-function QuizHotspot(parent, color, id, x, y, radius) {
-  var self = this;  
-  this.parent = parent;
-  this.color = color;
-  
-  this.radius = parseInt(self.parent.engine.conf.hotspot.radius);
-  
-  this.hotspot = self.parent.engine.r.circle(x, y, this.radius);  
-  this.hotspot.attr({fill: "#fff", stroke: "#000", "fill-opacity": 0.4, "stroke-opacity": 1, "stroke-width": 2});
-  this.hotspot.isHotspot=true;
-  
-  this.hotspot.margin = self.parent.engine.r.circle(x, y, this.radius+20);
-  this.hotspot.margin.isHotspotMargin = true;
-  this.hotspot.margin.hotspot = this.hotspot; 
-  this.hotspot.margin.hide();
-  
-  this.border = self.parent.engine.r.circle(x, y, this.radius+2);
-  this.border.attr({stroke: "#fff", "stroke-width": 2, "stroke-opacity": 1});
-  
-  this.ie_fix = self.parent.engine.r.rect(x-this.radius, y-this.radius, this.radius*2, this.radius*2);
-  this.ie_fix.attr({opacity:0});
-  this.ie_fix.toFront();
-  
-  if(self.parent.engine.isEditMode()) {
-    this.hotspot.attr({cursor: "move", stroke: this.color});
-  }
-  
-  if(QuizUtil.defined(id)) {
-    this.hotspot.id = id;
-  }
-  
-  if(self.parent.engine.isEditMode()) { 
-    this.set = self.parent.engine.r.set();
-    this.set.parent = parent;
-    this.set.push(this.hotspot, this.border, this.ie_fix);
-    this.hotspot.set = this.set;
-    this.border.set = this.set;
-    this.ie_fix.set = this.set;
-    
-    this.hotspot.set.drag(set_drag_move,set_drag_start,set_drag_finished);
-  } 
-  
-  this.connect = function() {
-    return this.ie_fix;
-  };
-  
-  this.toJson = function() {
-    return '"hotspot":{"id":'+this.hotspot.id+',"x":'+this.hotspot.attr("cx")+',"y":'+this.hotspot.attr("cy")+'}';
-  };
-  
-  this.remove = function() {    
-    this.hotspot.remove();
-    this.border.remove();
-  };
-  
-  this.setColor = function(color) {
-    this.color = color;
-    this.hotspot.attr({stroke: this.color});
-  };
-  
-  this.updateHotspotRadius = function(radius) {
-    var previous_radius = this.radius;
-    var diff = radius-previous_radius;
-    this.radius = radius;
-    
-    this.hotspot.attr({r: radius});
-    this.border.attr({r: radius+2});
-    this.ie_fix.attr({x: this.ie_fix.attr('x')-diff, y: this.ie_fix.attr('y')-diff, width: radius*2, height: radius*2});
-  };
-  
-  this.getCenter = function() {
-    if(this.hotspot) {
-      return {x: this.hotspot.attr('cx'), y:this.hotspot.attr('cy')};
-    }
-    else {
-      return null;
-    }
-  };
-  
-  this.getBBox = function() {
-    return this.hotspot.getBBox();
-  };
-  
-  this.move = function(deltax,deltay) {
-    this.hotspot.attr({"cx": this.hotspot.attr("cx") + deltax}); 
-    this.hotspot.attr({"cy": this.hotspot.attr("cy") + deltay});
-  };
-}
-
-
-/**
- * The quiz ddlines element.
- * 
- * It contains the metadata needed to draw the label, hotspot, pointer 
- * and connecting lines
- * 
- */
-function QuizElement (engine, color, text, label_id, label_x, label_y, hotspot_id, hotspot_x, hotspot_y, feedback_correct, feedback_wrong, correct_answer) {  
-  
-  var self = this;
-  this.engine = engine;
-  
-  text = QuizUtil.defined(text) ? text : "";
-  label_x = QuizUtil.defined(label_x) ? label_x : 5;
-  label_y = QuizUtil.defined(label_y) ? label_y : 5;
-  hotspot_x = QuizUtil.defined(hotspot_x) ? hotspot_x : 55;
-  hotspot_y = QuizUtil.defined(hotspot_y) ? hotspot_y : 100;  
-  this.feedback_wrong = QuizUtil.defined(feedback_wrong)  ? feedback_wrong : engine.conf.feedback.wrong;
-  this.feedback_correct = QuizUtil.defined(feedback_correct) ? feedback_correct : engine.conf.feedback.correct;
-  correct_answer = QuizUtil.defined(correct_answer) ? correct_answer : null;
-  
-  // Create default hotspot and label
-  this.color = color;
-  
-  if((correct_answer !== null || hotspot_id !== null) && hotspot_x !== null) {
-    this.uiHotspot = new QuizHotspot(this, color, hotspot_id, hotspot_x, hotspot_y);
-  }
-  
-  if(correct_answer !== null || label_id !== null) {
-    this.uiLabel = new QuizLabel(this, color, label_id, text, label_x, label_y);
-  }
-  
-  if(this.engine.isTakeMode()) {
-    if(this.engine.executionModeLines()) {
-      this.pointer = new QuizPointer(this, this.uiLabel.label, color);
-      this.uiLabel.set.drag(this.pointer.pointer_move, this.pointer.pointer_drag_start, this.pointer.pointer_up);
-    }
-    else {
-      // Make label beeing draggable
-      new QuizLabelDrag(this, this.uiLabel.label, color);
-    }
-  }
-  
-  this.createConnection = function() {
-    if(self.engine.isEditMode() || self.engine.executionModeLines()) {
-      this.connection = this.engine.r.connection(this.uiLabel.label, (this.engine.isTakeMode() ? this.pointer.connect() : this.uiHotspot.connect()), this.color, "#FFF|5");
-    }
-  };
-  
-  this.hideConnection = function() {
-    if(self.engine.isEditMode() || self.engine.executionModeLines()) {
-      this.connection.bg.hide();
-      this.connection.line.hide();
-    }
-  };
-  
-  if(this.engine.isEditMode() || (this.engine.isResultMode() && hotspot_id !== null && label_id !== null)) {
-    this.createConnection();
-  }
-  
-  if(this.engine.isResultMode() && (correct_answer != null || (label_id === null || hotspot_id === null))) {
-    
-    //var settings = {color: (correct_answer ? '#00bb27' : '#fe2020'), width: 20, fill: true, opacity: 1};
-    
-    var imagepath = this.engine.conf.quiz_imagepath + (correct_answer ? "icon_ok" : "icon_wrong") + ".gif";
-    
-    // Set glow color:
-    if(QuizUtil.defined(this.uiLabel)) {
-      // Create Image:
-      var bbox = this.uiLabel.getBBox();
-      this.engine.r.image(imagepath, bbox.x-8, bbox.y-8, 20, 20);
-      //this.engine.r.image.attr({'width': 16, 'height': 16});
-      
-      //this.uiLabel.label.glow(settings);
-    }
-    /*if(QuizUtil.defined(this.uiHotspot) && this.engine.executionModeLines()) {
-      //this.uiHotspot.hotspot.glow(settings);
-      var bbox = this.uiHotspot.getBBox();
-      this.engine.r.image(imagepath, bbox.x, bbox.y, 16, 16);
-    }*/
-  }
-  
-  this.redrawConnection = function() {
-    if(this.connection) {
-      this.connection.bg.show();
-      this.connection.line.show();
-      this.engine.r.connection(this.connection);
-    }
-    this.engine.r.safari();
-  };
-  
-  this.gotoEditMode = function() {
-    this.uiLabel.gotoEditMode();
-  };
-      
-  this.toJson = function() {
-    // Generates JSON
-    return '{"feedback_wrong":"'+this.feedback_wrong+'",'+'"feedback_correct":"'+this.feedback_correct+'","color":"'+this.color+'",'+this.uiLabel.toJson()+','+this.uiHotspot.toJson()+'}';
-  };
-  
-  this.setFeedback = function(correct, wrong) {
-    this.feedback_correct = correct;
-    this.feedback_wrong = wrong;
-    
-    // Update list:
-    QuizElementList.updateForm();
-  };
-  
-  this.setText = function(text) {
-    // Set the new text
-    this.uiLabel.setText(text);
-    
-    // Update form element, so it is ready to be saved
-    QuizElementList.updateForm();
-  };
-  
-  this.setColor = function(color) {
-    this.color = color;
-    
-    this.uiLabel.setColor(this.color);
-    this.uiHotspot.setColor(this.color);
-    
-    // Set glow if it exists:
-    if(QuizUtil.current_glow != null) {
-      QuizUtil.current_glow.attr({stroke: this.color});
-    }
-    
-    // Set connection color:
-    this.connection.line.attr({stroke: this.color});
-  };
-  
-  this.remove = function() {    
-    // Remove from list:
-    QuizElementList.remove(this);
-    
-    // Remove from canvas:
-    this.uiHotspot.remove();
-    this.uiLabel.remove();
-    this.connection.bg.remove();
-    this.connection.line.remove();
-  };
-  
-  this.getBBox = function() {
-    var bbox = this.uiLabel.getBBox();
-    
-    if(QuizUtil.defined(this.uiHotspot)) {
-      var bboxHotSpot = this.uiHotspot.getBBox();
-      bbox = QuizUtil.bboxUnion(bbox, bboxHotSpot);
-    }
-    
-    return bbox;
-  };
-  
-  this.move = function(deltax,deltay) {
-    this.uiLabel.move(deltax,deltay);
-    this.uiHotspot.move(deltax,deltay);
-  };
-}
-
-QuizElement.fromJson = function(json, engine) {
-  return new QuizElement(
-       engine, 
-       json.color, 
-       QuizUtil.defined(json.label) ? json.label.text : null, 
-       QuizUtil.defined(json.label) ? json.label.id : null, 
-       QuizUtil.defined(json.label) ? json.label.x : null, 
-       QuizUtil.defined(json.label) ? json.label.y : null, 
-       QuizUtil.defined(json.hotspot) ? json.hotspot.id : null, 
-       QuizUtil.defined(json.hotspot) ? json.hotspot.x : null, 
-       QuizUtil.defined(json.hotspot) ? json.hotspot.y : null, 
-       json.feedback_correct, 
-       json.feedback_wrong, 
-       QuizUtil.defined(json.correct) ? json.correct : null);
-};
-/*
-var Glow = {
-    instance: null,
-    glow: function(elem, color) {
-      this.remove();
-      
-      var settings = {};
-      
-      if(elem.type='circle') {
-        settings = {}
-      }
-      else {
-        
-      }
-      
-      this.instance = elem.glow({"color": color, width: 20, fill: true, opacity: 1.0});
-      
-      Engine.r.rect(self.label.attr('x')-4, self.label.attr('y')-4, self.label.attr('width')+8, self.label.attr('height')+8, 5);
-      
-      
-    },
-    remove: function() {
-      this.instance.remove();
-      this.instance = null;
-    },
-    isGlowing(): function() {
-      return (set != null);
-    }
-};*/
-
-
-
-
-
-}(jQuery));
\ No newline at end of file
diff --git a/question_types/quiz_ddlines/js/raphael-min.js b/question_types/quiz_ddlines/js/raphael-min.js
deleted file mode 100644
index d30dbad..0000000
--- a/question_types/quiz_ddlines/js/raphael-min.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// ┌────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël 2.1.0 - JavaScript Vector Library                          │ \\
-// ├────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
-// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
-// ├────────────────────────────────────────────────────────────────────┤ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
-// └────────────────────────────────────────────────────────────────────┘ \\
-
-(function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;t<u;t++)"zIndex"in f[t]&&(o.push(f[t].zIndex),f[t].zIndex<0&&(p[f[t].zIndex]=f[t]));o.sort(g);while(o[l]<0){n=p[o[l++]],q.push(n.apply(b,e));if(i){i=d;return q}}for(t=0;t<u;t++){n=f[t];if("zIndex"in n)if(n.zIndex==o[l]){q.push(n.apply(b,e));if(i)break;do{l++,n=p[o[l]],n&&q.push(n.apply(b,e));if(i)break}while(n)}else p[n.zIndex]=n;else{q.push(n.apply(b,e));if(i)break}}i=d,h=r;return q.length?q:null};k.listeners=function(a){var b=a.split(d),c=j,f,g,h,i,k,l,m,n,o=[c],p=[];for(i=0,k=b.length;i<k;i++){n=[];for(l=0,m=o.length;l<m;l++){c=o[l].n,g=[c[b[i]],c[e]],h=2;while(h--)f=g[h],f&&(n.push(f),p=p.concat(f.f||[]))}o=n}return p},k.on=function(a,b){var c=a.split(d),e=j;for(var g=0,h=c.length;g<h;g++)e=e.n,!e[c[g]]&&(e[c[g]]={n:{}}),e=e[c[g]];e.f=e.f||[];for(g=0,h=e.f.length;g<h;g++)if(e.f[g]==b)return f;e.f.push(b);return function(a){+a==+a&&(b.zIndex=+a)}},k.stop=function(){i=1},k.nt=function(a){if(a)return(new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)")).test(h);return h},k.off=k.unbind=function(a,b){var f=a.split(d),g,h,i,k,l,m,n,o=[j];for(k=0,l=f.length;k<l;k++)for(m=0;m<o.length;m+=i.length-2){i=[m,1],g=o[m].n;if(f[k]!=e)g[f[k]]&&i.push(g[f[k]]);else for(h in g)g[c](h)&&i.push(g[h]);o.splice.apply(o,i)}for(k=0,l=o.length;k<l;k++){g=o[k];while(g.n){if(b){if(g.f){for(m=0,n=g.f.length;m<n;m++)if(g.f[m]==b){g.f.splice(m,1);break}!g.f.length&&delete g.f}for(h in g.n)if(g.n[c](h)&&g.n[h].f){var p=g.n[h].f;for(m=0,n=p.length;m<n;m++)if(p[m]==b){p.splice(m,1);break}!p.length&&delete g.n[h].f}}else{delete g.f;for(h in g.n)g.n[c](h)&&g.n[h].f&&delete g.n[h].f}g=g.n}}},k.once=function(a,b){var c=function(){var d=b.apply(this,arguments);k.unbind(a,c);return d};return k.on(a,c)},k.version=b,k.toString=function(){return"You are running Eve "+b},typeof module!="undefined"&&module.exports?module.exports=k:typeof define!="undefined"?define("eve",[],function(){return k}):a.eve=k})(this),function(){function cF(a){for(var b=0;b<cy.length;b++)cy[b].el.paper==a&&cy.splice(b--,1)}function cE(b,d,e,f,h,i){e=Q(e);var j,k,l,m=[],o,p,q,t=b.ms,u={},v={},w={};if(f)for(y=0,z=cy.length;y<z;y++){var x=cy[y];if(x.el.id==d.id&&x.anim==b){x.percent!=e?(cy.splice(y,1),l=1):k=x,d.attr(x.totalOrigin);break}}else f=+v;for(var y=0,z=b.percents.length;y<z;y++){if(b.percents[y]==e||b.percents[y]>f*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;y<z;y++){w[A][y]=[0];for(var F=1,G=u[A][y].length;F<G;F++)w[A][y][F]=(E[y][F]-u[A][y][F])/t}break;case"transform":var H=d._,I=ca(H[A],v[A]);if(I){u[A]=I.from,v[A]=I.to,w[A]=[],w[A].real=!0;for(y=0,z=u[A].length;y<z;y++){w[A][y]=[u[A][y][0]];for(F=1,G=u[A][y].length;F<G;F++)w[A][y][F]=(v[A][y][F]-u[A][y][F])/t}}else{var J=d.matrix||new cb,K={_:{transform:H.transform},getBBox:function(){return d.getBBox(1)}};u[A]=[J.a,J.b,J.c,J.d,J.e,J.f],b$(K,v[A]),v[A]=K._.transform,w[A]=[(K.matrix.a-J.a)/t,(K.matrix.b-J.b)/t,(K.matrix.c-J.c)/t,(K.matrix.d-J.d)/t,(K.matrix.e-J.e)/t,(K.matrix.f-J.f)/t]}break;case"csv":var L=r(j[A])[s](c),M=r(u[A])[s](c);if(A=="clip-rect"){u[A]=M,w[A]=[],y=M.length;while(y--)w[A][y]=(L[y]-u[A][y])/t}v[A]=L;break;default:L=[][n](j[A]),M=[][n](u[A]),w[A]=[],y=d.paper.customAttributes[A].length;while(y--)w[A][y]=((L[y]||0)-(M[y]||0))/t}}var O=j.easing,P=a.easing_formulas[O];if(!P){P=r(O).match(N);if(P&&P.length==5){var R=P;P=function(a){return cC(a,+R[1],+R[2],+R[3],+R[4],t)}}else P=bf}q=j.start||b.start||+(new Date),x={anim:b,percent:e,timestamp:q,start:q+(b.del||0),status:0,initstatus:f||0,stop:!1,ms:t,easing:P,from:u,diff:w,to:v,el:d,callback:j.callback,prev:p,next:o,repeat:i||b.times,origin:d.attr(),totalOrigin:h},cy.push(x);if(f&&!k&&!l){x.stop=!0,x.start=new Date-t*f;if(cy.length==1)return cA()}l&&(x.start=new Date-x.ms*f),cy.length==1&&cz(cA)}else k.initstatus=f,k.start=new Date-k.ms*f;eve("raphael.anim.start."+d.id,d,b)}}function cD(a,b){var c=[],d={};this.ms=b,this.times=1;if(a){for(var e in a)a[g](e)&&(d[Q(e)]=a[e],c.push(Q(e)));c.sort(bd)}this.anim=d,this.top=c[c.length-1],this.percents=c}function cC(a,b,c,d,e,f){function o(a,b){var c,d,e,f,j,k;for(e=a,k=0;k<8;k++){f=m(e)-a;if(z(f)<b)return e;j=(3*i*e+2*h)*e+g;if(z(j)<1e-6)break;e=e-f/j}c=0,d=1,e=a;if(e<c)return c;if(e>d)return d;while(c<d){f=m(e);if(z(f-a)<b)return e;a>f?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" × "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p<q;p++){var r=b[p];if(r[0]=="M")e=i=r[1],f=j=r[2];else{r[0]=="C"?(m=[e,f].concat(r.slice(1)),e=m[6],f=m[7]):(m=[e,f,e,f,i,j,i,j],e=i,f=j);for(var s=0,t=c.length;s<t;s++){var u=c[s];if(u[0]=="M")g=k=u[1],h=l=u[2];else{u[0]=="C"?(n=[g,h].concat(u.slice(1)),g=n[6],h=n[7]):(n=[g,h,g,h,k,l,k,l],g=k,h=l);var v=bG(m,n,d);if(d)o+=v;else{for(var w=0,x=v.length;w<x;w++)v[w].segment1=p,v[w].segment2=s,v[w].bez1=m,v[w].bez2=n;o=o.concat(v)}}}}}return o}function bG(b,c,d){var e=a.bezierBBox(b),f=a.bezierBBox(c);if(!a.isBBoxIntersect(e,f))return d?0:[];var g=bB.apply(0,b),h=bB.apply(0,c),i=~~(g/5),j=~~(h/5),k=[],l=[],m={},n=d?0:[];for(var o=0;o<i+1;o++){var p=a.findDotsAtSegment.apply(a,b.concat(o/i));k.push({x:p.x,y:p.y,t:o/i})}for(o=0;o<j+1;o++)p=a.findDotsAtSegment.apply(a,c.concat(o/j)),l.push({x:p.x,y:p.y,t:o/j});for(o=0;o<i;o++)for(var q=0;q<j;q++){var r=k[o],s=k[o+1],t=l[q],u=l[q+1],v=z(s.x-r.x)<.001?"y":"x",w=z(u.x-t.x)<.001?"y":"x",x=bD(r.x,r.y,s.x,s.y,t.x,t.y,u.x,u.y);if(x){if(m[x.x.toFixed(4)]==x.y.toFixed(4))continue;m[x.x.toFixed(4)]=x.y.toFixed(4);var y=r.t+z((x[v]-r[v])/(s[v]-r[v]))*(s.t-r.t),A=t.t+z((x[w]-t[w])/(u[w]-t[w]))*(u.t-t.t);y>=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)<y(e,g)||y(a,c)>x(e,g)||x(b,d)<y(f,h)||y(b,d)>x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)<i)){var j=1,k=j/2,l=j-k,m,n=.01;m=bB(a,b,c,d,e,f,g,h,l);while(z(m-i)>n)k/=2,l+=(m<i?1:-1)*k,m=bB(a,b,c,d,e,f,g,h,l);return l}}function bB(a,b,c,d,e,f,g,h,i){i==null&&(i=1),i=i>1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;o<k;o++){var p=j*l[o]+j,q=bA(p,a,c,e,g),r=bA(p,b,d,f,h),s=q*q+r*r;n+=m[o]*w.sqrt(s)}return j*n}function bA(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function by(a,b){var c=[];for(var d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function bm(a){if(Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[g](c)&&(b[c]=bm(a[c]));return b}function a(c){if(a.is(c,"function"))return b?c():eve.on("raphael.DOMload",c);if(a.is(c,E))return a._engine.create[m](a,c.splice(0,3+a.is(c[0],C))).add(c);var d=Array.prototype.slice.call(arguments,0);if(a.is(d[d.length-1],"function")){var e=d.pop();return b?e.call(a._engine.create[m](a,d)):eve.on("raphael.DOMload",function(){e.call(a._engine.create[m](a,d))})}return a._engine.create[m](a,arguments)}a.version="2.1.0",a.eve=eve;var b,c=/[, ]+/,d={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},e=/\{(\d+)\}/g,f="prototype",g="hasOwnProperty",h={doc:document,win:window},i={was:Object.prototype[g].call(h.win,"Raphael"),is:h.win.Raphael},j=function(){this.ca=this.customAttributes={}},k,l="appendChild",m="apply",n="concat",o="createTouch"in h.doc,p="",q=" ",r=String,s="split",t="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[s](q),u={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},v=r.prototype.toLowerCase,w=Math,x=w.max,y=w.min,z=w.abs,A=w.pow,B=w.PI,C="number",D="string",E="array",F="toString",G="fill",H=Object.prototype.toString,I={},J="push",K=a._ISURL=/^url\(['"]?([^\)]+?)['"]?\)$/i,L=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,M={NaN:1,Infinity:1,"-Infinity":1},N=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,O=w.round,P="setAttribute",Q=parseFloat,R=parseInt,S=r.prototype.toUpperCase,T=a._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},U=a._availableAnimAttrs={blur:C,"clip-rect":"csv",cx:C,cy:C,fill:"colour","fill-opacity":C,"font-size":C,height:C,opacity:C,path:"path",r:C,rx:C,ry:C,stroke:"colour","stroke-opacity":C,"stroke-width":C,transform:"transform",width:C,x:C,y:C},V=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,W=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,X={hs:1,rg:1},Y=/,?([achlmqrstvxz]),?/gi,Z=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,$=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,_=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,ba=a._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,bb={},bc=function(a,b){return a.key-b.key},bd=function(a,b){return Q(a)-Q(b)},be=function(){},bf=function(a){return a},bg=a._rectPath=function(a,b,c,d,e){if(e)return[["M",a+e,b],["l",c-e*2,0],["a",e,e,0,0,1,e,e],["l",0,d-e*2],["a",e,e,0,0,1,-e,e],["l",e*2-c,0],["a",e,e,0,0,1,-e,-e],["l",0,e*2-d],["a",e,e,0,0,1,e,-e],["z"]];return[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},bh=function(a,b,c,d){d==null&&(d=c);return[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},bi=a._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return bh(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return bh(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return bg(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return bg(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return bg(b.x,b.y,b.width,b.height)}},bj=a.mapPath=function(a,b){if(!b)return a;var c,d,e,f,g,h,i;a=bR(a);for(e=0,g=a.length;e<g;e++){i=a[e];for(f=1,h=i.length;f<h;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d}return a};a._g=h,a.type=h.win.SVGAngle||h.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML";if(a.type=="VML"){var bk=h.doc.createElement("div"),bl;bk.innerHTML='<v:shape adj="1"/>',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(f<d)return c-f;if(f>b-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("<body>"),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r<t)&&(z+=180);return{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:x,y:y},alpha:z}},a.bezierBBox=function(b,c,d,e,f,g,h,i){a.is(b,"array")||(b=[b,c,d,e,f,g,h,i]);var j=bQ.apply(null,b);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},a.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.x<c.x2&&b.x>c.x||c.x<b.x2&&c.x>b.x)&&(b.y<c.y2&&b.y>c.y||c.y<b.y2&&c.y>b.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h<i;h++){g=a[h];if(g[0]=="M")c=g[1],d=g[2],e.push(c),f.push(d);else{var j=bQ(c,d,g[1],g[2],g[3],g[4],g[5],g[6]);e=e[n](j.min.x,j.max.x),f=f[n](j.min.y,j.max.y),c=g[5],d=g[6]}}var k=y[m](0,e),l=y[m](0,f),o=x[m](0,e),p=x[m](0,f),q={x:k,y:l,x2:o,y2:p,width:o-k,height:p-l};b.bbox=bm(q);return q},bJ=function(b){var c=bm(b);c.toString=a._path2string;return c},bK=a._pathToRelative=function(b){var c=bz(b);if(c.rel)return bJ(c.rel);if(!a.is(b,E)||!a.is(b&&b[0],E))b=a.parsePathString(b);var d=[],e=0,f=0,g=0,h=0,i=0;b[0][0]=="M"&&(e=b[0][1],f=b[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=b.length;j<k;j++){var l=d[j]=[],m=b[j];if(m[0]!=v.call(m[0])){l[0]=v.call(m[0]);switch(l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;n<o;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}}else{l=d[j]=[],m[0]=="m"&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;p<q;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}d.toString=a._path2string,c.rel=bJ(d);return d},bL=a._pathToAbsolute=function(b){var c=bz(b);if(c.abs)return bJ(c.abs);if(!a.is(b,E)||!a.is(b&&b[0],E))b=a.parsePathString(b);if(!b||!b.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,h=0,i=0;b[0][0]=="M"&&(e=+b[0][1],f=+b[0][2],g=e,h=f,i++,d[0]=["M",e,f]);var j=b.length==3&&b[0][0]=="M"&&b[1][0].toUpperCase()=="R"&&b[2][0].toUpperCase()=="Z";for(var k,l,m=i,o=b.length;m<o;m++){d.push(k=[]),l=b[m];if(l[0]!=S.call(l[0])){k[0]=S.call(l[0]);switch(k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":var p=[e,f][n](l.slice(1));for(var q=2,r=p.length;q<r;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[n](by(p,j));break;case"M":g=+l[1]+e,h=+l[2]+f;default:for(q=1,r=l.length;q<r;q++)k[q]=+l[q]+(q%2?e:f)}}else if(l[0]=="R")p=[e,f][n](l.slice(1)),d.pop(),d=d[n](by(p,j)),k=["R"][n](l.slice(-2));else for(var s=0,t=l.length;s<t;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=h;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],h=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}d.toString=a._path2string,c.abs=bJ(d);return d},bM=function(a,b,c,d){return[a,b,c,d,c,d]},bN=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},bO=function(a,b,c,d,e,f,g,h,i,j){var k=B*120/180,l=B/180*(+e||0),m=[],o,p=bv(function(a,b,c){var d=a*w.cos(c)-b*w.sin(c),e=a*w.sin(c)+b*w.cos(c);return{x:d,y:e}});if(!j){o=p(a,b,-l),a=o.x,b=o.y,o=p(h,i,-l),h=o.x,i=o.y;var q=w.cos(B/180*e),r=w.sin(B/180*e),t=(a-h)/2,u=(b-i)/2,v=t*t/(c*c)+u*u/(d*d);v>1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=a<C?B-E:E,F=h<C?B-F:F,E<0&&(E=B*2+E),F<0&&(F=B*2+F),g&&E>F&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W<X;W++)V[W]=W%2?p(m[W-1],m[W],l).y:p(m[W],m[W+1],l).x;return V},bP=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:A(j,3)*a+A(j,2)*3*i*c+j*3*i*i*e+A(i,3)*g,y:A(j,3)*b+A(j,2)*3*i*d+j*3*i*i*f+A(i,3)*h}},bQ=bv(function(a,b,c,d,e,f,g,h){var i=e-2*c+a-(g-2*e+c),j=2*(c-a)-2*(e-c),k=a-c,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,o=[b,h],p=[a,g],q;z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);k<l;k++){d[k]=h(d[k],f),i(d,k),e&&(e[k]=h(e[k],g)),e&&i(e,k),j(d,e,f,g,k),j(e,d,g,f,k);var o=d[k],p=e&&e[k],q=o.length,r=e&&p.length;f.x=o[q-2],f.y=o[q-1],f.bx=Q(o[q-4])||f.x,f.by=Q(o[q-3])||f.y,g.bx=e&&(Q(p[r-4])||g.x),g.by=e&&(Q(p[r-3])||g.y),g.x=e&&p[r-2],g.y=e&&p[r-1]}e||(c.curve=bJ(d));return e?[d,e]:d},null,bJ),bS=a._parseDots=bv(function(b){var c=[];for(var d=0,e=b.length;d<e;d++){var f={},g=b[d].match(/^([^:]*):?([\d\.]*)/);f.color=a.getRGB(g[1]);if(f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),c.push(f)}for(d=1,e=c.length-1;d<e;d++)if(!c[d].offset){var h=Q(c[d-1].offset||0),i=0;for(var j=d+1;j<e;j++)if(c[j].offset){i=c[j].offset;break}i||(i=100,j=e),i=Q(i);var k=(i-h)/(j-d+1);for(;d<j;d++)h+=k,c[d].offset=h+"%"}return c}),bT=a._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)},bU=a._tofront=function(a,b){b.top!==a&&(bT(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},bV=a._toback=function(a,b){b.bottom!==a&&(bT(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},bW=a._insertafter=function(a,b,c){bT(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},bX=a._insertbefore=function(a,b,c){bT(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},bY=a.toMatrix=function(a,b){var c=bI(a),d={_:{transform:p},getBBox:function(){return c}};b$(d,b);return d.matrix},bZ=a.transformPath=function(a,b){return bj(a,bY(a,b))},b$=a._extractTransform=function(b,c){if(c==null)return b._.transform;c=r(c).replace(/\.{3}|\u2026/g,b._.transform||p);var d=a.parseTransformString(c),e=0,f=0,g=0,h=1,i=1,j=b._,k=new cb;j.transform=d||[];if(d)for(var l=0,m=d.length;l<m;l++){var n=d[l],o=n.length,q=r(n[0]).toLowerCase(),s=n[0]!=q,t=s?k.invert():0,u,v,w,x,y;q=="t"&&o==3?s?(u=t.x(0,0),v=t.y(0,0),w=t.x(n[1],n[2]),x=t.y(n[1],n[2]),k.translate(w-u,x-v)):k.translate(n[1],n[2]):q=="r"?o==2?(y=y||b.getBBox(1),k.rotate(n[1],y.x+y.width/2,y.y+y.height/2),e+=n[1]):o==4&&(s?(w=t.x(n[2],n[3]),x=t.y(n[2],n[3]),k.rotate(n[1],w,x)):k.rotate(n[1],n[2],n[3]),e+=n[1]):q=="s"?o==2||o==3?(y=y||b.getBBox(1),k.scale(n[1],n[o-1],y.x+y.width/2,y.y+y.height/2),h*=n[1],i*=n[o-1]):o==5&&(s?(w=t.x(n[3],n[4]),x=t.y(n[3],n[4]),k.scale(n[1],n[2],w,x)):k.scale(n[1],n[2],n[3],n[4]),h*=n[1],i*=n[2]):q=="m"&&o==7&&k.add(n[1],n[2],n[3],n[4],n[5],n[6]),j.dirtyT=1,b.matrix=k}b.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,h==1&&i==1&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1},b_=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return a.length==4?[b,0,a[2],a[3]]:[b,0];case"s":return a.length==5?[b,1,1,a[3],a[4]]:a.length==3?[b,1,1]:[b,1]}},ca=a._equaliseTransform=function(b,c){c=r(c).replace(/\.{3}|\u2026/g,b),b=a.parseTransformString(b)||[],c=a.parseTransformString(c)||[];var d=x(b.length,c.length),e=[],f=[],g=0,h,i,j,k;for(;g<d;g++){j=b[g]||b_(c[g]),k=c[g]||b_(j);if(j[0]!=k[0]||j[0].toLowerCase()=="r"&&(j[2]!=k[2]||j[3]!=k[3])||j[0].toLowerCase()=="s"&&(j[3]!=k[3]||j[4]!=k[4]))return;e[g]=[],f[g]=[];for(h=0,i=x(j.length,k.length);h<i;h++)h in j&&(e[g][h]=j[h]),h in k&&(f[g][h]=k[h])}return{from:e,to:f}};a._getContainer=function(b,c,d,e){var f;f=e==null&&!a.is(b,"object")?h.doc.getElementById(b):b;if(f!=null){if(f.tagName)return c==null?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d};return{container:1,x:b,y:c,width:d,height:e}}},a.pathToRelative=bK,a._engine={},a.path2curve=bR,a.matrix=function(a,b,c,d,e,f){return new cb(a,b,c,d,e,f)},function(b){function d(a){var b=w.sqrt(c(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}function c(a){return a[0]*a[0]+a[1]*a[1]}b.add=function(a,b,c,d,e,f){var g=[[],[],[]],h=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],i=[[a,c,e],[b,d,f],[0,0,1]],j,k,l,m;a&&a instanceof cb&&(i=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]);for(j=0;j<3;j++)for(k=0;k<3;k++){m=0;for(l=0;l<3;l++)m+=h[j][l]*i[l][k];g[j][k]=m}this.a=g[0][0],this.b=g[1][0],this.c=g[0][1],this.d=g[1][1],this.e=g[0][2],this.f=g[1][2]},b.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new cb(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},b.clone=function(){return new cb(this.a,this.b,this.c,this.d,this.e,this.f)},b.translate=function(a,b){this.add(1,0,0,1,a,b)},b.scale=function(a,b,c,d){b==null&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},b.rotate=function(b,c,d){b=a.rad(b),c=c||0,d=d||0;var e=+w.cos(b).toFixed(9),f=+w.sin(b).toFixed(9);this.add(e,f,-f,e,c,d),this.add(1,0,0,1,-c,-d)},b.x=function(a,b){return a*this.a+b*this.c+this.e},b.y=function(a,b){return a*this.b+b*this.d+this.f},b.get=function(a){return+this[r.fromCharCode(97+a)].toFixed(4)},b.toString=function(){return a.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},b.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},b.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},b.split=function(){var b={};b.dx=this.e,b.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];b.scalex=w.sqrt(c(e[0])),d(e[0]),b.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*b.shear,e[1][1]-e[0][1]*b.shear],b.scaley=w.sqrt(c(e[1])),d(e[1]),b.shear/=b.scaley;var f=-e[0][1],g=e[1][1];g<0?(b.rotate=a.deg(w.acos(g)),f<0&&(b.rotate=360-b.rotate)):b.rotate=a.deg(w.asin(f)),b.isSimple=!+b.shear.toFixed(9)&&(b.scalex.toFixed(9)==b.scaley.toFixed(9)||!b.rotate),b.isSuperSimple=!+b.shear.toFixed(9)&&b.scalex.toFixed(9)==b.scaley.toFixed(9)&&!b.rotate,b.noRotation=!+b.shear.toFixed(9)&&!b.rotate;return b},b.toTransformString=function(a){var b=a||this[s]();if(b.isSimple){b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4);return(b.dx||b.dy?"t"+[b.dx,b.dy]:p)+(b.scalex!=1||b.scaley!=1?"s"+[b.scalex,b.scaley,0,0]:p)+(b.rotate?"r"+[b.rotate,0,0]:p)}return"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(cb.prototype);var cc=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);navigator.vendor=="Apple Computer, Inc."&&(cc&&cc[1]<4||navigator.platform.slice(0,2)=="iP")||navigator.vendor=="Google Inc."&&cc&&cc[1]<8?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:k.safari=be;var cd=function(){this.returnValue=!1},ce=function(){return this.originalEvent.preventDefault()},cf=function(){this.cancelBubble=!0},cg=function(){return this.originalEvent.stopPropagation()},ch=function(){if(h.doc.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,f=function(e){var f=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,i=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,j=e.clientX+i,k=e.clientY+f;if(o&&u[g](b))for(var l=0,m=e.targetTouches&&e.targetTouches.length;l<m;l++)if(e.targetTouches[l].target==a){var n=e;e=e.targetTouches[l],e.originalEvent=n,e.preventDefault=ce,e.stopPropagation=cg;break}return c.call(d,e,j,k)};a.addEventListener(e,f,!1);return function(){a.removeEventListener(e,f,!1);return!0}};if(h.doc.attachEvent)return function(a,b,c,d){var e=function(a){a=a||h.win.event;var b=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,e=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;a.preventDefault=a.preventDefault||cd,a.stopPropagation=a.stopPropagation||cf;return c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){a.detachEvent("on"+b,e);return!0};return f}}(),ci=[],cj=function(a){var b=a.clientX,c=a.clientY,d=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,e=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft,f,g=ci.length;while(g--){f=ci[g];if(o){var i=a.touches.length,j;while(i--){j=a.touches[i];if(j.identifier==f.el._drag.id){b=j.clientX,c=j.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}}else a.preventDefault();var k=f.el.node,l,m=k.nextSibling,n=k.parentNode,p=k.style.display;h.win.opera&&n.removeChild(k),k.style.display="none",l=f.el.paper.getElementByPoint(b,c),k.style.display=p,h.win.opera&&(m?n.insertBefore(k,m):n.appendChild(k)),l&&eve("raphael.drag.over."+f.el.id,f.el,l),b+=e,c+=d,eve("raphael.drag.move."+f.el.id,f.move_scope||f.el,b-f.el._drag.x,c-f.el._drag.y,b,c,a)}},ck=function(b){a.unmousemove(cj).unmouseup(ck);var c=ci.length,d;while(c--)d=ci[c],d.el._drag={},eve("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,b);ci=[]},cl=a.el={};for(var cm=t.length;cm--;)(function(b){a[b]=cl[b]=function(c,d){a.is(c,"function")&&(this.events=this.events||[],this.events.push({name:b,f:c,unbind:ch(this.shape||this.node||h.doc,b,c,d||this)}));return this},a["un"+b]=cl["un"+b]=function(a){var c=this.events||[],d=c.length;while(d--)if(c[d].name==b&&c[d].f==a){c[d].unbind(),c.splice(d,1),!c.length&&delete this.events;return this}return this}})(t[cm]);cl.data=function(b,c){var d=bb[this.id]=bb[this.id]||{};if(arguments.length==1){if(a.is(b,"object")){for(var e in b)b[g](e)&&this.data(e,b[e]);return this}eve("raphael.data.get."+this.id,this,d[b],b);return d[b]}d[b]=c,eve("raphael.data.set."+this.id,this,c,b);return this},cl.removeData=function(a){a==null?bb[this.id]={}:bb[this.id]&&delete bb[this.id][a];return this},cl.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},cl.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var cn=[];cl.drag=function(b,c,d,e,f,g){function i(i){(i.originalEvent||i).preventDefault();var j=h.doc.documentElement.scrollTop||h.doc.body.scrollTop,k=h.doc.documentElement.scrollLeft||h.doc.body.scrollLeft;this._drag.x=i.clientX+k,this._drag.y=i.clientY+j,this._drag.id=i.identifier,!ci.length&&a.mousemove(cj).mouseup(ck),ci.push({el:this,move_scope:e,start_scope:f,end_scope:g}),c&&eve.on("raphael.drag.start."+this.id,c),b&&eve.on("raphael.drag.move."+this.id,b),d&&eve.on("raphael.drag.end."+this.id,d),eve("raphael.drag.start."+this.id,f||e||this,i.clientX+k,i.clientY+j,i)}this._drag={},cn.push({el:this,start:i}),this.mousedown(i);return this},cl.onDragOver=function(a){a?eve.on("raphael.drag.over."+this.id,a):eve.unbind("raphael.drag.over."+this.id)},cl.undrag=function(){var b=cn.length;while(b--)cn[b].el==this&&(this.unmousedown(cn[b].start),cn.splice(b,1),eve.unbind("raphael.drag.*."+this.id));!cn.length&&a.unmousemove(cj).unmouseup(ck)},k.circle=function(b,c,d){var e=a._engine.circle(this,b||0,c||0,d||0);this.__set__&&this.__set__.push(e);return e},k.rect=function(b,c,d,e,f){var g=a._engine.rect(this,b||0,c||0,d||0,e||0,f||0);this.__set__&&this.__set__.push(g);return g},k.ellipse=function(b,c,d,e){var f=a._engine.ellipse(this,b||0,c||0,d||0,e||0);this.__set__&&this.__set__.push(f);return f},k.path=function(b){b&&!a.is(b,D)&&!a.is(b[0],E)&&(b+=p);var c=a._engine.path(a.format[m](a,arguments),this);this.__set__&&this.__set__.push(c);return c},k.image=function(b,c,d,e,f){var g=a._engine.image(this,b||"about:blank",c||0,d||0,e||0,f||0);this.__set__&&this.__set__.push(g);return g},k.text=function(b,c,d){var e=a._engine.text(this,b||0,c||0,r(d));this.__set__&&this.__set__.push(e);return e},k.set=function(b){!a.is(b,"array")&&(b=Array.prototype.splice.call(arguments,0,arguments.length));var c=new cG(b);this.__set__&&this.__set__.push(c);return c},k.setStart=function(a){this.__set__=a||this.set()},k.setFinish=function(a){var b=this.__set__;delete this.__set__;return b},k.setSize=function(b,c){return a._engine.setSize.call(this,b,c)},k.setViewBox=function(b,c,d,e,f){return a._engine.setViewBox.call(this,b,c,d,e,f)},k.top=k.bottom=null,k.raphael=a;var co=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,i=b.top+(h.win.pageYOffset||e.scrollTop||d.scrollTop)-f,j=b.left+(h.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:i,x:j}};k.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=h.doc.elementFromPoint(a,b);if(h.win.opera&&e.tagName=="svg"){var f=co(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var i=d.getIntersectionList(g,null);i.length&&(e=i[i.length-1])}if(!e)return null;while(e.parentNode&&e!=d.parentNode&&!e.raphael)e=e.parentNode;e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null;return e},k.getById=function(a){var b=this.bottom;while(b){if(b.id==a)return b;b=b.next}return null},k.forEach=function(a,b){var c=this.bottom;while(c){if(a.call(b,c)===!1)return this;c=c.next}return this},k.getElementsByPoint=function(a,b){var c=this.set();this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)});return c},cl.isPointInside=function(b,c){var d=this.realPath=this.realPath||bi[this.type](this);return a.isPointInsidePath(d,b,c)},cl.getBBox=function(a){if(this.removed)return{};var b=this._;if(a){if(b.dirty||!b.bboxwt)this.realPath=bi[this.type](this),b.bboxwt=bI(this.realPath),b.bboxwt.toString=cq,b.dirty=0;return b.bboxwt}if(b.dirty||b.dirtyT||!b.bbox){if(b.dirty||!this.realPath)b.bboxwt=0,this.realPath=bi[this.type](this);b.bbox=bI(bj(this.realPath,this.matrix)),b.bbox.toString=cq,b.dirty=b.dirtyT=0}return b.bbox},cl.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());this.__set__&&this.__set__.push(a);return a},cl.glow=function(a){if(this.type=="text")return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||bi[this.type](this);f=this.matrix?bj(f,this.matrix):f;for(var g=1;g<c+1;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cr={},cs=function(b,c,d,e,f,g,h,i,j){return j==null?bB(b,c,d,e,f,g,h,i):a.findDotsAtSegment(b,c,d,e,f,g,h,i,bC(b,c,d,e,f,g,h,i,j))},ct=function(b,c){return function(d,e,f){d=bR(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;o<p;o++){i=d[o];if(i[0]=="M")g=+i[1],h=+i[2];else{j=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6]);if(n+j>e){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c<cy.length;c++){var d=cy[c];if(d.el.removed||d.paused)continue;var e=b-d.start,f=d.ms,h=d.easing,i=d.from,j=d.diff,k=d.to,l=d.t,m=d.el,o={},p,r={},s;d.initstatus?(e=(d.initstatus*d.anim.top-d.prev)/(d.percent-d.prev)*f,d.status=d.initstatus,delete d.initstatus,d.stop&&cy.splice(c--,1)):d.status=(d.prev+(d.percent-d.prev)*(e/f))/d.anim.top;if(e<0)continue;if(e<f){var t=h(e/f);for(var u in i)if(i[g](u)){switch(U[u]){case C:p=+i[u]+t*f*j[u];break;case"colour":p="rgb("+[cB(O(i[u].r+t*f*j[u].r)),cB(O(i[u].g+t*f*j[u].g)),cB(O(i[u].b+t*f*j[u].b))].join(",")+")";break;case"path":p=[];for(var v=0,w=i[u].length;v<w;v++){p[v]=[i[u][v][0]];for(var x=1,y=i[u][v].length;x<y;x++)p[v][x]=+i[u][v][x]+t*f*j[u][v][x];p[v]=p[v].join(q)}p=p.join(q);break;case"transform":if(j[u].real){p=[];for(v=0,w=i[u].length;v<w;v++){p[v]=[i[u][v][0]];for(x=1,y=i[u][v].length;x<y;x++)p[v][x]=i[u][v][x]+t*f*j[u][v][x]}}else{var z=function(a){return+i[u][a]+t*f*j[u][a]};p=[["m",z(0),z(1),z(2),z(3),z(4),z(5)]]}break;case"csv":if(u=="clip-rect"){p=[],v=4;while(v--)p[v]=+i[u][v]+t*f*j[u][v]}break;default:var A=[][n](i[u]);p=[],v=m.paper.customAttributes[u].length;while(v--)p[v]=+A[v]+t*f*j[u][v]}o[u]=p}m.attr(o),function(a,b,c){setTimeout(function(){eve("raphael.anim.frame."+a,b,c)})}(m.id,m,d.anim)}else{(function(b,c,d){setTimeout(function(){eve("raphael.anim.frame."+c.id,c,d),eve("raphael.anim.finish."+c.id,c,d),a.is(b,"function")&&b.call(c)})})(d.callback,m,d.anim),m.attr(k),cy.splice(c--,1);if(d.repeat>1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l<m;l++)if(cy[l].anim==c&&cy[l].el==b){cy[m-1].start=cy[l].start;break}return h},cl.onAnimation=function(a){a?eve.on("raphael.anim.frame."+this.id,a):eve.unbind("raphael.anim.frame."+this.id);return this},cD.prototype.delay=function(a){var b=new cD(this.anim,this.ms);b.times=this.times,b.del=+a||0;return b},cD.prototype.repeat=function(a){var b=new cD(this.anim,this.ms);b.del=this.del,b.times=w.floor(x(a,0))||1;return b},a.animation=function(b,c,d,e){if(b instanceof cD)return b;if(a.is(d,"function")||!d)e=e||d||null,d=null;b=Object(b),c=+c||0;var f={},h,i;for(i in b)b[g](i)&&Q(i)!=i&&Q(i)+"%"!=i&&(h=!0,f[i]=b[i]);if(!h)return new cD(b,c);d&&(f.easing=d),e&&(f.callback=e);return new cD({100:f},c)},cl.animate=function(b,c,d,e){var f=this;if(f.removed){e&&e.call(f);return f}var g=b instanceof cD?b:a.animation(b,c,d,e);cE(g,f,g.percents[0],null,f.attr());return f},cl.setTime=function(a,b){a&&b!=null&&this.status(a,y(b,a.ms)/a.ms);return this},cl.status=function(a,b){var c=[],d=0,e,f;if(b!=null){cE(a,this,-1,y(b,1));return this}e=cy.length;for(;d<e;d++){f=cy[d];if(f.el.id==this.id&&(!a||f.anim==a)){if(a)return f.status;c.push({anim:f.anim,status:f.status})}}if(a)return 0;return c},cl.pause=function(a){for(var b=0;b<cy.length;b++)cy[b].el.id==this.id&&(!a||cy[b].anim==a)&&eve("raphael.anim.pause."+this.id,this,cy[b].anim)!==!1&&(cy[b].paused=!0);return this},cl.resume=function(a){for(var b=0;b<cy.length;b++)if(cy[b].el.id==this.id&&(!a||cy[b].anim==a)){var c=cy[b];eve("raphael.anim.resume."+this.id,this,c.anim)!==!1&&(delete c.paused,this.status(c.anim,c.status))}return this},cl.stop=function(a){for(var b=0;b<cy.length;b++)cy[b].el.id==this.id&&(!a||cy[b].anim==a)&&eve("raphael.anim.stop."+this.id,this,cy[b].anim)!==!1&&cy.splice(b--,1);return this},eve.on("raphael.remove",cF),eve.on("raphael.clear",cF),cl.toString=function(){return"Raphaël’s object"};var cG=function(a){this.items=[],this.length=0,this.type="set";if(a)for(var b=0,c=a.length;b<c;b++)a[b]&&(a[b].constructor==cl.constructor||a[b].constructor==cG)&&(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},cH=cG.prototype;cH.push=function(){var a,b;for(var c=0,d=arguments.length;c<d;c++)a=arguments[c],a&&(a.constructor==cl.constructor||a.constructor==cG)&&(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},cH.pop=function(){this.length&&delete this[this.length--];return this.items.pop()},cH.forEach=function(a,b){for(var c=0,d=this.items.length;c<d;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var cI in cl)cl[g](cI)&&(cH[cI]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][m](c,b)})}}(cI));cH.attr=function(b,c){if(b&&a.is(b,E)&&a.is(b[0],"object"))for(var d=0,e=b.length;d<e;d++)this.items[d].attr(b[d]);else for(var f=0,g=this.items.length;f<g;f++)this.items[f].attr(b,c);return this},cH.clear=function(){while(this.length)this.pop()},cH.splice=function(a,b,c){a=a<0?x(this.length+a,0):a,b=x(0,y(this.length-a,b));var d=[],e=[],f=[],g;for(g=2;g<arguments.length;g++)f.push(arguments[g]);for(g=0;g<b;g++)e.push(this[a+g]);for(;g<this.length-a;g++)d.push(this[a+g]);var h=f.length;for(g=0;g<h+d.length;g++)this.items[a+g]=this[a+g]=g<h?f[g]:d[g-h];g=this.items.length=this.length-=b-h;while(this[g])delete this[g++];return new cG(e)},cH.exclude=function(a){for(var b=0,c=this.length;b<c;b++)if(this[b]==a){this.splice(b,1);return!0}},cH.animate=function(b,c,d,e){(a.is(d,"function")||!d)&&(e=d||null);var f=this.items.length,g=f,h,i=this,j;if(!f)return this;e&&(j=function(){!--f&&e.call(i)}),d=a.is(d,D)?d:j;var k=a.animation(b,c,d,j);h=this.items[--g].animate(k);while(g--)this.items[g]&&!this.items[g].removed&&this.items[g].animateWith(h,k,k);return this},cH.insertAfter=function(a){var b=this.items.length;while(b--)this.items[b].insertAfter(a);return this},cH.getBBox=function(){var a=[],b=[],c=[],d=[];for(var e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}a=y[m](0,a),b=y[m](0,b),c=x[m](0,c),d=x[m](0,d);return{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},cH.clone=function(a){a=new cG;for(var b=0,c=this.items.length;b<c;b++)a.push(this.items[b].clone());return a},cH.toString=function(){return"Raphaël‘s set"},a.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[g](d)&&(b.face[d]=a.face[d]);this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b];if(!a.svg){b.face["units-per-em"]=R(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[g](e)){var f=a.glyphs[e];b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"};if(f.k)for(var h in f.k)f[g](h)&&(b.glyphs[e].k[h]=f.k[h])}}return a},k.getFont=function(b,c,d,e){e=e||"normal",d=d||"normal",c=+c||{normal:400,bold:700,lighter:300,bolder:800}[c]||400;if(!!a.fonts){var f=a.fonts[b];if(!f){var h=new RegExp("(^|\\s)"+b.replace(/[^\w\d\s+!~.:_-]/g,p)+"(\\s|$)","i");for(var i in a.fonts)if(a.fonts[g](i)&&h.test(i)){f=a.fonts[i];break}}var j;if(f)for(var k=0,l=f.length;k<l;k++){j=f[k];if(j.face["font-weight"]==c&&(j.face["font-style"]==d||!j.face["font-style"])&&j.face["font-stretch"]==e)break}return j}},k.print=function(b,d,e,f,g,h,i){h=h||"middle",i=x(y(i||0,1),-1);var j=r(e)[s](p),k=0,l=0,m=p,n;a.is(f,e)&&(f=this.getFont(f));if(f){n=(g||16)/f.face["units-per-em"];var o=f.face.bbox[s](c),q=+o[0],t=o[3]-o[1],u=0,v=+o[1]+(h=="baseline"?t+ +f.face.descent:t/2);for(var w=0,z=j.length;w<z;w++){if(j[w]=="\n")k=0,B=0,l=0,u+=t;else{var A=l&&f.glyphs[j[w-1]]||{},B=f.glyphs[j[w]];k+=l?(A.w||f.w)+(A.k&&A.k[j[w]]||0)+f.w*i:0,l=1}B&&B.d&&(m+=a.transformPath(B.d,["t",k*n,u*n,"s",n,n,q,v,"t",(b-q)/n,(d-v)/n]))}}return this.path(m).attr({fill:"#000",stroke:"none"})},k.add=function(b){if(a.is(b,"array")){var c=this.set(),e=0,f=b.length,h;for(;e<f;e++)h=b[e]||{},d[g](h.type)&&c.push(this[h.type]().attr(h))}return c},a.format=function(b,c){var d=a.is(c,E)?[0][n](c):arguments;b&&a.is(b,D)&&d.length-1&&(b=b.replace(e,function(a,b){return d[++b]==null?p:d[b]}));return b||p},a.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),typeof e=="function"&&f&&(e=e()))}),e=(e==null||e==d?a:e)+"";return e};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),a.ninja=function(){i.was?h.win.Raphael=i.is:delete Raphael;return a},a.st=cH,function(b,c,d){function e(){/in/.test(b.readyState)?setTimeout(e,9):a.eve("raphael.DOMload")}b.readyState==null&&b.addEventListener&&(b.addEventListener(c,d=function(){b.removeEventListener(c,d,!1),b.readyState="complete"},!1),b.readyState="loading"),e()}(document,"DOMContentLoaded"),i.was?h.win.Raphael=a:Raphael=a,eve.on("raphael.DOMload",function(){b=!0})}(),window.Raphael.svg&&function(a){var b="hasOwnProperty",c=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=a.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};a.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){typeof d=="string"&&(d=q(d));for(var f in e)e[b](f)&&(f.substring(0,6)=="xlink:"?d.setAttributeNS(n,f.substring(6),c(e[f])):d.setAttribute(f,c(e[f])))}else d=a._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(b,e){var j="linear",k=b.id+e,m=.5,n=.5,o=b.node,p=b.paper,r=o.style,s=a._g.doc.getElementById(k);if(!s){e=c(e).replace(a._radial_gradient,function(a,b,c){j="radial";if(b&&c){m=d(b),n=d(c);var e=(n>.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x<y;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}q(o,{fill:"url(#"+k+")",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1;return 1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if(d.type=="path"){var g=c(e).toLowerCase().split("-"),h=d.paper,i=f?"end":"start",j=d.node,k=d.attrs,m=k["stroke-width"],n=g.length,r="classic",s,t,u,v,w,x=3,y=3,z=5;while(n--)switch(g[n]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":r=g[n];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}r=="open"?(x+=2,y+=2,z+=2,u=1,v=f?4:1,w={fill:"none",stroke:k.stroke}):(v=u=x/2,w={fill:k.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={};if(r!="none"){var A="raphael-marker-"+r,B="raphael-marker-"+i+r+x+y;a._g.doc.getElementById(A)?p[A]++:(h.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[r],id:A})),p[A]=1);var C=a._g.doc.getElementById(B),D;C?(p[B]++,D=C.getElementsByTagName("use")[0]):(C=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:v,refY:y/2}),D=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),C.appendChild(D),h.defs.appendChild(C),p[B]=1),q(D,w);var F=u*(r!="diamond"&&r!="oval");f?(s=d._.arrows.startdx*m||0,t=a.getTotalLength(k.path)-F*m):(s=F*m,t=a.getTotalLength(k.path)-(d._.arrows.enddx*m||0)),w={},w["marker-"+i]="url(#"+B+")";if(t||s)w.d=Raphael.getSubpath(k.path,s,t);q(j,w),d._.arrows[i+"Path"]=A,d._.arrows[i+"Marker"]=B,d._.arrows[i+"dx"]=F,d._.arrows[i+"Type"]=r,d._.arrows[i+"String"]=e}else f?(s=d._.arrows.startdx*m||0,t=a.getTotalLength(k.path)-s):(s=0,t=a.getTotalLength(k.path)-(d._.arrows.enddx*m||0)),d._.arrows[i+"Path"]&&q(j,{d:Raphael.getSubpath(k.path,s,t)}),delete d._.arrows[i+"Path"],delete d._.arrows[i+"Marker"],delete d._.arrows[i+"dx"],delete d._.arrows[i+"Type"],delete d._.arrows[i+"String"];for(w in p)if(p[b](w)&&!p[w]){var G=a._g.doc.getElementById(w);G&&G.parentNode.removeChild(G)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,b,d){b=u[c(b).toLowerCase()];if(b){var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=b.length;while(h--)g[h]=b[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[b](o)){if(!a._availableAttrs[b](o))continue;var p=f[o];k[o]=p;switch(o){case"blur":d.blur(p);break;case"href":case"title":case"target":var u=i.parentNode;if(u.tagName.toLowerCase()!="a"){var w=q("a");u.insertBefore(w,i),w.appendChild(i),u=w}o=="target"?u.setAttributeNS(n,"show",p=="blank"?"new":p):u.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var x=c(p).split(j);if(x.length==4){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var z=q("clipPath"),A=q("rect");z.id=a.createUUID(),q(A,{x:x[0],y:x[1],width:x[2],height:x[3]}),z.appendChild(A),d.paper.defs.appendChild(z),q(i,{"clip-path":"url(#"+z.id+")"}),d.clip=A}if(!p){var B=i.getAttribute("clip-path");if(B){var C=a._g.doc.getElementById(B.replace(/(^url\(#|\)$)/g,l));C&&C.parentNode.removeChild(C),q(i,{"clip-path":l}),delete d.clip}}break;case"path":d.type=="path"&&(q(i,{d:p?k.path=a._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":i.setAttribute(o,p),d._.dirty=1;if(k.fx)o="x",p=k.x;else break;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if(o=="rx"&&d.type=="rect")break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":i.setAttribute(o,p),d._.dirty=1;if(k.fy)o="y",p=k.y;else break;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if(o=="ry"&&d.type=="rect")break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":d.type=="rect"?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":d.type=="image"&&i.setAttributeNS(n,"href",p);break;case"stroke-width":if(d._.sx!=1||d._.sy!=1)p/=g(h(d._.sx),h(d._.sy))||1;d.paper._vbSize&&(p*=d.paper._vbSize),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var D=c(p).match(a._ISURL);if(D){z=q("pattern");var F=q("image");z.id=a.createUUID(),q(z,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(F,{x:0,y:0,"xlink:href":D[1]}),z.appendChild(F),function(b){a._preload(D[1],function(){var a=this.offsetWidth,c=this.offsetHeight;q(b,{width:a,height:c}),q(F,{width:a,height:c}),d.paper.safari()})}(z),d.paper.defs.appendChild(z),q(i,{fill:"url(#"+z.id+")"}),d.pattern=z,d.pattern&&s(d);break}var G=a.getRGB(p);if(!G.error)delete f.gradient,delete k.gradient,!a.is(k.opacity,"undefined")&&a.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!a.is(k["fill-opacity"],"undefined")&&a.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});else if((d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(H){var I=H.getElementsByTagName("stop");q(I[I.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}G[b]("opacity")&&q(i,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n<o;n++)m=q("tspan"),n&&q(m,{dy:i*x,x:g.x}),m.appendChild(a._g.doc.createTextNode(j[n])),h.appendChild(m),k[n]=m}else{k=h.getElementsByTagName("tspan");for(n=0,o=k.length;n<o;n++)n?q(k[n],{dy:i*x,x:g.x}):q(k[0],{dy:0})}q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&a.is(r,"finite")&&q(k[0],{dy:r})}},z=function(b,c){var d=0,e=0;this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.matrix=a.matrix(),this.realPath=null,this.paper=c,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},A=a.el;z.prototype=A,A.constructor=z,a._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new z(c,b);d.type="path",w(d,{fill:"none",stroke:"#000",path:a});return d},A.rotate=function(a,b,e){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),e==null&&(b=e);if(b==null||e==null){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}this.transform(this._.transform.concat([["r",a,b,e]]));return this},A.scale=function(a,b,e,f){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),b==null&&(b=a),f==null&&(e=f);if(e==null||f==null)var g=this.getBBox(1);e=e==null?g.x+g.width/2:e,f=f==null?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]]));return this},A.translate=function(a,b){if(this.removed)return this;a=c(a).split(j),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this.transform(this._.transform.concat([["t",a,b]]));return this},A.transform=function(c){var d=this._;if(c==null)return d.transform;a._extractTransform(this,c),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix});if(d.sx!=1||d.sy!=1){var e=this.attrs[b]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},A.hide=function(){!this.removed&&this.paper.safari(this.node.style.display="none");return this},A.show=function(){!this.removed&&this.paper.safari(this.node.style.display="");return this},A.remove=function(){if(!this.removed&&!!this.node.parentNode){var b=this.paper;b.__set__&&b.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&b.defs.removeChild(this.gradient),a._tear(this,b),this.node.parentNode.tagName.toLowerCase()=="a"?this.node.parentNode.parentNode.removeChild(this.node.parentNode):this.node.parentNode.removeChild(this.node);for(var c in this)this[c]=typeof this[c]=="function"?a._removedFactory(c):null;this.removed=!0}},A._getBBox=function(){if(this.node.style.display=="none"){this.show();var a=!0}var b={};try{b=this.node.getBBox()}catch(c){}finally{b=b||{}}a&&this.hide();return b},A.attr=function(c,d){if(this.removed)return this;if(c==null){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);e.gradient&&e.fill=="none"&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform;return e}if(d==null&&a.is(c,"string")){if(c=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;if(c=="transform")return this._.transform;var g=c.split(j),h={};for(var i=0,l=g.length;i<l;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return l-1?h:h[g[0]]}if(d==null&&a.is(c,"array")){h={};for(i=0,l=c.length;i<l;i++)h[c[i]]=this.attr(c[i]);return h}if(d!=null){var m={};m[c]=d}else c!=null&&a.is(c,"object")&&(m=c);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[b](n)&&m[b](n)&&a.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[b](p)&&(m[p]=o[p])}w(this,m);return this},A.toFront=function(){if(this.removed)return this;this.node.parentNode.tagName.toLowerCase()=="a"?this.node.parentNode.parentNode.appendChild(this.node.parentNode):this.node.parentNode.appendChild(this.node);var b=this.paper;b.top!=this&&a._tofront(this,b);return this},A.toBack=function(){if(this.removed)return this;var b=this.node.parentNode;b.tagName.toLowerCase()=="a"?b.parentNode.insertBefore(this.node.parentNode,this.node.parentNode.parentNode.firstChild):b.firstChild!=this.node&&b.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper);var c=this.paper;return this},A.insertAfter=function(b){if(this.removed)return this;var c=b.node||b[b.length-1].node;c.nextSibling?c.parentNode.insertBefore(this.node,c.nextSibling):c.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper);return this},A.insertBefore=function(b){if(this.removed)return this;var c=b.node||b[0].node;c.parentNode.insertBefore(this.node,c),a._insertbefore(this,b,this.paper);return this},A.blur=function(b){var c=this;if(+b!==0){var d=q("filter"),e=q("feGaussianBlur");c.attrs.blur=b,d.id=a.createUUID(),q(e,{stdDeviation:+b||1.5}),d.appendChild(e),c.paper.defs.appendChild(d),c._blur=d,q(c.node,{filter:"url(#"+d.id+")"})}else c._blur&&(c._blur.parentNode.removeChild(c._blur),delete c._blur,delete c.attrs.blur),c.node.removeAttribute("filter")},a._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new z(e,a);f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs);return f},a._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs);return h},a._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new z(f,a);g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs);return g},a._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new z(g,a);h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image";return h},a._engine.text=function(b,c,d,e){var f=q("text");b.canvas&&b.canvas.appendChild(f);var g=new z(f,b);g.attrs={x:c,y:d,"text-anchor":"middle",text:e,font:a._availableAttrs.font,stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs);return g},a._engine.setSize=function(a,b){this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox);return this},a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,g=b.height;if(!c)throw new Error("SVG container not found.");var h=q("svg"),i="overflow:hidden;",j;d=d||0,e=e||0,f=f||512,g=g||342,q(h,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg"}),c==1?(h.style.cssText=i+"position:absolute;left:"+d+"px;top:"+e+"px",a._g.doc.body.appendChild(h),j=1):(h.style.cssText=i+"position:relative",c.firstChild?c.insertBefore(h,c.firstChild):c.appendChild(h)),c=new a._Paper,c.width=f,c.height=g,c.canvas=h,c.clear(),c._left=c._top=0,j&&(c.renderfix=function(){}),c.renderfix();return c},a._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f=g(c/this.width,d/this.height),h=this.top,i=e?"meet":"xMinYMin",j,l;a==null?(this._vbSize&&(f=1),delete this._vbSize,j="0 0 "+this.width+m+this.height):(this._vbSize=f,j=a+m+b+m+c+m+d),q(this.canvas,{viewBox:j,preserveAspectRatio:i});while(f&&h)l="stroke-width"in h.attrs?h.attrs["stroke-width"]:1,h.attr({"stroke-width":l}),h._.dirty=1,h._.dirtyT=1,h=h.prev;this._viewBox=[a,b,c,d,!!e];return this},a.prototype.renderfix=function(){var a=this.canvas,b=a.style,c;try{c=a.getScreenCTM()||a.createSVGMatrix()}catch(d){c=a.createSVGMatrix()}var e=-c.e%1,f=-c.f%1;if(e||f)e&&(this._left=(this._left+e)%1,b.left=this._left+"px"),f&&(this._top=(this._top+f)%1,b.top=this._top+"px")},a.prototype.clear=function(){a.eve("raphael.clear",this);var b=this.canvas;while(b.firstChild)b.removeChild(b.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(a._g.doc.createTextNode("Created with Raphaël "+a.version)),b.appendChild(this.desc),b.appendChild(this.defs=q("defs"))},a.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null};var B=a.st;for(var C in A)A[b](C)&&!B[b](C)&&(B[C]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(C))}(window.Raphael),window.Raphael.vml&&function(a){var b="hasOwnProperty",c=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=a.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(b){var d=/[ahqstv]/ig,e=a._pathToAbsolute;c(b).match(d)&&(e=a._path2curve),d=/[clmz]/g;if(e==a._pathToAbsolute&&!c(b).match(d)){var g=c(b).replace(q,function(a,b,c){var d=[],e=b.toLowerCase()=="m",g=p[b];c.replace(s,function(a){e&&d.length==2&&(g+=d+p[b=="m"?"l":"L"],d=[]),d.push(f(a*u))});return g+d});return g}var h=e(b),i,j;g=[];for(var k=0,l=h.length;k<l;k++){i=h[k],j=h[k][0].toLowerCase(),j=="z"&&(j="x");for(var m=1,r=i.length;m<r;m++)j+=f(i[m]*u)+(m!=r-1?",":o);g.push(j)}return g.join(n)},y=function(b,c,d){var e=a.matrix();e.rotate(-b,.5,.5);return{dx:e.x(c,d),dy:e.y(c,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q,r=u/b,s=u/c;m.visibility="hidden";if(!!b&&!!c){l.coordsize=i(r)+n+i(s),m.rotation=f*(b*c<0?-1:1);if(f){var t=y(f,d,e);d=t.dx,e=t.dy}b<0&&(p+="x"),c<0&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-r+n+e*-s;if(k||g.fillsize){var v=l.getElementsByTagName(j);v=v&&v[0],l.removeChild(v),k&&(t=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),v.position=t.dx*o+n+t.dy*o),g.fillsize&&(v.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(v)}m.visibility="visible"}};a.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,b,d){var e=c(b).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";while(g--)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q,r=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),s=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),t=e;for(var y in i)i[b](y)&&(m[y]=i[y]);r&&(m.path=a._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur);if(i.path&&e.type=="path"||r)l.path=x(~c(m.path).toLowerCase().indexOf("r")?a._pathToAbsolute(m.path):m.path),e.type=="image"&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0));"transform"in i&&e.transform(i.transform);if(s){var B=+m.cx,D=+m.cy,E=+m.rx||+m.r||0,G=+m.ry||+m.r||0;l.path=a.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((B-E)*u),f((D-G)*u),f((B+E)*u),f((D+G)*u),f(B*u))}if("clip-rect"in i){var H=c(i["clip-rect"]).split(k);if(H.length==4){H[2]=+H[2]+ +H[0],H[3]=+H[3]+ +H[1];var I=l.clipRect||a._g.doc.createElement("div"),J=I.style;J.clip=a.format("rect({1}px {2}px {3}px {0}px)",H),l.clipRect||(J.position="absolute",J.top=0,J.left=0,J.width=e.paper.width+"px",J.height=e.paper.height+"px",l.parentNode.insertBefore(I,l),I.appendChild(l),l.clipRect=I)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var K=e.textpath.style;i.font&&(K.font=i.font),i["font-family"]&&(K.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(K.fontSize=i["font-size"]),i["font-weight"]&&(K.fontWeight=i["font-weight"]),i["font-style"]&&(K.fontStyle=i["font-style"])}"arrow-start"in i&&A(t,i["arrow-start"]),"arrow-end"in i&&A(t,i["arrow-end"],1);if(i.opacity!=null||i["stroke-width"]!=null||i.fill!=null||i.src!=null||i.stroke!=null||i["stroke-width"]!=null||i["stroke-opacity"]!=null||i["fill-opacity"]!=null||i["stroke-dasharray"]!=null||i["stroke-miterlimit"]!=null||i["stroke-linejoin"]!=null||i["stroke-linecap"]!=null){var L=l.getElementsByTagName(j),M=!1;L=L&&L[0],!L&&(M=L=F(j)),e.type=="image"&&i.src&&(L.src=i.src),i.fill&&(L.on=!0);if(L.on==null||i.fill=="none"||i.fill===null)L.on=!1;if(L.on&&i.fill){var N=c(i.fill).match(a._ISURL);if(N){L.parentNode==l&&l.removeChild(L),L.rotate=!0,L.src=N[1],L.type="tile";var O=e.getBBox(1);L.position=O.x+n+O.y,e._.fillpos=[O.x,O.y],a._preload(N[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else L.color=a.getRGB(i.fill).hex,L.src=o,L.type="solid",a.getRGB(i.fill).error&&(t.type in{circle:1,ellipse:1}||c(i.fill).charAt()!="r")&&C(t,i.fill,L)&&(m.fill="none",m.gradient=i.fill,L.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var P=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+a.getRGB(i.fill).o+1||2)-1);P=h(g(P,0),1),L.opacity=P,L.src&&(L.color="none")}l.appendChild(L);var Q=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],T=!1;!Q&&(T=Q=F("stroke"));if(i.stroke&&i.stroke!="none"||i["stroke-width"]||i["stroke-opacity"]!=null||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])Q.on=!0;(i.stroke=="none"||i.stroke===null||Q.on==null||i.stroke==0||i["stroke-width"]==0)&&(Q.on=!1);var U=a.getRGB(i.stroke);Q.on&&i.stroke&&(Q.color=U.hex),P=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+U.o+1||2)-1);var V=(d(i["stroke-width"])||1)*.75;P=h(g(P,0),1),i["stroke-width"]==null&&(V=m["stroke-width"]),i["stroke-width"]&&(Q.weight=V),V&&V<1&&(P*=V)&&(Q.weight=1),Q.opacity=P,i["stroke-linejoin"]&&(Q.joinstyle=i["stroke-linejoin"]||"miter"),Q.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(Q.endcap=i["stroke-linecap"]=="butt"?"flat":i["stroke-linecap"]=="square"?"square":"round");if(i["stroke-dasharray"]){var W={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};Q.dashstyle=W[b](i["stroke-dasharray"])?W[i["stroke-dasharray"]]:o}T&&l.appendChild(Q)}if(t.type=="text"){t.paper.canvas.style.display=o;var X=t.paper.span,Y=100,Z=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=X.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),Z=d(m["font-size"]||Z&&Z[0])||10,p.fontSize=Z*Y+"px",t.textpath.string&&(X.innerHTML=c(t.textpath.string).replace(/</g,"&#60;").replace(/&/g,"&#38;").replace(/\n/g,"<br>"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba<bb;ba++)if(_[ba]in i){t._.dirty=1;break}switch(m["text-anchor"]){case"start":t.textpath.style["v-text-align"]="left",t.bbx=t.W/2;break;case"end":t.textpath.style["v-text-align"]="right",t.bbx=-t.W/2;break;default:t.textpath.style["v-text-align"]="center",t.bbx=0}t.textpath.style["v-text-kern"]=!0}},C=function(b,f,g){b.attrs=b.attrs||{};var h=b.attrs,i=Math.pow,j,k,l="linear",m=".5 .5";b.attrs.gradient=f,f=c(f).replace(a._radial_gradient,function(a,b,c){l="radial",b&&c&&(b=d(b),c=d(c),i(b-.5,2)+i(c-.5,2)>.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s<t;s++)q[s].offset&&r.push(q[s].offset+n+q[s].color);g.colors=r.length?r.join():"0% "+g.color,l=="radial"?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=m,g.angle=0):(g.type="gradient",g.angle=(270-p)%360),b.appendChild(g)}return 1},D=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=c,this.matrix=a.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},E=a.el;D.prototype=E,E.constructor=D,E.transform=function(b){if(b==null)return this._.transform;var d=this.paper._viewBoxShift,e=d?"s"+[d.scale,d.scale]+"-1-1t"+[d.dx,d.dy]:o,f;d&&(f=b=c(b).replace(/\.{3}|\u2026/g,this._.transform||o)),a._extractTransform(this,e+b);var g=this.matrix.clone(),h=this.skew,i=this.node,j,k=~c(this.attrs.fill).indexOf("-"),l=!c(this.attrs.fill).indexOf("url(");g.translate(-0.5,-0.5);if(l||k||this.type=="image"){h.matrix="1 0 0 1",h.offset="0 0",j=g.split();if(k&&j.noRotation||!j.isSimple){i.style.filter=g.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;i.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else i.style.filter=o,z(this,j.scalex,j.scaley,j.dx,j.dy,j.rotate)}else i.style.filter=o,h.matrix=c(g),h.offset=g.offset();f&&(this._.transform=f);return this},E.rotate=function(a,b,e){if(this.removed)return this;if(a!=null){a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),e==null&&(b=e);if(b==null||e==null){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,b,e]]));return this}},E.translate=function(a,b){if(this.removed)return this;a=c(a).split(k),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=b),this.transform(this._.transform.concat([["t",a,b]]));return this},E.scale=function(a,b,e,f){if(this.removed)return this;a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),b==null&&(b=a),f==null&&(e=f);if(e==null||f==null)var g=this.getBBox(1);e=e==null?g.x+g.width/2:e,f=f==null?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this._.dirtyT=1;return this},E.hide=function(){!this.removed&&(this.node.style.display="none");return this},E.show=function(){!this.removed&&(this.node.style.display=o);return this},E._getBBox=function(){if(this.removed)return{};return{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&!!this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),a.eve.unbind("raphael.*.*."+this.id),a._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;this.removed=!0}},E.attr=function(c,d){if(this.removed)return this;if(c==null){var e={};for(var f in this.attrs)this.attrs[b](f)&&(e[f]=this.attrs[f]);e.gradient&&e.fill=="none"&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform;return e}if(d==null&&a.is(c,"string")){if(c==j&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;var g=c.split(k),h={};for(var i=0,m=g.length;i<m;i++)c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return m-1?h:h[g[0]]}if(this.attrs&&d==null&&a.is(c,"array")){h={};for(i=0,m=c.length;i<m;i++)h[c[i]]=this.attr(c[i]);return h}var n;d!=null&&(n={},n[c]=d),d==null&&a.is(c,"object")&&(n=c);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[b](o)&&n[b](o)&&a.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[b](q)&&(n[q]=p[q])}n.text&&this.type=="text"&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&a._tofront(this,this.paper);return this},E.toBack=function(){if(this.removed)return this;this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper));return this},E.insertAfter=function(b){if(this.removed)return this;b.constructor==a.st.constructor&&(b=b[b.length-1]),b.node.nextSibling?b.node.parentNode.insertBefore(this.node,b.node.nextSibling):b.node.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper);return this},E.insertBefore=function(b){if(this.removed)return this;b.constructor==a.st.constructor&&(b=b[0]),b.node.parentNode.insertBefore(this.node,b.node),a._insertbefore(this,b,this.paper);return this},E.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;d=d.replace(r,o),+b!==0?(this.attrs.blur=b,c.filter=d+n+m+".Blur(pixelradius="+(+b||1.5)+")",c.margin=a.format("-{0}px 0 0 -{0}px",f(+b||1.5))):(c.filter=d,c.margin=0,delete this.attrs.blur)},a._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");f.on=!0,c.appendChild(f),d.skew=f,d.transform(o);return d},a._engine.rect=function(b,c,d,e,f,g){var h=a._rectPath(c,d,e,f,g),i=b.path(h),j=i.attrs;i.X=j.x=c,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect";return i},a._engine.ellipse=function(a,b,c,d,e){var f=a.path(),g=f.attrs;f.X=b-d,f.Y=c-e,f.W=d*2,f.H=e*2,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e});return f},a._engine.circle=function(a,b,c,d){var e=a.path(),f=e.attrs;e.X=b-d,e.Y=c-d,e.W=e.H=d*2,e.type="circle",B(e,{cx:b,cy:c,r:d});return e},a._engine.image=function(b,c,d,e,f,g){var h=a._rectPath(d,e,f,g),i=b.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];k.src=c,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=c,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0);return i},a._engine.text=function(b,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=a.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=c(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,b),l={fill:"#000",stroke:"none",font:a._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=c(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),b.canvas.appendChild(h);var m=F("skew");m.on=!0,h.appendChild(m),k.skew=m,k.transform(o);return k},a._engine.setSize=function(b,c){var d=this.canvas.style;this.width=b,this.height=c,b==+b&&(b+="px"),c==+c&&(c+="px"),d.width=b,d.height=c,d.clip="rect(0 "+b+" "+c+" 0)",this._viewBox&&a._engine.setViewBox.apply(this,this._viewBox);return this},a._engine.setViewBox=function(b,c,d,e,f){a.eve("raphael.setViewBox",this,this._viewBox,[b,c,d,e,f]);var h=this.width,i=this.height,j=1/g(d/h,e/i),k,l;f&&(k=i/e,l=h/d,d*k<h&&(b-=(h-d*k)/2/k),e*l<i&&(c-=(i-e*l)/2/l)),this._viewBox=[b,c,d,e,!!f],this._viewBoxShift={dx:-b,dy:-c,scale:j},this.forEach(function(a){a.transform("...")});return this};var F;a._engine.initWin=function(a){var b=a.document;b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael)
\ No newline at end of file
diff --git a/question_types/quiz_ddlines/js/raphael.js b/question_types/quiz_ddlines/js/raphael.js
deleted file mode 100644
index a704866..0000000
--- a/question_types/quiz_ddlines/js/raphael.js
+++ /dev/null
@@ -1,7955 +0,0 @@
-// ┌────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël 2.1.0 - JavaScript Vector Library                          │ \\
-// ├────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
-// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
-// ├────────────────────────────────────────────────────────────────────┤ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
-// └────────────────────────────────────────────────────────────────────┘ \\
-// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
-// 
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// 
-// http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// ┌────────────────────────────────────────────────────────────┐ \\
-// │ Eve 0.4.2 - JavaScript Events Library                      │ \\
-// ├────────────────────────────────────────────────────────────┤ \\
-// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
-// └────────────────────────────────────────────────────────────┘ \\
-
-(function (glob) {
-    var version = "0.4.2",
-        has = "hasOwnProperty",
-        separator = /[\.\/]/,
-        wildcard = "*",
-        fun = function () {},
-        numsort = function (a, b) {
-            return a - b;
-        },
-        current_event,
-        stop,
-        events = {n: {}},
-    /*\
-     * eve
-     [ method ]
-
-     * Fires event with given `name`, given scope and other parameters.
-
-     > Arguments
-
-     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
-     - scope (object) context for the event handlers
-     - varargs (...) the rest of arguments will be sent to event handlers
-
-     = (object) array of returned values from the listeners
-    \*/
-        eve = function (name, scope) {
-			name = String(name);
-            var e = events,
-                oldstop = stop,
-                args = Array.prototype.slice.call(arguments, 2),
-                listeners = eve.listeners(name),
-                z = 0,
-                f = false,
-                l,
-                indexed = [],
-                queue = {},
-                out = [],
-                ce = current_event,
-                errors = [];
-            current_event = name;
-            stop = 0;
-            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
-                indexed.push(listeners[i].zIndex);
-                if (listeners[i].zIndex < 0) {
-                    queue[listeners[i].zIndex] = listeners[i];
-                }
-            }
-            indexed.sort(numsort);
-            while (indexed[z] < 0) {
-                l = queue[indexed[z++]];
-                out.push(l.apply(scope, args));
-                if (stop) {
-                    stop = oldstop;
-                    return out;
-                }
-            }
-            for (i = 0; i < ii; i++) {
-                l = listeners[i];
-                if ("zIndex" in l) {
-                    if (l.zIndex == indexed[z]) {
-                        out.push(l.apply(scope, args));
-                        if (stop) {
-                            break;
-                        }
-                        do {
-                            z++;
-                            l = queue[indexed[z]];
-                            l && out.push(l.apply(scope, args));
-                            if (stop) {
-                                break;
-                            }
-                        } while (l)
-                    } else {
-                        queue[l.zIndex] = l;
-                    }
-                } else {
-                    out.push(l.apply(scope, args));
-                    if (stop) {
-                        break;
-                    }
-                }
-            }
-            stop = oldstop;
-            current_event = ce;
-            return out.length ? out : null;
-        };
-		// Undocumented. Debug only.
-		eve._events = events;
-    /*\
-     * eve.listeners
-     [ method ]
-
-     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
-
-     > Arguments
-
-     - name (string) name of the event, dot (`.`) or slash (`/`) separated
-
-     = (array) array of event handlers
-    \*/
-    eve.listeners = function (name) {
-        var names = name.split(separator),
-            e = events,
-            item,
-            items,
-            k,
-            i,
-            ii,
-            j,
-            jj,
-            nes,
-            es = [e],
-            out = [];
-        for (i = 0, ii = names.length; i < ii; i++) {
-            nes = [];
-            for (j = 0, jj = es.length; j < jj; j++) {
-                e = es[j].n;
-                items = [e[names[i]], e[wildcard]];
-                k = 2;
-                while (k--) {
-                    item = items[k];
-                    if (item) {
-                        nes.push(item);
-                        out = out.concat(item.f || []);
-                    }
-                }
-            }
-            es = nes;
-        }
-        return out;
-    };
-    
-    /*\
-     * eve.on
-     [ method ]
-     **
-     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
-     | eve.on("*.under.*", f);
-     | eve("mouse.under.floor"); // triggers f
-     * Use @eve to trigger the listener.
-     **
-     > Arguments
-     **
-     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
-     - f (function) event handler function
-     **
-     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. 
-     > Example:
-     | eve.on("mouse", eatIt)(2);
-     | eve.on("mouse", scream);
-     | eve.on("mouse", catchIt)(1);
-     * This will ensure that `catchIt()` function will be called before `eatIt()`.
-	 *
-     * If you want to put your handler before non-indexed handlers, specify a negative value.
-     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
-    \*/
-    eve.on = function (name, f) {
-		name = String(name);
-		if (typeof f != "function") {
-			return function () {};
-		}
-        var names = name.split(separator),
-            e = events;
-        for (var i = 0, ii = names.length; i < ii; i++) {
-            e = e.n;
-            e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
-        }
-        e.f = e.f || [];
-        for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
-            return fun;
-        }
-        e.f.push(f);
-        return function (zIndex) {
-            if (+zIndex == +zIndex) {
-                f.zIndex = +zIndex;
-            }
-        };
-    };
-    /*\
-     * eve.f
-     [ method ]
-     **
-     * Returns function that will fire given event with optional arguments.
-	 * Arguments that will be passed to the result function will be also
-	 * concated to the list of final arguments.
- 	 | el.onclick = eve.f("click", 1, 2);
- 	 | eve.on("click", function (a, b, c) {
- 	 |     console.log(a, b, c); // 1, 2, [event object]
- 	 | });
-     > Arguments
-	 - event (string) event name
-	 - varargs (…) and any other arguments
-	 = (function) possible event handler function
-    \*/
-	eve.f = function (event) {
-		var attrs = [].slice.call(arguments, 1);
-		return function () {
-			eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
-		};
-	};
-    /*\
-     * eve.stop
-     [ method ]
-     **
-     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
-    \*/
-    eve.stop = function () {
-        stop = 1;
-    };
-    /*\
-     * eve.nt
-     [ method ]
-     **
-     * Could be used inside event handler to figure out actual name of the event.
-     **
-     > Arguments
-     **
-     - subname (string) #optional subname of the event
-     **
-     = (string) name of the event, if `subname` is not specified
-     * or
-     = (boolean) `true`, if current event’s name contains `subname`
-    \*/
-    eve.nt = function (subname) {
-        if (subname) {
-            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
-        }
-        return current_event;
-    };
-    /*\
-     * eve.nts
-     [ method ]
-     **
-     * Could be used inside event handler to figure out actual name of the event.
-     **
-     **
-     = (array) names of the event
-    \*/
-    eve.nts = function () {
-        return current_event.split(separator);
-    };
-    /*\
-     * eve.off
-     [ method ]
-     **
-     * Removes given function from the list of event listeners assigned to given name.
-	 * If no arguments specified all the events will be cleared.
-     **
-     > Arguments
-     **
-     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
-     - f (function) event handler function
-    \*/
-    /*\
-     * eve.unbind
-     [ method ]
-     **
-     * See @eve.off
-    \*/
-    eve.off = eve.unbind = function (name, f) {
-		if (!name) {
-		    eve._events = events = {n: {}};
-			return;
-		}
-        var names = name.split(separator),
-            e,
-            key,
-            splice,
-            i, ii, j, jj,
-            cur = [events];
-        for (i = 0, ii = names.length; i < ii; i++) {
-            for (j = 0; j < cur.length; j += splice.length - 2) {
-                splice = [j, 1];
-                e = cur[j].n;
-                if (names[i] != wildcard) {
-                    if (e[names[i]]) {
-                        splice.push(e[names[i]]);
-                    }
-                } else {
-                    for (key in e) if (e[has](key)) {
-                        splice.push(e[key]);
-                    }
-                }
-                cur.splice.apply(cur, splice);
-            }
-        }
-        for (i = 0, ii = cur.length; i < ii; i++) {
-            e = cur[i];
-            while (e.n) {
-                if (f) {
-                    if (e.f) {
-                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
-                            e.f.splice(j, 1);
-                            break;
-                        }
-                        !e.f.length && delete e.f;
-                    }
-                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
-                        var funcs = e.n[key].f;
-                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
-                            funcs.splice(j, 1);
-                            break;
-                        }
-                        !funcs.length && delete e.n[key].f;
-                    }
-                } else {
-                    delete e.f;
-                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
-                        delete e.n[key].f;
-                    }
-                }
-                e = e.n;
-            }
-        }
-    };
-    /*\
-     * eve.once
-     [ method ]
-     **
-     * Binds given event handler with a given name to only run once then unbind itself.
-     | eve.once("login", f);
-     | eve("login"); // triggers f
-     | eve("login"); // no listeners
-     * Use @eve to trigger the listener.
-     **
-     > Arguments
-     **
-     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
-     - f (function) event handler function
-     **
-     = (function) same return function as @eve.on
-    \*/
-    eve.once = function (name, f) {
-        var f2 = function () {
-            eve.unbind(name, f2);
-            return f.apply(this, arguments);
-        };
-        return eve.on(name, f2);
-    };
-    /*\
-     * eve.version
-     [ property (string) ]
-     **
-     * Current version of the library.
-    \*/
-    eve.version = version;
-    eve.toString = function () {
-        return "You are running Eve " + version;
-    };
-    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
-})(this);
-
-// ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ "Raphaël 2.1.0" - JavaScript Vector Library                         │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
-// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
-// └─────────────────────────────────────────────────────────────────────┘ \\
-(function () {
-    /*\
-     * Raphael
-     [ method ]
-     **
-     * Creates a canvas object on which to draw.
-     * You must do this first, as all future calls to drawing methods
-     * from this instance will be bound to this canvas.
-     > Parameters
-     **
-     - container (HTMLElement|string) DOM element or its ID which is going to be a parent for drawing surface
-     - width (number)
-     - height (number)
-     - callback (function) #optional callback function which is going to be executed in the context of newly created paper
-     * or
-     - x (number)
-     - y (number)
-     - width (number)
-     - height (number)
-     - callback (function) #optional callback function which is going to be executed in the context of newly created paper
-     * or
-     - all (array) (first 3 or 4 elements in the array are equal to [containerID, width, height] or [x, y, width, height]. The rest are element descriptions in format {type: type, <attributes>}). See @Paper.add.
-     - callback (function) #optional callback function which is going to be executed in the context of newly created paper
-     * or
-     - onReadyCallback (function) function that is going to be called on DOM ready event. You can also subscribe to this event via Eve’s “DOMLoad” event. In this case method returns `undefined`.
-     = (object) @Paper
-     > Usage
-     | // Each of the following examples create a canvas
-     | // that is 320px wide by 200px high.
-     | // Canvas is created at the viewport’s 10,50 coordinate.
-     | var paper = Raphael(10, 50, 320, 200);
-     | // Canvas is created at the top left corner of the #notepad element
-     | // (or its top right corner in dir="rtl" elements)
-     | var paper = Raphael(document.getElementById("notepad"), 320, 200);
-     | // Same as above
-     | var paper = Raphael("notepad", 320, 200);
-     | // Image dump
-     | var set = Raphael(["notepad", 320, 200, {
-     |     type: "rect",
-     |     x: 10,
-     |     y: 10,
-     |     width: 25,
-     |     height: 25,
-     |     stroke: "#f00"
-     | }, {
-     |     type: "text",
-     |     x: 30,
-     |     y: 40,
-     |     text: "Dump"
-     | }]);
-    \*/
-    function R(first) {
-        if (R.is(first, "function")) {
-            return loaded ? first() : eve.on("raphael.DOMload", first);
-        } else if (R.is(first, array)) {
-            return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
-        } else {
-            var args = Array.prototype.slice.call(arguments, 0);
-            if (R.is(args[args.length - 1], "function")) {
-                var f = args.pop();
-                return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
-                    f.call(R._engine.create[apply](R, args));
-                });
-            } else {
-                return R._engine.create[apply](R, arguments);
-            }
-        }
-    }
-    R.version = "2.1.0";
-    R.eve = eve;
-    var loaded,
-        separator = /[, ]+/,
-        elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
-        formatrg = /\{(\d+)\}/g,
-        proto = "prototype",
-        has = "hasOwnProperty",
-        g = {
-            doc: document,
-            win: window
-        },
-        oldRaphael = {
-            was: Object.prototype[has].call(g.win, "Raphael"),
-            is: g.win.Raphael
-        },
-        Paper = function () {
-            /*\
-             * Paper.ca
-             [ property (object) ]
-             **
-             * Shortcut for @Paper.customAttributes
-            \*/
-            /*\
-             * Paper.customAttributes
-             [ property (object) ]
-             **
-             * If you have a set of attributes that you would like to represent
-             * as a function of some number you can do it easily with custom attributes:
-             > Usage
-             | paper.customAttributes.hue = function (num) {
-             |     num = num % 1;
-             |     return {fill: "hsb(" + num + ", 0.75, 1)"};
-             | };
-             | // Custom attribute “hue” will change fill
-             | // to be given hue with fixed saturation and brightness.
-             | // Now you can use it like this:
-             | var c = paper.circle(10, 10, 10).attr({hue: .45});
-             | // or even like this:
-             | c.animate({hue: 1}, 1e3);
-             | 
-             | // You could also create custom attribute
-             | // with multiple parameters:
-             | paper.customAttributes.hsb = function (h, s, b) {
-             |     return {fill: "hsb(" + [h, s, b].join(",") + ")"};
-             | };
-             | c.attr({hsb: "0.5 .8 1"});
-             | c.animate({hsb: [1, 0, 0.5]}, 1e3);
-            \*/
-            this.ca = this.customAttributes = {};
-        },
-        paperproto,
-        appendChild = "appendChild",
-        apply = "apply",
-        concat = "concat",
-        supportsTouch = "createTouch" in g.doc,
-        E = "",
-        S = " ",
-        Str = String,
-        split = "split",
-        events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
-        touchMap = {
-            mousedown: "touchstart",
-            mousemove: "touchmove",
-            mouseup: "touchend"
-        },
-        lowerCase = Str.prototype.toLowerCase,
-        math = Math,
-        mmax = math.max,
-        mmin = math.min,
-        abs = math.abs,
-        pow = math.pow,
-        PI = math.PI,
-        nu = "number",
-        string = "string",
-        array = "array",
-        toString = "toString",
-        fillString = "fill",
-        objectToString = Object.prototype.toString,
-        paper = {},
-        push = "push",
-        ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
-        colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
-        isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
-        bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
-        round = math.round,
-        setAttribute = "setAttribute",
-        toFloat = parseFloat,
-        toInt = parseInt,
-        upperCase = Str.prototype.toUpperCase,
-        availableAttrs = R._availableAttrs = {
-            "arrow-end": "none",
-            "arrow-start": "none",
-            blur: 0,
-            "clip-rect": "0 0 1e9 1e9",
-            cursor: "default",
-            cx: 0,
-            cy: 0,
-            fill: "#fff",
-            "fill-opacity": 1,
-            font: '10px "Arial"',
-            "font-family": '"Arial"',
-            "font-size": "10",
-            "font-style": "normal",
-            "font-weight": 400,
-            gradient: 0,
-            height: 0,
-            href: "http://raphaeljs.com/",
-            "letter-spacing": 0,
-            opacity: 1,
-            path: "M0,0",
-            r: 0,
-            rx: 0,
-            ry: 0,
-            src: "",
-            stroke: "#000",
-            "stroke-dasharray": "",
-            "stroke-linecap": "butt",
-            "stroke-linejoin": "butt",
-            "stroke-miterlimit": 0,
-            "stroke-opacity": 1,
-            "stroke-width": 1,
-            target: "_blank",
-            "text-anchor": "middle",
-            title: "Raphael",
-            transform: "",
-            width: 0,
-            x: 0,
-            y: 0
-        },
-        availableAnimAttrs = R._availableAnimAttrs = {
-            blur: nu,
-            "clip-rect": "csv",
-            cx: nu,
-            cy: nu,
-            fill: "colour",
-            "fill-opacity": nu,
-            "font-size": nu,
-            height: nu,
-            opacity: nu,
-            path: "path",
-            r: nu,
-            rx: nu,
-            ry: nu,
-            stroke: "colour",
-            "stroke-opacity": nu,
-            "stroke-width": nu,
-            transform: "transform",
-            width: nu,
-            x: nu,
-            y: nu
-        },
-        whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
-        commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
-        hsrg = {hs: 1, rg: 1},
-        p2s = /,?([achlmqrstvxz]),?/gi,
-        pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
-        tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
-        pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
-        radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
-        eldata = {},
-        sortByKey = function (a, b) {
-            return a.key - b.key;
-        },
-        sortByNumber = function (a, b) {
-            return toFloat(a) - toFloat(b);
-        },
-        fun = function () {},
-        pipe = function (x) {
-            return x;
-        },
-        rectPath = R._rectPath = function (x, y, w, h, r) {
-            if (r) {
-                return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
-            }
-            return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
-        },
-        ellipsePath = function (x, y, rx, ry) {
-            if (ry == null) {
-                ry = rx;
-            }
-            return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
-        },
-        getPath = R._getPath = {
-            path: function (el) {
-                return el.attr("path");
-            },
-            circle: function (el) {
-                var a = el.attrs;
-                return ellipsePath(a.cx, a.cy, a.r);
-            },
-            ellipse: function (el) {
-                var a = el.attrs;
-                return ellipsePath(a.cx, a.cy, a.rx, a.ry);
-            },
-            rect: function (el) {
-                var a = el.attrs;
-                return rectPath(a.x, a.y, a.width, a.height, a.r);
-            },
-            image: function (el) {
-                var a = el.attrs;
-                return rectPath(a.x, a.y, a.width, a.height);
-            },
-            text: function (el) {
-                var bbox = el._getBBox();
-                return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
-            },
-            set : function(el) {
-                var bbox = el._getBBox();
-                return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
-            }
-        },
-        /*\
-         * Raphael.mapPath
-         [ method ]
-         **
-         * Transform the path string with given matrix.
-         > Parameters
-         - path (string) path string
-         - matrix (object) see @Matrix
-         = (string) transformed path string
-        \*/
-        mapPath = R.mapPath = function (path, matrix) {
-            if (!matrix) {
-                return path;
-            }
-            var x, y, i, j, ii, jj, pathi;
-            path = path2curve(path);
-            for (i = 0, ii = path.length; i < ii; i++) {
-                pathi = path[i];
-                for (j = 1, jj = pathi.length; j < jj; j += 2) {
-                    x = matrix.x(pathi[j], pathi[j + 1]);
-                    y = matrix.y(pathi[j], pathi[j + 1]);
-                    pathi[j] = x;
-                    pathi[j + 1] = y;
-                }
-            }
-            return path;
-        };
-
-    R._g = g;
-    /*\
-     * Raphael.type
-     [ property (string) ]
-     **
-     * Can be “SVG”, “VML” or empty, depending on browser support.
-    \*/
-    R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
-    if (R.type == "VML") {
-        var d = g.doc.createElement("div"),
-            b;
-        d.innerHTML = '<v:shape adj="1"/>';
-        b = d.firstChild;
-        b.style.behavior = "url(#default#VML)";
-        if (!(b && typeof b.adj == "object")) {
-            return (R.type = E);
-        }
-        d = null;
-    }
-    /*\
-     * Raphael.svg
-     [ property (boolean) ]
-     **
-     * `true` if browser supports SVG.
-    \*/
-    /*\
-     * Raphael.vml
-     [ property (boolean) ]
-     **
-     * `true` if browser supports VML.
-    \*/
-    R.svg = !(R.vml = R.type == "VML");
-    R._Paper = Paper;
-    /*\
-     * Raphael.fn
-     [ property (object) ]
-     **
-     * You can add your own method to the canvas. For example if you want to draw a pie chart,
-     * you can create your own pie chart function and ship it as a Raphaël plugin. To do this
-     * you need to extend the `Raphael.fn` object. You should modify the `fn` object before a
-     * Raphaël instance is created, otherwise it will take no effect. Please note that the
-     * ability for namespaced plugins was removed in Raphael 2.0. It is up to the plugin to
-     * ensure any namespacing ensures proper context.
-     > Usage
-     | Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
-     |     return this.path( ... );
-     | };
-     | // or create namespace
-     | Raphael.fn.mystuff = {
-     |     arrow: function () {…},
-     |     star: function () {…},
-     |     // etc…
-     | };
-     | var paper = Raphael(10, 10, 630, 480);
-     | // then use it
-     | paper.arrow(10, 10, 30, 30, 5).attr({fill: "#f00"});
-     | paper.mystuff.arrow();
-     | paper.mystuff.star();
-    \*/
-    R.fn = paperproto = Paper.prototype = R.prototype;
-    R._id = 0;
-    R._oid = 0;
-    /*\
-     * Raphael.is
-     [ method ]
-     **
-     * Handfull replacement for `typeof` operator.
-     > Parameters
-     - o (…) any object or primitive
-     - type (string) name of the type, i.e. “string”, “function”, “number”, etc.
-     = (boolean) is given value is of given type
-    \*/
-    R.is = function (o, type) {
-        type = lowerCase.call(type);
-        if (type == "finite") {
-            return !isnan[has](+o);
-        }
-        if (type == "array") {
-            return o instanceof Array;
-        }
-        return  (type == "null" && o === null) ||
-                (type == typeof o && o !== null) ||
-                (type == "object" && o === Object(o)) ||
-                (type == "array" && Array.isArray && Array.isArray(o)) ||
-                objectToString.call(o).slice(8, -1).toLowerCase() == type;
-    };
-
-    function clone(obj) {
-        if (Object(obj) !== obj) {
-            return obj;
-        }
-        var res = new obj.constructor;
-        for (var key in obj) if (obj[has](key)) {
-            res[key] = clone(obj[key]);
-        }
-        return res;
-    }
-
-    /*\
-     * Raphael.angle
-     [ method ]
-     **
-     * Returns angle between two or three points
-     > Parameters
-     - x1 (number) x coord of first point
-     - y1 (number) y coord of first point
-     - x2 (number) x coord of second point
-     - y2 (number) y coord of second point
-     - x3 (number) #optional x coord of third point
-     - y3 (number) #optional y coord of third point
-     = (number) angle in degrees.
-    \*/
-    R.angle = function (x1, y1, x2, y2, x3, y3) {
-        if (x3 == null) {
-            var x = x1 - x2,
-                y = y1 - y2;
-            if (!x && !y) {
-                return 0;
-            }
-            return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
-        } else {
-            return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
-        }
-    };
-    /*\
-     * Raphael.rad
-     [ method ]
-     **
-     * Transform angle to radians
-     > Parameters
-     - deg (number) angle in degrees
-     = (number) angle in radians.
-    \*/
-    R.rad = function (deg) {
-        return deg % 360 * PI / 180;
-    };
-    /*\
-     * Raphael.deg
-     [ method ]
-     **
-     * Transform angle to degrees
-     > Parameters
-     - deg (number) angle in radians
-     = (number) angle in degrees.
-    \*/
-    R.deg = function (rad) {
-        return rad * 180 / PI % 360;
-    };
-    /*\
-     * Raphael.snapTo
-     [ method ]
-     **
-     * Snaps given value to given grid.
-     > Parameters
-     - values (array|number) given array of values or step of the grid
-     - value (number) value to adjust
-     - tolerance (number) #optional tolerance for snapping. Default is `10`.
-     = (number) adjusted value.
-    \*/
-    R.snapTo = function (values, value, tolerance) {
-        tolerance = R.is(tolerance, "finite") ? tolerance : 10;
-        if (R.is(values, array)) {
-            var i = values.length;
-            while (i--) if (abs(values[i] - value) <= tolerance) {
-                return values[i];
-            }
-        } else {
-            values = +values;
-            var rem = value % values;
-            if (rem < tolerance) {
-                return value - rem;
-            }
-            if (rem > values - tolerance) {
-                return value - rem + values;
-            }
-        }
-        return value;
-    };
-    
-    /*\
-     * Raphael.createUUID
-     [ method ]
-     **
-     * Returns RFC4122, version 4 ID
-    \*/
-    var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
-        return function () {
-            return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
-        };
-    })(/[xy]/g, function (c) {
-        var r = math.random() * 16 | 0,
-            v = c == "x" ? r : (r & 3 | 8);
-        return v.toString(16);
-    });
-
-    /*\
-     * Raphael.setWindow
-     [ method ]
-     **
-     * Used when you need to draw in `&lt;iframe>`. Switched window to the iframe one.
-     > Parameters
-     - newwin (window) new window object
-    \*/
-    R.setWindow = function (newwin) {
-        eve("raphael.setWindow", R, g.win, newwin);
-        g.win = newwin;
-        g.doc = g.win.document;
-        if (R._engine.initWin) {
-            R._engine.initWin(g.win);
-        }
-    };
-    var toHex = function (color) {
-        if (R.vml) {
-            // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
-            var trim = /^\s+|\s+$/g;
-            var bod;
-            try {
-                var docum = new ActiveXObject("htmlfile");
-                docum.write("<body>");
-                docum.close();
-                bod = docum.body;
-            } catch(e) {
-                bod = createPopup().document.body;
-            }
-            var range = bod.createTextRange();
-            toHex = cacher(function (color) {
-                try {
-                    bod.style.color = Str(color).replace(trim, E);
-                    var value = range.queryCommandValue("ForeColor");
-                    value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
-                    return "#" + ("000000" + value.toString(16)).slice(-6);
-                } catch(e) {
-                    return "none";
-                }
-            });
-        } else {
-            var i = g.doc.createElement("i");
-            i.title = "Rapha\xebl Colour Picker";
-            i.style.display = "none";
-            g.doc.body.appendChild(i);
-            toHex = cacher(function (color) {
-                i.style.color = color;
-                return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
-            });
-        }
-        return toHex(color);
-    },
-    hsbtoString = function () {
-        return "hsb(" + [this.h, this.s, this.b] + ")";
-    },
-    hsltoString = function () {
-        return "hsl(" + [this.h, this.s, this.l] + ")";
-    },
-    rgbtoString = function () {
-        return this.hex;
-    },
-    prepareRGB = function (r, g, b) {
-        if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
-            b = r.b;
-            g = r.g;
-            r = r.r;
-        }
-        if (g == null && R.is(r, string)) {
-            var clr = R.getRGB(r);
-            r = clr.r;
-            g = clr.g;
-            b = clr.b;
-        }
-        if (r > 1 || g > 1 || b > 1) {
-            r /= 255;
-            g /= 255;
-            b /= 255;
-        }
-        
-        return [r, g, b];
-    },
-    packageRGB = function (r, g, b, o) {
-        r *= 255;
-        g *= 255;
-        b *= 255;
-        var rgb = {
-            r: r,
-            g: g,
-            b: b,
-            hex: R.rgb(r, g, b),
-            toString: rgbtoString
-        };
-        R.is(o, "finite") && (rgb.opacity = o);
-        return rgb;
-    };
-    
-    /*\
-     * Raphael.color
-     [ method ]
-     **
-     * Parses the color string and returns object with all values for the given color.
-     > Parameters
-     - clr (string) color string in one of the supported formats (see @Raphael.getRGB)
-     = (object) Combined RGB & HSB object in format:
-     o {
-     o     r (number) red,
-     o     g (number) green,
-     o     b (number) blue,
-     o     hex (string) color in HTML/CSS format: #••••••,
-     o     error (boolean) `true` if string can’t be parsed,
-     o     h (number) hue,
-     o     s (number) saturation,
-     o     v (number) value (brightness),
-     o     l (number) lightness
-     o }
-    \*/
-    R.color = function (clr) {
-        var rgb;
-        if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
-            rgb = R.hsb2rgb(clr);
-            clr.r = rgb.r;
-            clr.g = rgb.g;
-            clr.b = rgb.b;
-            clr.hex = rgb.hex;
-        } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
-            rgb = R.hsl2rgb(clr);
-            clr.r = rgb.r;
-            clr.g = rgb.g;
-            clr.b = rgb.b;
-            clr.hex = rgb.hex;
-        } else {
-            if (R.is(clr, "string")) {
-                clr = R.getRGB(clr);
-            }
-            if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
-                rgb = R.rgb2hsl(clr);
-                clr.h = rgb.h;
-                clr.s = rgb.s;
-                clr.l = rgb.l;
-                rgb = R.rgb2hsb(clr);
-                clr.v = rgb.b;
-            } else {
-                clr = {hex: "none"};
-                clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
-            }
-        }
-        clr.toString = rgbtoString;
-        return clr;
-    };
-    /*\
-     * Raphael.hsb2rgb
-     [ method ]
-     **
-     * Converts HSB values to RGB object.
-     > Parameters
-     - h (number) hue
-     - s (number) saturation
-     - v (number) value or brightness
-     = (object) RGB object in format:
-     o {
-     o     r (number) red,
-     o     g (number) green,
-     o     b (number) blue,
-     o     hex (string) color in HTML/CSS format: #••••••
-     o }
-    \*/
-    R.hsb2rgb = function (h, s, v, o) {
-        if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
-            v = h.b;
-            s = h.s;
-            h = h.h;
-            o = h.o;
-        }
-        h *= 360;
-        var R, G, B, X, C;
-        h = (h % 360) / 60;
-        C = v * s;
-        X = C * (1 - abs(h % 2 - 1));
-        R = G = B = v - C;
-
-        h = ~~h;
-        R += [C, X, 0, 0, X, C][h];
-        G += [X, C, C, X, 0, 0][h];
-        B += [0, 0, X, C, C, X][h];
-        return packageRGB(R, G, B, o);
-    };
-    /*\
-     * Raphael.hsl2rgb
-     [ method ]
-     **
-     * Converts HSL values to RGB object.
-     > Parameters
-     - h (number) hue
-     - s (number) saturation
-     - l (number) luminosity
-     = (object) RGB object in format:
-     o {
-     o     r (number) red,
-     o     g (number) green,
-     o     b (number) blue,
-     o     hex (string) color in HTML/CSS format: #••••••
-     o }
-    \*/
-    R.hsl2rgb = function (h, s, l, o) {
-        if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
-            l = h.l;
-            s = h.s;
-            h = h.h;
-        }
-        if (h > 1 || s > 1 || l > 1) {
-            h /= 360;
-            s /= 100;
-            l /= 100;
-        }
-        h *= 360;
-        var R, G, B, X, C;
-        h = (h % 360) / 60;
-        C = 2 * s * (l < .5 ? l : 1 - l);
-        X = C * (1 - abs(h % 2 - 1));
-        R = G = B = l - C / 2;
-
-        h = ~~h;
-        R += [C, X, 0, 0, X, C][h];
-        G += [X, C, C, X, 0, 0][h];
-        B += [0, 0, X, C, C, X][h];
-        return packageRGB(R, G, B, o);
-    };
-    /*\
-     * Raphael.rgb2hsb
-     [ method ]
-     **
-     * Converts RGB values to HSB object.
-     > Parameters
-     - r (number) red
-     - g (number) green
-     - b (number) blue
-     = (object) HSB object in format:
-     o {
-     o     h (number) hue
-     o     s (number) saturation
-     o     b (number) brightness
-     o }
-    \*/
-    R.rgb2hsb = function (r, g, b) {
-        b = prepareRGB(r, g, b);
-        r = b[0];
-        g = b[1];
-        b = b[2];
-
-        var H, S, V, C;
-        V = mmax(r, g, b);
-        C = V - mmin(r, g, b);
-        H = (C == 0 ? null :
-             V == r ? (g - b) / C :
-             V == g ? (b - r) / C + 2 :
-                      (r - g) / C + 4
-            );
-        H = ((H + 360) % 6) * 60 / 360;
-        S = C == 0 ? 0 : C / V;
-        return {h: H, s: S, b: V, toString: hsbtoString};
-    };
-    /*\
-     * Raphael.rgb2hsl
-     [ method ]
-     **
-     * Converts RGB values to HSL object.
-     > Parameters
-     - r (number) red
-     - g (number) green
-     - b (number) blue
-     = (object) HSL object in format:
-     o {
-     o     h (number) hue
-     o     s (number) saturation
-     o     l (number) luminosity
-     o }
-    \*/
-    R.rgb2hsl = function (r, g, b) {
-        b = prepareRGB(r, g, b);
-        r = b[0];
-        g = b[1];
-        b = b[2];
-
-        var H, S, L, M, m, C;
-        M = mmax(r, g, b);
-        m = mmin(r, g, b);
-        C = M - m;
-        H = (C == 0 ? null :
-             M == r ? (g - b) / C :
-             M == g ? (b - r) / C + 2 :
-                      (r - g) / C + 4);
-        H = ((H + 360) % 6) * 60 / 360;
-        L = (M + m) / 2;
-        S = (C == 0 ? 0 :
-             L < .5 ? C / (2 * L) :
-                      C / (2 - 2 * L));
-        return {h: H, s: S, l: L, toString: hsltoString};
-    };
-    R._path2string = function () {
-        return this.join(",").replace(p2s, "$1");
-    };
-    function repush(array, item) {
-        for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
-            return array.push(array.splice(i, 1)[0]);
-        }
-    }
-    function cacher(f, scope, postprocessor) {
-        function newf() {
-            var arg = Array.prototype.slice.call(arguments, 0),
-                args = arg.join("\u2400"),
-                cache = newf.cache = newf.cache || {},
-                count = newf.count = newf.count || [];
-            if (cache[has](args)) {
-                repush(count, args);
-                return postprocessor ? postprocessor(cache[args]) : cache[args];
-            }
-            count.length >= 1e3 && delete cache[count.shift()];
-            count.push(args);
-            cache[args] = f[apply](scope, arg);
-            return postprocessor ? postprocessor(cache[args]) : cache[args];
-        }
-        return newf;
-    }
-
-    var preload = R._preload = function (src, f) {
-        var img = g.doc.createElement("img");
-        img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
-        img.onload = function () {
-            f.call(this);
-            this.onload = null;
-            g.doc.body.removeChild(this);
-        };
-        img.onerror = function () {
-            g.doc.body.removeChild(this);
-        };
-        g.doc.body.appendChild(img);
-        img.src = src;
-    };
-    
-    function clrToString() {
-        return this.hex;
-    }
-
-    /*\
-     * Raphael.getRGB
-     [ method ]
-     **
-     * Parses colour string as RGB object
-     > Parameters
-     - colour (string) colour string in one of formats:
-     # <ul>
-     #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
-     #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
-     #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
-     #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
-     #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
-     #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
-     #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
-     #     <li>hsl(•••, •••, •••) — same as hsb</li>
-     #     <li>hsl(•••%, •••%, •••%) — same as hsb</li>
-     # </ul>
-     = (object) RGB object in format:
-     o {
-     o     r (number) red,
-     o     g (number) green,
-     o     b (number) blue
-     o     hex (string) color in HTML/CSS format: #••••••,
-     o     error (boolean) true if string can’t be parsed
-     o }
-    \*/
-    R.getRGB = cacher(function (colour) {
-        if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
-            return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
-        }
-        if (colour == "none") {
-            return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
-        }
-        !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
-        var res,
-            red,
-            green,
-            blue,
-            opacity,
-            t,
-            values,
-            rgb = colour.match(colourRegExp);
-        if (rgb) {
-            if (rgb[2]) {
-                blue = toInt(rgb[2].substring(5), 16);
-                green = toInt(rgb[2].substring(3, 5), 16);
-                red = toInt(rgb[2].substring(1, 3), 16);
-            }
-            if (rgb[3]) {
-                blue = toInt((t = rgb[3].charAt(3)) + t, 16);
-                green = toInt((t = rgb[3].charAt(2)) + t, 16);
-                red = toInt((t = rgb[3].charAt(1)) + t, 16);
-            }
-            if (rgb[4]) {
-                values = rgb[4][split](commaSpaces);
-                red = toFloat(values[0]);
-                values[0].slice(-1) == "%" && (red *= 2.55);
-                green = toFloat(values[1]);
-                values[1].slice(-1) == "%" && (green *= 2.55);
-                blue = toFloat(values[2]);
-                values[2].slice(-1) == "%" && (blue *= 2.55);
-                rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
-                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
-            }
-            if (rgb[5]) {
-                values = rgb[5][split](commaSpaces);
-                red = toFloat(values[0]);
-                values[0].slice(-1) == "%" && (red *= 2.55);
-                green = toFloat(values[1]);
-                values[1].slice(-1) == "%" && (green *= 2.55);
-                blue = toFloat(values[2]);
-                values[2].slice(-1) == "%" && (blue *= 2.55);
-                (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
-                rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
-                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
-                return R.hsb2rgb(red, green, blue, opacity);
-            }
-            if (rgb[6]) {
-                values = rgb[6][split](commaSpaces);
-                red = toFloat(values[0]);
-                values[0].slice(-1) == "%" && (red *= 2.55);
-                green = toFloat(values[1]);
-                values[1].slice(-1) == "%" && (green *= 2.55);
-                blue = toFloat(values[2]);
-                values[2].slice(-1) == "%" && (blue *= 2.55);
-                (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
-                rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
-                values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
-                return R.hsl2rgb(red, green, blue, opacity);
-            }
-            rgb = {r: red, g: green, b: blue, toString: clrToString};
-            rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
-            R.is(opacity, "finite") && (rgb.opacity = opacity);
-            return rgb;
-        }
-        return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
-    }, R);
-    /*\
-     * Raphael.hsb
-     [ method ]
-     **
-     * Converts HSB values to hex representation of the colour.
-     > Parameters
-     - h (number) hue
-     - s (number) saturation
-     - b (number) value or brightness
-     = (string) hex representation of the colour.
-    \*/
-    R.hsb = cacher(function (h, s, b) {
-        return R.hsb2rgb(h, s, b).hex;
-    });
-    /*\
-     * Raphael.hsl
-     [ method ]
-     **
-     * Converts HSL values to hex representation of the colour.
-     > Parameters
-     - h (number) hue
-     - s (number) saturation
-     - l (number) luminosity
-     = (string) hex representation of the colour.
-    \*/
-    R.hsl = cacher(function (h, s, l) {
-        return R.hsl2rgb(h, s, l).hex;
-    });
-    /*\
-     * Raphael.rgb
-     [ method ]
-     **
-     * Converts RGB values to hex representation of the colour.
-     > Parameters
-     - r (number) red
-     - g (number) green
-     - b (number) blue
-     = (string) hex representation of the colour.
-    \*/
-    R.rgb = cacher(function (r, g, b) {
-        return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
-    });
-    /*\
-     * Raphael.getColor
-     [ method ]
-     **
-     * On each call returns next colour in the spectrum. To reset it back to red call @Raphael.getColor.reset
-     > Parameters
-     - value (number) #optional brightness, default is `0.75`
-     = (string) hex representation of the colour.
-    \*/
-    R.getColor = function (value) {
-        var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
-            rgb = this.hsb2rgb(start.h, start.s, start.b);
-        start.h += .075;
-        if (start.h > 1) {
-            start.h = 0;
-            start.s -= .2;
-            start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
-        }
-        return rgb.hex;
-    };
-    /*\
-     * Raphael.getColor.reset
-     [ method ]
-     **
-     * Resets spectrum position for @Raphael.getColor back to red.
-    \*/
-    R.getColor.reset = function () {
-        delete this.start;
-    };
-
-    // http://schepers.cc/getting-to-the-point
-    function catmullRom2bezier(crp, z) {
-        var d = [];
-        for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
-            var p = [
-                        {x: +crp[i - 2], y: +crp[i - 1]},
-                        {x: +crp[i],     y: +crp[i + 1]},
-                        {x: +crp[i + 2], y: +crp[i + 3]},
-                        {x: +crp[i + 4], y: +crp[i + 5]}
-                    ];
-            if (z) {
-                if (!i) {
-                    p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
-                } else if (iLen - 4 == i) {
-                    p[3] = {x: +crp[0], y: +crp[1]};
-                } else if (iLen - 2 == i) {
-                    p[2] = {x: +crp[0], y: +crp[1]};
-                    p[3] = {x: +crp[2], y: +crp[3]};
-                }
-            } else {
-                if (iLen - 4 == i) {
-                    p[3] = p[2];
-                } else if (!i) {
-                    p[0] = {x: +crp[i], y: +crp[i + 1]};
-                }
-            }
-            d.push(["C",
-                  (-p[0].x + 6 * p[1].x + p[2].x) / 6,
-                  (-p[0].y + 6 * p[1].y + p[2].y) / 6,
-                  (p[1].x + 6 * p[2].x - p[3].x) / 6,
-                  (p[1].y + 6*p[2].y - p[3].y) / 6,
-                  p[2].x,
-                  p[2].y
-            ]);
-        }
-
-        return d;
-    }
-    /*\
-     * Raphael.parsePathString
-     [ method ]
-     **
-     * Utility method
-     **
-     * Parses given path string into an array of arrays of path segments.
-     > Parameters
-     - pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
-     = (array) array of segments.
-    \*/
-    R.parsePathString = function (pathString) {
-        if (!pathString) {
-            return null;
-        }
-        var pth = paths(pathString);
-        if (pth.arr) {
-            return pathClone(pth.arr);
-        }
-        
-        var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
-            data = [];
-        if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
-            data = pathClone(pathString);
-        }
-        if (!data.length) {
-            Str(pathString).replace(pathCommand, function (a, b, c) {
-                var params = [],
-                    name = b.toLowerCase();
-                c.replace(pathValues, function (a, b) {
-                    b && params.push(+b);
-                });
-                if (name == "m" && params.length > 2) {
-                    data.push([b][concat](params.splice(0, 2)));
-                    name = "l";
-                    b = b == "m" ? "l" : "L";
-                }
-                if (name == "r") {
-                    data.push([b][concat](params));
-                } else while (params.length >= paramCounts[name]) {
-                    data.push([b][concat](params.splice(0, paramCounts[name])));
-                    if (!paramCounts[name]) {
-                        break;
-                    }
-                }
-            });
-        }
-        data.toString = R._path2string;
-        pth.arr = pathClone(data);
-        return data;
-    };
-    /*\
-     * Raphael.parseTransformString
-     [ method ]
-     **
-     * Utility method
-     **
-     * Parses given path string into an array of transformations.
-     > Parameters
-     - TString (string|array) transform string or array of transformations (in the last case it will be returned straight away)
-     = (array) array of transformations.
-    \*/
-    R.parseTransformString = cacher(function (TString) {
-        if (!TString) {
-            return null;
-        }
-        var paramCounts = {r: 3, s: 4, t: 2, m: 6},
-            data = [];
-        if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
-            data = pathClone(TString);
-        }
-        if (!data.length) {
-            Str(TString).replace(tCommand, function (a, b, c) {
-                var params = [],
-                    name = lowerCase.call(b);
-                c.replace(pathValues, function (a, b) {
-                    b && params.push(+b);
-                });
-                data.push([b][concat](params));
-            });
-        }
-        data.toString = R._path2string;
-        return data;
-    });
-    // PATHS
-    var paths = function (ps) {
-        var p = paths.ps = paths.ps || {};
-        if (p[ps]) {
-            p[ps].sleep = 100;
-        } else {
-            p[ps] = {
-                sleep: 100
-            };
-        }
-        setTimeout(function () {
-            for (var key in p) if (p[has](key) && key != ps) {
-                p[key].sleep--;
-                !p[key].sleep && delete p[key];
-            }
-        });
-        return p[ps];
-    };
-    /*\
-     * Raphael.findDotsAtSegment
-     [ method ]
-     **
-     * Utility method
-     **
-     * Find dot coordinates on the given cubic bezier curve at the given t.
-     > Parameters
-     - p1x (number) x of the first point of the curve
-     - p1y (number) y of the first point of the curve
-     - c1x (number) x of the first anchor of the curve
-     - c1y (number) y of the first anchor of the curve
-     - c2x (number) x of the second anchor of the curve
-     - c2y (number) y of the second anchor of the curve
-     - p2x (number) x of the second point of the curve
-     - p2y (number) y of the second point of the curve
-     - t (number) position on the curve (0..1)
-     = (object) point information in format:
-     o {
-     o     x: (number) x coordinate of the point
-     o     y: (number) y coordinate of the point
-     o     m: {
-     o         x: (number) x coordinate of the left anchor
-     o         y: (number) y coordinate of the left anchor
-     o     }
-     o     n: {
-     o         x: (number) x coordinate of the right anchor
-     o         y: (number) y coordinate of the right anchor
-     o     }
-     o     start: {
-     o         x: (number) x coordinate of the start of the curve
-     o         y: (number) y coordinate of the start of the curve
-     o     }
-     o     end: {
-     o         x: (number) x coordinate of the end of the curve
-     o         y: (number) y coordinate of the end of the curve
-     o     }
-     o     alpha: (number) angle of the curve derivative at the point
-     o }
-    \*/
-    R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
-        var t1 = 1 - t,
-            t13 = pow(t1, 3),
-            t12 = pow(t1, 2),
-            t2 = t * t,
-            t3 = t2 * t,
-            x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
-            y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
-            mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
-            my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
-            nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
-            ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
-            ax = t1 * p1x + t * c1x,
-            ay = t1 * p1y + t * c1y,
-            cx = t1 * c2x + t * p2x,
-            cy = t1 * c2y + t * p2y,
-            alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
-        (mx > nx || my < ny) && (alpha += 180);
-        return {
-            x: x,
-            y: y,
-            m: {x: mx, y: my},
-            n: {x: nx, y: ny},
-            start: {x: ax, y: ay},
-            end: {x: cx, y: cy},
-            alpha: alpha
-        };
-    };
-    /*\
-     * Raphael.bezierBBox
-     [ method ]
-     **
-     * Utility method
-     **
-     * Return bounding box of a given cubic bezier curve
-     > Parameters
-     - p1x (number) x of the first point of the curve
-     - p1y (number) y of the first point of the curve
-     - c1x (number) x of the first anchor of the curve
-     - c1y (number) y of the first anchor of the curve
-     - c2x (number) x of the second anchor of the curve
-     - c2y (number) y of the second anchor of the curve
-     - p2x (number) x of the second point of the curve
-     - p2y (number) y of the second point of the curve
-     * or
-     - bez (array) array of six points for bezier curve
-     = (object) point information in format:
-     o {
-     o     min: {
-     o         x: (number) x coordinate of the left point
-     o         y: (number) y coordinate of the top point
-     o     }
-     o     max: {
-     o         x: (number) x coordinate of the right point
-     o         y: (number) y coordinate of the bottom point
-     o     }
-     o }
-    \*/
-    R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
-        if (!R.is(p1x, "array")) {
-            p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
-        }
-        var bbox = curveDim.apply(null, p1x);
-        return {
-            x: bbox.min.x,
-            y: bbox.min.y,
-            x2: bbox.max.x,
-            y2: bbox.max.y,
-            width: bbox.max.x - bbox.min.x,
-            height: bbox.max.y - bbox.min.y
-        };
-    };
-    /*\
-     * Raphael.isPointInsideBBox
-     [ method ]
-     **
-     * Utility method
-     **
-     * Returns `true` if given point is inside bounding boxes.
-     > Parameters
-     - bbox (string) bounding box
-     - x (string) x coordinate of the point
-     - y (string) y coordinate of the point
-     = (boolean) `true` if point inside
-    \*/
-    R.isPointInsideBBox = function (bbox, x, y) {
-        return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
-    };
-    /*\
-     * Raphael.isBBoxIntersect
-     [ method ]
-     **
-     * Utility method
-     **
-     * Returns `true` if two bounding boxes intersect
-     > Parameters
-     - bbox1 (string) first bounding box
-     - bbox2 (string) second bounding box
-     = (boolean) `true` if they intersect
-    \*/
-    R.isBBoxIntersect = function (bbox1, bbox2) {
-        var i = R.isPointInsideBBox;
-        return i(bbox2, bbox1.x, bbox1.y)
-            || i(bbox2, bbox1.x2, bbox1.y)
-            || i(bbox2, bbox1.x, bbox1.y2)
-            || i(bbox2, bbox1.x2, bbox1.y2)
-            || i(bbox1, bbox2.x, bbox2.y)
-            || i(bbox1, bbox2.x2, bbox2.y)
-            || i(bbox1, bbox2.x, bbox2.y2)
-            || i(bbox1, bbox2.x2, bbox2.y2)
-            || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
-            && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
-    };
-    function base3(t, p1, p2, p3, p4) {
-        var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
-            t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
-        return t * t2 - 3 * p1 + 3 * p2;
-    }
-    function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
-        if (z == null) {
-            z = 1;
-        }
-        z = z > 1 ? 1 : z < 0 ? 0 : z;
-        var z2 = z / 2,
-            n = 12,
-            Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],
-            Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
-            sum = 0;
-        for (var i = 0; i < n; i++) {
-            var ct = z2 * Tvalues[i] + z2,
-                xbase = base3(ct, x1, x2, x3, x4),
-                ybase = base3(ct, y1, y2, y3, y4),
-                comb = xbase * xbase + ybase * ybase;
-            sum += Cvalues[i] * math.sqrt(comb);
-        }
-        return z2 * sum;
-    }
-    function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
-        if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
-            return;
-        }
-        var t = 1,
-            step = t / 2,
-            t2 = t - step,
-            l,
-            e = .01;
-        l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
-        while (abs(l - ll) > e) {
-            step /= 2;
-            t2 += (l < ll ? 1 : -1) * step;
-            l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
-        }
-        return t2;
-    }
-    function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
-        if (
-            mmax(x1, x2) < mmin(x3, x4) ||
-            mmin(x1, x2) > mmax(x3, x4) ||
-            mmax(y1, y2) < mmin(y3, y4) ||
-            mmin(y1, y2) > mmax(y3, y4)
-        ) {
-            return;
-        }
-        var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
-            ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
-            denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
-
-        if (!denominator) {
-            return;
-        }
-        var px = nx / denominator,
-            py = ny / denominator,
-            px2 = +px.toFixed(2),
-            py2 = +py.toFixed(2);
-        if (
-            px2 < +mmin(x1, x2).toFixed(2) ||
-            px2 > +mmax(x1, x2).toFixed(2) ||
-            px2 < +mmin(x3, x4).toFixed(2) ||
-            px2 > +mmax(x3, x4).toFixed(2) ||
-            py2 < +mmin(y1, y2).toFixed(2) ||
-            py2 > +mmax(y1, y2).toFixed(2) ||
-            py2 < +mmin(y3, y4).toFixed(2) ||
-            py2 > +mmax(y3, y4).toFixed(2)
-        ) {
-            return;
-        }
-        return {x: px, y: py};
-    }
-    function inter(bez1, bez2) {
-        return interHelper(bez1, bez2);
-    }
-    function interCount(bez1, bez2) {
-        return interHelper(bez1, bez2, 1);
-    }
-    function interHelper(bez1, bez2, justCount) {
-        var bbox1 = R.bezierBBox(bez1),
-            bbox2 = R.bezierBBox(bez2);
-        if (!R.isBBoxIntersect(bbox1, bbox2)) {
-            return justCount ? 0 : [];
-        }
-        var l1 = bezlen.apply(0, bez1),
-            l2 = bezlen.apply(0, bez2),
-            n1 = ~~(l1 / 5),
-            n2 = ~~(l2 / 5),
-            dots1 = [],
-            dots2 = [],
-            xy = {},
-            res = justCount ? 0 : [];
-        for (var i = 0; i < n1 + 1; i++) {
-            var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
-            dots1.push({x: p.x, y: p.y, t: i / n1});
-        }
-        for (i = 0; i < n2 + 1; i++) {
-            p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
-            dots2.push({x: p.x, y: p.y, t: i / n2});
-        }
-        for (i = 0; i < n1; i++) {
-            for (var j = 0; j < n2; j++) {
-                var di = dots1[i],
-                    di1 = dots1[i + 1],
-                    dj = dots2[j],
-                    dj1 = dots2[j + 1],
-                    ci = abs(di1.x - di.x) < .001 ? "y" : "x",
-                    cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
-                    is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
-                if (is) {
-                    if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
-                        continue;
-                    }
-                    xy[is.x.toFixed(4)] = is.y.toFixed(4);
-                    var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
-                        t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
-                    if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
-                        if (justCount) {
-                            res++;
-                        } else {
-                            res.push({
-                                x: is.x,
-                                y: is.y,
-                                t1: t1,
-                                t2: t2
-                            });
-                        }
-                    }
-                }
-            }
-        }
-        return res;
-    }
-    /*\
-     * Raphael.pathIntersection
-     [ method ]
-     **
-     * Utility method
-     **
-     * Finds intersections of two paths
-     > Parameters
-     - path1 (string) path string
-     - path2 (string) path string
-     = (array) dots of intersection
-     o [
-     o     {
-     o         x: (number) x coordinate of the point
-     o         y: (number) y coordinate of the point
-     o         t1: (number) t value for segment of path1
-     o         t2: (number) t value for segment of path2
-     o         segment1: (number) order number for segment of path1
-     o         segment2: (number) order number for segment of path2
-     o         bez1: (array) eight coordinates representing beziér curve for the segment of path1
-     o         bez2: (array) eight coordinates representing beziér curve for the segment of path2
-     o     }
-     o ]
-    \*/
-    R.pathIntersection = function (path1, path2) {
-        return interPathHelper(path1, path2);
-    };
-    R.pathIntersectionNumber = function (path1, path2) {
-        return interPathHelper(path1, path2, 1);
-    };
-    function interPathHelper(path1, path2, justCount) {
-        path1 = R._path2curve(path1);
-        path2 = R._path2curve(path2);
-        var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
-            res = justCount ? 0 : [];
-        for (var i = 0, ii = path1.length; i < ii; i++) {
-            var pi = path1[i];
-            if (pi[0] == "M") {
-                x1 = x1m = pi[1];
-                y1 = y1m = pi[2];
-            } else {
-                if (pi[0] == "C") {
-                    bez1 = [x1, y1].concat(pi.slice(1));
-                    x1 = bez1[6];
-                    y1 = bez1[7];
-                } else {
-                    bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
-                    x1 = x1m;
-                    y1 = y1m;
-                }
-                for (var j = 0, jj = path2.length; j < jj; j++) {
-                    var pj = path2[j];
-                    if (pj[0] == "M") {
-                        x2 = x2m = pj[1];
-                        y2 = y2m = pj[2];
-                    } else {
-                        if (pj[0] == "C") {
-                            bez2 = [x2, y2].concat(pj.slice(1));
-                            x2 = bez2[6];
-                            y2 = bez2[7];
-                        } else {
-                            bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
-                            x2 = x2m;
-                            y2 = y2m;
-                        }
-                        var intr = interHelper(bez1, bez2, justCount);
-                        if (justCount) {
-                            res += intr;
-                        } else {
-                            for (var k = 0, kk = intr.length; k < kk; k++) {
-                                intr[k].segment1 = i;
-                                intr[k].segment2 = j;
-                                intr[k].bez1 = bez1;
-                                intr[k].bez2 = bez2;
-                            }
-                            res = res.concat(intr);
-                        }
-                    }
-                }
-            }
-        }
-        return res;
-    }
-    /*\
-     * Raphael.isPointInsidePath
-     [ method ]
-     **
-     * Utility method
-     **
-     * Returns `true` if given point is inside a given closed path.
-     > Parameters
-     - path (string) path string
-     - x (number) x of the point
-     - y (number) y of the point
-     = (boolean) true, if point is inside the path
-    \*/
-    R.isPointInsidePath = function (path, x, y) {
-        var bbox = R.pathBBox(path);
-        return R.isPointInsideBBox(bbox, x, y) &&
-               interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
-    };
-    R._removedFactory = function (methodname) {
-        return function () {
-            eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
-        };
-    };
-    /*\
-     * Raphael.pathBBox
-     [ method ]
-     **
-     * Utility method
-     **
-     * Return bounding box of a given path
-     > Parameters
-     - path (string) path string
-     = (object) bounding box
-     o {
-     o     x: (number) x coordinate of the left top point of the box
-     o     y: (number) y coordinate of the left top point of the box
-     o     x2: (number) x coordinate of the right bottom point of the box
-     o     y2: (number) y coordinate of the right bottom point of the box
-     o     width: (number) width of the box
-     o     height: (number) height of the box
-     o }
-    \*/
-    var pathDimensions = R.pathBBox = function (path) {
-        var pth = paths(path);
-        if (pth.bbox) {
-            return clone(pth.bbox);
-        }
-        if (!path) {
-            return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
-        }
-        path = path2curve(path);
-        var x = 0, 
-            y = 0,
-            X = [],
-            Y = [],
-            p;
-        for (var i = 0, ii = path.length; i < ii; i++) {
-            p = path[i];
-            if (p[0] == "M") {
-                x = p[1];
-                y = p[2];
-                X.push(x);
-                Y.push(y);
-            } else {
-                var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
-                X = X[concat](dim.min.x, dim.max.x);
-                Y = Y[concat](dim.min.y, dim.max.y);
-                x = p[5];
-                y = p[6];
-            }
-        }
-        var xmin = mmin[apply](0, X),
-            ymin = mmin[apply](0, Y),
-            xmax = mmax[apply](0, X),
-            ymax = mmax[apply](0, Y),
-            bb = {
-                x: xmin,
-                y: ymin,
-                x2: xmax,
-                y2: ymax,
-                width: xmax - xmin,
-                height: ymax - ymin
-            };
-        pth.bbox = clone(bb);
-        return bb;
-    },
-        pathClone = function (pathArray) {
-            var res = clone(pathArray);
-            res.toString = R._path2string;
-            return res;
-        },
-        pathToRelative = R._pathToRelative = function (pathArray) {
-            var pth = paths(pathArray);
-            if (pth.rel) {
-                return pathClone(pth.rel);
-            }
-            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
-                pathArray = R.parsePathString(pathArray);
-            }
-            var res = [],
-                x = 0,
-                y = 0,
-                mx = 0,
-                my = 0,
-                start = 0;
-            if (pathArray[0][0] == "M") {
-                x = pathArray[0][1];
-                y = pathArray[0][2];
-                mx = x;
-                my = y;
-                start++;
-                res.push(["M", x, y]);
-            }
-            for (var i = start, ii = pathArray.length; i < ii; i++) {
-                var r = res[i] = [],
-                    pa = pathArray[i];
-                if (pa[0] != lowerCase.call(pa[0])) {
-                    r[0] = lowerCase.call(pa[0]);
-                    switch (r[0]) {
-                        case "a":
-                            r[1] = pa[1];
-                            r[2] = pa[2];
-                            r[3] = pa[3];
-                            r[4] = pa[4];
-                            r[5] = pa[5];
-                            r[6] = +(pa[6] - x).toFixed(3);
-                            r[7] = +(pa[7] - y).toFixed(3);
-                            break;
-                        case "v":
-                            r[1] = +(pa[1] - y).toFixed(3);
-                            break;
-                        case "m":
-                            mx = pa[1];
-                            my = pa[2];
-                        default:
-                            for (var j = 1, jj = pa.length; j < jj; j++) {
-                                r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
-                            }
-                    }
-                } else {
-                    r = res[i] = [];
-                    if (pa[0] == "m") {
-                        mx = pa[1] + x;
-                        my = pa[2] + y;
-                    }
-                    for (var k = 0, kk = pa.length; k < kk; k++) {
-                        res[i][k] = pa[k];
-                    }
-                }
-                var len = res[i].length;
-                switch (res[i][0]) {
-                    case "z":
-                        x = mx;
-                        y = my;
-                        break;
-                    case "h":
-                        x += +res[i][len - 1];
-                        break;
-                    case "v":
-                        y += +res[i][len - 1];
-                        break;
-                    default:
-                        x += +res[i][len - 2];
-                        y += +res[i][len - 1];
-                }
-            }
-            res.toString = R._path2string;
-            pth.rel = pathClone(res);
-            return res;
-        },
-        pathToAbsolute = R._pathToAbsolute = function (pathArray) {
-            var pth = paths(pathArray);
-            if (pth.abs) {
-                return pathClone(pth.abs);
-            }
-            if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
-                pathArray = R.parsePathString(pathArray);
-            }
-            if (!pathArray || !pathArray.length) {
-                return [["M", 0, 0]];
-            }
-            var res = [],
-                x = 0,
-                y = 0,
-                mx = 0,
-                my = 0,
-                start = 0;
-            if (pathArray[0][0] == "M") {
-                x = +pathArray[0][1];
-                y = +pathArray[0][2];
-                mx = x;
-                my = y;
-                start++;
-                res[0] = ["M", x, y];
-            }
-            var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
-            for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
-                res.push(r = []);
-                pa = pathArray[i];
-                if (pa[0] != upperCase.call(pa[0])) {
-                    r[0] = upperCase.call(pa[0]);
-                    switch (r[0]) {
-                        case "A":
-                            r[1] = pa[1];
-                            r[2] = pa[2];
-                            r[3] = pa[3];
-                            r[4] = pa[4];
-                            r[5] = pa[5];
-                            r[6] = +(pa[6] + x);
-                            r[7] = +(pa[7] + y);
-                            break;
-                        case "V":
-                            r[1] = +pa[1] + y;
-                            break;
-                        case "H":
-                            r[1] = +pa[1] + x;
-                            break;
-                        case "R":
-                            var dots = [x, y][concat](pa.slice(1));
-                            for (var j = 2, jj = dots.length; j < jj; j++) {
-                                dots[j] = +dots[j] + x;
-                                dots[++j] = +dots[j] + y;
-                            }
-                            res.pop();
-                            res = res[concat](catmullRom2bezier(dots, crz));
-                            break;
-                        case "M":
-                            mx = +pa[1] + x;
-                            my = +pa[2] + y;
-                        default:
-                            for (j = 1, jj = pa.length; j < jj; j++) {
-                                r[j] = +pa[j] + ((j % 2) ? x : y);
-                            }
-                    }
-                } else if (pa[0] == "R") {
-                    dots = [x, y][concat](pa.slice(1));
-                    res.pop();
-                    res = res[concat](catmullRom2bezier(dots, crz));
-                    r = ["R"][concat](pa.slice(-2));
-                } else {
-                    for (var k = 0, kk = pa.length; k < kk; k++) {
-                        r[k] = pa[k];
-                    }
-                }
-                switch (r[0]) {
-                    case "Z":
-                        x = mx;
-                        y = my;
-                        break;
-                    case "H":
-                        x = r[1];
-                        break;
-                    case "V":
-                        y = r[1];
-                        break;
-                    case "M":
-                        mx = r[r.length - 2];
-                        my = r[r.length - 1];
-                    default:
-                        x = r[r.length - 2];
-                        y = r[r.length - 1];
-                }
-            }
-            res.toString = R._path2string;
-            pth.abs = pathClone(res);
-            return res;
-        },
-        l2c = function (x1, y1, x2, y2) {
-            return [x1, y1, x2, y2, x2, y2];
-        },
-        q2c = function (x1, y1, ax, ay, x2, y2) {
-            var _13 = 1 / 3,
-                _23 = 2 / 3;
-            return [
-                    _13 * x1 + _23 * ax,
-                    _13 * y1 + _23 * ay,
-                    _13 * x2 + _23 * ax,
-                    _13 * y2 + _23 * ay,
-                    x2,
-                    y2
-                ];
-        },
-        a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
-            // for more information of where this math came from visit:
-            // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
-            var _120 = PI * 120 / 180,
-                rad = PI / 180 * (+angle || 0),
-                res = [],
-                xy,
-                rotate = cacher(function (x, y, rad) {
-                    var X = x * math.cos(rad) - y * math.sin(rad),
-                        Y = x * math.sin(rad) + y * math.cos(rad);
-                    return {x: X, y: Y};
-                });
-            if (!recursive) {
-                xy = rotate(x1, y1, -rad);
-                x1 = xy.x;
-                y1 = xy.y;
-                xy = rotate(x2, y2, -rad);
-                x2 = xy.x;
-                y2 = xy.y;
-                var cos = math.cos(PI / 180 * angle),
-                    sin = math.sin(PI / 180 * angle),
-                    x = (x1 - x2) / 2,
-                    y = (y1 - y2) / 2;
-                var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
-                if (h > 1) {
-                    h = math.sqrt(h);
-                    rx = h * rx;
-                    ry = h * ry;
-                }
-                var rx2 = rx * rx,
-                    ry2 = ry * ry,
-                    k = (large_arc_flag == sweep_flag ? -1 : 1) *
-                        math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
-                    cx = k * rx * y / ry + (x1 + x2) / 2,
-                    cy = k * -ry * x / rx + (y1 + y2) / 2,
-                    f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
-                    f2 = math.asin(((y2 - cy) / ry).toFixed(9));
-
-                f1 = x1 < cx ? PI - f1 : f1;
-                f2 = x2 < cx ? PI - f2 : f2;
-                f1 < 0 && (f1 = PI * 2 + f1);
-                f2 < 0 && (f2 = PI * 2 + f2);
-                if (sweep_flag && f1 > f2) {
-                    f1 = f1 - PI * 2;
-                }
-                if (!sweep_flag && f2 > f1) {
-                    f2 = f2 - PI * 2;
-                }
-            } else {
-                f1 = recursive[0];
-                f2 = recursive[1];
-                cx = recursive[2];
-                cy = recursive[3];
-            }
-            var df = f2 - f1;
-            if (abs(df) > _120) {
-                var f2old = f2,
-                    x2old = x2,
-                    y2old = y2;
-                f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
-                x2 = cx + rx * math.cos(f2);
-                y2 = cy + ry * math.sin(f2);
-                res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
-            }
-            df = f2 - f1;
-            var c1 = math.cos(f1),
-                s1 = math.sin(f1),
-                c2 = math.cos(f2),
-                s2 = math.sin(f2),
-                t = math.tan(df / 4),
-                hx = 4 / 3 * rx * t,
-                hy = 4 / 3 * ry * t,
-                m1 = [x1, y1],
-                m2 = [x1 + hx * s1, y1 - hy * c1],
-                m3 = [x2 + hx * s2, y2 - hy * c2],
-                m4 = [x2, y2];
-            m2[0] = 2 * m1[0] - m2[0];
-            m2[1] = 2 * m1[1] - m2[1];
-            if (recursive) {
-                return [m2, m3, m4][concat](res);
-            } else {
-                res = [m2, m3, m4][concat](res).join()[split](",");
-                var newres = [];
-                for (var i = 0, ii = res.length; i < ii; i++) {
-                    newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
-                }
-                return newres;
-            }
-        },
-        findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
-            var t1 = 1 - t;
-            return {
-                x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
-                y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
-            };
-        },
-        curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
-            var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
-                b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
-                c = p1x - c1x,
-                t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
-                t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
-                y = [p1y, p2y],
-                x = [p1x, p2x],
-                dot;
-            abs(t1) > "1e12" && (t1 = .5);
-            abs(t2) > "1e12" && (t2 = .5);
-            if (t1 > 0 && t1 < 1) {
-                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
-                x.push(dot.x);
-                y.push(dot.y);
-            }
-            if (t2 > 0 && t2 < 1) {
-                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
-                x.push(dot.x);
-                y.push(dot.y);
-            }
-            a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
-            b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
-            c = p1y - c1y;
-            t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
-            t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
-            abs(t1) > "1e12" && (t1 = .5);
-            abs(t2) > "1e12" && (t2 = .5);
-            if (t1 > 0 && t1 < 1) {
-                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
-                x.push(dot.x);
-                y.push(dot.y);
-            }
-            if (t2 > 0 && t2 < 1) {
-                dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
-                x.push(dot.x);
-                y.push(dot.y);
-            }
-            return {
-                min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
-                max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
-            };
-        }),
-        path2curve = R._path2curve = cacher(function (path, path2) {
-            var pth = !path2 && paths(path);
-            if (!path2 && pth.curve) {
-                return pathClone(pth.curve);
-            }
-            var p = pathToAbsolute(path),
-                p2 = path2 && pathToAbsolute(path2),
-                attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
-                attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
-                processPath = function (path, d) {
-                    var nx, ny;
-                    if (!path) {
-                        return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
-                    }
-                    !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null);
-                    switch (path[0]) {
-                        case "M":
-                            d.X = path[1];
-                            d.Y = path[2];
-                            break;
-                        case "A":
-                            path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
-                            break;
-                        case "S":
-                            nx = d.x + (d.x - (d.bx || d.x));
-                            ny = d.y + (d.y - (d.by || d.y));
-                            path = ["C", nx, ny][concat](path.slice(1));
-                            break;
-                        case "T":
-                            d.qx = d.x + (d.x - (d.qx || d.x));
-                            d.qy = d.y + (d.y - (d.qy || d.y));
-                            path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
-                            break;
-                        case "Q":
-                            d.qx = path[1];
-                            d.qy = path[2];
-                            path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
-                            break;
-                        case "L":
-                            path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
-                            break;
-                        case "H":
-                            path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
-                            break;
-                        case "V":
-                            path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
-                            break;
-                        case "Z":
-                            path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
-                            break;
-                    }
-                    return path;
-                },
-                fixArc = function (pp, i) {
-                    if (pp[i].length > 7) {
-                        pp[i].shift();
-                        var pi = pp[i];
-                        while (pi.length) {
-                            pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
-                        }
-                        pp.splice(i, 1);
-                        ii = mmax(p.length, p2 && p2.length || 0);
-                    }
-                },
-                fixM = function (path1, path2, a1, a2, i) {
-                    if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
-                        path2.splice(i, 0, ["M", a2.x, a2.y]);
-                        a1.bx = 0;
-                        a1.by = 0;
-                        a1.x = path1[i][1];
-                        a1.y = path1[i][2];
-                        ii = mmax(p.length, p2 && p2.length || 0);
-                    }
-                };
-            for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
-                p[i] = processPath(p[i], attrs);
-                fixArc(p, i);
-                p2 && (p2[i] = processPath(p2[i], attrs2));
-                p2 && fixArc(p2, i);
-                fixM(p, p2, attrs, attrs2, i);
-                fixM(p2, p, attrs2, attrs, i);
-                var seg = p[i],
-                    seg2 = p2 && p2[i],
-                    seglen = seg.length,
-                    seg2len = p2 && seg2.length;
-                attrs.x = seg[seglen - 2];
-                attrs.y = seg[seglen - 1];
-                attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
-                attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
-                attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
-                attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
-                attrs2.x = p2 && seg2[seg2len - 2];
-                attrs2.y = p2 && seg2[seg2len - 1];
-            }
-            if (!p2) {
-                pth.curve = pathClone(p);
-            }
-            return p2 ? [p, p2] : p;
-        }, null, pathClone),
-        parseDots = R._parseDots = cacher(function (gradient) {
-            var dots = [];
-            for (var i = 0, ii = gradient.length; i < ii; i++) {
-                var dot = {},
-                    par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
-                dot.color = R.getRGB(par[1]);
-                if (dot.color.error) {
-                    return null;
-                }
-                dot.color = dot.color.hex;
-                par[2] && (dot.offset = par[2] + "%");
-                dots.push(dot);
-            }
-            for (i = 1, ii = dots.length - 1; i < ii; i++) {
-                if (!dots[i].offset) {
-                    var start = toFloat(dots[i - 1].offset || 0),
-                        end = 0;
-                    for (var j = i + 1; j < ii; j++) {
-                        if (dots[j].offset) {
-                            end = dots[j].offset;
-                            break;
-                        }
-                    }
-                    if (!end) {
-                        end = 100;
-                        j = ii;
-                    }
-                    end = toFloat(end);
-                    var d = (end - start) / (j - i + 1);
-                    for (; i < j; i++) {
-                        start += d;
-                        dots[i].offset = start + "%";
-                    }
-                }
-            }
-            return dots;
-        }),
-        tear = R._tear = function (el, paper) {
-            el == paper.top && (paper.top = el.prev);
-            el == paper.bottom && (paper.bottom = el.next);
-            el.next && (el.next.prev = el.prev);
-            el.prev && (el.prev.next = el.next);
-        },
-        tofront = R._tofront = function (el, paper) {
-            if (paper.top === el) {
-                return;
-            }
-            tear(el, paper);
-            el.next = null;
-            el.prev = paper.top;
-            paper.top.next = el;
-            paper.top = el;
-        },
-        toback = R._toback = function (el, paper) {
-            if (paper.bottom === el) {
-                return;
-            }
-            tear(el, paper);
-            el.next = paper.bottom;
-            el.prev = null;
-            paper.bottom.prev = el;
-            paper.bottom = el;
-        },
-        insertafter = R._insertafter = function (el, el2, paper) {
-            tear(el, paper);
-            el2 == paper.top && (paper.top = el);
-            el2.next && (el2.next.prev = el);
-            el.next = el2.next;
-            el.prev = el2;
-            el2.next = el;
-        },
-        insertbefore = R._insertbefore = function (el, el2, paper) {
-            tear(el, paper);
-            el2 == paper.bottom && (paper.bottom = el);
-            el2.prev && (el2.prev.next = el);
-            el.prev = el2.prev;
-            el2.prev = el;
-            el.next = el2;
-        },
-        /*\
-         * Raphael.toMatrix
-         [ method ]
-         **
-         * Utility method
-         **
-         * Returns matrix of transformations applied to a given path
-         > Parameters
-         - path (string) path string
-         - transform (string|array) transformation string
-         = (object) @Matrix
-        \*/
-        toMatrix = R.toMatrix = function (path, transform) {
-            var bb = pathDimensions(path),
-                el = {
-                    _: {
-                        transform: E
-                    },
-                    getBBox: function () {
-                        return bb;
-                    }
-                };
-            extractTransform(el, transform);
-            return el.matrix;
-        },
-        /*\
-         * Raphael.transformPath
-         [ method ]
-         **
-         * Utility method
-         **
-         * Returns path transformed by a given transformation
-         > Parameters
-         - path (string) path string
-         - transform (string|array) transformation string
-         = (string) path
-        \*/
-        transformPath = R.transformPath = function (path, transform) {
-            return mapPath(path, toMatrix(path, transform));
-        },
-        extractTransform = R._extractTransform = function (el, tstr) {
-            if (tstr == null) {
-                return el._.transform;
-            }
-            tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
-            var tdata = R.parseTransformString(tstr),
-                deg = 0,
-                dx = 0,
-                dy = 0,
-                sx = 1,
-                sy = 1,
-                _ = el._,
-                m = new Matrix;
-            _.transform = tdata || [];
-            if (tdata) {
-                for (var i = 0, ii = tdata.length; i < ii; i++) {
-                    var t = tdata[i],
-                        tlen = t.length,
-                        command = Str(t[0]).toLowerCase(),
-                        absolute = t[0] != command,
-                        inver = absolute ? m.invert() : 0,
-                        x1,
-                        y1,
-                        x2,
-                        y2,
-                        bb;
-                    if (command == "t" && tlen == 3) {
-                        if (absolute) {
-                            x1 = inver.x(0, 0);
-                            y1 = inver.y(0, 0);
-                            x2 = inver.x(t[1], t[2]);
-                            y2 = inver.y(t[1], t[2]);
-                            m.translate(x2 - x1, y2 - y1);
-                        } else {
-                            m.translate(t[1], t[2]);
-                        }
-                    } else if (command == "r") {
-                        if (tlen == 2) {
-                            bb = bb || el.getBBox(1);
-                            m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
-                            deg += t[1];
-                        } else if (tlen == 4) {
-                            if (absolute) {
-                                x2 = inver.x(t[2], t[3]);
-                                y2 = inver.y(t[2], t[3]);
-                                m.rotate(t[1], x2, y2);
-                            } else {
-                                m.rotate(t[1], t[2], t[3]);
-                            }
-                            deg += t[1];
-                        }
-                    } else if (command == "s") {
-                        if (tlen == 2 || tlen == 3) {
-                            bb = bb || el.getBBox(1);
-                            m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
-                            sx *= t[1];
-                            sy *= t[tlen - 1];
-                        } else if (tlen == 5) {
-                            if (absolute) {
-                                x2 = inver.x(t[3], t[4]);
-                                y2 = inver.y(t[3], t[4]);
-                                m.scale(t[1], t[2], x2, y2);
-                            } else {
-                                m.scale(t[1], t[2], t[3], t[4]);
-                            }
-                            sx *= t[1];
-                            sy *= t[2];
-                        }
-                    } else if (command == "m" && tlen == 7) {
-                        m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
-                    }
-                    _.dirtyT = 1;
-                    el.matrix = m;
-                }
-            }
-
-            /*\
-             * Element.matrix
-             [ property (object) ]
-             **
-             * Keeps @Matrix object, which represents element transformation
-            \*/
-            el.matrix = m;
-
-            _.sx = sx;
-            _.sy = sy;
-            _.deg = deg;
-            _.dx = dx = m.e;
-            _.dy = dy = m.f;
-
-            if (sx == 1 && sy == 1 && !deg && _.bbox) {
-                _.bbox.x += +dx;
-                _.bbox.y += +dy;
-            } else {
-                _.dirtyT = 1;
-            }
-        },
-        getEmpty = function (item) {
-            var l = item[0];
-            switch (l.toLowerCase()) {
-                case "t": return [l, 0, 0];
-                case "m": return [l, 1, 0, 0, 1, 0, 0];
-                case "r": if (item.length == 4) {
-                    return [l, 0, item[2], item[3]];
-                } else {
-                    return [l, 0];
-                }
-                case "s": if (item.length == 5) {
-                    return [l, 1, 1, item[3], item[4]];
-                } else if (item.length == 3) {
-                    return [l, 1, 1];
-                } else {
-                    return [l, 1];
-                }
-            }
-        },
-        equaliseTransform = R._equaliseTransform = function (t1, t2) {
-            t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
-            t1 = R.parseTransformString(t1) || [];
-            t2 = R.parseTransformString(t2) || [];
-            var maxlength = mmax(t1.length, t2.length),
-                from = [],
-                to = [],
-                i = 0, j, jj,
-                tt1, tt2;
-            for (; i < maxlength; i++) {
-                tt1 = t1[i] || getEmpty(t2[i]);
-                tt2 = t2[i] || getEmpty(tt1);
-                if ((tt1[0] != tt2[0]) ||
-                    (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
-                    (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
-                    ) {
-                    return;
-                }
-                from[i] = [];
-                to[i] = [];
-                for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
-                    j in tt1 && (from[i][j] = tt1[j]);
-                    j in tt2 && (to[i][j] = tt2[j]);
-                }
-            }
-            return {
-                from: from,
-                to: to
-            };
-        };
-    R._getContainer = function (x, y, w, h) {
-        var container;
-        container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
-        if (container == null) {
-            return;
-        }
-        if (container.tagName) {
-            if (y == null) {
-                return {
-                    container: container,
-                    width: container.style.pixelWidth || container.offsetWidth,
-                    height: container.style.pixelHeight || container.offsetHeight
-                };
-            } else {
-                return {
-                    container: container,
-                    width: y,
-                    height: w
-                };
-            }
-        }
-        return {
-            container: 1,
-            x: x,
-            y: y,
-            width: w,
-            height: h
-        };
-    };
-    /*\
-     * Raphael.pathToRelative
-     [ method ]
-     **
-     * Utility method
-     **
-     * Converts path to relative form
-     > Parameters
-     - pathString (string|array) path string or array of segments
-     = (array) array of segments.
-    \*/
-    R.pathToRelative = pathToRelative;
-    R._engine = {};
-    /*\
-     * Raphael.path2curve
-     [ method ]
-     **
-     * Utility method
-     **
-     * Converts path to a new path where all segments are cubic bezier curves.
-     > Parameters
-     - pathString (string|array) path string or array of segments
-     = (array) array of segments.
-    \*/
-    R.path2curve = path2curve;
-    /*\
-     * Raphael.matrix
-     [ method ]
-     **
-     * Utility method
-     **
-     * Returns matrix based on given parameters.
-     > Parameters
-     - a (number)
-     - b (number)
-     - c (number)
-     - d (number)
-     - e (number)
-     - f (number)
-     = (object) @Matrix
-    \*/
-    R.matrix = function (a, b, c, d, e, f) {
-        return new Matrix(a, b, c, d, e, f);
-    };
-    function Matrix(a, b, c, d, e, f) {
-        if (a != null) {
-            this.a = +a;
-            this.b = +b;
-            this.c = +c;
-            this.d = +d;
-            this.e = +e;
-            this.f = +f;
-        } else {
-            this.a = 1;
-            this.b = 0;
-            this.c = 0;
-            this.d = 1;
-            this.e = 0;
-            this.f = 0;
-        }
-    }
-    (function (matrixproto) {
-        /*\
-         * Matrix.add
-         [ method ]
-         **
-         * Adds given matrix to existing one.
-         > Parameters
-         - a (number)
-         - b (number)
-         - c (number)
-         - d (number)
-         - e (number)
-         - f (number)
-         or
-         - matrix (object) @Matrix
-        \*/
-        matrixproto.add = function (a, b, c, d, e, f) {
-            var out = [[], [], []],
-                m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
-                matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
-                x, y, z, res;
-
-            if (a && a instanceof Matrix) {
-                matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
-            }
-
-            for (x = 0; x < 3; x++) {
-                for (y = 0; y < 3; y++) {
-                    res = 0;
-                    for (z = 0; z < 3; z++) {
-                        res += m[x][z] * matrix[z][y];
-                    }
-                    out[x][y] = res;
-                }
-            }
-            this.a = out[0][0];
-            this.b = out[1][0];
-            this.c = out[0][1];
-            this.d = out[1][1];
-            this.e = out[0][2];
-            this.f = out[1][2];
-        };
-        /*\
-         * Matrix.invert
-         [ method ]
-         **
-         * Returns inverted version of the matrix
-         = (object) @Matrix
-        \*/
-        matrixproto.invert = function () {
-            var me = this,
-                x = me.a * me.d - me.b * me.c;
-            return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
-        };
-        /*\
-         * Matrix.clone
-         [ method ]
-         **
-         * Returns copy of the matrix
-         = (object) @Matrix
-        \*/
-        matrixproto.clone = function () {
-            return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
-        };
-        /*\
-         * Matrix.translate
-         [ method ]
-         **
-         * Translate the matrix
-         > Parameters
-         - x (number)
-         - y (number)
-        \*/
-        matrixproto.translate = function (x, y) {
-            this.add(1, 0, 0, 1, x, y);
-        };
-        /*\
-         * Matrix.scale
-         [ method ]
-         **
-         * Scales the matrix
-         > Parameters
-         - x (number)
-         - y (number) #optional
-         - cx (number) #optional
-         - cy (number) #optional
-        \*/
-        matrixproto.scale = function (x, y, cx, cy) {
-            y == null && (y = x);
-            (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
-            this.add(x, 0, 0, y, 0, 0);
-            (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
-        };
-        /*\
-         * Matrix.rotate
-         [ method ]
-         **
-         * Rotates the matrix
-         > Parameters
-         - a (number)
-         - x (number)
-         - y (number)
-        \*/
-        matrixproto.rotate = function (a, x, y) {
-            a = R.rad(a);
-            x = x || 0;
-            y = y || 0;
-            var cos = +math.cos(a).toFixed(9),
-                sin = +math.sin(a).toFixed(9);
-            this.add(cos, sin, -sin, cos, x, y);
-            this.add(1, 0, 0, 1, -x, -y);
-        };
-        /*\
-         * Matrix.x
-         [ method ]
-         **
-         * Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y
-         > Parameters
-         - x (number)
-         - y (number)
-         = (number) x
-        \*/
-        matrixproto.x = function (x, y) {
-            return x * this.a + y * this.c + this.e;
-        };
-        /*\
-         * Matrix.y
-         [ method ]
-         **
-         * Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x
-         > Parameters
-         - x (number)
-         - y (number)
-         = (number) y
-        \*/
-        matrixproto.y = function (x, y) {
-            return x * this.b + y * this.d + this.f;
-        };
-        matrixproto.get = function (i) {
-            return +this[Str.fromCharCode(97 + i)].toFixed(4);
-        };
-        matrixproto.toString = function () {
-            return R.svg ?
-                "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
-                [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
-        };
-        matrixproto.toFilter = function () {
-            return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
-                ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
-                ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
-        };
-        matrixproto.offset = function () {
-            return [this.e.toFixed(4), this.f.toFixed(4)];
-        };
-        function norm(a) {
-            return a[0] * a[0] + a[1] * a[1];
-        }
-        function normalize(a) {
-            var mag = math.sqrt(norm(a));
-            a[0] && (a[0] /= mag);
-            a[1] && (a[1] /= mag);
-        }
-        /*\
-         * Matrix.split
-         [ method ]
-         **
-         * Splits matrix into primitive transformations
-         = (object) in format:
-         o dx (number) translation by x
-         o dy (number) translation by y
-         o scalex (number) scale by x
-         o scaley (number) scale by y
-         o shear (number) shear
-         o rotate (number) rotation in deg
-         o isSimple (boolean) could it be represented via simple transformations
-        \*/
-        matrixproto.split = function () {
-            var out = {};
-            // translation
-            out.dx = this.e;
-            out.dy = this.f;
-
-            // scale and shear
-            var row = [[this.a, this.c], [this.b, this.d]];
-            out.scalex = math.sqrt(norm(row[0]));
-            normalize(row[0]);
-
-            out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
-            row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
-
-            out.scaley = math.sqrt(norm(row[1]));
-            normalize(row[1]);
-            out.shear /= out.scaley;
-
-            // rotation
-            var sin = -row[0][1],
-                cos = row[1][1];
-            if (cos < 0) {
-                out.rotate = R.deg(math.acos(cos));
-                if (sin < 0) {
-                    out.rotate = 360 - out.rotate;
-                }
-            } else {
-                out.rotate = R.deg(math.asin(sin));
-            }
-
-            out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
-            out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
-            out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
-            return out;
-        };
-        /*\
-         * Matrix.toTransformString
-         [ method ]
-         **
-         * Return transform string that represents given matrix
-         = (string) transform string
-        \*/
-        matrixproto.toTransformString = function (shorter) {
-            var s = shorter || this[split]();
-            if (s.isSimple) {
-                s.scalex = +s.scalex.toFixed(4);
-                s.scaley = +s.scaley.toFixed(4);
-                s.rotate = +s.rotate.toFixed(4);
-                return  (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) + 
-                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
-                        (s.rotate ? "r" + [s.rotate, 0, 0] : E);
-            } else {
-                return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
-            }
-        };
-    })(Matrix.prototype);
-
-    // WebKit rendering bug workaround method
-    var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
-    if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
-        (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
-        /*\
-         * Paper.safari
-         [ method ]
-         **
-         * There is an inconvenient rendering bug in Safari (WebKit):
-         * sometimes the rendering should be forced.
-         * This method should help with dealing with this bug.
-        \*/
-        paperproto.safari = function () {
-            var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
-            setTimeout(function () {rect.remove();});
-        };
-    } else {
-        paperproto.safari = fun;
-    }
- 
-    var preventDefault = function () {
-        this.returnValue = false;
-    },
-    preventTouch = function () {
-        return this.originalEvent.preventDefault();
-    },
-    stopPropagation = function () {
-        this.cancelBubble = true;
-    },
-    stopTouch = function () {
-        return this.originalEvent.stopPropagation();
-    },
-    addEvent = (function () {
-        if (g.doc.addEventListener) {
-            return function (obj, type, fn, element) {
-                var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
-                    f = function (e) {
-                        var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
-                            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
-                            x = e.clientX + scrollX,
-                            y = e.clientY + scrollY;
-                    if (supportsTouch && touchMap[has](type)) {
-                        for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
-                            if (e.targetTouches[i].target == obj) {
-                                var olde = e;
-                                e = e.targetTouches[i];
-                                e.originalEvent = olde;
-                                e.preventDefault = preventTouch;
-                                e.stopPropagation = stopTouch;
-                                break;
-                            }
-                        }
-                    }
-                    return fn.call(element, e, x, y);
-                };
-                obj.addEventListener(realName, f, false);
-                return function () {
-                    obj.removeEventListener(realName, f, false);
-                    return true;
-                };
-            };
-        } else if (g.doc.attachEvent) {
-            return function (obj, type, fn, element) {
-                var f = function (e) {
-                    e = e || g.win.event;
-                    var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
-                        scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
-                        x = e.clientX + scrollX,
-                        y = e.clientY + scrollY;
-                    e.preventDefault = e.preventDefault || preventDefault;
-                    e.stopPropagation = e.stopPropagation || stopPropagation;
-                    return fn.call(element, e, x, y);
-                };
-                obj.attachEvent("on" + type, f);
-                var detacher = function () {
-                    obj.detachEvent("on" + type, f);
-                    return true;
-                };
-                return detacher;
-            };
-        }
-    })(),
-    drag = [],
-    dragMove = function (e) {
-        var x = e.clientX,
-            y = e.clientY,
-            scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
-            scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
-            dragi,
-            j = drag.length;
-        while (j--) {
-            dragi = drag[j];
-            if (supportsTouch) {
-                var i = e.touches.length,
-                    touch;
-                while (i--) {
-                    touch = e.touches[i];
-                    if (touch.identifier == dragi.el._drag.id) {
-                        x = touch.clientX;
-                        y = touch.clientY;
-                        (e.originalEvent ? e.originalEvent : e).preventDefault();
-                        break;
-                    }
-                }
-            } else {
-                e.preventDefault();
-            }
-            var node = dragi.el.node,
-                o,
-                next = node.nextSibling,
-                parent = node.parentNode,
-                display = node.style.display;
-            g.win.opera && parent.removeChild(node);
-            node.style.display = "none";
-            o = dragi.el.paper.getElementByPoint(x, y);
-            node.style.display = display;
-            g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
-            o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
-            x += scrollX;
-            y += scrollY;
-            eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
-        }
-    },
-    dragUp = function (e) {
-        R.unmousemove(dragMove).unmouseup(dragUp);
-        var i = drag.length,
-            dragi;
-        while (i--) {
-            dragi = drag[i];
-            dragi.el._drag = {};
-            eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
-        }
-        drag = [];
-    },
-    /*\
-     * Raphael.el
-     [ property (object) ]
-     **
-     * You can add your own method to elements. This is usefull when you want to hack default functionality or
-     * want to wrap some common transformation or attributes in one method. In difference to canvas methods,
-     * you can redefine element method at any time. Expending element methods wouldn’t affect set.
-     > Usage
-     | Raphael.el.red = function () {
-     |     this.attr({fill: "#f00"});
-     | };
-     | // then use it
-     | paper.circle(100, 100, 20).red();
-    \*/
-    elproto = R.el = {};
-    /*\
-     * Element.click
-     [ method ]
-     **
-     * Adds event handler for click for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.unclick
-     [ method ]
-     **
-     * Removes event handler for click for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.dblclick
-     [ method ]
-     **
-     * Adds event handler for double click for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.undblclick
-     [ method ]
-     **
-     * Removes event handler for double click for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.mousedown
-     [ method ]
-     **
-     * Adds event handler for mousedown for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.unmousedown
-     [ method ]
-     **
-     * Removes event handler for mousedown for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.mousemove
-     [ method ]
-     **
-     * Adds event handler for mousemove for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.unmousemove
-     [ method ]
-     **
-     * Removes event handler for mousemove for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.mouseout
-     [ method ]
-     **
-     * Adds event handler for mouseout for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.unmouseout
-     [ method ]
-     **
-     * Removes event handler for mouseout for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.mouseover
-     [ method ]
-     **
-     * Adds event handler for mouseover for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.unmouseover
-     [ method ]
-     **
-     * Removes event handler for mouseover for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.mouseup
-     [ method ]
-     **
-     * Adds event handler for mouseup for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.unmouseup
-     [ method ]
-     **
-     * Removes event handler for mouseup for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.touchstart
-     [ method ]
-     **
-     * Adds event handler for touchstart for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.untouchstart
-     [ method ]
-     **
-     * Removes event handler for touchstart for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.touchmove
-     [ method ]
-     **
-     * Adds event handler for touchmove for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.untouchmove
-     [ method ]
-     **
-     * Removes event handler for touchmove for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.touchend
-     [ method ]
-     **
-     * Adds event handler for touchend for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.untouchend
-     [ method ]
-     **
-     * Removes event handler for touchend for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    
-    /*\
-     * Element.touchcancel
-     [ method ]
-     **
-     * Adds event handler for touchcancel for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    /*\
-     * Element.untouchcancel
-     [ method ]
-     **
-     * Removes event handler for touchcancel for the element.
-     > Parameters
-     - handler (function) handler for the event
-     = (object) @Element
-    \*/
-    for (var i = events.length; i--;) {
-        (function (eventName) {
-            R[eventName] = elproto[eventName] = function (fn, scope) {
-                if (R.is(fn, "function")) {
-                    this.events = this.events || [];
-                    this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)});
-                }
-                return this;
-            };
-            R["un" + eventName] = elproto["un" + eventName] = function (fn) {
-                var events = this.events || [],
-                    l = events.length;
-                while (l--) if (events[l].name == eventName && events[l].f == fn) {
-                    events[l].unbind();
-                    events.splice(l, 1);
-                    !events.length && delete this.events;
-                    return this;
-                }
-                return this;
-            };
-        })(events[i]);
-    }
-    
-    /*\
-     * Element.data
-     [ method ]
-     **
-     * Adds or retrieves given value asociated with given key.
-     ** 
-     * See also @Element.removeData
-     > Parameters
-     - key (string) key to store data
-     - value (any) #optional value to store
-     = (object) @Element
-     * or, if value is not specified:
-     = (any) value
-     > Usage
-     | for (var i = 0, i < 5, i++) {
-     |     paper.circle(10 + 15 * i, 10, 10)
-     |          .attr({fill: "#000"})
-     |          .data("i", i)
-     |          .click(function () {
-     |             alert(this.data("i"));
-     |          });
-     | }
-    \*/
-    elproto.data = function (key, value) {
-        var data = eldata[this.id] = eldata[this.id] || {};
-        if (arguments.length == 1) {
-            if (R.is(key, "object")) {
-                for (var i in key) if (key[has](i)) {
-                    this.data(i, key[i]);
-                }
-                return this;
-            }
-            eve("raphael.data.get." + this.id, this, data[key], key);
-            return data[key];
-        }
-        data[key] = value;
-        eve("raphael.data.set." + this.id, this, value, key);
-        return this;
-    };
-    /*\
-     * Element.removeData
-     [ method ]
-     **
-     * Removes value associated with an element by given key.
-     * If key is not provided, removes all the data of the element.
-     > Parameters
-     - key (string) #optional key
-     = (object) @Element
-    \*/
-    elproto.removeData = function (key) {
-        if (key == null) {
-            eldata[this.id] = {};
-        } else {
-            eldata[this.id] && delete eldata[this.id][key];
-        }
-        return this;
-    };
-     /*\
-     * Element.getData
-     [ method ]
-     **
-     * Retrieves the element data
-     = (object) data
-    \*/
-    elproto.getData = function () {
-        return eldata[this.id] || {};
-    };
-    /*\
-     * Element.hover
-     [ method ]
-     **
-     * Adds event handlers for hover for the element.
-     > Parameters
-     - f_in (function) handler for hover in
-     - f_out (function) handler for hover out
-     - icontext (object) #optional context for hover in handler
-     - ocontext (object) #optional context for hover out handler
-     = (object) @Element
-    \*/
-    elproto.hover = function (f_in, f_out, scope_in, scope_out) {
-        return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
-    };
-    /*\
-     * Element.unhover
-     [ method ]
-     **
-     * Removes event handlers for hover for the element.
-     > Parameters
-     - f_in (function) handler for hover in
-     - f_out (function) handler for hover out
-     = (object) @Element
-    \*/
-    elproto.unhover = function (f_in, f_out) {
-        return this.unmouseover(f_in).unmouseout(f_out);
-    };
-    var draggable = [];
-    /*\
-     * Element.drag
-     [ method ]
-     **
-     * Adds event handlers for drag of the element.
-     > Parameters
-     - onmove (function) handler for moving
-     - onstart (function) handler for drag start
-     - onend (function) handler for drag end
-     - mcontext (object) #optional context for moving handler
-     - scontext (object) #optional context for drag start handler
-     - econtext (object) #optional context for drag end handler
-     * Additionaly following `drag` events will be triggered: `drag.start.<id>` on start, 
-     * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element 
-     * `drag.over.<id>` will be fired as well.
-     *
-     * Start event and start handler will be called in specified context or in context of the element with following parameters:
-     o x (number) x position of the mouse
-     o y (number) y position of the mouse
-     o event (object) DOM event object
-     * Move event and move handler will be called in specified context or in context of the element with following parameters:
-     o dx (number) shift by x from the start point
-     o dy (number) shift by y from the start point
-     o x (number) x position of the mouse
-     o y (number) y position of the mouse
-     o event (object) DOM event object
-     * End event and end handler will be called in specified context or in context of the element with following parameters:
-     o event (object) DOM event object
-     = (object) @Element
-    \*/
-    elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
-        function start(e) {
-            (e.originalEvent || e).preventDefault();
-            var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
-                scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
-            this._drag.x = e.clientX + scrollX;
-            this._drag.y = e.clientY + scrollY;
-            this._drag.id = e.identifier;
-            !drag.length && R.mousemove(dragMove).mouseup(dragUp);
-            drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
-            onstart && eve.on("raphael.drag.start." + this.id, onstart);
-            onmove && eve.on("raphael.drag.move." + this.id, onmove);
-            onend && eve.on("raphael.drag.end." + this.id, onend);
-            eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
-        }
-        this._drag = {};
-        draggable.push({el: this, start: start});
-        this.mousedown(start);
-        return this;
-    };
-    /*\
-     * Element.onDragOver
-     [ method ]
-     **
-     * Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
-     > Parameters
-     - f (function) handler for event, first argument would be the element you are dragging over
-    \*/
-    elproto.onDragOver = function (f) {
-        f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
-    };
-    /*\
-     * Element.undrag
-     [ method ]
-     **
-     * Removes all drag event handlers from given element.
-    \*/
-    elproto.undrag = function () {
-        var i = draggable.length;
-        while (i--) if (draggable[i].el == this) {
-            this.unmousedown(draggable[i].start);
-            draggable.splice(i, 1);
-            eve.unbind("raphael.drag.*." + this.id);
-        }
-        !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
-        drag = [];
-    };
-    /*\
-     * Paper.circle
-     [ method ]
-     **
-     * Draws a circle.
-     **
-     > Parameters
-     **
-     - x (number) x coordinate of the centre
-     - y (number) y coordinate of the centre
-     - r (number) radius
-     = (object) Raphaël element object with type “circle”
-     **
-     > Usage
-     | var c = paper.circle(50, 50, 40);
-    \*/
-    paperproto.circle = function (x, y, r) {
-        var out = R._engine.circle(this, x || 0, y || 0, r || 0);
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Paper.rect
-     [ method ]
-     *
-     * Draws a rectangle.
-     **
-     > Parameters
-     **
-     - x (number) x coordinate of the top left corner
-     - y (number) y coordinate of the top left corner
-     - width (number) width
-     - height (number) height
-     - r (number) #optional radius for rounded corners, default is 0
-     = (object) Raphaël element object with type “rect”
-     **
-     > Usage
-     | // regular rectangle
-     | var c = paper.rect(10, 10, 50, 50);
-     | // rectangle with rounded corners
-     | var c = paper.rect(40, 40, 50, 50, 10);
-    \*/
-    paperproto.rect = function (x, y, w, h, r) {
-        var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Paper.ellipse
-     [ method ]
-     **
-     * Draws an ellipse.
-     **
-     > Parameters
-     **
-     - x (number) x coordinate of the centre
-     - y (number) y coordinate of the centre
-     - rx (number) horizontal radius
-     - ry (number) vertical radius
-     = (object) Raphaël element object with type “ellipse”
-     **
-     > Usage
-     | var c = paper.ellipse(50, 50, 40, 20);
-    \*/
-    paperproto.ellipse = function (x, y, rx, ry) {
-        var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Paper.path
-     [ method ]
-     **
-     * Creates a path element by given path data string.
-     > Parameters
-     - pathString (string) #optional path string in SVG format.
-     * Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example:
-     | "M10,20L30,40"
-     * Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative.
-     *
-     # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a>.</p>
-     # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
-     # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
-     # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
-     # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
-     # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
-     # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
-     # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
-     # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
-     # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
-     # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
-     # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
-     # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
-     * * “Catmull-Rom curveto” is a not standard SVG command and added in 2.0 to make life easier.
-     * Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning.
-     > Usage
-     | var c = paper.path("M10 10L90 90");
-     | // draw a diagonal line:
-     | // move to 10,10, line to 90,90
-     * For example of path strings, check out these icons: http://raphaeljs.com/icons/
-    \*/
-    paperproto.path = function (pathString) {
-        pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
-        var out = R._engine.path(R.format[apply](R, arguments), this);
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Paper.image
-     [ method ]
-     **
-     * Embeds an image into the surface.
-     **
-     > Parameters
-     **
-     - src (string) URI of the source image
-     - x (number) x coordinate position
-     - y (number) y coordinate position
-     - width (number) width of the image
-     - height (number) height of the image
-     = (object) Raphaël element object with type “image”
-     **
-     > Usage
-     | var c = paper.image("apple.png", 10, 10, 80, 80);
-    \*/
-    paperproto.image = function (src, x, y, w, h) {
-        var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Paper.text
-     [ method ]
-     **
-     * Draws a text string. If you need line breaks, put “\n” in the string.
-     **
-     > Parameters
-     **
-     - x (number) x coordinate position
-     - y (number) y coordinate position
-     - text (string) The text string to draw
-     = (object) Raphaël element object with type “text”
-     **
-     > Usage
-     | var t = paper.text(50, 50, "Raphaël\nkicks\nbutt!");
-    \*/
-    paperproto.text = function (x, y, text) {
-        var out = R._engine.text(this, x || 0, y || 0, Str(text));
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Paper.set
-     [ method ]
-     **
-     * Creates array-like object to keep and operate several elements at once.
-     * Warning: it doesn’t create any elements for itself in the page, it just groups existing elements.
-     * Sets act as pseudo elements — all methods available to an element can be used on a set.
-     = (object) array-like object that represents set of elements
-     **
-     > Usage
-     | var st = paper.set();
-     | st.push(
-     |     paper.circle(10, 10, 5),
-     |     paper.circle(30, 10, 5)
-     | );
-     | st.attr({fill: "red"}); // changes the fill of both circles
-    \*/
-    paperproto.set = function (itemsArray) {
-        !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
-        var out = new Set(itemsArray);
-        this.__set__ && this.__set__.push(out);
-        out["paper"] = this;
-        out["type"] = "set";
-        return out;
-    };
-    /*\
-     * Paper.setStart
-     [ method ]
-     **
-     * Creates @Paper.set. All elements that will be created after calling this method and before calling
-     * @Paper.setFinish will be added to the set.
-     **
-     > Usage
-     | paper.setStart();
-     | paper.circle(10, 10, 5),
-     | paper.circle(30, 10, 5)
-     | var st = paper.setFinish();
-     | st.attr({fill: "red"}); // changes the fill of both circles
-    \*/
-    paperproto.setStart = function (set) {
-        this.__set__ = set || this.set();
-    };
-    /*\
-     * Paper.setFinish
-     [ method ]
-     **
-     * See @Paper.setStart. This method finishes catching and returns resulting set.
-     **
-     = (object) set
-    \*/
-    paperproto.setFinish = function (set) {
-        var out = this.__set__;
-        delete this.__set__;
-        return out;
-    };
-    /*\
-     * Paper.setSize
-     [ method ]
-     **
-     * If you need to change dimensions of the canvas call this method
-     **
-     > Parameters
-     **
-     - width (number) new width of the canvas
-     - height (number) new height of the canvas
-    \*/
-    paperproto.setSize = function (width, height) {
-        return R._engine.setSize.call(this, width, height);
-    };
-    /*\
-     * Paper.setViewBox
-     [ method ]
-     **
-     * Sets the view box of the paper. Practically it gives you ability to zoom and pan whole paper surface by 
-     * specifying new boundaries.
-     **
-     > Parameters
-     **
-     - x (number) new x position, default is `0`
-     - y (number) new y position, default is `0`
-     - w (number) new width of the canvas
-     - h (number) new height of the canvas
-     - fit (boolean) `true` if you want graphics to fit into new boundary box
-    \*/
-    paperproto.setViewBox = function (x, y, w, h, fit) {
-        return R._engine.setViewBox.call(this, x, y, w, h, fit);
-    };
-    /*\
-     * Paper.top
-     [ property ]
-     **
-     * Points to the topmost element on the paper
-    \*/
-    /*\
-     * Paper.bottom
-     [ property ]
-     **
-     * Points to the bottom element on the paper
-    \*/
-    paperproto.top = paperproto.bottom = null;
-    /*\
-     * Paper.raphael
-     [ property ]
-     **
-     * Points to the @Raphael object/function
-    \*/
-    paperproto.raphael = R;
-    var getOffset = function (elem) {
-        var box = elem.getBoundingClientRect(),
-            doc = elem.ownerDocument,
-            body = doc.body,
-            docElem = doc.documentElement,
-            clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
-            top  = box.top  + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
-            left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
-        return {
-            y: top,
-            x: left
-        };
-    };
-    /*\
-     * Paper.getElementByPoint
-     [ method ]
-     **
-     * Returns you topmost element under given point.
-     **
-     = (object) Raphaël element object
-     > Parameters
-     **
-     - x (number) x coordinate from the top left corner of the window
-     - y (number) y coordinate from the top left corner of the window
-     > Usage
-     | paper.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
-    \*/
-    paperproto.getElementByPoint = function (x, y) {
-        var paper = this,
-            svg = paper.canvas,
-            target = g.doc.elementFromPoint(x, y);
-        if (g.win.opera && target.tagName == "svg") {
-            var so = getOffset(svg),
-                sr = svg.createSVGRect();
-            sr.x = x - so.x;
-            sr.y = y - so.y;
-            sr.width = sr.height = 1;
-            var hits = svg.getIntersectionList(sr, null);
-            if (hits.length) {
-                target = hits[hits.length - 1];
-            }
-        }
-        if (!target) {
-            return null;
-        }
-        while (target.parentNode && target != svg.parentNode && !target.raphael) {
-            target = target.parentNode;
-        }
-        target == paper.canvas.parentNode && (target = svg);
-        target = target && target.raphael ? paper.getById(target.raphaelid) : null;
-        return target;
-    };
-
-    /*\
-     * Paper.getElementsByBBox
-     [ method ]
-     **
-     * Returns set of elements that have an intersecting bounding box
-     **
-     > Parameters
-     **
-     - bbox (string) bbox to check with
-     = (object) @Set
-     \*/
-    paperproto.getElementsByBBox = function (bbox) {
-        var set = this.set();
-        this.forEach(function (el) {
-            if (Raphael.isBBoxIntersect(el.getBBox(), bbox)) {
-                set.push(el);
-            }
-        });
-        return set;
-    };
-
-    /*\
-     * Paper.getById
-     [ method ]
-     **
-     * Returns you element by its internal ID.
-     **
-     > Parameters
-     **
-     - id (number) id
-     = (object) Raphaël element object
-    \*/
-    paperproto.getById = function (id) {
-        var bot = this.bottom;
-        while (bot) {
-            if (bot.id == id) {
-                return bot;
-            }
-            bot = bot.next;
-        }
-        return null;
-    };
-    /*\
-     * Paper.forEach
-     [ method ]
-     **
-     * Executes given function for each element on the paper
-     *
-     * If callback function returns `false` it will stop loop running.
-     **
-     > Parameters
-     **
-     - callback (function) function to run
-     - thisArg (object) context object for the callback
-     = (object) Paper object
-     > Usage
-     | paper.forEach(function (el) {
-     |     el.attr({ stroke: "blue" });
-     | });
-    \*/
-    paperproto.forEach = function (callback, thisArg) {
-        var bot = this.bottom;
-        while (bot) {
-            if (callback.call(thisArg, bot) === false) {
-                return this;
-            }
-            bot = bot.next;
-        }
-        return this;
-    };
-    /*\
-     * Paper.getElementsByPoint
-     [ method ]
-     **
-     * Returns set of elements that have common point inside
-     **
-     > Parameters
-     **
-     - x (number) x coordinate of the point
-     - y (number) y coordinate of the point
-     = (object) @Set
-    \*/
-    paperproto.getElementsByPoint = function (x, y) {
-        var set = this.set();
-        this.forEach(function (el) {
-            if (el.isPointInside(x, y)) {
-                set.push(el);
-            }
-        });
-        return set;
-    };
-    function x_y() {
-        return this.x + S + this.y;
-    }
-    function x_y_w_h() {
-        return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
-    }
-    /*\
-     * Element.isPointInside
-     [ method ]
-     **
-     * Determine if given point is inside this element’s shape
-     **
-     > Parameters
-     **
-     - x (number) x coordinate of the point
-     - y (number) y coordinate of the point
-     = (boolean) `true` if point inside the shape
-    \*/
-    elproto.isPointInside = function (x, y) {
-        var rp = this.realPath = this.realPath || getPath[this.type](this);
-        return R.isPointInsidePath(rp, x, y);
-    };
-    /*\
-     * Element.getBBox
-     [ method ]
-     **
-     * Return bounding box for a given element
-     **
-     > Parameters
-     **
-     - isWithoutTransform (boolean) flag, `true` if you want to have bounding box before transformations. Default is `false`.
-     = (object) Bounding box object:
-     o {
-     o     x: (number) top left corner x
-     o     y: (number) top left corner y
-     o     x2: (number) bottom right corner x
-     o     y2: (number) bottom right corner y
-     o     width: (number) width
-     o     height: (number) height
-     o }
-    \*/
-    elproto.getBBox = function (isWithoutTransform) {
-        if (this.removed) {
-            return {};
-        }
-        var _ = this._;
-        if (isWithoutTransform) {
-            if (_.dirty || !_.bboxwt) {
-                this.realPath = getPath[this.type](this);
-                _.bboxwt = pathDimensions(this.realPath);
-                _.bboxwt.toString = x_y_w_h;
-                _.dirty = 0;
-            }
-            return _.bboxwt;
-        }
-        if (_.dirty || _.dirtyT || !_.bbox) {
-            if (_.dirty || !this.realPath) {
-                _.bboxwt = 0;
-                this.realPath = getPath[this.type](this);
-            }
-            _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
-            _.bbox.toString = x_y_w_h;
-            _.dirty = _.dirtyT = 0;
-        }
-        return _.bbox;
-    };
-    /*\
-     * Element.clone
-     [ method ]
-     **
-     = (object) clone of a given element
-     **
-    \*/
-    elproto.clone = function () {
-        if (this.removed) {
-            return null;
-        }
-        var out = this.paper[this.type]().attr(this.attr());
-        this.__set__ && this.__set__.push(out);
-        return out;
-    };
-    /*\
-     * Element.glow
-     [ method ]
-     **
-     * Return set of elements that create glow-like effect around given element. See @Paper.set.
-     *
-     * Note: Glow is not connected to the element. If you change element attributes it won’t adjust itself.
-     **
-     > Parameters
-     **
-     - glow (object) #optional parameters object with all properties optional:
-     o {
-     o     width (number) size of the glow, default is `10`
-     o     fill (boolean) will it be filled, default is `false`
-     o     opacity (number) opacity, default is `0.5`
-     o     offsetx (number) horizontal offset, default is `0`
-     o     offsety (number) vertical offset, default is `0`
-     o     color (string) glow colour, default is `black`
-     o }
-     = (object) @Paper.set of elements that represents glow
-    \*/
-    elproto.glow = function (glow) {
-        if (this.type == "text") {
-            return null;
-        }
-        glow = glow || {};
-        var s = {
-            width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
-            fill: glow.fill || false,
-            opacity: glow.opacity || .5,
-            offsetx: glow.offsetx || 0,
-            offsety: glow.offsety || 0,
-            color: glow.color || "#000"
-        },
-            c = s.width / 2,
-            r = this.paper,
-            out = r.set(),
-            path = this.realPath || getPath[this.type](this);
-        path = this.matrix ? mapPath(path, this.matrix) : path;
-        for (var i = 1; i < c + 1; i++) {
-            out.push(r.path(path).attr({
-                stroke: s.color,
-                fill: s.fill ? s.color : "none",
-                "stroke-linejoin": "round",
-                "stroke-linecap": "round",
-                "stroke-width": +(s.width / c * i).toFixed(3),
-                opacity: +(s.opacity / c).toFixed(3)
-            }));
-        }
-        return out.insertBefore(this).translate(s.offsetx, s.offsety);
-    };
-    var curveslengths = {},
-    getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
-        if (length == null) {
-            return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
-        } else {
-            return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
-        }
-    },
-    getLengthFactory = function (istotal, subpath) {
-        return function (path, length, onlystart) {
-            path = path2curve(path);
-            var x, y, p, l, sp = "", subpaths = {}, point,
-                len = 0;
-            for (var i = 0, ii = path.length; i < ii; i++) {
-                p = path[i];
-                if (p[0] == "M") {
-                    x = +p[1];
-                    y = +p[2];
-                } else {
-                    l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
-                    if (len + l > length) {
-                        if (subpath && !subpaths.start) {
-                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
-                            sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
-                            if (onlystart) {return sp;}
-                            subpaths.start = sp;
-                            sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
-                            len += l;
-                            x = +p[5];
-                            y = +p[6];
-                            continue;
-                        }
-                        if (!istotal && !subpath) {
-                            point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
-                            return {x: point.x, y: point.y, alpha: point.alpha};
-                        }
-                    }
-                    len += l;
-                    x = +p[5];
-                    y = +p[6];
-                }
-                sp += p.shift() + p;
-            }
-            subpaths.end = sp;
-            point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
-            point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
-            return point;
-        };
-    };
-    var getTotalLength = getLengthFactory(1),
-        getPointAtLength = getLengthFactory(),
-        getSubpathsAtLength = getLengthFactory(0, 1);
-    /*\
-     * Raphael.getTotalLength
-     [ method ]
-     **
-     * Returns length of the given path in pixels.
-     **
-     > Parameters
-     **
-     - path (string) SVG path string.
-     **
-     = (number) length.
-    \*/
-    R.getTotalLength = getTotalLength;
-    /*\
-     * Raphael.getPointAtLength
-     [ method ]
-     **
-     * Return coordinates of the point located at the given length on the given path.
-     **
-     > Parameters
-     **
-     - path (string) SVG path string
-     - length (number)
-     **
-     = (object) representation of the point:
-     o {
-     o     x: (number) x coordinate
-     o     y: (number) y coordinate
-     o     alpha: (number) angle of derivative
-     o }
-    \*/
-    R.getPointAtLength = getPointAtLength;
-    /*\
-     * Raphael.getSubpath
-     [ method ]
-     **
-     * Return subpath of a given path from given length to given length.
-     **
-     > Parameters
-     **
-     - path (string) SVG path string
-     - from (number) position of the start of the segment
-     - to (number) position of the end of the segment
-     **
-     = (string) pathstring for the segment
-    \*/
-    R.getSubpath = function (path, from, to) {
-        if (this.getTotalLength(path) - to < 1e-6) {
-            return getSubpathsAtLength(path, from).end;
-        }
-        var a = getSubpathsAtLength(path, to, 1);
-        return from ? getSubpathsAtLength(a, from).end : a;
-    };
-    /*\
-     * Element.getTotalLength
-     [ method ]
-     **
-     * Returns length of the path in pixels. Only works for element of “path” type.
-     = (number) length.
-    \*/
-    elproto.getTotalLength = function () {
-        if (this.type != "path") {return;}
-        if (this.node.getTotalLength) {
-            return this.node.getTotalLength();
-        }
-        return getTotalLength(this.attrs.path);
-    };
-    /*\
-     * Element.getPointAtLength
-     [ method ]
-     **
-     * Return coordinates of the point located at the given length on the given path. Only works for element of “path” type.
-     **
-     > Parameters
-     **
-     - length (number)
-     **
-     = (object) representation of the point:
-     o {
-     o     x: (number) x coordinate
-     o     y: (number) y coordinate
-     o     alpha: (number) angle of derivative
-     o }
-    \*/
-    elproto.getPointAtLength = function (length) {
-        if (this.type != "path") {return;}
-        return getPointAtLength(this.attrs.path, length);
-    };
-    /*\
-     * Element.getSubpath
-     [ method ]
-     **
-     * Return subpath of a given element from given length to given length. Only works for element of “path” type.
-     **
-     > Parameters
-     **
-     - from (number) position of the start of the segment
-     - to (number) position of the end of the segment
-     **
-     = (string) pathstring for the segment
-    \*/
-    elproto.getSubpath = function (from, to) {
-        if (this.type != "path") {return;}
-        return R.getSubpath(this.attrs.path, from, to);
-    };
-    /*\
-     * Raphael.easing_formulas
-     [ property ]
-     **
-     * Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
-     # <ul>
-     #     <li>“linear”</li>
-     #     <li>“&lt;” or “easeIn” or “ease-in”</li>
-     #     <li>“>” or “easeOut” or “ease-out”</li>
-     #     <li>“&lt;>” or “easeInOut” or “ease-in-out”</li>
-     #     <li>“backIn” or “back-in”</li>
-     #     <li>“backOut” or “back-out”</li>
-     #     <li>“elastic”</li>
-     #     <li>“bounce”</li>
-     # </ul>
-     # <p>See also <a href="http://raphaeljs.com/easing.html">Easing demo</a>.</p>
-    \*/
-    var ef = R.easing_formulas = {
-        linear: function (n) {
-            return n;
-        },
-        "<": function (n) {
-            return pow(n, 1.7);
-        },
-        ">": function (n) {
-            return pow(n, .48);
-        },
-        "<>": function (n) {
-            var q = .48 - n / 1.04,
-                Q = math.sqrt(.1734 + q * q),
-                x = Q - q,
-                X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
-                y = -Q - q,
-                Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
-                t = X + Y + .5;
-            return (1 - t) * 3 * t * t + t * t * t;
-        },
-        backIn: function (n) {
-            var s = 1.70158;
-            return n * n * ((s + 1) * n - s);
-        },
-        backOut: function (n) {
-            n = n - 1;
-            var s = 1.70158;
-            return n * n * ((s + 1) * n + s) + 1;
-        },
-        elastic: function (n) {
-            if (n == !!n) {
-                return n;
-            }
-            return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
-        },
-        bounce: function (n) {
-            var s = 7.5625,
-                p = 2.75,
-                l;
-            if (n < (1 / p)) {
-                l = s * n * n;
-            } else {
-                if (n < (2 / p)) {
-                    n -= (1.5 / p);
-                    l = s * n * n + .75;
-                } else {
-                    if (n < (2.5 / p)) {
-                        n -= (2.25 / p);
-                        l = s * n * n + .9375;
-                    } else {
-                        n -= (2.625 / p);
-                        l = s * n * n + .984375;
-                    }
-                }
-            }
-            return l;
-        }
-    };
-    ef.easeIn = ef["ease-in"] = ef["<"];
-    ef.easeOut = ef["ease-out"] = ef[">"];
-    ef.easeInOut = ef["ease-in-out"] = ef["<>"];
-    ef["back-in"] = ef.backIn;
-    ef["back-out"] = ef.backOut;
-
-    var animationElements = [],
-        requestAnimFrame = window.requestAnimationFrame       ||
-                           window.webkitRequestAnimationFrame ||
-                           window.mozRequestAnimationFrame    ||
-                           window.oRequestAnimationFrame      ||
-                           window.msRequestAnimationFrame     ||
-                           function (callback) {
-                               setTimeout(callback, 16);
-                           },
-        animation = function () {
-            var Now = +new Date,
-                l = 0;
-            for (; l < animationElements.length; l++) {
-                var e = animationElements[l];
-                if (e.el.removed || e.paused) {
-                    continue;
-                }
-                var time = Now - e.start,
-                    ms = e.ms,
-                    easing = e.easing,
-                    from = e.from,
-                    diff = e.diff,
-                    to = e.to,
-                    t = e.t,
-                    that = e.el,
-                    set = {},
-                    now,
-                    init = {},
-                    key;
-                if (e.initstatus) {
-                    time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
-                    e.status = e.initstatus;
-                    delete e.initstatus;
-                    e.stop && animationElements.splice(l--, 1);
-                } else {
-                    e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
-                }
-                if (time < 0) {
-                    continue;
-                }
-                if (time < ms) {
-                    var pos = easing(time / ms);
-                    for (var attr in from) if (from[has](attr)) {
-                        switch (availableAnimAttrs[attr]) {
-                            case nu:
-                                now = +from[attr] + pos * ms * diff[attr];
-                                break;
-                            case "colour":
-                                now = "rgb(" + [
-                                    upto255(round(from[attr].r + pos * ms * diff[attr].r)),
-                                    upto255(round(from[attr].g + pos * ms * diff[attr].g)),
-                                    upto255(round(from[attr].b + pos * ms * diff[attr].b))
-                                ].join(",") + ")";
-                                break;
-                            case "path":
-                                now = [];
-                                for (var i = 0, ii = from[attr].length; i < ii; i++) {
-                                    now[i] = [from[attr][i][0]];
-                                    for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
-                                        now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
-                                    }
-                                    now[i] = now[i].join(S);
-                                }
-                                now = now.join(S);
-                                break;
-                            case "transform":
-                                if (diff[attr].real) {
-                                    now = [];
-                                    for (i = 0, ii = from[attr].length; i < ii; i++) {
-                                        now[i] = [from[attr][i][0]];
-                                        for (j = 1, jj = from[attr][i].length; j < jj; j++) {
-                                            now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
-                                        }
-                                    }
-                                } else {
-                                    var get = function (i) {
-                                        return +from[attr][i] + pos * ms * diff[attr][i];
-                                    };
-                                    // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
-                                    now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
-                                }
-                                break;
-                            case "csv":
-                                if (attr == "clip-rect") {
-                                    now = [];
-                                    i = 4;
-                                    while (i--) {
-                                        now[i] = +from[attr][i] + pos * ms * diff[attr][i];
-                                    }
-                                }
-                                break;
-                            default:
-                                var from2 = [][concat](from[attr]);
-                                now = [];
-                                i = that.paper.customAttributes[attr].length;
-                                while (i--) {
-                                    now[i] = +from2[i] + pos * ms * diff[attr][i];
-                                }
-                                break;
-                        }
-                        set[attr] = now;
-                    }
-                    that.attr(set);
-                    (function (id, that, anim) {
-                        setTimeout(function () {
-                            eve("raphael.anim.frame." + id, that, anim);
-                        });
-                    })(that.id, that, e.anim);
-                } else {
-                    (function(f, el, a) {
-                        setTimeout(function() {
-                            eve("raphael.anim.frame." + el.id, el, a);
-                            eve("raphael.anim.finish." + el.id, el, a);
-                            R.is(f, "function") && f.call(el);
-                        });
-                    })(e.callback, that, e.anim);
-                    that.attr(to);
-                    animationElements.splice(l--, 1);
-                    if (e.repeat > 1 && !e.next) {
-                        for (key in to) if (to[has](key)) {
-                            init[key] = e.totalOrigin[key];
-                        }
-                        e.el.attr(init);
-                        runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
-                    }
-                    if (e.next && !e.stop) {
-                        runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
-                    }
-                }
-            }
-            R.svg && that && that.paper && that.paper.safari();
-            animationElements.length && requestAnimFrame(animation);
-        },
-        upto255 = function (color) {
-            return color > 255 ? 255 : color < 0 ? 0 : color;
-        };
-    /*\
-     * Element.animateWith
-     [ method ]
-     **
-     * Acts similar to @Element.animate, but ensure that given animation runs in sync with another given element.
-     **
-     > Parameters
-     **
-     - el (object) element to sync with
-     - anim (object) animation to sync with
-     - params (object) #optional final attributes for the element, see also @Element.attr
-     - ms (number) #optional number of milliseconds for animation to run
-     - easing (string) #optional easing type. Accept on of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
-     - callback (function) #optional callback function. Will be called at the end of animation.
-     * or
-     - element (object) element to sync with
-     - anim (object) animation to sync with
-     - animation (object) #optional animation object, see @Raphael.animation
-     **
-     = (object) original element
-    \*/
-    elproto.animateWith = function (el, anim, params, ms, easing, callback) {
-        var element = this;
-        if (element.removed) {
-            callback && callback.call(element);
-            return element;
-        }
-        var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
-            x, y;
-        runAnimation(a, element, a.percents[0], null, element.attr());
-        for (var i = 0, ii = animationElements.length; i < ii; i++) {
-            if (animationElements[i].anim == anim && animationElements[i].el == el) {
-                animationElements[ii - 1].start = animationElements[i].start;
-                break;
-            }
-        }
-        return element;
-        // 
-        // 
-        // var a = params ? R.animation(params, ms, easing, callback) : anim,
-        //     status = element.status(anim);
-        // return this.animate(a).status(a, status * anim.ms / a.ms);
-    };
-    function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
-        var cx = 3 * p1x,
-            bx = 3 * (p2x - p1x) - cx,
-            ax = 1 - cx - bx,
-            cy = 3 * p1y,
-            by = 3 * (p2y - p1y) - cy,
-            ay = 1 - cy - by;
-        function sampleCurveX(t) {
-            return ((ax * t + bx) * t + cx) * t;
-        }
-        function solve(x, epsilon) {
-            var t = solveCurveX(x, epsilon);
-            return ((ay * t + by) * t + cy) * t;
-        }
-        function solveCurveX(x, epsilon) {
-            var t0, t1, t2, x2, d2, i;
-            for(t2 = x, i = 0; i < 8; i++) {
-                x2 = sampleCurveX(t2) - x;
-                if (abs(x2) < epsilon) {
-                    return t2;
-                }
-                d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
-                if (abs(d2) < 1e-6) {
-                    break;
-                }
-                t2 = t2 - x2 / d2;
-            }
-            t0 = 0;
-            t1 = 1;
-            t2 = x;
-            if (t2 < t0) {
-                return t0;
-            }
-            if (t2 > t1) {
-                return t1;
-            }
-            while (t0 < t1) {
-                x2 = sampleCurveX(t2);
-                if (abs(x2 - x) < epsilon) {
-                    return t2;
-                }
-                if (x > x2) {
-                    t0 = t2;
-                } else {
-                    t1 = t2;
-                }
-                t2 = (t1 - t0) / 2 + t0;
-            }
-            return t2;
-        }
-        return solve(t, 1 / (200 * duration));
-    }
-    elproto.onAnimation = function (f) {
-        f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
-        return this;
-    };
-    function Animation(anim, ms) {
-        var percents = [],
-            newAnim = {};
-        this.ms = ms;
-        this.times = 1;
-        if (anim) {
-            for (var attr in anim) if (anim[has](attr)) {
-                newAnim[toFloat(attr)] = anim[attr];
-                percents.push(toFloat(attr));
-            }
-            percents.sort(sortByNumber);
-        }
-        this.anim = newAnim;
-        this.top = percents[percents.length - 1];
-        this.percents = percents;
-    }
-    /*\
-     * Animation.delay
-     [ method ]
-     **
-     * Creates a copy of existing animation object with given delay.
-     **
-     > Parameters
-     **
-     - delay (number) number of ms to pass between animation start and actual animation
-     **
-     = (object) new altered Animation object
-     | var anim = Raphael.animation({cx: 10, cy: 20}, 2e3);
-     | circle1.animate(anim); // run the given animation immediately
-     | circle2.animate(anim.delay(500)); // run the given animation after 500 ms
-    \*/
-    Animation.prototype.delay = function (delay) {
-        var a = new Animation(this.anim, this.ms);
-        a.times = this.times;
-        a.del = +delay || 0;
-        return a;
-    };
-    /*\
-     * Animation.repeat
-     [ method ]
-     **
-     * Creates a copy of existing animation object with given repetition.
-     **
-     > Parameters
-     **
-     - repeat (number) number iterations of animation. For infinite animation pass `Infinity`
-     **
-     = (object) new altered Animation object
-    \*/
-    Animation.prototype.repeat = function (times) { 
-        var a = new Animation(this.anim, this.ms);
-        a.del = this.del;
-        a.times = math.floor(mmax(times, 0)) || 1;
-        return a;
-    };
-    function runAnimation(anim, element, percent, status, totalOrigin, times) {
-        percent = toFloat(percent);
-        var params,
-            isInAnim,
-            isInAnimSet,
-            percents = [],
-            next,
-            prev,
-            timestamp,
-            ms = anim.ms,
-            from = {},
-            to = {},
-            diff = {};
-        if (status) {
-            for (i = 0, ii = animationElements.length; i < ii; i++) {
-                var e = animationElements[i];
-                if (e.el.id == element.id && e.anim == anim) {
-                    if (e.percent != percent) {
-                        animationElements.splice(i, 1);
-                        isInAnimSet = 1;
-                    } else {
-                        isInAnim = e;
-                    }
-                    element.attr(e.totalOrigin);
-                    break;
-                }
-            }
-        } else {
-            status = +to; // NaN
-        }
-        for (var i = 0, ii = anim.percents.length; i < ii; i++) {
-            if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
-                percent = anim.percents[i];
-                prev = anim.percents[i - 1] || 0;
-                ms = ms / anim.top * (percent - prev);
-                next = anim.percents[i + 1];
-                params = anim.anim[percent];
-                break;
-            } else if (status) {
-                element.attr(anim.anim[anim.percents[i]]);
-            }
-        }
-        if (!params) {
-            return;
-        }
-        if (!isInAnim) {
-            for (var attr in params) if (params[has](attr)) {
-                if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
-                    from[attr] = element.attr(attr);
-                    (from[attr] == null) && (from[attr] = availableAttrs[attr]);
-                    to[attr] = params[attr];
-                    switch (availableAnimAttrs[attr]) {
-                        case nu:
-                            diff[attr] = (to[attr] - from[attr]) / ms;
-                            break;
-                        case "colour":
-                            from[attr] = R.getRGB(from[attr]);
-                            var toColour = R.getRGB(to[attr]);
-                            diff[attr] = {
-                                r: (toColour.r - from[attr].r) / ms,
-                                g: (toColour.g - from[attr].g) / ms,
-                                b: (toColour.b - from[attr].b) / ms
-                            };
-                            break;
-                        case "path":
-                            var pathes = path2curve(from[attr], to[attr]),
-                                toPath = pathes[1];
-                            from[attr] = pathes[0];
-                            diff[attr] = [];
-                            for (i = 0, ii = from[attr].length; i < ii; i++) {
-                                diff[attr][i] = [0];
-                                for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
-                                    diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
-                                }
-                            }
-                            break;
-                        case "transform":
-                            var _ = element._,
-                                eq = equaliseTransform(_[attr], to[attr]);
-                            if (eq) {
-                                from[attr] = eq.from;
-                                to[attr] = eq.to;
-                                diff[attr] = [];
-                                diff[attr].real = true;
-                                for (i = 0, ii = from[attr].length; i < ii; i++) {
-                                    diff[attr][i] = [from[attr][i][0]];
-                                    for (j = 1, jj = from[attr][i].length; j < jj; j++) {
-                                        diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
-                                    }
-                                }
-                            } else {
-                                var m = (element.matrix || new Matrix),
-                                    to2 = {
-                                        _: {transform: _.transform},
-                                        getBBox: function () {
-                                            return element.getBBox(1);
-                                        }
-                                    };
-                                from[attr] = [
-                                    m.a,
-                                    m.b,
-                                    m.c,
-                                    m.d,
-                                    m.e,
-                                    m.f
-                                ];
-                                extractTransform(to2, to[attr]);
-                                to[attr] = to2._.transform;
-                                diff[attr] = [
-                                    (to2.matrix.a - m.a) / ms,
-                                    (to2.matrix.b - m.b) / ms,
-                                    (to2.matrix.c - m.c) / ms,
-                                    (to2.matrix.d - m.d) / ms,
-                                    (to2.matrix.e - m.e) / ms,
-                                    (to2.matrix.f - m.f) / ms
-                                ];
-                                // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
-                                // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
-                                // extractTransform(to2, to[attr]);
-                                // diff[attr] = [
-                                //     (to2._.sx - _.sx) / ms,
-                                //     (to2._.sy - _.sy) / ms,
-                                //     (to2._.deg - _.deg) / ms,
-                                //     (to2._.dx - _.dx) / ms,
-                                //     (to2._.dy - _.dy) / ms
-                                // ];
-                            }
-                            break;
-                        case "csv":
-                            var values = Str(params[attr])[split](separator),
-                                from2 = Str(from[attr])[split](separator);
-                            if (attr == "clip-rect") {
-                                from[attr] = from2;
-                                diff[attr] = [];
-                                i = from2.length;
-                                while (i--) {
-                                    diff[attr][i] = (values[i] - from[attr][i]) / ms;
-                                }
-                            }
-                            to[attr] = values;
-                            break;
-                        default:
-                            values = [][concat](params[attr]);
-                            from2 = [][concat](from[attr]);
-                            diff[attr] = [];
-                            i = element.paper.customAttributes[attr].length;
-                            while (i--) {
-                                diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
-                            }
-                            break;
-                    }
-                }
-            }
-            var easing = params.easing,
-                easyeasy = R.easing_formulas[easing];
-            if (!easyeasy) {
-                easyeasy = Str(easing).match(bezierrg);
-                if (easyeasy && easyeasy.length == 5) {
-                    var curve = easyeasy;
-                    easyeasy = function (t) {
-                        return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
-                    };
-                } else {
-                    easyeasy = pipe;
-                }
-            }
-            timestamp = params.start || anim.start || +new Date;
-            e = {
-                anim: anim,
-                percent: percent,
-                timestamp: timestamp,
-                start: timestamp + (anim.del || 0),
-                status: 0,
-                initstatus: status || 0,
-                stop: false,
-                ms: ms,
-                easing: easyeasy,
-                from: from,
-                diff: diff,
-                to: to,
-                el: element,
-                callback: params.callback,
-                prev: prev,
-                next: next,
-                repeat: times || anim.times,
-                origin: element.attr(),
-                totalOrigin: totalOrigin
-            };
-            animationElements.push(e);
-            if (status && !isInAnim && !isInAnimSet) {
-                e.stop = true;
-                e.start = new Date - ms * status;
-                if (animationElements.length == 1) {
-                    return animation();
-                }
-            }
-            if (isInAnimSet) {
-                e.start = new Date - e.ms * status;
-            }
-            animationElements.length == 1 && requestAnimFrame(animation);
-        } else {
-            isInAnim.initstatus = status;
-            isInAnim.start = new Date - isInAnim.ms * status;
-        }
-        eve("raphael.anim.start." + element.id, element, anim);
-    }
-    /*\
-     * Raphael.animation
-     [ method ]
-     **
-     * Creates an animation object that can be passed to the @Element.animate or @Element.animateWith methods.
-     * See also @Animation.delay and @Animation.repeat methods.
-     **
-     > Parameters
-     **
-     - params (object) final attributes for the element, see also @Element.attr
-     - ms (number) number of milliseconds for animation to run
-     - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
-     - callback (function) #optional callback function. Will be called at the end of animation.
-     **
-     = (object) @Animation
-    \*/
-    R.animation = function (params, ms, easing, callback) {
-        if (params instanceof Animation) {
-            return params;
-        }
-        if (R.is(easing, "function") || !easing) {
-            callback = callback || easing || null;
-            easing = null;
-        }
-        params = Object(params);
-        ms = +ms || 0;
-        var p = {},
-            json,
-            attr;
-        for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
-            json = true;
-            p[attr] = params[attr];
-        }
-        if (!json) {
-            return new Animation(params, ms);
-        } else {
-            easing && (p.easing = easing);
-            callback && (p.callback = callback);
-            return new Animation({100: p}, ms);
-        }
-    };
-    /*\
-     * Element.animate
-     [ method ]
-     **
-     * Creates and starts animation for given element.
-     **
-     > Parameters
-     **
-     - params (object) final attributes for the element, see also @Element.attr
-     - ms (number) number of milliseconds for animation to run
-     - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
-     - callback (function) #optional callback function. Will be called at the end of animation.
-     * or
-     - animation (object) animation object, see @Raphael.animation
-     **
-     = (object) original element
-    \*/
-    elproto.animate = function (params, ms, easing, callback) {
-        var element = this;
-        if (element.removed) {
-            callback && callback.call(element);
-            return element;
-        }
-        var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
-        runAnimation(anim, element, anim.percents[0], null, element.attr());
-        return element;
-    };
-    /*\
-     * Element.setTime
-     [ method ]
-     **
-     * Sets the status of animation of the element in milliseconds. Similar to @Element.status method.
-     **
-     > Parameters
-     **
-     - anim (object) animation object
-     - value (number) number of milliseconds from the beginning of the animation
-     **
-     = (object) original element if `value` is specified
-     * Note, that during animation following events are triggered:
-     *
-     * On each animation frame event `anim.frame.<id>`, on start `anim.start.<id>` and on end `anim.finish.<id>`.
-    \*/
-    elproto.setTime = function (anim, value) {
-        if (anim && value != null) {
-            this.status(anim, mmin(value, anim.ms) / anim.ms);
-        }
-        return this;
-    };
-    /*\
-     * Element.status
-     [ method ]
-     **
-     * Gets or sets the status of animation of the element.
-     **
-     > Parameters
-     **
-     - anim (object) #optional animation object
-     - value (number) #optional 0 – 1. If specified, method works like a setter and sets the status of a given animation to the value. This will cause animation to jump to the given position.
-     **
-     = (number) status
-     * or
-     = (array) status if `anim` is not specified. Array of objects in format:
-     o {
-     o     anim: (object) animation object
-     o     status: (number) status
-     o }
-     * or
-     = (object) original element if `value` is specified
-    \*/
-    elproto.status = function (anim, value) {
-        var out = [],
-            i = 0,
-            len,
-            e;
-        if (value != null) {
-            runAnimation(anim, this, -1, mmin(value, 1));
-            return this;
-        } else {
-            len = animationElements.length;
-            for (; i < len; i++) {
-                e = animationElements[i];
-                if (e.el.id == this.id && (!anim || e.anim == anim)) {
-                    if (anim) {
-                        return e.status;
-                    }
-                    out.push({
-                        anim: e.anim,
-                        status: e.status
-                    });
-                }
-            }
-            if (anim) {
-                return 0;
-            }
-            return out;
-        }
-    };
-    /*\
-     * Element.pause
-     [ method ]
-     **
-     * Stops animation of the element with ability to resume it later on.
-     **
-     > Parameters
-     **
-     - anim (object) #optional animation object
-     **
-     = (object) original element
-    \*/
-    elproto.pause = function (anim) {
-        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
-            if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
-                animationElements[i].paused = true;
-            }
-        }
-        return this;
-    };
-    /*\
-     * Element.resume
-     [ method ]
-     **
-     * Resumes animation if it was paused with @Element.pause method.
-     **
-     > Parameters
-     **
-     - anim (object) #optional animation object
-     **
-     = (object) original element
-    \*/
-    elproto.resume = function (anim) {
-        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
-            var e = animationElements[i];
-            if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
-                delete e.paused;
-                this.status(e.anim, e.status);
-            }
-        }
-        return this;
-    };
-    /*\
-     * Element.stop
-     [ method ]
-     **
-     * Stops animation of the element.
-     **
-     > Parameters
-     **
-     - anim (object) #optional animation object
-     **
-     = (object) original element
-    \*/
-    elproto.stop = function (anim) {
-        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
-            if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
-                animationElements.splice(i--, 1);
-            }
-        }
-        return this;
-    };
-    function stopAnimation(paper) {
-        for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
-            animationElements.splice(i--, 1);
-        }
-    }
-    eve.on("raphael.remove", stopAnimation);
-    eve.on("raphael.clear", stopAnimation);
-    elproto.toString = function () {
-        return "Rapha\xebl\u2019s object";
-    };
-
-    // Set
-    var Set = function (items) {
-        this.items = [];
-        this.length = 0;
-        this.type = "set";
-        if (items) {
-            for (var i = 0, ii = items.length; i < ii; i++) {
-                if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
-                    this[this.items.length] = this.items[this.items.length] = items[i];
-                    this.length++;
-                }
-            }
-        }
-    },
-    setproto = Set.prototype;
-    /*\
-     * Set.push
-     [ method ]
-     **
-     * Adds each argument to the current set.
-     = (object) original element
-    \*/
-    setproto.push = function () {
-        var item,
-            len;
-        for (var i = 0, ii = arguments.length; i < ii; i++) {
-            item = arguments[i];
-            if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
-                len = this.items.length;
-                this[len] = this.items[len] = item;
-                this.length++;
-            }
-        }
-        return this;
-    };
-    /*\
-     * Set.pop
-     [ method ]
-     **
-     * Removes last element and returns it.
-     = (object) element
-    \*/
-    setproto.pop = function () {
-        this.length && delete this[this.length--];
-        return this.items.pop();
-    };
-    /*\
-     * Set.forEach
-     [ method ]
-     **
-     * Executes given function for each element in the set.
-     *
-     * If function returns `false` it will stop loop running.
-     **
-     > Parameters
-     **
-     - callback (function) function to run
-     - thisArg (object) context object for the callback
-     = (object) Set object
-    \*/
-    setproto.forEach = function (callback, thisArg) {
-        for (var i = 0, ii = this.items.length; i < ii; i++) {
-            if (callback.call(thisArg, this.items[i], i) === false) {
-                return this;
-            }
-        }
-        return this;
-    };
-    for (var method in elproto) if (elproto[has](method)) {
-        setproto[method] = (function (methodname) {
-            return function () {
-                var arg = arguments;
-                return this.forEach(function (el) {
-                    el[methodname][apply](el, arg);
-                });
-            };
-        })(method);
-    }
-    setproto.attr = function (name, value) {
-        if (name && R.is(name, array) && R.is(name[0], "object")) {
-            for (var j = 0, jj = name.length; j < jj; j++) {
-                this.items[j].attr(name[j]);
-            }
-        } else {
-            for (var i = 0, ii = this.items.length; i < ii; i++) {
-                this.items[i].attr(name, value);
-            }
-        }
-        return this;
-    };
-    /*\
-     * Set.clear
-     [ method ]
-     **
-     * Removeds all elements from the set
-    \*/
-    setproto.clear = function () {
-        while (this.length) {
-            this.pop();
-        }
-    };
-    /*\
-     * Set.splice
-     [ method ]
-     **
-     * Removes given element from the set
-     **
-     > Parameters
-     **
-     - index (number) position of the deletion
-     - count (number) number of element to remove
-     - insertion… (object) #optional elements to insert
-     = (object) set elements that were deleted
-    \*/
-    setproto.splice = function (index, count, insertion) {
-        index = index < 0 ? mmax(this.length + index, 0) : index;
-        count = mmax(0, mmin(this.length - index, count));
-        var tail = [],
-            todel = [],
-            args = [],
-            i;
-        for (i = 2; i < arguments.length; i++) {
-            args.push(arguments[i]);
-        }
-        for (i = 0; i < count; i++) {
-            todel.push(this[index + i]);
-        }
-        for (; i < this.length - index; i++) {
-            tail.push(this[index + i]);
-        }
-        var arglen = args.length;
-        for (i = 0; i < arglen + tail.length; i++) {
-            this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
-        }
-        i = this.items.length = this.length -= count - arglen;
-        while (this[i]) {
-            delete this[i++];
-        }
-        return new Set(todel);
-    };
-    /*\
-     * Set.exclude
-     [ method ]
-     **
-     * Removes given element from the set
-     **
-     > Parameters
-     **
-     - element (object) element to remove
-     = (boolean) `true` if object was found & removed from the set
-    \*/
-    setproto.exclude = function (el) {
-        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
-            this.splice(i, 1);
-            return true;
-        }
-    };
-    setproto.animate = function (params, ms, easing, callback) {
-        (R.is(easing, "function") || !easing) && (callback = easing || null);
-        var len = this.items.length,
-            i = len,
-            item,
-            set = this,
-            collector;
-        if (!len) {
-            return this;
-        }
-        callback && (collector = function () {
-            !--len && callback.call(set);
-        });
-        easing = R.is(easing, string) ? easing : collector;
-        var anim = R.animation(params, ms, easing, collector);
-        item = this.items[--i].animate(anim);
-        while (i--) {
-            this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
-        }
-        return this;
-    };
-    setproto.insertAfter = function (el) {
-        var i = this.items.length;
-        while (i--) {
-            this.items[i].insertAfter(el);
-        }
-        return this;
-    };
-    setproto.getBBox = function () {
-        var x = [],
-            y = [],
-            x2 = [],
-            y2 = [];
-        for (var i = this.items.length; i--;) if (!this.items[i].removed) {
-            var box = this.items[i].getBBox();
-            x.push(box.x);
-            y.push(box.y);
-            x2.push(box.x + box.width);
-            y2.push(box.y + box.height);
-        }
-        x = mmin[apply](0, x);
-        y = mmin[apply](0, y);
-        x2 = mmax[apply](0, x2);
-        y2 = mmax[apply](0, y2);
-        return {
-            x: x,
-            y: y,
-            x2: x2,
-            y2: y2,
-            width: x2 - x,
-            height: y2 - y
-        };
-    };
-    setproto.clone = function (s) {
-        s = this.paper.set();
-        for (var i = 0, ii = this.items.length; i < ii; i++) {
-            s.push(this.items[i].clone());
-        }
-        return s;
-    };
-    setproto.toString = function () {
-        return "Rapha\xebl\u2018s set";
-    };
-
-    setproto.glow = function(glowConfig) {
-        var ret = this.paper.set();
-        this.forEach(function(shape, index){
-            var g = shape.glow(glowConfig);
-            if(g != null){
-                g.forEach(function(shape2, index2){
-                    ret.push(shape2);
-                });
-            }
-        });
-        return ret;
-    };
-
-    /*\
-     * Raphael.registerFont
-     [ method ]
-     **
-     * Adds given font to the registered set of fonts for Raphaël. Should be used as an internal call from within Cufón’s font file.
-     * Returns original parameter, so it could be used with chaining.
-     # <a href="http://wiki.github.com/sorccu/cufon/about">More about Cufón and how to convert your font form TTF, OTF, etc to JavaScript file.</a>
-     **
-     > Parameters
-     **
-     - font (object) the font to register
-     = (object) the font you passed in
-     > Usage
-     | Cufon.registerFont(Raphael.registerFont({…}));
-    \*/
-    R.registerFont = function (font) {
-        if (!font.face) {
-            return font;
-        }
-        this.fonts = this.fonts || {};
-        var fontcopy = {
-                w: font.w,
-                face: {},
-                glyphs: {}
-            },
-            family = font.face["font-family"];
-        for (var prop in font.face) if (font.face[has](prop)) {
-            fontcopy.face[prop] = font.face[prop];
-        }
-        if (this.fonts[family]) {
-            this.fonts[family].push(fontcopy);
-        } else {
-            this.fonts[family] = [fontcopy];
-        }
-        if (!font.svg) {
-            fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
-            for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
-                var path = font.glyphs[glyph];
-                fontcopy.glyphs[glyph] = {
-                    w: path.w,
-                    k: {},
-                    d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
-                            return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
-                        }) + "z"
-                };
-                if (path.k) {
-                    for (var k in path.k) if (path[has](k)) {
-                        fontcopy.glyphs[glyph].k[k] = path.k[k];
-                    }
-                }
-            }
-        }
-        return font;
-    };
-    /*\
-     * Paper.getFont
-     [ method ]
-     **
-     * Finds font object in the registered fonts by given parameters. You could specify only one word from the font name, like “Myriad” for “Myriad Pro”.
-     **
-     > Parameters
-     **
-     - family (string) font family name or any word from it
-     - weight (string) #optional font weight
-     - style (string) #optional font style
-     - stretch (string) #optional font stretch
-     = (object) the font object
-     > Usage
-     | paper.print(100, 100, "Test string", paper.getFont("Times", 800), 30);
-    \*/
-    paperproto.getFont = function (family, weight, style, stretch) {
-        stretch = stretch || "normal";
-        style = style || "normal";
-        weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
-        if (!R.fonts) {
-            return;
-        }
-        var font = R.fonts[family];
-        if (!font) {
-            var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
-            for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
-                if (name.test(fontName)) {
-                    font = R.fonts[fontName];
-                    break;
-                }
-            }
-        }
-        var thefont;
-        if (font) {
-            for (var i = 0, ii = font.length; i < ii; i++) {
-                thefont = font[i];
-                if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
-                    break;
-                }
-            }
-        }
-        return thefont;
-    };
-    /*\
-     * Paper.print
-     [ method ]
-     **
-     * Creates path that represent given text written using given font at given position with given size.
-     * Result of the method is path element that contains whole text as a separate path.
-     **
-     > Parameters
-     **
-     - x (number) x position of the text
-     - y (number) y position of the text
-     - string (string) text to print
-     - font (object) font object, see @Paper.getFont
-     - size (number) #optional size of the font, default is `16`
-     - origin (string) #optional could be `"baseline"` or `"middle"`, default is `"middle"`
-     - letter_spacing (number) #optional number in range `-1..1`, default is `0`
-     = (object) resulting path element, which consist of all letters
-     > Usage
-     | var txt = r.print(10, 50, "print", r.getFont("Museo"), 30).attr({fill: "#fff"});
-    \*/
-    paperproto.print = function (x, y, string, font, size, origin, letter_spacing) {
-        origin = origin || "middle"; // baseline|middle
-        letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
-        var letters = Str(string)[split](E),
-            shift = 0,
-            notfirst = 0,
-            path = E,
-            scale;
-        R.is(font, string) && (font = this.getFont(font));
-        if (font) {
-            scale = (size || 16) / font.face["units-per-em"];
-            var bb = font.face.bbox[split](separator),
-                top = +bb[0],
-                lineHeight = bb[3] - bb[1],
-                shifty = 0,
-                height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
-            for (var i = 0, ii = letters.length; i < ii; i++) {
-                if (letters[i] == "\n") {
-                    shift = 0;
-                    curr = 0;
-                    notfirst = 0;
-                    shifty += lineHeight;
-                } else {
-                    var prev = notfirst && font.glyphs[letters[i - 1]] || {},
-                        curr = font.glyphs[letters[i]];
-                    shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
-                    notfirst = 1;
-                }
-                if (curr && curr.d) {
-                    path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
-                }
-            }
-        }
-        return this.path(path).attr({
-            fill: "#000",
-            stroke: "none"
-        });
-    };
-
-    /*\
-     * Paper.add
-     [ method ]
-     **
-     * Imports elements in JSON array in format `{type: type, <attributes>}`
-     **
-     > Parameters
-     **
-     - json (array)
-     = (object) resulting set of imported elements
-     > Usage
-     | paper.add([
-     |     {
-     |         type: "circle",
-     |         cx: 10,
-     |         cy: 10,
-     |         r: 5
-     |     },
-     |     {
-     |         type: "rect",
-     |         x: 10,
-     |         y: 10,
-     |         width: 10,
-     |         height: 10,
-     |         fill: "#fc0"
-     |     }
-     | ]);
-    \*/
-    paperproto.add = function (json) {
-        if (R.is(json, "array")) {
-            var res = this.set(),
-                i = 0,
-                ii = json.length,
-                j;
-            for (; i < ii; i++) {
-                j = json[i] || {};
-                elements[has](j.type) && res.push(this[j.type]().attr(j));
-            }
-        }
-        return res;
-    };
-
-    /*\
-     * Raphael.format
-     [ method ]
-     **
-     * Simple format function. Replaces construction of type “`{<number>}`” to the corresponding argument.
-     **
-     > Parameters
-     **
-     - token (string) string to format
-     - … (string) rest of arguments will be treated as parameters for replacement
-     = (string) formated string
-     > Usage
-     | var x = 10,
-     |     y = 20,
-     |     width = 40,
-     |     height = 50;
-     | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
-     | paper.path(Raphael.format("M{0},{1}h{2}v{3}h{4}z", x, y, width, height, -width));
-    \*/
-    R.format = function (token, params) {
-        var args = R.is(params, array) ? [0][concat](params) : arguments;
-        token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
-            return args[++i] == null ? E : args[i];
-        }));
-        return token || E;
-    };
-    /*\
-     * Raphael.fullfill
-     [ method ]
-     **
-     * A little bit more advanced format function than @Raphael.format. Replaces construction of type “`{<name>}`” to the corresponding argument.
-     **
-     > Parameters
-     **
-     - token (string) string to format
-     - json (object) object which properties will be used as a replacement
-     = (string) formated string
-     > Usage
-     | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
-     | paper.path(Raphael.fullfill("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
-     |     x: 10,
-     |     y: 20,
-     |     dim: {
-     |         width: 40,
-     |         height: 50,
-     |         "negative width": -40
-     |     }
-     | }));
-    \*/
-    R.fullfill = (function () {
-        var tokenRegex = /\{([^\}]+)\}/g,
-            objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
-            replacer = function (all, key, obj) {
-                var res = obj;
-                key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
-                    name = name || quotedName;
-                    if (res) {
-                        if (name in res) {
-                            res = res[name];
-                        }
-                        typeof res == "function" && isFunc && (res = res());
-                    }
-                });
-                res = (res == null || res == obj ? all : res) + "";
-                return res;
-            };
-        return function (str, obj) {
-            return String(str).replace(tokenRegex, function (all, key) {
-                return replacer(all, key, obj);
-            });
-        };
-    })();
-    /*\
-     * Raphael.ninja
-     [ method ]
-     **
-     * If you want to leave no trace of Raphaël (Well, Raphaël creates only one global variable `Raphael`, but anyway.) You can use `ninja` method.
-     * Beware, that in this case plugins could stop working, because they are depending on global variable existance.
-     **
-     = (object) Raphael object
-     > Usage
-     | (function (local_raphael) {
-     |     var paper = local_raphael(10, 10, 320, 200);
-     |     …
-     | })(Raphael.ninja());
-    \*/
-    R.ninja = function () {
-        oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
-        return R;
-    };
-    /*\
-     * Raphael.st
-     [ property (object) ]
-     **
-     * You can add your own method to elements and sets. It is wise to add a set method for each element method
-     * you added, so you will be able to call the same method on sets too.
-     **
-     * See also @Raphael.el.
-     > Usage
-     | Raphael.el.red = function () {
-     |     this.attr({fill: "#f00"});
-     | };
-     | Raphael.st.red = function () {
-     |     this.forEach(function (el) {
-     |         el.red();
-     |     });
-     | };
-     | // then use it
-     | paper.set(paper.circle(100, 100, 20), paper.circle(110, 100, 20)).red();
-    \*/
-    R.st = setproto;
-    // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
-    (function (doc, loaded, f) {
-        if (doc.readyState == null && doc.addEventListener){
-            doc.addEventListener(loaded, f = function () {
-                doc.removeEventListener(loaded, f, false);
-                doc.readyState = "complete";
-            }, false);
-            doc.readyState = "loading";
-        }
-        function isLoaded() {
-            (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
-        }
-        isLoaded();
-    })(document, "DOMContentLoaded");
-
-    oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
-    
-    eve.on("raphael.DOMload", function () {
-        loaded = true;
-    });
-})();
-
-// ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël - JavaScript Vector Library                                 │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ SVG Module                                                          │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
-// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
-// └─────────────────────────────────────────────────────────────────────┘ \\
-window.Raphael && window.Raphael.svg && function (R) {
-    var has = "hasOwnProperty",
-        Str = String,
-        toFloat = parseFloat,
-        toInt = parseInt,
-        math = Math,
-        mmax = math.max,
-        abs = math.abs,
-        pow = math.pow,
-        separator = /[, ]+/,
-        eve = R.eve,
-        E = "",
-        S = " ";
-    var xlink = "http://www.w3.org/1999/xlink",
-        markers = {
-            block: "M5,0 0,2.5 5,5z",
-            classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z",
-            diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
-            open: "M6,1 1,3.5 6,6",
-            oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
-        },
-        markerCounter = {};
-    R.toString = function () {
-        return  "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
-    };
-    var $ = function (el, attr) {
-        if (attr) {
-            if (typeof el == "string") {
-                el = $(el);
-            }
-            for (var key in attr) if (attr[has](key)) {
-                if (key.substring(0, 6) == "xlink:") {
-                    el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
-                } else {
-                    el.setAttribute(key, Str(attr[key]));
-                }
-            }
-        } else {
-            el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
-            el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
-        }
-        return el;
-    },
-    addGradientFill = function (element, gradient) {
-        var type = "linear",
-            id = element.id + gradient,
-            fx = .5, fy = .5,
-            o = element.node,
-            SVG = element.paper,
-            s = o.style,
-            el = R._g.doc.getElementById(id);
-        if (!el) {
-            gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
-                type = "radial";
-                if (_fx && _fy) {
-                    fx = toFloat(_fx);
-                    fy = toFloat(_fy);
-                    var dir = ((fy > .5) * 2 - 1);
-                    pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
-                        (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
-                        fy != .5 &&
-                        (fy = fy.toFixed(5) - 1e-5 * dir);
-                }
-                return E;
-            });
-            gradient = gradient.split(/\s*\-\s*/);
-            if (type == "linear") {
-                var angle = gradient.shift();
-                angle = -toFloat(angle);
-                if (isNaN(angle)) {
-                    return null;
-                }
-                var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
-                    max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
-                vector[2] *= max;
-                vector[3] *= max;
-                if (vector[2] < 0) {
-                    vector[0] = -vector[2];
-                    vector[2] = 0;
-                }
-                if (vector[3] < 0) {
-                    vector[1] = -vector[3];
-                    vector[3] = 0;
-                }
-            }
-            var dots = R._parseDots(gradient);
-            if (!dots) {
-                return null;
-            }
-            id = id.replace(/[\(\)\s,\xb0#]/g, "_");
-            
-            if (element.gradient && id != element.gradient.id) {
-                SVG.defs.removeChild(element.gradient);
-                delete element.gradient;
-            }
-
-            if (!element.gradient) {
-                el = $(type + "Gradient", {id: id});
-                element.gradient = el;
-                $(el, type == "radial" ? {
-                    fx: fx,
-                    fy: fy
-                } : {
-                    x1: vector[0],
-                    y1: vector[1],
-                    x2: vector[2],
-                    y2: vector[3],
-                    gradientTransform: element.matrix.invert()
-                });
-                SVG.defs.appendChild(el);
-                for (var i = 0, ii = dots.length; i < ii; i++) {
-                    el.appendChild($("stop", {
-                        offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
-                        "stop-color": dots[i].color || "#fff"
-                    }));
-                }
-            }
-        }
-        $(o, {
-            fill: "url(#" + id + ")",
-            opacity: 1,
-            "fill-opacity": 1
-        });
-        s.fill = E;
-        s.opacity = 1;
-        s.fillOpacity = 1;
-        return 1;
-    },
-    updatePosition = function (o) {
-        var bbox = o.getBBox(1);
-        $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
-    },
-    addArrow = function (o, value, isEnd) {
-        if (o.type == "path") {
-            var values = Str(value).toLowerCase().split("-"),
-                p = o.paper,
-                se = isEnd ? "end" : "start",
-                node = o.node,
-                attrs = o.attrs,
-                stroke = attrs["stroke-width"],
-                i = values.length,
-                type = "classic",
-                from,
-                to,
-                dx,
-                refX,
-                attr,
-                w = 3,
-                h = 3,
-                t = 5;
-            while (i--) {
-                switch (values[i]) {
-                    case "block":
-                    case "classic":
-                    case "oval":
-                    case "diamond":
-                    case "open":
-                    case "none":
-                        type = values[i];
-                        break;
-                    case "wide": h = 5; break;
-                    case "narrow": h = 2; break;
-                    case "long": w = 5; break;
-                    case "short": w = 2; break;
-                }
-            }
-            if (type == "open") {
-                w += 2;
-                h += 2;
-                t += 2;
-                dx = 1;
-                refX = isEnd ? 4 : 1;
-                attr = {
-                    fill: "none",
-                    stroke: attrs.stroke
-                };
-            } else {
-                refX = dx = w / 2;
-                attr = {
-                    fill: attrs.stroke,
-                    stroke: "none"
-                };
-            }
-            if (o._.arrows) {
-                if (isEnd) {
-                    o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
-                    o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
-                } else {
-                    o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
-                    o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
-                }
-            } else {
-                o._.arrows = {};
-            }
-            if (type != "none") {
-                var pathId = "raphael-marker-" + type,
-                    markerId = "raphael-marker-" + se + type + w + h;
-                if (!R._g.doc.getElementById(pathId)) {
-                    p.defs.appendChild($($("path"), {
-                        "stroke-linecap": "round",
-                        d: markers[type],
-                        id: pathId
-                    }));
-                    markerCounter[pathId] = 1;
-                } else {
-                    markerCounter[pathId]++;
-                }
-                var marker = R._g.doc.getElementById(markerId),
-                    use;
-                if (!marker) {
-                    marker = $($("marker"), {
-                        id: markerId,
-                        markerHeight: h,
-                        markerWidth: w,
-                        orient: "auto",
-                        refX: refX,
-                        refY: h / 2
-                    });
-                    use = $($("use"), {
-                        "xlink:href": "#" + pathId,
-                        transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
-                        "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
-                    });
-                    marker.appendChild(use);
-                    p.defs.appendChild(marker);
-                    markerCounter[markerId] = 1;
-                } else {
-                    markerCounter[markerId]++;
-                    use = marker.getElementsByTagName("use")[0];
-                }
-                $(use, attr);
-                var delta = dx * (type != "diamond" && type != "oval");
-                if (isEnd) {
-                    from = o._.arrows.startdx * stroke || 0;
-                    to = R.getTotalLength(attrs.path) - delta * stroke;
-                } else {
-                    from = delta * stroke;
-                    to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
-                }
-                attr = {};
-                attr["marker-" + se] = "url(#" + markerId + ")";
-                if (to || from) {
-                    attr.d = Raphael.getSubpath(attrs.path, from, to);
-                }
-                $(node, attr);
-                o._.arrows[se + "Path"] = pathId;
-                o._.arrows[se + "Marker"] = markerId;
-                o._.arrows[se + "dx"] = delta;
-                o._.arrows[se + "Type"] = type;
-                o._.arrows[se + "String"] = value;
-            } else {
-                if (isEnd) {
-                    from = o._.arrows.startdx * stroke || 0;
-                    to = R.getTotalLength(attrs.path) - from;
-                } else {
-                    from = 0;
-                    to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
-                }
-                o._.arrows[se + "Path"] && $(node, {d: Raphael.getSubpath(attrs.path, from, to)});
-                delete o._.arrows[se + "Path"];
-                delete o._.arrows[se + "Marker"];
-                delete o._.arrows[se + "dx"];
-                delete o._.arrows[se + "Type"];
-                delete o._.arrows[se + "String"];
-            }
-            for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
-                var item = R._g.doc.getElementById(attr);
-                item && item.parentNode.removeChild(item);
-            }
-        }
-    },
-    dasharray = {
-        "": [0],
-        "none": [0],
-        "-": [3, 1],
-        ".": [1, 1],
-        "-.": [3, 1, 1, 1],
-        "-..": [3, 1, 1, 1, 1, 1],
-        ". ": [1, 3],
-        "- ": [4, 3],
-        "--": [8, 3],
-        "- .": [4, 3, 1, 3],
-        "--.": [8, 3, 1, 3],
-        "--..": [8, 3, 1, 3, 1, 3]
-    },
-    addDashes = function (o, value, params) {
-        value = dasharray[Str(value).toLowerCase()];
-        if (value) {
-            var width = o.attrs["stroke-width"] || "1",
-                butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
-                dashes = [],
-                i = value.length;
-            while (i--) {
-                dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
-            }
-            $(o.node, {"stroke-dasharray": dashes.join(",")});
-        }
-    },
-    setFillAndStroke = function (o, params) {
-        var node = o.node,
-            attrs = o.attrs,
-            vis = node.style.visibility;
-        node.style.visibility = "hidden";
-        for (var att in params) {
-            if (params[has](att)) {
-                if (!R._availableAttrs[has](att)) {
-                    continue;
-                }
-                var value = params[att];
-                attrs[att] = value;
-                switch (att) {
-                    case "blur":
-                        o.blur(value);
-                        break;
-                    case "href":
-                    case "title":
-                    case "target":
-                        var pn = node.parentNode;
-                        if (pn.tagName.toLowerCase() != "a") {
-                            var hl = $("a");
-                            pn.insertBefore(hl, node);
-                            hl.appendChild(node);
-                            pn = hl;
-                        }
-                        if (att == "target") {
-                            pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
-                        } else {
-                            pn.setAttributeNS(xlink, att, value);
-                        }
-                        break;
-                    case "cursor":
-                        node.style.cursor = value;
-                        break;
-                    case "transform":
-                        o.transform(value);
-                        break;
-                    case "arrow-start":
-                        addArrow(o, value);
-                        break;
-                    case "arrow-end":
-                        addArrow(o, value, 1);
-                        break;
-                    case "clip-rect":
-                        var rect = Str(value).split(separator);
-                        if (rect.length == 4) {
-                            o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
-                            var el = $("clipPath"),
-                                rc = $("rect");
-                            el.id = R.createUUID();
-                            $(rc, {
-                                x: rect[0],
-                                y: rect[1],
-                                width: rect[2],
-                                height: rect[3]
-                            });
-                            el.appendChild(rc);
-                            o.paper.defs.appendChild(el);
-                            $(node, {"clip-path": "url(#" + el.id + ")"});
-                            o.clip = rc;
-                        }
-                        if (!value) {
-                            var path = node.getAttribute("clip-path");
-                            if (path) {
-                                var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
-                                clip && clip.parentNode.removeChild(clip);
-                                $(node, {"clip-path": E});
-                                delete o.clip;
-                            }
-                        }
-                    break;
-                    case "path":
-                        if (o.type == "path") {
-                            $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
-                            o._.dirty = 1;
-                            if (o._.arrows) {
-                                "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
-                                "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
-                            }
-                        }
-                        break;
-                    case "width":
-                        node.setAttribute(att, value);
-                        o._.dirty = 1;
-                        if (attrs.fx) {
-                            att = "x";
-                            value = attrs.x;
-                        } else {
-                            break;
-                        }
-                    case "x":
-                        if (attrs.fx) {
-                            value = -attrs.x - (attrs.width || 0);
-                        }
-                    case "rx":
-                        if (att == "rx" && o.type == "rect") {
-                            break;
-                        }
-                    case "cx":
-                        node.setAttribute(att, value);
-                        o.pattern && updatePosition(o);
-                        o._.dirty = 1;
-                        break;
-                    case "height":
-                        node.setAttribute(att, value);
-                        o._.dirty = 1;
-                        if (attrs.fy) {
-                            att = "y";
-                            value = attrs.y;
-                        } else {
-                            break;
-                        }
-                    case "y":
-                        if (attrs.fy) {
-                            value = -attrs.y - (attrs.height || 0);
-                        }
-                    case "ry":
-                        if (att == "ry" && o.type == "rect") {
-                            break;
-                        }
-                    case "cy":
-                        node.setAttribute(att, value);
-                        o.pattern && updatePosition(o);
-                        o._.dirty = 1;
-                        break;
-                    case "r":
-                        if (o.type == "rect") {
-                            $(node, {rx: value, ry: value});
-                        } else {
-                            node.setAttribute(att, value);
-                        }
-                        o._.dirty = 1;
-                        break;
-                    case "src":
-                        if (o.type == "image") {
-                            node.setAttributeNS(xlink, "href", value);
-                        }
-                        break;
-                    case "stroke-width":
-                        if (o._.sx != 1 || o._.sy != 1) {
-                            value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
-                        }
-                        if (o.paper._vbSize) {
-                            value *= o.paper._vbSize;
-                        }
-                        node.setAttribute(att, value);
-                        if (attrs["stroke-dasharray"]) {
-                            addDashes(o, attrs["stroke-dasharray"], params);
-                        }
-                        if (o._.arrows) {
-                            "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
-                            "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
-                        }
-                        break;
-                    case "stroke-dasharray":
-                        addDashes(o, value, params);
-                        break;
-                    case "fill":
-                        var isURL = Str(value).match(R._ISURL);
-                        if (isURL) {
-                            el = $("pattern");
-                            var ig = $("image");
-                            el.id = R.createUUID();
-                            $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
-                            $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
-                            el.appendChild(ig);
-
-                            (function (el) {
-                                R._preload(isURL[1], function () {
-                                    var w = this.offsetWidth,
-                                        h = this.offsetHeight;
-                                    $(el, {width: w, height: h});
-                                    $(ig, {width: w, height: h});
-                                    o.paper.safari();
-                                });
-                            })(el);
-                            o.paper.defs.appendChild(el);
-                            $(node, {fill: "url(#" + el.id + ")"});
-                            o.pattern = el;
-                            o.pattern && updatePosition(o);
-                            break;
-                        }
-                        var clr = R.getRGB(value);
-                        if (!clr.error) {
-                            delete params.gradient;
-                            delete attrs.gradient;
-                            !R.is(attrs.opacity, "undefined") &&
-                                R.is(params.opacity, "undefined") &&
-                                $(node, {opacity: attrs.opacity});
-                            !R.is(attrs["fill-opacity"], "undefined") &&
-                                R.is(params["fill-opacity"], "undefined") &&
-                                $(node, {"fill-opacity": attrs["fill-opacity"]});
-                        } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
-                            if ("opacity" in attrs || "fill-opacity" in attrs) {
-                                var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
-                                if (gradient) {
-                                    var stops = gradient.getElementsByTagName("stop");
-                                    $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
-                                }
-                            }
-                            attrs.gradient = value;
-                            attrs.fill = "none";
-                            break;
-                        }
-                        clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
-                    case "stroke":
-                        clr = R.getRGB(value);
-                        node.setAttribute(att, clr.hex);
-                        att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
-                        if (att == "stroke" && o._.arrows) {
-                            "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
-                            "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
-                        }
-                        break;
-                    case "gradient":
-                        (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
-                        break;
-                    case "opacity":
-                        if (attrs.gradient && !attrs[has]("stroke-opacity")) {
-                            $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
-                        }
-                        // fall
-                    case "fill-opacity":
-                        if (attrs.gradient) {
-                            gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
-                            if (gradient) {
-                                stops = gradient.getElementsByTagName("stop");
-                                $(stops[stops.length - 1], {"stop-opacity": value});
-                            }
-                            break;
-                        }
-                    default:
-                        att == "font-size" && (value = toInt(value, 10) + "px");
-                        var cssrule = att.replace(/(\-.)/g, function (w) {
-                            return w.substring(1).toUpperCase();
-                        });
-                        node.style[cssrule] = value;
-                        o._.dirty = 1;
-                        node.setAttribute(att, value);
-                        break;
-                }
-            }
-        }
-
-        tuneText(o, params);
-        node.style.visibility = vis;
-    },
-    leading = 1.2,
-    tuneText = function (el, params) {
-        if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
-            return;
-        }
-        var a = el.attrs,
-            node = el.node,
-            fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
-
-        if (params[has]("text")) {
-            a.text = params.text;
-            while (node.firstChild) {
-                node.removeChild(node.firstChild);
-            }
-            var texts = Str(params.text).split("\n"),
-                tspans = [],
-                tspan;
-            for (var i = 0, ii = texts.length; i < ii; i++) {
-                tspan = $("tspan");
-                i && $(tspan, {dy: fontSize * leading, x: a.x});
-                tspan.appendChild(R._g.doc.createTextNode(texts[i]));
-                node.appendChild(tspan);
-                tspans[i] = tspan;
-            }
-        } else {
-            tspans = node.getElementsByTagName("tspan");
-            for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
-                $(tspans[i], {dy: fontSize * leading, x: a.x});
-            } else {
-                $(tspans[0], {dy: 0});
-            }
-        }
-        $(node, {x: a.x, y: a.y});
-        el._.dirty = 1;
-        var bb = el._getBBox(),
-            dif = a.y - (bb.y + bb.height / 2);
-        dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
-    },
-    Element = function (node, svg) {
-        var X = 0,
-            Y = 0;
-        /*\
-         * Element.node
-         [ property (object) ]
-         **
-         * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
-         **
-         * Note: Don’t mess with it.
-         > Usage
-         | // draw a circle at coordinate 10,10 with radius of 10
-         | var c = paper.circle(10, 10, 10);
-         | c.node.onclick = function () {
-         |     c.attr("fill", "red");
-         | };
-        \*/
-        this[0] = this.node = node;
-        /*\
-         * Element.raphael
-         [ property (object) ]
-         **
-         * Internal reference to @Raphael object. In case it is not available.
-         > Usage
-         | Raphael.el.red = function () {
-         |     var hsb = this.paper.raphael.rgb2hsb(this.attr("fill"));
-         |     hsb.h = 1;
-         |     this.attr({fill: this.paper.raphael.hsb2rgb(hsb).hex});
-         | }
-        \*/
-        node.raphael = true;
-        /*\
-         * Element.id
-         [ property (number) ]
-         **
-         * Unique id of the element. Especially usesful when you want to listen to events of the element, 
-         * because all events are fired in format `<module>.<action>.<id>`. Also useful for @Paper.getById method.
-        \*/
-        this.id = R._oid++;
-        node.raphaelid = this.id;
-        this.matrix = R.matrix();
-        this.realPath = null;
-        /*\
-         * Element.paper
-         [ property (object) ]
-         **
-         * Internal reference to “paper” where object drawn. Mainly for use in plugins and element extensions.
-         > Usage
-         | Raphael.el.cross = function () {
-         |     this.attr({fill: "red"});
-         |     this.paper.path("M10,10L50,50M50,10L10,50")
-         |         .attr({stroke: "red"});
-         | }
-        \*/
-        this.paper = svg;
-        this.attrs = this.attrs || {};
-        this._ = {
-            transform: [],
-            sx: 1,
-            sy: 1,
-            deg: 0,
-            dx: 0,
-            dy: 0,
-            dirty: 1
-        };
-        !svg.bottom && (svg.bottom = this);
-        /*\
-         * Element.prev
-         [ property (object) ]
-         **
-         * Reference to the previous element in the hierarchy.
-        \*/
-        this.prev = svg.top;
-        svg.top && (svg.top.next = this);
-        svg.top = this;
-        /*\
-         * Element.next
-         [ property (object) ]
-         **
-         * Reference to the next element in the hierarchy.
-        \*/
-        this.next = null;
-    },
-    elproto = R.el;
-
-    Element.prototype = elproto;
-    elproto.constructor = Element;
-
-    R._engine.path = function (pathString, SVG) {
-        var el = $("path");
-        SVG.canvas && SVG.canvas.appendChild(el);
-        var p = new Element(el, SVG);
-        p.type = "path";
-        setFillAndStroke(p, {
-            fill: "none",
-            stroke: "#000",
-            path: pathString
-        });
-        return p;
-    };
-    /*\
-     * Element.rotate
-     [ method ]
-     **
-     * Deprecated! Use @Element.transform instead.
-     * Adds rotation by given angle around given point to the list of
-     * transformations of the element.
-     > Parameters
-     - deg (number) angle in degrees
-     - cx (number) #optional x coordinate of the centre of rotation
-     - cy (number) #optional y coordinate of the centre of rotation
-     * If cx & cy aren’t specified centre of the shape is used as a point of rotation.
-     = (object) @Element
-    \*/
-    elproto.rotate = function (deg, cx, cy) {
-        if (this.removed) {
-            return this;
-        }
-        deg = Str(deg).split(separator);
-        if (deg.length - 1) {
-            cx = toFloat(deg[1]);
-            cy = toFloat(deg[2]);
-        }
-        deg = toFloat(deg[0]);
-        (cy == null) && (cx = cy);
-        if (cx == null || cy == null) {
-            var bbox = this.getBBox(1);
-            cx = bbox.x + bbox.width / 2;
-            cy = bbox.y + bbox.height / 2;
-        }
-        this.transform(this._.transform.concat([["r", deg, cx, cy]]));
-        return this;
-    };
-    /*\
-     * Element.scale
-     [ method ]
-     **
-     * Deprecated! Use @Element.transform instead.
-     * Adds scale by given amount relative to given point to the list of
-     * transformations of the element.
-     > Parameters
-     - sx (number) horisontal scale amount
-     - sy (number) vertical scale amount
-     - cx (number) #optional x coordinate of the centre of scale
-     - cy (number) #optional y coordinate of the centre of scale
-     * If cx & cy aren’t specified centre of the shape is used instead.
-     = (object) @Element
-    \*/
-    elproto.scale = function (sx, sy, cx, cy) {
-        if (this.removed) {
-            return this;
-        }
-        sx = Str(sx).split(separator);
-        if (sx.length - 1) {
-            sy = toFloat(sx[1]);
-            cx = toFloat(sx[2]);
-            cy = toFloat(sx[3]);
-        }
-        sx = toFloat(sx[0]);
-        (sy == null) && (sy = sx);
-        (cy == null) && (cx = cy);
-        if (cx == null || cy == null) {
-            var bbox = this.getBBox(1);
-        }
-        cx = cx == null ? bbox.x + bbox.width / 2 : cx;
-        cy = cy == null ? bbox.y + bbox.height / 2 : cy;
-        this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
-        return this;
-    };
-    /*\
-     * Element.translate
-     [ method ]
-     **
-     * Deprecated! Use @Element.transform instead.
-     * Adds translation by given amount to the list of transformations of the element.
-     > Parameters
-     - dx (number) horisontal shift
-     - dy (number) vertical shift
-     = (object) @Element
-    \*/
-    elproto.translate = function (dx, dy) {
-        if (this.removed) {
-            return this;
-        }
-        dx = Str(dx).split(separator);
-        if (dx.length - 1) {
-            dy = toFloat(dx[1]);
-        }
-        dx = toFloat(dx[0]) || 0;
-        dy = +dy || 0;
-        this.transform(this._.transform.concat([["t", dx, dy]]));
-        return this;
-    };
-    /*\
-     * Element.transform
-     [ method ]
-     **
-     * Adds transformation to the element which is separate to other attributes,
-     * i.e. translation doesn’t change `x` or `y` of the rectange. The format
-     * of transformation string is similar to the path string syntax:
-     | "t100,100r30,100,100s2,2,100,100r45s1.5"
-     * Each letter is a command. There are four commands: `t` is for translate, `r` is for rotate, `s` is for
-     * scale and `m` is for matrix.
-     *
-     * There are also alternative “absolute” translation, rotation and scale: `T`, `R` and `S`. They will not take previous transformation into account. For example, `...T100,0` will always move element 100 px horisontally, while `...t100,0` could move it vertically if there is `r90` before. Just compare results of `r90t100,0` and `r90T100,0`.
-     *
-     * So, the example line above could be read like “translate by 100, 100; rotate 30° around 100, 100; scale twice around 100, 100;
-     * rotate 45° around centre; scale 1.5 times relative to centre”. As you can see rotate and scale commands have origin
-     * coordinates as optional parameters, the default is the centre point of the element.
-     * Matrix accepts six parameters.
-     > Usage
-     | var el = paper.rect(10, 20, 300, 200);
-     | // translate 100, 100, rotate 45°, translate -100, 0
-     | el.transform("t100,100r45t-100,0");
-     | // if you want you can append or prepend transformations
-     | el.transform("...t50,50");
-     | el.transform("s2...");
-     | // or even wrap
-     | el.transform("t50,50...t-50-50");
-     | // to reset transformation call method with empty string
-     | el.transform("");
-     | // to get current value call it without parameters
-     | console.log(el.transform());
-     > Parameters
-     - tstr (string) #optional transformation string
-     * If tstr isn’t specified
-     = (string) current transformation string
-     * else
-     = (object) @Element
-    \*/
-    elproto.transform = function (tstr) {
-        var _ = this._;
-        if (tstr == null) {
-            return _.transform;
-        }
-        R._extractTransform(this, tstr);
-
-        this.clip && $(this.clip, {transform: this.matrix.invert()});
-        this.pattern && updatePosition(this);
-        this.node && $(this.node, {transform: this.matrix});
-    
-        if (_.sx != 1 || _.sy != 1) {
-            var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
-            this.attr({"stroke-width": sw});
-        }
-
-        return this;
-    };
-    /*\
-     * Element.hide
-     [ method ]
-     **
-     * Makes element invisible. See @Element.show.
-     = (object) @Element
-    \*/
-    elproto.hide = function () {
-        !this.removed && this.paper.safari(this.node.style.display = "none");
-        return this;
-    };
-    /*\
-     * Element.show
-     [ method ]
-     **
-     * Makes element visible. See @Element.hide.
-     = (object) @Element
-    \*/
-    elproto.show = function () {
-        !this.removed && this.paper.safari(this.node.style.display = "");
-        return this;
-    };
-    /*\
-     * Element.remove
-     [ method ]
-     **
-     * Removes element from the paper.
-    \*/
-    elproto.remove = function () {
-        if (this.removed || !this.node.parentNode) {
-            return;
-        }
-        var paper = this.paper;
-        paper.__set__ && paper.__set__.exclude(this);
-        eve.unbind("raphael.*.*." + this.id);
-        if (this.gradient) {
-            paper.defs.removeChild(this.gradient);
-        }
-        R._tear(this, paper);
-        if (this.node.parentNode.tagName.toLowerCase() == "a") {
-            this.node.parentNode.parentNode.removeChild(this.node.parentNode);
-        } else {
-            this.node.parentNode.removeChild(this.node);
-        }
-        for (var i in this) {
-            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
-        }
-        this.removed = true;
-    };
-    elproto._getBBox = function () {
-        if (this.node.style.display == "none") {
-            this.show();
-            var hide = true;
-        }
-        var bbox = {};
-        try {
-            bbox = this.node.getBBox();
-        } catch(e) {
-            // Firefox 3.0.x plays badly here
-        } finally {
-            bbox = bbox || {};
-        }
-        hide && this.hide();
-        return bbox;
-    };
-    /*\
-     * Element.attr
-     [ method ]
-     **
-     * Sets the attributes of the element.
-     > Parameters
-     - attrName (string) attribute’s name
-     - value (string) value
-     * or
-     - params (object) object of name/value pairs
-     * or
-     - attrName (string) attribute’s name
-     * or
-     - attrNames (array) in this case method returns array of current values for given attribute names
-     = (object) @Element if attrsName & value or params are passed in.
-     = (...) value of the attribute if only attrsName is passed in.
-     = (array) array of values of the attribute if attrsNames is passed in.
-     = (object) object of attributes if nothing is passed in.
-     > Possible parameters
-     # <p>Please refer to the <a href="http://www.w3.org/TR/SVG/" title="The W3C Recommendation for the SVG language describes these properties in detail.">SVG specification</a> for an explanation of these parameters.</p>
-     o arrow-end (string) arrowhead on the end of the path. The format for string is `<type>[-<width>[-<length>]]`. Possible types: `classic`, `block`, `open`, `oval`, `diamond`, `none`, width: `wide`, `narrow`, `medium`, length: `long`, `short`, `midium`.
-     o clip-rect (string) comma or space separated values: x, y, width and height
-     o cursor (string) CSS type of the cursor
-     o cx (number) the x-axis coordinate of the center of the circle, or ellipse
-     o cy (number) the y-axis coordinate of the center of the circle, or ellipse
-     o fill (string) colour, gradient or image
-     o fill-opacity (number)
-     o font (string)
-     o font-family (string)
-     o font-size (number) font size in pixels
-     o font-weight (string)
-     o height (number)
-     o href (string) URL, if specified element behaves as hyperlink
-     o opacity (number)
-     o path (string) SVG path string format
-     o r (number) radius of the circle, ellipse or rounded corner on the rect
-     o rx (number) horisontal radius of the ellipse
-     o ry (number) vertical radius of the ellipse
-     o src (string) image URL, only works for @Element.image element
-     o stroke (string) stroke colour
-     o stroke-dasharray (string) [“”, “`-`”, “`.`”, “`-.`”, “`-..`”, “`. `”, “`- `”, “`--`”, “`- .`”, “`--.`”, “`--..`”]
-     o stroke-linecap (string) [“`butt`”, “`square`”, “`round`”]
-     o stroke-linejoin (string) [“`bevel`”, “`round`”, “`miter`”]
-     o stroke-miterlimit (number)
-     o stroke-opacity (number)
-     o stroke-width (number) stroke width in pixels, default is '1'
-     o target (string) used with href
-     o text (string) contents of the text element. Use `\n` for multiline text
-     o text-anchor (string) [“`start`”, “`middle`”, “`end`”], default is “`middle`”
-     o title (string) will create tooltip with a given text
-     o transform (string) see @Element.transform
-     o width (number)
-     o x (number)
-     o y (number)
-     > Gradients
-     * Linear gradient format: “`‹angle›-‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`90-#fff-#000`” – 90°
-     * gradient from white to black or “`0-#fff-#f00:20-#000`” – 0° gradient from white via red (at 20%) to black.
-     *
-     * radial gradient: “`r[(‹fx›, ‹fy›)]‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`r#fff-#000`” –
-     * gradient from white to black or “`r(0.25, 0.75)#fff-#000`” – gradient from white to black with focus point
-     * at 0.25, 0.75. Focus point coordinates are in 0..1 range. Radial gradients can only be applied to circles and ellipses.
-     > Path String
-     # <p>Please refer to <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path’s data attribute’s format are described in the SVG specification.">SVG documentation regarding path string</a>. Raphaël fully supports it.</p>
-     > Colour Parsing
-     # <ul>
-     #     <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
-     #     <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
-     #     <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
-     #     <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
-     #     <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
-     #     <li>rgba(•••, •••, •••, •••) — red, green and blue channels’ values: (“<code>rgba(200,&nbsp;100,&nbsp;0, .5)</code>”)</li>
-     #     <li>rgba(•••%, •••%, •••%, •••%) — same as above, but in %: (“<code>rgba(100%,&nbsp;175%,&nbsp;0%, 50%)</code>”)</li>
-     #     <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
-     #     <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
-     #     <li>hsba(•••, •••, •••, •••) — same as above, but with opacity</li>
-     #     <li>hsl(•••, •••, •••) — almost the same as hsb, see <a href="http://en.wikipedia.org/wiki/HSL_and_HSV" title="HSL and HSV - Wikipedia, the free encyclopedia">Wikipedia page</a></li>
-     #     <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
-     #     <li>hsla(•••, •••, •••, •••) — same as above, but with opacity</li>
-     #     <li>Optionally for hsb and hsl you could specify hue as a degree: “<code>hsl(240deg,&nbsp;1,&nbsp;.5)</code>” or, if you want to go fancy, “<code>hsl(240°,&nbsp;1,&nbsp;.5)</code>”</li>
-     # </ul>
-    \*/
-    elproto.attr = function (name, value) {
-        if (this.removed) {
-            return this;
-        }
-        if (name == null) {
-            var res = {};
-            for (var a in this.attrs) if (this.attrs[has](a)) {
-                res[a] = this.attrs[a];
-            }
-            res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
-            res.transform = this._.transform;
-            return res;
-        }
-        if (value == null && R.is(name, "string")) {
-            if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
-                return this.attrs.gradient;
-            }
-            if (name == "transform") {
-                return this._.transform;
-            }
-            var names = name.split(separator),
-                out = {};
-            for (var i = 0, ii = names.length; i < ii; i++) {
-                name = names[i];
-                if (name in this.attrs) {
-                    out[name] = this.attrs[name];
-                } else if (R.is(this.paper.customAttributes[name], "function")) {
-                    out[name] = this.paper.customAttributes[name].def;
-                } else {
-                    out[name] = R._availableAttrs[name];
-                }
-            }
-            return ii - 1 ? out : out[names[0]];
-        }
-        if (value == null && R.is(name, "array")) {
-            out = {};
-            for (i = 0, ii = name.length; i < ii; i++) {
-                out[name[i]] = this.attr(name[i]);
-            }
-            return out;
-        }
-        if (value != null) {
-            var params = {};
-            params[name] = value;
-        } else if (name != null && R.is(name, "object")) {
-            params = name;
-        }
-        for (var key in params) {
-            eve("raphael.attr." + key + "." + this.id, this, params[key]);
-        }
-        for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
-            var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
-            this.attrs[key] = params[key];
-            for (var subkey in par) if (par[has](subkey)) {
-                params[subkey] = par[subkey];
-            }
-        }
-        setFillAndStroke(this, params);
-        return this;
-    };
-    /*\
-     * Element.toFront
-     [ method ]
-     **
-     * Moves the element so it is the closest to the viewer’s eyes, on top of other elements.
-     = (object) @Element
-    \*/
-    elproto.toFront = function () {
-        if (this.removed) {
-            return this;
-        }
-        if (this.node.parentNode.tagName.toLowerCase() == "a") {
-            this.node.parentNode.parentNode.appendChild(this.node.parentNode);
-        } else {
-            this.node.parentNode.appendChild(this.node);
-        }
-        var svg = this.paper;
-        svg.top != this && R._tofront(this, svg);
-        return this;
-    };
-    /*\
-     * Element.toBack
-     [ method ]
-     **
-     * Moves the element so it is the furthest from the viewer’s eyes, behind other elements.
-     = (object) @Element
-    \*/
-    elproto.toBack = function () {
-        if (this.removed) {
-            return this;
-        }
-        var parent = this.node.parentNode;
-        if (parent.tagName.toLowerCase() == "a") {
-            parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild); 
-        } else if (parent.firstChild != this.node) {
-            parent.insertBefore(this.node, this.node.parentNode.firstChild);
-        }
-        R._toback(this, this.paper);
-        var svg = this.paper;
-        return this;
-    };
-    /*\
-     * Element.insertAfter
-     [ method ]
-     **
-     * Inserts current object after the given one.
-     = (object) @Element
-    \*/
-    elproto.insertAfter = function (element) {
-        if (this.removed) {
-            return this;
-        }
-        var node = element.node || element[element.length - 1].node;
-        if (node.nextSibling) {
-            node.parentNode.insertBefore(this.node, node.nextSibling);
-        } else {
-            node.parentNode.appendChild(this.node);
-        }
-        R._insertafter(this, element, this.paper);
-        return this;
-    };
-    /*\
-     * Element.insertBefore
-     [ method ]
-     **
-     * Inserts current object before the given one.
-     = (object) @Element
-    \*/
-    elproto.insertBefore = function (element) {
-        if (this.removed) {
-            return this;
-        }
-        var node = element.node || element[0].node;
-        node.parentNode.insertBefore(this.node, node);
-        R._insertbefore(this, element, this.paper);
-        return this;
-    };
-    elproto.blur = function (size) {
-        // Experimental. No Safari support. Use it on your own risk.
-        var t = this;
-        if (+size !== 0) {
-            var fltr = $("filter"),
-                blur = $("feGaussianBlur");
-            t.attrs.blur = size;
-            fltr.id = R.createUUID();
-            $(blur, {stdDeviation: +size || 1.5});
-            fltr.appendChild(blur);
-            t.paper.defs.appendChild(fltr);
-            t._blur = fltr;
-            $(t.node, {filter: "url(#" + fltr.id + ")"});
-        } else {
-            if (t._blur) {
-                t._blur.parentNode.removeChild(t._blur);
-                delete t._blur;
-                delete t.attrs.blur;
-            }
-            t.node.removeAttribute("filter");
-        }
-    };
-    R._engine.circle = function (svg, x, y, r) {
-        var el = $("circle");
-        svg.canvas && svg.canvas.appendChild(el);
-        var res = new Element(el, svg);
-        res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
-        res.type = "circle";
-        $(el, res.attrs);
-        return res;
-    };
-    R._engine.rect = function (svg, x, y, w, h, r) {
-        var el = $("rect");
-        svg.canvas && svg.canvas.appendChild(el);
-        var res = new Element(el, svg);
-        res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
-        res.type = "rect";
-        $(el, res.attrs);
-        return res;
-    };
-    R._engine.ellipse = function (svg, x, y, rx, ry) {
-        var el = $("ellipse");
-        svg.canvas && svg.canvas.appendChild(el);
-        var res = new Element(el, svg);
-        res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
-        res.type = "ellipse";
-        $(el, res.attrs);
-        return res;
-    };
-    R._engine.image = function (svg, src, x, y, w, h) {
-        var el = $("image");
-        $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
-        el.setAttributeNS(xlink, "href", src);
-        svg.canvas && svg.canvas.appendChild(el);
-        var res = new Element(el, svg);
-        res.attrs = {x: x, y: y, width: w, height: h, src: src};
-        res.type = "image";
-        return res;
-    };
-    R._engine.text = function (svg, x, y, text) {
-        var el = $("text");
-        svg.canvas && svg.canvas.appendChild(el);
-        var res = new Element(el, svg);
-        res.attrs = {
-            x: x,
-            y: y,
-            "text-anchor": "middle",
-            text: text,
-            font: R._availableAttrs.font,
-            stroke: "none",
-            fill: "#000"
-        };
-        res.type = "text";
-        setFillAndStroke(res, res.attrs);
-        return res;
-    };
-    R._engine.setSize = function (width, height) {
-        this.width = width || this.width;
-        this.height = height || this.height;
-        this.canvas.setAttribute("width", this.width);
-        this.canvas.setAttribute("height", this.height);
-        if (this._viewBox) {
-            this.setViewBox.apply(this, this._viewBox);
-        }
-        return this;
-    };
-    R._engine.create = function () {
-        var con = R._getContainer.apply(0, arguments),
-            container = con && con.container,
-            x = con.x,
-            y = con.y,
-            width = con.width,
-            height = con.height;
-        if (!container) {
-            throw new Error("SVG container not found.");
-        }
-        var cnvs = $("svg"),
-            css = "overflow:hidden;",
-            isFloating;
-        x = x || 0;
-        y = y || 0;
-        width = width || 512;
-        height = height || 342;
-        $(cnvs, {
-            height: height,
-            version: 1.1,
-            width: width,
-            xmlns: "http://www.w3.org/2000/svg"
-        });
-        if (container == 1) {
-            cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
-            R._g.doc.body.appendChild(cnvs);
-            isFloating = 1;
-        } else {
-            cnvs.style.cssText = css + "position:relative";
-            if (container.firstChild) {
-                container.insertBefore(cnvs, container.firstChild);
-            } else {
-                container.appendChild(cnvs);
-            }
-        }
-        container = new R._Paper;
-        container.width = width;
-        container.height = height;
-        container.canvas = cnvs;
-        container.clear();
-        container._left = container._top = 0;
-        isFloating && (container.renderfix = function () {});
-        container.renderfix();
-        return container;
-    };
-    R._engine.setViewBox = function (x, y, w, h, fit) {
-        eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
-        var size = mmax(w / this.width, h / this.height),
-            top = this.top,
-            aspectRatio = fit ? "meet" : "xMinYMin",
-            vb,
-            sw;
-        if (x == null) {
-            if (this._vbSize) {
-                size = 1;
-            }
-            delete this._vbSize;
-            vb = "0 0 " + this.width + S + this.height;
-        } else {
-            this._vbSize = size;
-            vb = x + S + y + S + w + S + h;
-        }
-        $(this.canvas, {
-            viewBox: vb,
-            preserveAspectRatio: aspectRatio
-        });
-        while (size && top) {
-            sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
-            top.attr({"stroke-width": sw});
-            top._.dirty = 1;
-            top._.dirtyT = 1;
-            top = top.prev;
-        }
-        this._viewBox = [x, y, w, h, !!fit];
-        return this;
-    };
-    /*\
-     * Paper.renderfix
-     [ method ]
-     **
-     * Fixes the issue of Firefox and IE9 regarding subpixel rendering. If paper is dependant
-     * on other elements after reflow it could shift half pixel which cause for lines to lost their crispness.
-     * This method fixes the issue.
-     **
-       Special thanks to Mariusz Nowak (http://www.medikoo.com/) for this method.
-    \*/
-    R.prototype.renderfix = function () {
-        var cnvs = this.canvas,
-            s = cnvs.style,
-            pos;
-        try {
-            pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
-        } catch (e) {
-            pos = cnvs.createSVGMatrix();
-        }
-        var left = -pos.e % 1,
-            top = -pos.f % 1;
-        if (left || top) {
-            if (left) {
-                this._left = (this._left + left) % 1;
-                s.left = this._left + "px";
-            }
-            if (top) {
-                this._top = (this._top + top) % 1;
-                s.top = this._top + "px";
-            }
-        }
-    };
-    /*\
-     * Paper.clear
-     [ method ]
-     **
-     * Clears the paper, i.e. removes all the elements.
-    \*/
-    R.prototype.clear = function () {
-        R.eve("raphael.clear", this);
-        var c = this.canvas;
-        while (c.firstChild) {
-            c.removeChild(c.firstChild);
-        }
-        this.bottom = this.top = null;
-        (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
-        c.appendChild(this.desc);
-        c.appendChild(this.defs = $("defs"));
-    };
-    /*\
-     * Paper.remove
-     [ method ]
-     **
-     * Removes the paper from the DOM.
-    \*/
-    R.prototype.remove = function () {
-        eve("raphael.remove", this);
-        this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
-        for (var i in this) {
-            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
-        }
-    };
-    var setproto = R.st;
-    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
-        setproto[method] = (function (methodname) {
-            return function () {
-                var arg = arguments;
-                return this.forEach(function (el) {
-                    el[methodname].apply(el, arg);
-                });
-            };
-        })(method);
-    }
-}(window.Raphael);
-// ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël - JavaScript Vector Library                                 │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ VML Module                                                          │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
-// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
-// └─────────────────────────────────────────────────────────────────────┘ \\
-window.Raphael && window.Raphael.vml && function (R) {
-    var has = "hasOwnProperty",
-        Str = String,
-        toFloat = parseFloat,
-        math = Math,
-        round = math.round,
-        mmax = math.max,
-        mmin = math.min,
-        abs = math.abs,
-        fillString = "fill",
-        separator = /[, ]+/,
-        eve = R.eve,
-        ms = " progid:DXImageTransform.Microsoft",
-        S = " ",
-        E = "",
-        map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
-        bites = /([clmz]),?([^clmz]*)/gi,
-        blurregexp = / progid:\S+Blur\([^\)]+\)/g,
-        val = /-?[^,\s-]+/g,
-        cssDot = "position:absolute;left:0;top:0;width:1px;height:1px",
-        zoom = 21600,
-        pathTypes = {path: 1, rect: 1, image: 1},
-        ovalTypes = {circle: 1, ellipse: 1},
-        path2vml = function (path) {
-            var total =  /[ahqstv]/ig,
-                command = R._pathToAbsolute;
-            Str(path).match(total) && (command = R._path2curve);
-            total = /[clmz]/g;
-            if (command == R._pathToAbsolute && !Str(path).match(total)) {
-                var res = Str(path).replace(bites, function (all, command, args) {
-                    var vals = [],
-                        isMove = command.toLowerCase() == "m",
-                        res = map[command];
-                    args.replace(val, function (value) {
-                        if (isMove && vals.length == 2) {
-                            res += vals + map[command == "m" ? "l" : "L"];
-                            vals = [];
-                        }
-                        vals.push(round(value * zoom));
-                    });
-                    return res + vals;
-                });
-                return res;
-            }
-            var pa = command(path), p, r;
-            res = [];
-            for (var i = 0, ii = pa.length; i < ii; i++) {
-                p = pa[i];
-                r = pa[i][0].toLowerCase();
-                r == "z" && (r = "x");
-                for (var j = 1, jj = p.length; j < jj; j++) {
-                    r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
-                }
-                res.push(r);
-            }
-            return res.join(S);
-        },
-        compensation = function (deg, dx, dy) {
-            var m = R.matrix();
-            m.rotate(-deg, .5, .5);
-            return {
-                dx: m.x(dx, dy),
-                dy: m.y(dx, dy)
-            };
-        },
-        setCoords = function (p, sx, sy, dx, dy, deg) {
-            var _ = p._,
-                m = p.matrix,
-                fillpos = _.fillpos,
-                o = p.node,
-                s = o.style,
-                y = 1,
-                flip = "",
-                dxdy,
-                kx = zoom / sx,
-                ky = zoom / sy;
-            s.visibility = "hidden";
-            if (!sx || !sy) {
-                return;
-            }
-            o.coordsize = abs(kx) + S + abs(ky);
-            s.rotation = deg * (sx * sy < 0 ? -1 : 1);
-            if (deg) {
-                var c = compensation(deg, dx, dy);
-                dx = c.dx;
-                dy = c.dy;
-            }
-            sx < 0 && (flip += "x");
-            sy < 0 && (flip += " y") && (y = -1);
-            s.flip = flip;
-            o.coordorigin = (dx * -kx) + S + (dy * -ky);
-            if (fillpos || _.fillsize) {
-                var fill = o.getElementsByTagName(fillString);
-                fill = fill && fill[0];
-                o.removeChild(fill);
-                if (fillpos) {
-                    c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
-                    fill.position = c.dx * y + S + c.dy * y;
-                }
-                if (_.fillsize) {
-                    fill.size = _.fillsize[0] * abs(sx)/1.34 + "pt" + S + _.fillsize[1] * abs(sy)/1.34 + "pt";
-                }
-                o.appendChild(fill);
-            }
-            s.visibility = "visible";
-        };
-    R.toString = function () {
-        return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
-    };
-    var addArrow = function (o, value, isEnd) {
-        var values = Str(value).toLowerCase().split("-"),
-            se = isEnd ? "end" : "start",
-            i = values.length,
-            type = "classic",
-            w = "medium",
-            h = "medium";
-        while (i--) {
-            switch (values[i]) {
-                case "block":
-                case "classic":
-                case "oval":
-                case "diamond":
-                case "open":
-                case "none":
-                    type = values[i];
-                    break;
-                case "wide":
-                case "narrow": h = values[i]; break;
-                case "long":
-                case "short": w = values[i]; break;
-            }
-        }
-        var stroke = o.node.getElementsByTagName("stroke")[0];
-        stroke[se + "arrow"] = type;
-        stroke[se + "arrowlength"] = w;
-        stroke[se + "arrowwidth"] = h;
-    },
-    setFillAndStroke = function (o, params) {
-        // o.paper.canvas.style.display = "none";
-        o.attrs = o.attrs || {};
-        var node = o.node,
-            a = o.attrs,
-            s = node.style,
-            xy,
-            newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
-            isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
-            res = o;
-
-
-        for (var par in params) if (params[has](par)) {
-            a[par] = params[par];
-        }
-        if (newpath) {
-            a.path = R._getPath[o.type](o);
-            o._.dirty = 1;
-        }
-        params.href && (node.href = params.href);
-        params.title && (node.title = params.title);
-        params.target && (node.target = params.target);
-        params.cursor && (s.cursor = params.cursor);
-        "blur" in params && o.blur(params.blur);
-        if (params.path && o.type == "path" || newpath) {
-            node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
-            if (o.type == "image") {
-                o._.fillpos = [a.x, a.y];
-                o._.fillsize = [a.width, a.height];
-                setCoords(o, 1, 1, 0, 0, 0);
-            }
-        }
-        "transform" in params && o.transform(params.transform);
-        if (isOval) {
-            var cx = +a.cx,
-                cy = +a.cy,
-                rx = +a.rx || +a.r || 0,
-                ry = +a.ry || +a.r || 0;
-            node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
-        }
-        if ("clip-rect" in params) {
-            var rect = Str(params["clip-rect"]).split(separator);
-            if (rect.length == 4) {
-                rect[2] = +rect[2] + (+rect[0]);
-                rect[3] = +rect[3] + (+rect[1]);
-                var div = node.clipRect || R._g.doc.createElement("div"),
-                    dstyle = div.style;
-                dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
-                if (!node.clipRect) {
-                    dstyle.position = "absolute";
-                    dstyle.top = 0;
-                    dstyle.left = 0;
-                    dstyle.width = o.paper.width + "px";
-                    dstyle.height = o.paper.height + "px";
-                    node.parentNode.insertBefore(div, node);
-                    div.appendChild(node);
-                    node.clipRect = div;
-                }
-            }
-            if (!params["clip-rect"]) {
-                node.clipRect && (node.clipRect.style.clip = "auto");
-            }
-        }
-        if (o.textpath) {
-            var textpathStyle = o.textpath.style;
-            params.font && (textpathStyle.font = params.font);
-            params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
-            params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
-            params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
-            params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
-        }
-        if ("arrow-start" in params) {
-            addArrow(res, params["arrow-start"]);
-        }
-        if ("arrow-end" in params) {
-            addArrow(res, params["arrow-end"], 1);
-        }
-        if (params.opacity != null || 
-            params["stroke-width"] != null ||
-            params.fill != null ||
-            params.src != null ||
-            params.stroke != null ||
-            params["stroke-width"] != null ||
-            params["stroke-opacity"] != null ||
-            params["fill-opacity"] != null ||
-            params["stroke-dasharray"] != null ||
-            params["stroke-miterlimit"] != null ||
-            params["stroke-linejoin"] != null ||
-            params["stroke-linecap"] != null) {
-            var fill = node.getElementsByTagName(fillString),
-                newfill = false;
-            fill = fill && fill[0];
-            !fill && (newfill = fill = createNode(fillString));
-            if (o.type == "image" && params.src) {
-                fill.src = params.src;
-            }
-            params.fill && (fill.on = true);
-            if (fill.on == null || params.fill == "none" || params.fill === null) {
-                fill.on = false;
-            }
-            if (fill.on && params.fill) {
-                var isURL = Str(params.fill).match(R._ISURL);
-                if (isURL) {
-                    fill.parentNode == node && node.removeChild(fill);
-                    fill.rotate = true;
-                    fill.src = isURL[1];
-                    fill.type = "tile";
-                    var bbox = o.getBBox(1);
-                    fill.position = bbox.x + S + bbox.y;
-                    o._.fillpos = [bbox.x, bbox.y];
-
-                    R._preload(isURL[1], function () {
-                        o._.fillsize = [this.offsetWidth, this.offsetHeight];
-                    });
-                } else {
-                    fill.color = R.getRGB(params.fill).hex;
-                    fill.src = E;
-                    fill.type = "solid";
-                    if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
-                        a.fill = "none";
-                        a.gradient = params.fill;
-                        fill.rotate = false;
-                    }
-                }
-            }
-            if ("fill-opacity" in params || "opacity" in params) {
-                var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
-                opacity = mmin(mmax(opacity, 0), 1);
-                fill.opacity = opacity;
-                if (fill.src) {
-                    fill.color = "none";
-                }
-            }
-            node.appendChild(fill);
-            var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
-            newstroke = false;
-            !stroke && (newstroke = stroke = createNode("stroke"));
-            if ((params.stroke && params.stroke != "none") ||
-                params["stroke-width"] ||
-                params["stroke-opacity"] != null ||
-                params["stroke-dasharray"] ||
-                params["stroke-miterlimit"] ||
-                params["stroke-linejoin"] ||
-                params["stroke-linecap"]) {
-                stroke.on = true;
-            }
-            (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
-            var strokeColor = R.getRGB(params.stroke);
-            stroke.on && params.stroke && (stroke.color = strokeColor.hex);
-            opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
-            var width = (toFloat(params["stroke-width"]) || 1) * .75;
-            opacity = mmin(mmax(opacity, 0), 1);
-            params["stroke-width"] == null && (width = a["stroke-width"]);
-            params["stroke-width"] && (stroke.weight = width);
-            width && width < 1 && (opacity *= width) && (stroke.weight = 1);
-            stroke.opacity = opacity;
-        
-            params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
-            stroke.miterlimit = params["stroke-miterlimit"] || 8;
-            params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
-            if (params["stroke-dasharray"]) {
-                var dasharray = {
-                    "-": "shortdash",
-                    ".": "shortdot",
-                    "-.": "shortdashdot",
-                    "-..": "shortdashdotdot",
-                    ". ": "dot",
-                    "- ": "dash",
-                    "--": "longdash",
-                    "- .": "dashdot",
-                    "--.": "longdashdot",
-                    "--..": "longdashdotdot"
-                };
-                stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
-            }
-            newstroke && node.appendChild(stroke);
-        }
-        if (res.type == "text") {
-            res.paper.canvas.style.display = E;
-            var span = res.paper.span,
-                m = 100,
-                fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
-            s = span.style;
-            a.font && (s.font = a.font);
-            a["font-family"] && (s.fontFamily = a["font-family"]);
-            a["font-weight"] && (s.fontWeight = a["font-weight"]);
-            a["font-style"] && (s.fontStyle = a["font-style"]);
-            fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
-            s.fontSize = fontSize * m + "px";
-            res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
-            var brect = span.getBoundingClientRect();
-            res.W = a.w = (brect.right - brect.left) / m;
-            res.H = a.h = (brect.bottom - brect.top) / m;
-            // res.paper.canvas.style.display = "none";
-            res.X = a.x;
-            res.Y = a.y + res.H / 2;
-
-            ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
-            var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
-            for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
-                res._.dirty = 1;
-                break;
-            }
-        
-            // text-anchor emulation
-            switch (a["text-anchor"]) {
-                case "start":
-                    res.textpath.style["v-text-align"] = "left";
-                    res.bbx = res.W / 2;
-                break;
-                case "end":
-                    res.textpath.style["v-text-align"] = "right";
-                    res.bbx = -res.W / 2;
-                break;
-                default:
-                    res.textpath.style["v-text-align"] = "center";
-                    res.bbx = 0;
-                break;
-            }
-            res.textpath.style["v-text-kern"] = true;
-        }
-        // res.paper.canvas.style.display = E;
-    },
-    addGradientFill = function (o, gradient, fill) {
-        o.attrs = o.attrs || {};
-        var attrs = o.attrs,
-            pow = Math.pow,
-            opacity,
-            oindex,
-            type = "linear",
-            fxfy = ".5 .5";
-        o.attrs.gradient = gradient;
-        gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
-            type = "radial";
-            if (fx && fy) {
-                fx = toFloat(fx);
-                fy = toFloat(fy);
-                pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
-                fxfy = fx + S + fy;
-            }
-            return E;
-        });
-        gradient = gradient.split(/\s*\-\s*/);
-        if (type == "linear") {
-            var angle = gradient.shift();
-            angle = -toFloat(angle);
-            if (isNaN(angle)) {
-                return null;
-            }
-        }
-        var dots = R._parseDots(gradient);
-        if (!dots) {
-            return null;
-        }
-        o = o.shape || o.node;
-        if (dots.length) {
-            o.removeChild(fill);
-            fill.on = true;
-            fill.method = "none";
-            fill.color = dots[0].color;
-            fill.color2 = dots[dots.length - 1].color;
-            var clrs = [];
-            for (var i = 0, ii = dots.length; i < ii; i++) {
-                dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
-            }
-            fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
-            if (type == "radial") {
-                fill.type = "gradientTitle";
-                fill.focus = "100%";
-                fill.focussize = "0 0";
-                fill.focusposition = fxfy;
-                fill.angle = 0;
-            } else {
-                // fill.rotate= true;
-                fill.type = "gradient";
-                fill.angle = (270 - angle) % 360;
-            }
-            o.appendChild(fill);
-        }
-        return 1;
-    },
-    Element = function (node, vml) {
-        this[0] = this.node = node;
-        node.raphael = true;
-        this.id = R._oid++;
-        node.raphaelid = this.id;
-        this.X = 0;
-        this.Y = 0;
-        this.attrs = {};
-        this.paper = vml;
-        this.matrix = R.matrix();
-        this._ = {
-            transform: [],
-            sx: 1,
-            sy: 1,
-            dx: 0,
-            dy: 0,
-            deg: 0,
-            dirty: 1,
-            dirtyT: 1
-        };
-        !vml.bottom && (vml.bottom = this);
-        this.prev = vml.top;
-        vml.top && (vml.top.next = this);
-        vml.top = this;
-        this.next = null;
-    };
-    var elproto = R.el;
-
-    Element.prototype = elproto;
-    elproto.constructor = Element;
-    elproto.transform = function (tstr) {
-        if (tstr == null) {
-            return this._.transform;
-        }
-        var vbs = this.paper._viewBoxShift,
-            vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
-            oldt;
-        if (vbs) {
-            oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
-        }
-        R._extractTransform(this, vbt + tstr);
-        var matrix = this.matrix.clone(),
-            skew = this.skew,
-            o = this.node,
-            split,
-            isGrad = ~Str(this.attrs.fill).indexOf("-"),
-            isPatt = !Str(this.attrs.fill).indexOf("url(");
-        matrix.translate(-.5, -.5);
-        if (isPatt || isGrad || this.type == "image") {
-            skew.matrix = "1 0 0 1";
-            skew.offset = "0 0";
-            split = matrix.split();
-            if ((isGrad && split.noRotation) || !split.isSimple) {
-                o.style.filter = matrix.toFilter();
-                var bb = this.getBBox(),
-                    bbt = this.getBBox(1),
-                    dx = bb.x - bbt.x,
-                    dy = bb.y - bbt.y;
-                o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
-                setCoords(this, 1, 1, dx, dy, 0);
-            } else {
-                o.style.filter = E;
-                setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
-            }
-        } else {
-            o.style.filter = E;
-            skew.matrix = Str(matrix);
-            skew.offset = matrix.offset();
-        }
-        oldt && (this._.transform = oldt);
-        return this;
-    };
-    elproto.rotate = function (deg, cx, cy) {
-        if (this.removed) {
-            return this;
-        }
-        if (deg == null) {
-            return;
-        }
-        deg = Str(deg).split(separator);
-        if (deg.length - 1) {
-            cx = toFloat(deg[1]);
-            cy = toFloat(deg[2]);
-        }
-        deg = toFloat(deg[0]);
-        (cy == null) && (cx = cy);
-        if (cx == null || cy == null) {
-            var bbox = this.getBBox(1);
-            cx = bbox.x + bbox.width / 2;
-            cy = bbox.y + bbox.height / 2;
-        }
-        this._.dirtyT = 1;
-        this.transform(this._.transform.concat([["r", deg, cx, cy]]));
-        return this;
-    };
-    elproto.translate = function (dx, dy) {
-        if (this.removed) {
-            return this;
-        }
-        dx = Str(dx).split(separator);
-        if (dx.length - 1) {
-            dy = toFloat(dx[1]);
-        }
-        dx = toFloat(dx[0]) || 0;
-        dy = +dy || 0;
-        if (this._.bbox) {
-            this._.bbox.x += dx;
-            this._.bbox.y += dy;
-        }
-        this.transform(this._.transform.concat([["t", dx, dy]]));
-        return this;
-    };
-    elproto.scale = function (sx, sy, cx, cy) {
-        if (this.removed) {
-            return this;
-        }
-        sx = Str(sx).split(separator);
-        if (sx.length - 1) {
-            sy = toFloat(sx[1]);
-            cx = toFloat(sx[2]);
-            cy = toFloat(sx[3]);
-            isNaN(cx) && (cx = null);
-            isNaN(cy) && (cy = null);
-        }
-        sx = toFloat(sx[0]);
-        (sy == null) && (sy = sx);
-        (cy == null) && (cx = cy);
-        if (cx == null || cy == null) {
-            var bbox = this.getBBox(1);
-        }
-        cx = cx == null ? bbox.x + bbox.width / 2 : cx;
-        cy = cy == null ? bbox.y + bbox.height / 2 : cy;
-    
-        this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
-        this._.dirtyT = 1;
-        return this;
-    };
-    elproto.hide = function () {
-        !this.removed && (this.node.style.display = "none");
-        return this;
-    };
-    elproto.show = function () {
-        !this.removed && (this.node.style.display = E);
-        return this;
-    };
-    elproto._getBBox = function () {
-        if (this.removed) {
-            return {};
-        }
-        return {
-            x: this.X + (this.bbx || 0) - this.W / 2,
-            y: this.Y - this.H,
-            width: this.W,
-            height: this.H
-        };
-    };
-    elproto.remove = function () {
-        if (this.removed || !this.node.parentNode) {
-            return;
-        }
-        this.paper.__set__ && this.paper.__set__.exclude(this);
-        R.eve.unbind("raphael.*.*." + this.id);
-        R._tear(this, this.paper);
-        this.node.parentNode.removeChild(this.node);
-        this.shape && this.shape.parentNode.removeChild(this.shape);
-        for (var i in this) {
-            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
-        }
-        this.removed = true;
-    };
-    elproto.attr = function (name, value) {
-        if (this.removed) {
-            return this;
-        }
-        if (name == null) {
-            var res = {};
-            for (var a in this.attrs) if (this.attrs[has](a)) {
-                res[a] = this.attrs[a];
-            }
-            res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
-            res.transform = this._.transform;
-            return res;
-        }
-        if (value == null && R.is(name, "string")) {
-            if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
-                return this.attrs.gradient;
-            }
-            var names = name.split(separator),
-                out = {};
-            for (var i = 0, ii = names.length; i < ii; i++) {
-                name = names[i];
-                if (name in this.attrs) {
-                    out[name] = this.attrs[name];
-                } else if (R.is(this.paper.customAttributes[name], "function")) {
-                    out[name] = this.paper.customAttributes[name].def;
-                } else {
-                    out[name] = R._availableAttrs[name];
-                }
-            }
-            return ii - 1 ? out : out[names[0]];
-        }
-        if (this.attrs && value == null && R.is(name, "array")) {
-            out = {};
-            for (i = 0, ii = name.length; i < ii; i++) {
-                out[name[i]] = this.attr(name[i]);
-            }
-            return out;
-        }
-        var params;
-        if (value != null) {
-            params = {};
-            params[name] = value;
-        }
-        value == null && R.is(name, "object") && (params = name);
-        for (var key in params) {
-            eve("raphael.attr." + key + "." + this.id, this, params[key]);
-        }
-        if (params) {
-            for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
-                var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
-                this.attrs[key] = params[key];
-                for (var subkey in par) if (par[has](subkey)) {
-                    params[subkey] = par[subkey];
-                }
-            }
-            // this.paper.canvas.style.display = "none";
-            if (params.text && this.type == "text") {
-                this.textpath.string = params.text;
-            }
-            setFillAndStroke(this, params);
-            // this.paper.canvas.style.display = E;
-        }
-        return this;
-    };
-    elproto.toFront = function () {
-        !this.removed && this.node.parentNode.appendChild(this.node);
-        this.paper && this.paper.top != this && R._tofront(this, this.paper);
-        return this;
-    };
-    elproto.toBack = function () {
-        if (this.removed) {
-            return this;
-        }
-        if (this.node.parentNode.firstChild != this.node) {
-            this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
-            R._toback(this, this.paper);
-        }
-        return this;
-    };
-    elproto.insertAfter = function (element) {
-        if (this.removed) {
-            return this;
-        }
-        if (element.constructor == R.st.constructor) {
-            element = element[element.length - 1];
-        }
-        if (element.node.nextSibling) {
-            element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
-        } else {
-            element.node.parentNode.appendChild(this.node);
-        }
-        R._insertafter(this, element, this.paper);
-        return this;
-    };
-    elproto.insertBefore = function (element) {
-        if (this.removed) {
-            return this;
-        }
-        if (element.constructor == R.st.constructor) {
-            element = element[0];
-        }
-        element.node.parentNode.insertBefore(this.node, element.node);
-        R._insertbefore(this, element, this.paper);
-        return this;
-    };
-    elproto.blur = function (size) {
-        var s = this.node.runtimeStyle,
-            f = s.filter;
-        f = f.replace(blurregexp, E);
-        if (+size !== 0) {
-            this.attrs.blur = size;
-            s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
-            s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
-        } else {
-            s.filter = f;
-            s.margin = 0;
-            delete this.attrs.blur;
-        }
-    };
-
-    R._engine.path = function (pathString, vml) {
-        var el = createNode("shape");
-        el.style.cssText = cssDot;
-        el.coordsize = zoom + S + zoom;
-        el.coordorigin = vml.coordorigin;
-        var p = new Element(el, vml),
-            attr = {fill: "none", stroke: "#000"};
-        pathString && (attr.path = pathString);
-        p.type = "path";
-        p.path = [];
-        p.Path = E;
-        setFillAndStroke(p, attr);
-        vml.canvas.appendChild(el);
-        var skew = createNode("skew");
-        skew.on = true;
-        el.appendChild(skew);
-        p.skew = skew;
-        p.transform(E);
-        return p;
-    };
-    R._engine.rect = function (vml, x, y, w, h, r) {
-        var path = R._rectPath(x, y, w, h, r),
-            res = vml.path(path),
-            a = res.attrs;
-        res.X = a.x = x;
-        res.Y = a.y = y;
-        res.W = a.width = w;
-        res.H = a.height = h;
-        a.r = r;
-        a.path = path;
-        res.type = "rect";
-        return res;
-    };
-    R._engine.ellipse = function (vml, x, y, rx, ry) {
-        var res = vml.path(),
-            a = res.attrs;
-        res.X = x - rx;
-        res.Y = y - ry;
-        res.W = rx * 2;
-        res.H = ry * 2;
-        res.type = "ellipse";
-        setFillAndStroke(res, {
-            cx: x,
-            cy: y,
-            rx: rx,
-            ry: ry
-        });
-        return res;
-    };
-    R._engine.circle = function (vml, x, y, r) {
-        var res = vml.path(),
-            a = res.attrs;
-        res.X = x - r;
-        res.Y = y - r;
-        res.W = res.H = r * 2;
-        res.type = "circle";
-        setFillAndStroke(res, {
-            cx: x,
-            cy: y,
-            r: r
-        });
-        return res;
-    };
-    R._engine.image = function (vml, src, x, y, w, h) {
-        var path = R._rectPath(x, y, w, h),
-            res = vml.path(path).attr({stroke: "none"}),
-            a = res.attrs,
-            node = res.node,
-            fill = node.getElementsByTagName(fillString)[0];
-        a.src = src;
-        res.X = a.x = x;
-        res.Y = a.y = y;
-        res.W = a.width = w;
-        res.H = a.height = h;
-        a.path = path;
-        res.type = "image";
-        fill.parentNode == node && node.removeChild(fill);
-        fill.rotate = true;
-        fill.src = src;
-        fill.type = "tile";
-        res._.fillpos = [x, y];
-        res._.fillsize = [w, h];
-        node.appendChild(fill);
-        setCoords(res, 1, 1, 0, 0, 0);
-        return res;
-    };
-    R._engine.text = function (vml, x, y, text) {
-        var el = createNode("shape"),
-            path = createNode("path"),
-            o = createNode("textpath");
-        x = x || 0;
-        y = y || 0;
-        text = text || "";
-        path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
-        path.textpathok = true;
-        o.string = Str(text);
-        o.on = true;
-        el.style.cssText = cssDot;
-        el.coordsize = zoom + S + zoom;
-        el.coordorigin = "0 0";
-        var p = new Element(el, vml),
-            attr = {
-                fill: "#000",
-                stroke: "none",
-                font: R._availableAttrs.font,
-                text: text
-            };
-        p.shape = el;
-        p.path = path;
-        p.textpath = o;
-        p.type = "text";
-        p.attrs.text = Str(text);
-        p.attrs.x = x;
-        p.attrs.y = y;
-        p.attrs.w = 1;
-        p.attrs.h = 1;
-        setFillAndStroke(p, attr);
-        el.appendChild(o);
-        el.appendChild(path);
-        vml.canvas.appendChild(el);
-        var skew = createNode("skew");
-        skew.on = true;
-        el.appendChild(skew);
-        p.skew = skew;
-        p.transform(E);
-        return p;
-    };
-    R._engine.setSize = function (width, height) {
-        var cs = this.canvas.style;
-        this.width = width;
-        this.height = height;
-        width == +width && (width += "px");
-        height == +height && (height += "px");
-        cs.width = width;
-        cs.height = height;
-        cs.clip = "rect(0 " + width + " " + height + " 0)";
-        if (this._viewBox) {
-            R._engine.setViewBox.apply(this, this._viewBox);
-        }
-        return this;
-    };
-    R._engine.setViewBox = function (x, y, w, h, fit) {
-        R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
-        var width = this.width,
-            height = this.height,
-            size = 1 / mmax(w / width, h / height),
-            H, W;
-        if (fit) {
-            H = height / h;
-            W = width / w;
-            if (w * H < width) {
-                x -= (width - w * H) / 2 / H;
-            }
-            if (h * W < height) {
-                y -= (height - h * W) / 2 / W;
-            }
-        }
-        this._viewBox = [x, y, w, h, !!fit];
-        this._viewBoxShift = {
-            dx: -x,
-            dy: -y,
-            scale: size
-        };
-        this.forEach(function (el) {
-            el.transform("...");
-        });
-        return this;
-    };
-    var createNode;
-    R._engine.initWin = function (win) {
-            var doc = win.document;
-            doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
-            try {
-                !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
-                createNode = function (tagName) {
-                    return doc.createElement('<rvml:' + tagName + ' class="rvml">');
-                };
-            } catch (e) {
-                createNode = function (tagName) {
-                    return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
-                };
-            }
-        };
-    R._engine.initWin(R._g.win);
-    R._engine.create = function () {
-        var con = R._getContainer.apply(0, arguments),
-            container = con.container,
-            height = con.height,
-            s,
-            width = con.width,
-            x = con.x,
-            y = con.y;
-        if (!container) {
-            throw new Error("VML container not found.");
-        }
-        var res = new R._Paper,
-            c = res.canvas = R._g.doc.createElement("div"),
-            cs = c.style;
-        x = x || 0;
-        y = y || 0;
-        width = width || 512;
-        height = height || 342;
-        res.width = width;
-        res.height = height;
-        width == +width && (width += "px");
-        height == +height && (height += "px");
-        res.coordsize = zoom * 1e3 + S + zoom * 1e3;
-        res.coordorigin = "0 0";
-        res.span = R._g.doc.createElement("span");
-        res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
-        c.appendChild(res.span);
-        cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
-        if (container == 1) {
-            R._g.doc.body.appendChild(c);
-            cs.left = x + "px";
-            cs.top = y + "px";
-            cs.position = "absolute";
-        } else {
-            if (container.firstChild) {
-                container.insertBefore(c, container.firstChild);
-            } else {
-                container.appendChild(c);
-            }
-        }
-        res.renderfix = function () {};
-        return res;
-    };
-    R.prototype.clear = function () {
-        R.eve("raphael.clear", this);
-        this.canvas.innerHTML = E;
-        this.span = R._g.doc.createElement("span");
-        this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
-        this.canvas.appendChild(this.span);
-        this.bottom = this.top = null;
-    };
-    R.prototype.remove = function () {
-        R.eve("raphael.remove", this);
-        this.canvas.parentNode.removeChild(this.canvas);
-        for (var i in this) {
-            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
-        }
-        return true;
-    };
-
-    var setproto = R.st;
-    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
-        setproto[method] = (function (methodname) {
-            return function () {
-                var arg = arguments;
-                return this.forEach(function (el) {
-                    el[methodname].apply(el, arg);
-                });
-            };
-        })(method);
-    }
-}(window.Raphael);
diff --git a/question_types/quiz_ddlines/quiz_ddlines.classes.inc b/question_types/quiz_ddlines/quiz_ddlines.classes.inc
deleted file mode 100644
index 344ffd9..0000000
--- a/question_types/quiz_ddlines/quiz_ddlines.classes.inc
+++ /dev/null
@@ -1,562 +0,0 @@
-<?php
-
-/**
- * The main classes for the drag and drop with lines question type.
- *
- * These inherit or implement code found in quiz_question.classes.inc.
- *
- * Sponsored by: Senter for IKT i utdanningen
- * Code: paalj
- *
- * Based on:
- * Other question types in the quiz framework.
- *
- * @file
- * Question type, enabling the creation of dragging a choice to the correct location in an image
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class DDLinesQuestion extends QuizQuestion {
-   /**
-   * Get the form used to create a new question.
-   *
-   * @param
-   *  FAPI form state
-   * @return
-   *  Must return a FAPI array.
-   */
-    
-  public function getCreationForm(array &$form_state = NULL) {
-    
-    $elements = '';
-    if(isset($this->node->translation_source)) {
-      $elements = $this->node->translation_source->ddlines_elements;
-    }
-    elseif(isset($this->node->ddlines_elements)) {
-      $elements = $this->node->ddlines_elements;
-    }
-    
-    $form['ddlines_elements'] = array(
-      '#type' => 'hidden',
-      '#default_value' => $elements,
-    );
-    
-    $default_settings = $this->getDefaultAltSettings();
-    
-    $form['settings'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Settings'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#weight' => -3,
-    );
-    $form['settings']['feedback_enabled'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enable feedback'),
-      '#description' => t('When taking the test, and this option is enabled, a wrong placement of an alternative, will make it jump back. Also, this makes it possible to add comments to both correct and wrong answers.'),
-      '#default_value' => isset($this->node->translation_source) ? $this->node->translation_source->feedback_enabled : $default_settings['feedback']['enabled'],
-      '#parents' => array('feedback_enabled'),
-    );
-    $form['settings']['hotspot_radius'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Hotspot radius'),
-      '#description' => t('The radius of the hotspot in pixels'),
-      '#default_value' => isset($this->node->translation_source) ? $this->node->translation_source->hotspot_radius : $default_settings['hotspot']['radius'],
-      '#parents' => array('hotspot_radius'),
-    );    
-    
-    $form['settings']['execution_mode'] = array(
-      '#type' => 'radios',
-      '#title' => t('Execution mode'),
-      '#description' => t('The mode for taking the test.'),
-      '#default_value' => isset($this->node->translation_source) ? $this->node->translation_source->execution_mode : $default_settings['execution_mode'],
-      '#options' => array (
-        0 => t('With lines'),
-        1 => t('Drag label'),
-      ),
-      '#parents' => array('execution_mode'),
-    );    
-    
-    drupal_add_library('system','ui.resizable');
-    
-    $default_settings['mode'] = 'edit';
-    $default_settings['editmode'] = isset($this->node->nid) ? 'update' : 'add';
-    $form['#attached']['js'][] = array(
-  		'data' => array(
-    		'quiz_ddlines' => $default_settings
-        ),
-  		'type' => 'setting'
-    );
-    
-    _quiz_ddlines_add_js_and_css();
-    
-    return $form;
-  }
-  
-  /**
-   * This makes max_score beeing updated for all occurrences of
-   * this question in quizzes.
-   */
-  protected function autoUpdateMaxScore() {
-    return true;
-  }
-  
-  /**
-   * Helper function provding the default settings for the creation form.
-   *
-   * @return
-   *  Array with the default settings
-   */
-  private function getDefaultAltSettings() {
-    $settings = array();
-    
-    // If the node exists, use saved value
-    if (isset($this->node->nid)) {      
-      $settings['feedback']['enabled'] = $this->node->feedback_enabled;
-      $settings['hotspot']['radius'] = $this->node->hotspot_radius;
-      $settings['execution_mode'] = $this->node->execution_mode;
-    }      
-    else {      
-      $settings['feedback']['enabled'] = 0;
-      $settings['hotspot']['radius'] = variable_get('quiz_ddlines_hotspot_radius', Defaults::HOTSPOT_RADIUS);
-      $settings['execution_mode'] = 0;
-    }
-
-    // Pick these from settings:
-    $settings['feedback']['correct'] = variable_get('quiz_ddlines_feedback_correct', t('Correct'));
-    $settings['feedback']['wrong'] = variable_get('quiz_ddlines_feedback_wrong', t('Wrong'));
-    $settings['canvas']['width'] = variable_get('quiz_ddlines_canvas_width', Defaults::CANVAS_WIDTH);
-    $settings['canvas']['height'] = variable_get('quiz_ddlines_canvas_height', Defaults::CANVAS_HEIGHT); 
-    $settings['pointer']['radius'] = variable_get('quiz_ddlines_pointer_radius', Defaults::POINTER_RADIUS);
-    
-    return $settings;
-  }
-  
-  /**
-   * Generates the question form.
-   *
-   * This is called whenever a question is rendered, either
-   * to an administrator or to a quiz taker.
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    
-    $form = parent::getAnsweringForm($form_state, $result_id);
-    
-    $form['helptext'] = array(
-      '#markup' => t('Answer this question by dragging each rectangular label to the correct circular hotspot.'),
-      '#weight' => 0,
-    );
-    
-    // Form element containing the correct answers    
-    $form['ddlines_elements'] = array(
-      '#type' => 'hidden',
-      '#default_value' => isset($this->node->ddlines_elements) ? $this->node->ddlines_elements : '',
-    );
-    
-    // Form element containing the user answers
-    // The quiz module requires this element to be named "tries":
-    $form['tries'] = array (
-      '#type' => 'hidden',
-      '#default_value' => '',
-    );
-    
-    $image_uri = $this->node->field_image['und'][0]['uri'];
-    $image_url = image_style_url('large',$image_uri);
-    
-    $form['image'] = array (      
-      '#markup' => '<div class="image-preview">'.theme('image', array('path' => $image_url)).'</div>',
-      /*'#weight' => 0,*/
-    );
-    
-    $default_settings = $this->getDefaultAltSettings();
-    $default_settings['mode'] = 'take';
-    $form['#attached']['js'][] = array(
-  		'data' => array(
-    		'quiz_ddlines' => $default_settings
-        ),
-  		'type' => 'setting'
-    );
-    
-    _quiz_ddlines_add_js_and_css();
-    
-    return $form;
-  }
-
-  /**
-   * Get the maximum possible score for this question.
-   */
-  public function getMaximumScore() {
-    // 1 point per correct hotspot location    
-    $ddlines_elements = json_decode($this->node->ddlines_elements);
-    
-    $max_score = isset($ddlines_elements->elements) ? sizeof($ddlines_elements->elements) : 0;
-    
-    return $max_score;
-  }
-
-  /**
-   * Save question type specific node properties
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    if ($is_new || $this->node->revision == 1) {
-      $id = db_insert('quiz_ddlines_node')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'feedback_enabled' => $this->node->feedback_enabled,
-          'hotspot_radius' => $this->node->hotspot_radius,
-          'ddlines_elements' => $this->node->ddlines_elements,
-          'execution_mode' => $this->node->execution_mode,
-        ))
-        ->execute();
-    }
-    else {
-      db_update('quiz_ddlines_node')
-        ->fields(array(
-          'ddlines_elements' => $this->node->ddlines_elements,
-          'hotspot_radius' => $this->node->hotspot_radius,
-          'feedback_enabled' => $this->node->feedback_enabled,
-          'execution_mode' => $this->node->execution_mode,
-        ))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-  }
-  
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties) && !empty($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-
-    $res_a = db_query('SELECT feedback_enabled, hotspot_radius, ddlines_elements, execution_mode FROM {quiz_ddlines_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
-
-    if (is_array($res_a)) {
-      $props = array_merge($props, $res_a);
-    }
-    $this->nodeProperties = $props;
-    return $props;
-  }
-  
-  /**
-   * Provides validation for question before it is created.
-   *
-   * When a new question is created and initially submited, this is
-   * called to validate that the settings are acceptible.
-   *
-   * @param $form
-   *  The processed form.
-   */
-  public function validateNode(array &$form) {
-    // Nothing todo here
-  }
-  
-/**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete()
-   */
-  public function delete($only_this_version = FALSE) {
-    $delete_node = db_delete('quiz_ddlines_node')->condition('nid', $this->node->nid);    
-    $delete_results = db_delete('quiz_ddlines_user_answers')->condition('question_nid', $this->node->nid);
-
-    if ($only_this_version) {
-      $delete_node->condition('vid', $this->node->vid);
-      $delete_results->condition('question_vid', $this->node->vid);
-    }
-
-    // Delete from table quiz_ddlines_user_answer_multi
-    $user_answer_ids = array();
-    if ($only_this_version) {
-      $query = db_query('SELECT id FROM {quiz_ddlines_user_answers} WHERE question_nid = :nid AND question_vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-    }
-    else {
-      $query = db_query('SELECT id FROM {quiz_ddlines_user_answers} WHERE question_nid = :nid', array(':nid' => $this->node->nid));
-    }
-    while ($user_answer = $query->fetch()) {    
-      $user_answer_ids[] = $user_answer->id;
-    }
-
-    if (count($user_answer_ids)) {
-      db_delete('quiz_ddlines_user_answer_multi')
-        ->condition('user_answer_id', $user_answer_ids, 'IN')
-        ->execute();
-    }
-    
-    $delete_node->execute();    
-    $delete_results->execute();
-    parent::delete($only_this_version);
-  }
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class DDLinesResponse extends QuizQuestionResponse {
-  
-  // Contains a assoc array with label-ID as key 
-  // and hotspot-ID as value:
-  protected $user_answers = array();
-  
-  /**
-   * Constructor
-   */
-  public function __construct($result_id, stdClass $question_node, $tries = NULL) {
-    parent::__construct($result_id, $question_node, $tries);
-    
-    // Is answers set in form?
-    if (isset($tries)) {
-      // Tries contains the answer decoded as JSON:
-      // {"label_id":x,"hotspot_id":y},{...}
-      $decoded = json_decode($tries);
-      if(is_array($decoded)) {
-        foreach ($decoded as $answer) {
-          $this->user_answers[$answer->label_id] = $answer->hotspot_id;  
-        }
-      }
-    }
-    // Load from database
-    else {
-      $res = db_query('SELECT label_id, hotspot_id FROM {quiz_ddlines_user_answers} ua
-              LEFT OUTER JOIN {quiz_ddlines_user_answer_multi} uam ON(uam.user_answer_id = ua.id)
-              WHERE ua.result_id = :result_id AND ua.question_nid = :question_nid AND ua.question_vid = :question_vid', array(':result_id' => $result_id, ':question_nid' => $this->question->nid, ':question_vid' => $this->question->vid));
-      while ($row = $res->fetch()) {
-        $this->user_answers[$row->label_id] = $row->hotspot_id;
-      }
-    }
-  }
-  
-  /**
-   * Save the current response.
-   */
-  public function save() {
-    $user_answer_id = db_insert('quiz_ddlines_user_answers')
-      ->fields(array(
-        'question_nid' => $this->question->nid,
-        'question_vid' => $this->question->vid,
-        'result_id' => $this->result_id,
-      ))
-      ->execute();
-      
-    // Each alternative is inserted as a separate row
-    $query = db_insert('quiz_ddlines_user_answer_multi')
-      ->fields(array('user_answer_id', 'label_id', 'hotspot_id'));
-    foreach ($this->user_answers as $key => $value) {
-      $query->values(array($user_answer_id, $key, $value));
-    }
-    $query->execute();
-   
-  }
-
-  /**
-   * Delete the response.
-   */
-  public function delete() {
-
-    $user_answer_ids = array();
-    $query = db_query('SELECT id FROM {quiz_ddlines_user_answers} WHERE question_nid = :nid AND question_vid = :vid AND result_id = :result_id', array(':nid' => $this->question->nid, ':vid' => $this->question->vid, ':result_id' => $this->result_id));
-    while ($answer = $query->fetch()) {
-      $user_answer_ids[] = $answer->id;
-    }
-
-    if (!empty($user_answer_ids)) {
-      db_delete('quiz_ddlines_user_answer_multi')
-        ->condition('user_answer_id', $user_answer_ids, 'IN')
-        ->execute();
-    }
-
-    db_delete('quiz_ddlines_user_answers')
-      ->condition('result_id', $this->result_id)
-      ->condition('question_nid', $this->question->nid)
-      ->condition('question_vid', $this->question->vid)
-      ->execute();
-  }
-
-  /**
-   * Calculate the score for the response.
-   */
-  public function score() {    
-    $results = $this->getDragDropResults();
-    
-    // Count number of correct answers:
-    $correct_count = 0;
-    
-    foreach($results as $result) {
-      $correct_count += ($result == AnswerStatus::CORRECT) ? 1 : 0;
-    } 
-    
-    return $correct_count;
-  }
-
-  /**
-   * Get the user's response.
-   */
-  public function getResponse() {
-    return $this->user_answers;
-  }
-  
-  public function getReportFormResponse() {
-    // Have to do node_load, since quiz does not do this. Need the field_image... 
-    $img_field = field_get_items('node', node_load($this->question->nid), 'field_image');
-    $img_rendered = theme('image', array('path' => image_style_url('large',$img_field[0]['uri'])));
-    
-    $image_path = base_path() . drupal_get_path('module', 'quiz_ddlines').'/theme/images/';
-    
-    $html = '<h3>'.t('Your answers').'</h3>';
-    $html .= '<div class="icon-descriptions"><div><img src="'.$image_path.'icon_ok.gif">'.t('Means alternative is placed on the correct spot').'</div>';
-    $html .= '<div><img src="'.$image_path.'icon_wrong.gif">'.t('Means alternative is placed on the wrong spot, or not placed at all'). '</div></div>';
-    $html .= '<div class="quiz-ddlines-user-answers" id="'.$this->question->nid.'">';
-    $html .= $img_rendered;
-    $html .= '</div>';
-    $html .= '<h3>'.t('Correct answers').'</h3>';
-    $html .= '<div class="quiz-ddlines-correct-answers" id="'.$this->question->nid.'">';
-    $html .= $img_rendered;
-    $html .= '</div>';
-    
-    // No form to put things in, are therefore using the js settings instead
-    $settings = array();
-    $correct_id = "correct-{$this->question->nid}";
-    $settings[$correct_id] = json_decode($this->question->ddlines_elements);
-    $elements = $settings[$correct_id]->elements;
-    
-    // Convert the user's answers to the same format as the correct answers
-    $answers = clone $settings[$correct_id]; 
-    // Keep everything except the elements:
-    $answers->elements = array();    
-    
-    $elements_answered = array();
-    
-    foreach ($this->user_answers as $label_id => $hotspot_id ) {
-      
-      if(!isset($hotspot_id)) {
-        continue;
-      }
-      
-      // Find correct answer:
-      $element = array(
-        'feedback_wrong' => '',
-        'feedback_correct' => '',
-        'color' => $this->getElementColor($elements, $label_id)
-      );
-      
-      $label = $this->getLabel($elements, $label_id);
-      $hotspot = $this->getHotspot($elements, $hotspot_id);
-      
-      if(isset($hotspot)) {
-        $elements_answered[] = $hotspot->id;
-        $element['hotspot'] = $hotspot;
-      }
-      
-      if(isset($label)) {
-        $elements_answered[] = $label->id;
-        $element['label'] = $label;
-      }
-      
-      $element['correct'] = $this->isAnswerCorrect($elements, $label_id, $hotspot_id);
-      $answers->elements[] = $element;
-    }
-    
-    // Need to add the alternatives not answered by the user. 
-    // Create dummy elements for these:
-    foreach ($elements as $el) {
-      if(!in_array($el->label->id, $elements_answered)) {
-        $element = array(
-        	'feedback_wrong' => '',
-        	'feedback_correct' => '',
-        	'color' => $el->color,
-            'label' => $el->label,
-        );
-        $answers->elements[] = $element;
-      }
-     
-      if(!in_array($el->hotspot->id, $elements_answered)) {
-        $element = array(
-        	'feedback_wrong' => '',
-        	'feedback_correct' => '',
-        	'color' => $el->color,
-            'hotspot' => $el->hotspot,
-        );
-        $answers->elements[] = $element;
-      }
-    }
-    
-    $settings["answers-{$this->question->nid}"] = $answers;
-    $settings['mode'] = 'result';
-    $settings['execution_mode'] = $this->question->execution_mode;
-    $settings['hotspot']['radius'] = $this->question->hotspot_radius;
-    
-    // Image path:
-    $settings['quiz_imagepath'] = base_path() . drupal_get_path('module', 'quiz_ddlines').'/theme/images/';
-    
-    drupal_add_js(array('quiz_ddlines' => $settings), 'setting');
-    
-    _quiz_ddlines_add_js_and_css();
-    
-    return array('#markup' => $html);
-  }
-  
-  private function getElementColor($list, $id) {
-    foreach($list as $element) {
-      if ($element->label->id == $id) {
-        return $element->color;
-      }
-    }
-  }
-  
-  private function getHotspot($list, $id) {
-    foreach($list as $element) {
-      if ($element->hotspot->id == $id) {
-        return $element->hotspot;
-      }
-    }
-  }
-  
-  private function getLabel($list, $id) {
-    foreach($list as $element) {
-      if ($element->label->id == $id) {
-        return $element->label;
-      }
-    }
-  }
-  
-  private function isAnswerCorrect($list, $label_id, $hotspot_id) {
-    foreach ($list as $element) {
-      if($element->label->id == $label_id) {
-        return ($element->hotspot->id == $hotspot_id);
-      }
-    }
-    
-    return false;
-  }
-  
-  /**
-   * 
-   * Get a list of the labels, tagged correct, false, or no answer
-   */
-  private function getDragDropResults() {
-    $results = array();
-    
-    // Iterate through the correct answers, and check 
-    // the users answer:    
-    foreach(json_decode($this->question->ddlines_elements)->elements as $element) {
-      $source_id = $element->label->id;
-      
-      if(isset($this->user_answers[$source_id])) {
-        $results[$element->label->id] = ($this->user_answers[$source_id] == $element->hotspot->id) ? AnswerStatus::CORRECT : AnswerStatus::WRONG;
-      }
-      else {
-        $results[$element->label->id] = AnswerStatus::NO_ANSWER;
-      }
-    }
-    
-    return $results;
-  }
-}
diff --git a/question_types/quiz_ddlines/quiz_ddlines.info b/question_types/quiz_ddlines/quiz_ddlines.info
deleted file mode 100644
index 58d1780..0000000
--- a/question_types/quiz_ddlines/quiz_ddlines.info
+++ /dev/null
@@ -1,8 +0,0 @@
-name = Drag and drop with lines for Quiz
-package = Quiz Question
-description = Drag and drop with lines question type for quiz.
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-dependencies[] = image
-files[] = quiz_ddlines.classes.inc
\ No newline at end of file
diff --git a/question_types/quiz_ddlines/quiz_ddlines.install b/question_types/quiz_ddlines/quiz_ddlines.install
deleted file mode 100644
index 65b1193..0000000
--- a/question_types/quiz_ddlines/quiz_ddlines.install
+++ /dev/null
@@ -1,218 +0,0 @@
-<?php
-
-/**
- * Sponsored by: Senter for IKT i utdanningen
- * Code: paalj
- *
- * @file
- * Drag and drop Install (a quiz question type)
- */
-
-
-/**
- * Implements hook_install().
- */
-function quiz_ddlines_install() {
-  // Add body field to multichoice node
-  quiz_question_add_body_field('quiz_ddlines');
-
-  // At this point quiz core has created the ddlines node type.
-  // Now it's time to add the image field to the node:
-  if (!field_info_field('field_image')) {
-     $field = array(
-        'field_name' => 'field_image',
-        'type' => 'image',
-    );
-    field_create_field($field);
-  }
-
-  $instance = field_info_instance('node', 'field_image', 'quiz_ddlines');
-  if (empty($instance)) {
-
-    $help_text = t("<p>Start by uploading a background image. The image is movable within the canvas.<br/>
-    				The next step is to add the alternatives, by clicking in the canvas.
-    				Each alternative consists of a circular hotspot, a label, and a connecting line. You need
-    				to double click the rectangular label to add the text, and move the hotspot to the correct
-    				position. When selecting a label, a popup-window is displayed, which gives you the following
-    				alternatives:
-    				<ul>
-    					<li>Set the alternative's feedback (only possible if feedback is enabled)</li>
-    					<li>Set the color of each alternative</li>
-    					<li>Delete the alternative</li>
-    				</ul>
-    				</p>");
-
-    $instance = array(
-      'field_name' => 'field_image',
-      'entity_type' => 'node',
-      'bundle' => 'quiz_ddlines',
-      'label' => 'Background image',
-      'description' => $help_text,
-      'required' => TRUE,
-      'settings' => array(
-        'no_ui' => TRUE,
-      ),
-      'widget' => array (
-        'settings' => array (
-          'preview_image_style' => 'quiz_ddlines',
-      	  'no_ui' => TRUE,
-        ),
-      ),
-    );
-
-    field_create_instance($instance);
-  }
-}
-
-/**
- * Implements hook_schema().
- */
-function quiz_ddlines_schema() {
-  $schema['quiz_ddlines_node'] = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'feedback_enabled' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-	  'hotspot_radius' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 10,
-      ),
-      'ddlines_elements' => array(
-        'type' => 'text',
-      ),
-  	  'execution_mode' => array(
-        'type' => 'int',
-        'description' => "Execution mode. 0->with lines, 1->without lines",
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'foreign keys' => array(
-      'node' => array(
-        'table' => 'node',
-        'columns' => array('nid' => 'nid'),
-      ),
-    ),
-    'primary key' => array(
-      'vid', 'nid'
-    ),
-  );
-
-  // Place to store user answers
-  $schema['quiz_ddlines_user_answers'] = array(
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'id'
-    ),
-    'indexes' => array(
-      'answer_id' => array('result_id', 'question_nid', 'question_vid'),
-    ),
-  );
-
-  $schema['quiz_ddlines_user_answer_multi'] = array(
-    'fields' => array(
-      'user_answer_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'hotspot_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'label_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'foreign keys' => array(
-      'answer_id' => array(
-        'table' => 'quiz_ddlines_user_answers',
-        'columns' => array('user_answer_id' => 'id'),
-      ),
-    ),
-    'indexes' => array(
-      'answer_id' => array('user_answer_id'),
-    ),
-  );
-
-  return $schema;
-}
-
-/**
- * Implements hook_uninstall().
- */
-function quiz_ddlines_uninstall() {
-  // Delete instance:
-
-  $instance = field_info_instance('node', 'field_image', 'quiz_ddlines');
-  field_delete_instance($instance, FALSE);
-
-  $var = array(
-    'quiz_ddlines_canvas_width',
-    'quiz_ddlines_canvas_height',
-    'quiz_ddlines_hotspot_radius',
-    'quiz_ddlines_pointer_radius',
-    'quiz_ddlines_feedback_correct',
-    'quiz_ddlines_feedback_wrong',
-  );
-  foreach ($var as $v) {
-    variable_del($v);
-  }
-  drupal_set_message(t("The Quiz drag and drop module has been uninstalled. Nodes of this type may still exist, but they will not function properly."));
-}
-
-/**
- * Adding Execution mode column. Makes it possible to
- * run the test in two different modes.
- */
-function quiz_ddlines_update_7401() {
-  // Add field for execution mode
-  $spec = array(
-    'type' => 'int',
-    'description' => "Execution mode. 0->with lines, 1->without lines",
-    'not null' => TRUE,
-    'unsigned' => TRUE,
-    'default' => 0,
-  );
-  db_add_field('quiz_ddlines_node', 'execution_mode', $spec);
-}
diff --git a/question_types/quiz_ddlines/quiz_ddlines.module b/question_types/quiz_ddlines/quiz_ddlines.module
deleted file mode 100644
index c43f344..0000000
--- a/question_types/quiz_ddlines/quiz_ddlines.module
+++ /dev/null
@@ -1,183 +0,0 @@
-<?php
-
-/**
- * The main file for Quiz drag and drop with lines.
- *
- * Sponsored by: Senter for IKT i utdanningen
- * Code: paalj
- *
- * @file
- * Drag and drop with lines question type for the Quiz module.
- *
- */
-
-/**
- * Default values
- */
-class Defaults {
-  const CANVAS_WIDTH = 700;
-  const CANVAS_HEIGHT = 500;
-  const HOTSPOT_RADIUS = 10;
-  const POINTER_RADIUS = 5;
-}
-
-/**
- * Status for each alternative as "enum"
- */
-class AnswerStatus {
-  const NO_ANSWER = 0;
-  const WRONG = 1;
-  const CORRECT = 2;
-
-  private static $titles;
-  private static $css_class;
-
-  public static function init() {
-    self::$titles = array(
-      t('You did not move this alternative to any hotspot'),
-      t('Wrong answer'),
-      t('Correct answer'));
-
-    self::$css_class = array('no-answer','wrong','correct');
-  }
-
-  public static function getTitle($status) {
-    return self::$titles[$status];
-  }
-
-  public static function getCssClass($status) {
-    return self::$css_class[$status];
-  }
-}
-AnswerStatus::init();
-
-/**
- * Implements hook_help().
- */
-function quiz_ddlines_help($path, $args) {
-  if ($path == 'admin/help#quiz_ddlines') {
-    return t("Help text TBD");
-  }
-}
-/**
- * Implements hook_quiz_question_info().
- */
-function quiz_ddlines_quiz_question_info() {
-  return array(
-    'quiz_ddlines' => array(
-      'name' => t('Drag and drop question with lines'),
-      'description' => t('This provides drag and drop question types with lines for the Quiz module.'),
-      'question provider' => 'DDLinesQuestion',
-      'response provider' => 'DDLinesResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_image_default_styles
- */
-function quiz_ddlines_image_default_styles() {
-  $styles = array();
-
-  $styles['quiz_ddlines'] = array(
-    'effects' => array(
-      array(
-        'name' => 'image_scale',
-        'data' => array(
-          'width' => 700,
-          'height' => null,
-          'upscale' => 0,
-        ),
-        'weight' => 0,
-      ),
-    ),
-  );
-
-  return $styles;
-}
-
-/**
- * Implements hook_theme().
- */
-function quiz_ddlines_theme() {
-  return array(
-    'quiz_ddlines_response' => array(
-      'variables' => array(
-        'result' => array(),
-      ),
-      'path' => drupal_get_path('module', 'quiz_ddlines') . '/theme',
-      'file' => 'quiz_ddlines.theme.inc',
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function quiz_ddlines_quiz_question_config() {
-
-  $form['quiz_ddlines_canvas_width'] = array(
-    '#type' => 'textfield',
-    '#title' => t('The default width of the canvas'),
-    '#default_value' => variable_get('quiz_ddlines_canvas_width', Defaults::CANVAS_WIDTH),
-  );
-  $form['quiz_ddlines_canvas_height'] = array(
-    '#type' => 'textfield',
-    '#title' => t('The default height of the canvas'),
-    '#default_value' => variable_get('quiz_ddlines_canvas_height', Defaults::CANVAS_HEIGHT),
-  );
-  $form['quiz_ddlines_hotspot_radius'] = array(
-    '#type' => 'textfield',
-    '#title' => t('The radius of the hotspot'),
-    '#default_value' => variable_get('quiz_ddlines_hotspot_radius', Defaults::HOTSPOT_RADIUS),
-  );
-  $form['quiz_ddlines_pointer_radius'] = array(
-    '#type' => 'textfield',
-    '#title' => t('The radius of the pointer'),
-    '#default_value' => variable_get('quiz_ddlines_pointer_radius', Defaults::POINTER_RADIUS),
-  );
-  $form['quiz_ddlines_pointer_radius'] = array(
-    '#type' => 'textfield',
-    '#title' => t('The radius of the pointer'),
-    '#default_value' => variable_get('quiz_ddlines_pointer_radius', Defaults::POINTER_RADIUS),
-  );
-  $form['quiz_ddlines_feedback_correct'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default feedback for correct answers'),
-    '#default_value' => variable_get('quiz_ddlines_feedback_correct', ""),
-  );
-   $form['quiz_ddlines_feedback_wrong'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default feedback for wrong answers'),
-    '#default_value' => variable_get('quiz_ddlines_feedback_wrong', ""),
-  );
-
-  $form['#validate'][] = 'quiz_ddlines_config_validate';
-  return $form;
-}
-
-/**
- * Validate the config form values
- */
-function quiz_ddlines_config_validate($form, $form_state) {
-
-  $int_fields = array('quiz_ddlines_canvas_width', 'quiz_ddlines_canvas_height', 'quiz_ddlines_hotspot_radius', 'quiz_ddlines_pointer_radius');
-  foreach ($int_fields as $field) {
-    if (!_quiz_is_int($form_state['values'][$field])) {
-      form_set_error($field, t('The value must be a positive number'));
-    }
-  }
-
-  // Feedback fields may contain nothing or everything; no need to validate it
-}
-
-function _quiz_ddlines_add_js_and_css() {
-  $module_path = drupal_get_path('module', 'quiz_ddlines');
-
-  drupal_add_js($module_path.'/js/raphael.js');
-  drupal_add_js($module_path.'/js/colorwheel.js');
-  drupal_add_js($module_path.'/js/quiz_ddlines.js');
-  drupal_add_css($module_path.'/theme/quiz_ddlines.css');
-}
-
diff --git a/question_types/quiz_ddlines/theme/images/close.png b/question_types/quiz_ddlines/theme/images/close.png
deleted file mode 100644
index 7cb985935f1ec74ff5923e7e1a47991ba4a46135..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1119
zcmV-l1fctgP)<h;3K|Lk000e1NJLTq000&M000&U1^@s6#I$TX00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#@kvBMRCwBA
z{Qv(y11$hR0I`4wMn*=Q3aqW89DtMq%mE-8h!31NvGM>eJs>pz0mKL<&<wV*i4g!&
z1^@s5%V%U{5@2OzVPIop18eyC^B2RfUq2cC{{8a-sAeKiY{Kc&Yd@eH3=lvV1_KQ>
z08;arn3xPqO^q4+{T&%JHRTw%xY*%pu3vx5uxr<GhRvI|GyM7U`vTC6#4~5sT|hP%
zAb?N|h8ntGNJvPaurP^1U0oJ$I6i*-ied8Pr9g|Wf?Vu)_U!r#FoOXC2yQUc`}+k2
z1q})c5*Z{Vg@NAv$H2_Ygw;@(-1qN48M?dYG2FU!17xV<xpNynfXoC4AY@$yAnykS
zc`<Nsuz_8ppdij5D$39B{{3eZ{N%|i25oHxV7Rj|{P^*UAtuJ3ft#Dx0BB$V%rJle
zVnT$=zkC%HWuQR<44*!IWw5f+1}f%c5EkZVke8Q$8~X0uCk7W66R;j7B`F4Wb~Z2@
z6d(Wo{mX}$2M|Ckup|YFePv~NutpGg@!~y$vN9)_EhQxkObNdkE?l_H5D;Js_u{i>
zZy7diI>7Ms=?kExAAmu_ETE_714^C;00M{ws>T79q~5*z2o~77^(cd@s~Ipw$b<O`
z3KC!p1CJiPWT>s3%JB8;SGX7(8#^$S`~e#b5I{_@SY%~oW%&I03((*XaJ+KmE}$##
zU_=}?LqUL(lam3KkpKdS1!gb^ynXut!;sUbuQ3=HsGx{Fc<=(lP!Ra{50oqaf!P27
z1Pcmqj(Yd*J&Hn0OJj!AR6h&@?CkV`WE{h!NsCa#zkT}xcO*an!GhudD4YKJ^^4*6
z@84i-W@f;UlM{^M{X>T?q8Of+7YEJ*F#O}k53pGvd4K={8w@H?KzaZB_wS&XWe^t^
zW5~}BL&;%HO*0v$PhZY(<j7?d!(Cl%8CY0Y7`}Y@3@$&eUE2vZ7$AU<GswgbAKn8a
zfCCsTKN%J++5r}P`t%J$Z|`CTVCG@q<>dj2Z)7-m@;X=!M4vfx8La2^>sMeiV1@w%
z5DQYlF##AH{;yuWHc(Sj0j7rQ;PASA`ytp+9v*JE3s$b&!SL|mbD&Qzg2k^~xeCnM
z-#}S<0!$4+0HG8@CMH3kY`I@rT2??yOPv9fw($W_rnz+KDlnDY1s6isukV5vLI44T
zQEVC;2Z9Wr&&kPUprfM$%(mD|xI1?qGF-fP0hsnbfecH8845EPAb_wI^oEB1;DR2M
z_u1Lm1%!k|fTf)TSi`et&lujkc@0bf-{A#4&`^wm9w30Q8jK8}c^_Og{rmS9rtttQ
l_Hk8B00G2EtI8B0zyRRb`ov(v=I8(b002ovPDHLkV1heR;aUIy

diff --git a/question_types/quiz_ddlines/theme/images/icon_ok.gif b/question_types/quiz_ddlines/theme/images/icon_ok.gif
deleted file mode 100644
index 35e949963e4b7a5f04fd1c8932a980901e3d4385..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 263
zcmV+i0r>t$Nk%w1VGsZi0K^{vcEd+Ya4xgmmXXkDk(iA6{P)e~xr@?RgUwTMzeLpS
z#Wq<Qev@rdf-IBfSBd0I?DyrU<#)p7sa<+Te$_{S%TJ8uP)uMvaf@7w&S0e4cYxVQ
zX}CU8XF~t~|NsC0A^8LW0018VEC2ui01yBW000Gq;3s}wX?kNyPDINv6RLWcVMtLP
zHL^$>hu{H$s5p4O4v<)^3=A2^0_EulhRp%QGjIf`R%Ru$RUj;1k~O1%3=|$g0$`b{
z1p`T8E1%cF0ssJ7eF+H<c2yc2f`*119DhC+2MLgu91m$D9+VFhpc|SV1v3{7sHjst
NDJLc>u(2Z{06R}5X@39!

diff --git a/question_types/quiz_ddlines/theme/images/icon_wrong.gif b/question_types/quiz_ddlines/theme/images/icon_wrong.gif
deleted file mode 100644
index c941c19028c479d4a27a7ee04d6e0e66b6bce599..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 258
zcmZ?wbhEHb6krfwI3myRx}@ZPMa9G9<ZoNH{6Bf}V|@IdsZ;+gTQ)g2_g_|4rMmj{
z(9maDS@+`NX7u!ASy;5{>sPtC-3SYNk)IzaCG{5wQd4hOTmQRpBi`QrZFzZjOw5(w
z;B+Ua|6ri_lZBCiL7qW}0SG{LGO*S^P_K*2tV;HB^Xpa+Tva9QCeXgnd6P%76fc9p
zOamsHcy<jHDaj@V)~7Fd+U7kvs@!3>;@YDln|Vuw<0nl$s8=rCxw48uw_56lMa6}$
zEFa1XibC1A+uFH>WUA~WrMhG$PUPgWRp}FBoy96PRn(YYfNQZp@O&*5b=4JX)+#bs
F0{{l~XioqD

diff --git a/question_types/quiz_ddlines/theme/images/icons.png b/question_types/quiz_ddlines/theme/images/icons.png
deleted file mode 100644
index efcc6fe283aeda80f8c7492b766cdee6fa43ad70..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3484
zcmV;N4P)|&P)<h;3K|Lk000e1NJLTq005u>001Be1^@s6m49>f00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmY3labT3lag+-G2N4000McNliru+yx&E1~p-WPP+gA4Jk=P
zK~#9!?VNjXRn-;7fA{9*l7u`UfEWuJrS*;Yz+!zAq1Dk=ic+;w1<}?zt+lp}587&#
zmX0lBp`9v33<hcyNu$_mZ3U|~GWCJ-P(%?!h>1iXkDK>><(&R;*1lP}+>rZ(aVCp7
zGxwac&tChi{hhVGz19veBttSJLoy^oG9*JXBtvqT#fC{X+-JkoH1st!14DGl;RZZ%
zWXoaB-KyP?91c0T{^V=0oqwm-o_=KlpxJDLEysOoB3%jeahnaT(#MiC0&hO>j-vt_
zs0Ho^iscvq6aaaE7sxtXKrAaOD=jN4v&+iL?3F85PCP8m#KyKA3=|F2#+?bpQ$Cwe
zy?xyF)#iLl>6X7?QyS-F>{h?;55eCnU@VX$lj<JCd%gnvQNRFDD&EN}hbM{mz8Yu-
z9stf%*Gq9el3L(fz$nq1FCZKy`VJ`wtE#FN$d~}|`~B}mdGCzMMw|0?+W5@*PN*A5
zzbD<w-Im*a(^YfejgKf25CK>~80d}B#XMjoaJdX{#<j5i4Vd)^Gy#4f82zjblYmzh
z@Cd=<OW+qkk-WI&TqYoW*m1nl%|O{8Z3=-3U=dIWbj#7D#vAS%YUX6ss#U(Grlx)#
zCp$a4S8VT!!~QABi#BT~%iI}hvuPekzboC~vs~7JH{M_xUtxy~AS@H&mm`*BwZQjA
z!XxyJxcv#3Jr_30<ob;PV}Q?B57!(Gn}FfsiEII056~&kTXDQrC2*DLS3vfHfbJ^s
zj$>8(p8{Kfc6IcK?yw5q83A8UPftJa<>cga&YL&y7_mXT_D>po7e|5br7$mCl+B!h
z^k-!fd?z#=wY&MPjyYVwF5yNw68_8=5E`hv-@}ec@X84=(*?s^Fzi(L)^ymj5_Xx1
zw4HvpDDZ9B0@t1hJH+!92?(>42Q)`KK=1)`fM<bgfscTZa_qzTAWl*G&ytV9gnC;(
zlwQ$)$iSDMpI<R|?%XTH28q}|W$<-C?}j?Ayl50l52imY9B_Qw=(cF^mDH5{ddHYO
zUu7D*grFA*FUpRSgW0oS``z&E4%mr<pn|Ee^erb$7Xi68{8xi-5zIKl;T#4eg+Oi;
ze@p}fFNxv4C!RM)@llU>x5aV~U<@!tbQdd5n4|b$TBWkG@>LDKtgI}%uCDI<>#n=*
zJ~fU+?H{-N)8;k}zV~hR7T&{zub1-nVER+$fKxu3Q2l0MO<})_uXADJxsAI;d4URn
z@$w+!H3APp?OX8ur(j_TTmW!3JWvNG7df7BlJQhq;Fi0cXN~8}70(w`M<~wF8!Po<
zz|$-Jbj!7NU_CHX?jH+mSMRL#r!@>$RaGr$YHI42Zo28Fn?}ipm#qC0T5wEu7JUHh
zg7#D0oD{Eq_aJGM(!o+KZ;q|Gpr7a4RkDC{sZ}7z6FnitI|73cU`r;e8)&bFhNIxN
z9S$D@;Aogr<nS*5HpBI2z$a09vqV=r(B?!2x~1<#Kxiw_`GrIJa27@TfxW=Ts(rEO
z&XWnqS6(<S+EZ3mRvJ&6rlzJ}>1z!Q4d;o@RO}zO9PdP+NZ6gQfQgCdcMrtpB#lx#
zAP8+rb*cqmeEVo$<Ala?QCuit$CfbF2DB<tJ{bS#m4SB1WVf6M3%A0G=!W}X?U_!P
zHfa#^(th)-=<Z0;_=dCirH@tt-ag<npfRd_n>^zY8|105oi$h(UbSkK52&rEsOULZ
zyJ5qIeWcHeii*}RT)6O)RPFzw&DuRSJ^!(J^<es6v)NpzKBx^%+{*n}iRh0Bz$iXf
zi;pi#Ta)2u%H9coiGHqrQ`4Ln%DY8zvkamu0T37vb{Qa@4vY7yuMWqxoI2Ub$&Hc)
zhg+UAFYQZEZ!8aJ(l?z7)W!pj6OL%)LjkN?<sh>Ke1q~Y!{-8lKu&pixut9$v42%n
z)q+4EkZqpvdcEP<vu9tQ4*OpWmzO}!beQ_CO=B$m8*R=zA5M?3E{kp77{jxP>W`0%
z7O|7J`En$T?Bn?8&^!L!@pm$fkJM@hLAp;NzdM2ao1XkgINk<lkBus^6~LF)!Na$}
zX)@Wp@}g}@Z;0f$k6y(Fq_0ltx5J4-tWXP~Pl0Ns%N=6^U4wYGh<6Hy!>;AamxtxU
ziiB~W&v$P>Z}i%0Q^fWu+kb<TRRCZj9QnRQW3>KZP-WVD27z<4n4XOOfdF{y0awoA
zbzb5kApeNG5nKA@n!;YIep3C@g8V#1{5ALLQ6w}e540D?oT3PCFLZ<*FfbC1T@1gy
z1->L;@rzEsBL1m};ebH_VnC`N|BJHMFQck0mP)@^@hm`YxZwt`*Xxbs=q$@}m6w;d
znHBYuPd@oZI2?|EI4>`+zM`U{HC^_<z{MG_*i=WjzKbKahWNnff4-CX6X8g6|D8@M
zgy$x#-#x&ZZ#Vln;ehL3W9`_DV{hL#+?T5_`>TUjsPdCX6lpKvPY3Be0-n~Dj>T}(
ze7G~@cssn1_Yf?34lYS^ro&c;CGWBncq)qb-3&B{r|`sB1^33g__^`M8}oB=a=Nr9
zeC)Bu-k&jJ#wqRX?UN%D=yuz4=gvJR-S(gEX8M0Z{M($L=HUx3Hu5k~baoz*h0k`I
zeU}yRn`HI74>H>|yu(+bFYiBV|1bJQXaDdpU*kh@(OwnzJ7f@n6g;0sz!DVa_!REF
z3%36OcK#R^1pD%ETn6)NoDy8FgoErf=HDg&Zv#J2*WLrJp)dKJt5$wJs&ZpM7?(Gm
zH*a2v%hfmRU@%xTYu2o1`tje~+z(~)({BH-d%1dtMN5Pm9?8sq)$rRRTzDf3gyx9%
zOG>}{pmRQ%ZAWcB>YjrvP{?^N|3Q^zaH~Z_XPm3uc))kPl(-yk0MCtsRVJyq9BO95
z&zt*--W&(t-2;D`?JN*XIWE-%>_}jpR6C3;1FjQ*6^kv57w8r*U~Cagz!TYk{qoB%
zpQA4Z0)bKbe9f9QKg^K*ZNNS;i-~^q!CZ12*O=ekU|lwb@>KM@2jg9P&fK%iV|fPr
zV)yRb+bP5|Ta0UJt;yuuVhv&viuc)2cRTzn+U5Tnc3%j0wLxSjL<!7%6rNt_l*$YU
zQz<{*?O;11Kf<Y3j}q_|s6|k>5OAO3K%oSZ&^8cSvu4fC+}zxzSPi^hZ|M2wpZ90T
z{$}Oxf3=C#u^Mb@<6U|8r1iVw7(jQ)`z3e8YB0KaL<^Ti3jVP|PCAmEdTW*MHJJNn
zC!7{V=liz7Czry_&9K=#JRDA#4=-E}<JAf+*$Pmt`u!c8ns1>Z-<tol$l+5wD4YNw
zBKEJYuD&={gNBBNwF3N1*gvBG(lPv`-s+<`MjpDE#i{BaEF@V#u>Q3ArAKy-?25kW
z|2Vu!jPI4UCQZ?kj+Lhkw*eQL^H%uXR42?9i3iM<m+p|9+`iXg_h`7j&e^d7j>{{k
zfO1tDGQ2QRSq^*`m<GH-|GF@$>e3>4uNIYfO~xBpKyby16(1B76l{pTSy@^6-%Q$H
zRiwJ!YouS==hHAfYu4aTRlhslLF9cp^7iN(oxV<=;#L~{!BoAU4aeZ@HjePVV)(1P
zaDjU1Zj~4|M{6nERSEM=nr4K<+lr$=n2aZUMiw1<`@Ko~w5tSlti)?FHDC(}Zr!?7
zFM9BJJiXhtZEMez{iF4Na~x&+ZGz@vV>^waJ0<<`l5%U;s9pW45BXd3*K-NaMtv!!
z&X<mDKKJX;dzZ5aDNtE<ljv+$M88WVpj*EK&uw%(AOC|V<oOZwZPD^3IHvXTj#sFX
znx(+ksyv?tU#c4rtVN3!?eu!R0dryX>eaWZ{xV_z13<XFmrXc|?6aqFcRKVZmYS$x
z;)yxqmNqv8-rhfD|3By}x_8i58*P@aAl2|&fwS$3I=Hsf33;a0n=LQgD&EGYsvXc*
zgUuJPjZj3{l;~PinJylgkFzBo;5pz91!NTYZdZ$r-bB(aamcV`%a+}`cJ11&>(;Fc
zXVU%y>3{rjPMI?YKh7rHl=UZ?8#6mRih<EGu^#!-n*|JQ0*+KuFUJ7$T44Jq*dY3f
zMSrhMbdvz7D@KXQBjI7V0^x9J)1^v8&2bLt5taK}3OuZ+Kv?=T;OnAq+gCb)aLDX`
zsPrewfqD%oRm*OZ`{^W&+k9NkV&gXgnqvAkbB0vtjZSZ93CE~j1l4#GS&;%f3S1-{
z@?SEZEM*t7%Q~p~PPZ32WcEK)`ja8ydRtP6zJ315uJqzf2^bwHp4T;qPO~`B0Q9Ke
zi5Tl0;3O5+3+ewyHqqPeGxf(uhr|9E)1M3gE#+bZ^#5zKPzDUIH>ZYGvNPW8RhA0R
zVft?p2qx~BBhOl@zpxrxCc$?&?4MEno+Q0dNV#8>(R(xC`OG#bo4!A236R+C8cT&n
zSJar38BbUl-z6R)6a~Ue$)U7=#`S+5{_BXWD88jZU@;`0YxzIZ!YBA$vZ3Pu0000<
KMNUMnLSTYxpwT!0

diff --git a/question_types/quiz_ddlines/theme/quiz_ddlines.css b/question_types/quiz_ddlines/theme/quiz_ddlines.css
deleted file mode 100644
index d699356..0000000
--- a/question_types/quiz_ddlines/theme/quiz_ddlines.css
+++ /dev/null
@@ -1,147 +0,0 @@
-@CHARSET "UTF-8";
-
-/**
-* This container contains the img and svg "panel"
-* The ID is set by javascript.
-*/
-div.ddlines-container {
-	position: relative;
-	margin-top: 5px;
-	border: 3px solid #AAA;
-	padding: 0 !important;
-}
-div.ddlines-container > img {
-	display: none;
-	visibility: hidden;
-}
- 
-/**
-* The feedback pane for the quiz taker
-*/
-#quiz-ddlines-feedback {
-	position: absolute;
-	top: 100px;
-	right: 0;
-	left: 0;
-	width: 200px;
-	background-color: white;
-	margin: 0px auto;
-	border: 8px double;
-	vertical-align: center;
-	padding: 10px;
-	z-index: 100000;
-}
-
-span#feedback-correct,
-span#feedback-wrong {
-	display: inline-block;
-	font-weight: normal;
-	font-size: 90%;
-	width: 160px;	
-	color: #777;
-	position: relative;
-	top: 3px;
-}
-
-span#feedback-wrong {
-	clear: both;
-}
-
-label#settings-for {
-	font-size: 110%;
-	max-width: 300px;
-	display: inline-block;
-	background-color: #EEE;
-	padding: 0px 5px;
-}
-
-/**
-* The editor toolpane
-*/
-#quiz-ddlines-toolpane {
-	background-color: white;
-	border-style: double;
-	border-width: 5px;
-	width: 400px;
-	margin: 0px auto;
-	position: relative;
-	bottom: 0px;
-	padding: 5px 10px 10px 10px;
-}
-
-#quiz-ddlines-feedback-pane > input.feedback {
-	margin-right: 5px;
-	width: 150px;
-}
-
-#quiz-ddlines-toolpane > #quiz-ddlines-buttonpane {
-	position: absolute;
-	right: 3px;
-	top: 11px;
-}
-
-#quiz-ddlines-buttonpane > input {
-	float: left;
-	clear: left;
-	width: 100%;
-	height: 20px;
-	margin-bottom: 3px;
-	font-size: 90%;
-	padding-top: 0px;
-}
-
-#quiz-ddlines-toolpane > #colorwheel-container {
-	background-color: white;
-	border: 5px double black;
-	position: absolute;
-	z-index: 1000;
-	display: none; /* Hide by default */
-	top: -165px;
-	right: 5px;
-	border-bottom: none;
-	padding: 5px;
-}
-#quiz-ddlines-feedback-pane > #feedback-disabled {
-	position: absolute;
-	top: 37px;
-	height: 42px;
-	background-color: #EEE;
-	text-align: center;
-	vertical-align: middle;
-	opacity: 0.8;
-	filter: alpha(opacity=80);
-	width: 318px;
-}
-#quiz-ddlines-feedback-pane > #feedback-disabled > p {
-	margin-top: 13px;
-	font-size: 150%;
-    font-weight: bold;
-}
-
-/**
-* The response
-*/
-.quiz-ddlines-icon-cell {
-  width: 32px;
-  padding: 0 5px;
-}
-.quiz-ddlines-icon {
-  display: inline-block;
-  width: 32px;
-  height: 32px;
-  background: url(images/icons.png) no-repeat;
-}
-.quiz-ddlines-icon.wrong {
-  background-position: -32px 0;
-}
-.quiz-ddlines-icon.no-answer {
-  background-position: -64px 0;
-}
-#quiz-report-form .icon-descriptions div {
-  margin-bottom: 5px;
-}
-#quiz-report-form .icon-descriptions img {
-  position: relative;
-  top: 3px;
-  margin-right: 5px;
-}
\ No newline at end of file
diff --git a/question_types/quiz_ddlines/theme/quiz_ddlines.theme.inc b/question_types/quiz_ddlines/theme/quiz_ddlines.theme.inc
deleted file mode 100644
index b82386b..0000000
--- a/question_types/quiz_ddlines/theme/quiz_ddlines.theme.inc
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php 
-
-/**
- * The theme file for quiz ddlines.
- *
- * Sponsored by: Senter for IKT i utdanningen 
- * Code: paalj
- *
- * @file
- * Theming functions for the ddlines question type.
- */
-
-/**
- * Theming function for the results
- * 
- * @param array $vars
- */
-function theme_quiz_ddlines_response($vars) {
-  static $css_added;
-  if (!$css_added) {
-    drupal_add_css(drupal_get_path('module', 'quiz_ddlines') . '/theme/quiz_ddlines.css');
-    $css_added = TRUE;
-  }
-  
-  $rows = array();
-  foreach ($vars['result'] as $title => $status) {    
-    $rows[] = array(
-      array(
-        'data' => '<span class="quiz-ddlines-icon '.AnswerStatus::getCssClass($status).'" title="' . AnswerStatus::getTitle($status) . '"></span>',
-        'class' => 'selector-td quiz-ddlines-icon-cell',
-      ),
-      $title,
-    );
-  }
-  
-  return theme('table', array('header' => NULL, 'rows' => $rows));;
-}
diff --git a/question_types/quiz_directions/quiz_directions.classes.inc b/question_types/quiz_directions/quiz_directions.classes.inc
deleted file mode 100644
index 4af5ce5..0000000
--- a/question_types/quiz_directions/quiz_directions.classes.inc
+++ /dev/null
@@ -1,167 +0,0 @@
-<?php
-
-/**
- * quiz_directions.classes
- *
- * This module uses the question interface to define something which is actually not a question.
- *
- * A Quiz Directions node is a placeholder for adding directions to a quiz. It can be inserted any number
- * of times into a quiz. Example uses may include:
- *
- * - Initial quiz-wide directions
- * - Section directions, e.g. "The next five questions are multiple choice, please..." (Won't work if the question order is randomized)
- * - Final confirmation, e.g. "You have answered all questions. Click submit to submit this quiz."
- *
- * @file
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class QuizDirectionsQuestion extends QuizQuestion {
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validateNode($form)
-   */
-  public function validateNode(array &$form) { }
-
-  /**
-   * Implementation of saveNodeProperties
-   *
-   * @see QuizQuestion#saveNodeProperties($is_new)
-   */
-  public function saveNodeProperties($is_new = FALSE) { }
-
-  /**
-   * Implementation of getAnsweringForm
-   *
-   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $form = parent::getAnsweringForm($form_state, $result_id);
-
-    $form['tries'] = array(
-      '#type' => 'hidden',
-      '#value' => 0,
-    );
-    $form['empty_space'] = array(
-      '#type' => 'markup',
-      '#value' => '<br/>',
-    );
-    return $form;
-  }
-
-  /**
-   * Implementation of getBodyFieldTitle
-   *
-   * @see QuizQuestion#getBodyFieldTitle()
-   */
-  public function getBodyFieldTitle() {
-    return t('Direction');
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm($form_state)
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    return array();
-  }
-
-  /**
-   * Implementation of getMaximumScore
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    return 0;
-  }
-
-  public function isGraded() {
-    return FALSE;
-  }
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class QuizDirectionsResponse extends QuizQuestionResponse {
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() { }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() { }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    // First, due to popular demand, if the directions are at the beginning of
-    // the quiz, we restart the timer after the user has read the question.
-    $quiz_nid = arg(1);
-    $quiz_key = 'quiz_' . $quiz_nid;
-    if (isset($_SESSION[$quiz_key]['previous_quiz_questions']) && count($_SESSION[$quiz_key]['previous_quiz_questions']) === 1) {
-      // reset the timer.
-      $sql = 'UPDATE {quiz_node_results} SET time_start=%d WHERE result_id = %d';
-      // TODO Please review the conversion of this statement to the D7 database API syntax.
-      /* db_query($sql, REQUEST_TIME, $this->result_id) */
-      db_update('quiz_node_results')
-  ->fields(array(
-    'time_start' => REQUEST_TIME,
-  ))
-  ->condition('result_id', $this->result_id)
-  ->execute();
-    }
-
-    // Set the score
-    $this->score = 0;
-    return 0;
-  }
-
-  /**
-   * Implementation of isCorrect
-   *
-   * @see QuizQuestionResponse#isCorrect()
-   */
-  public function isCorrect() {
-    return TRUE;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer;
-  }
-
-  /**
-   * Implementation of getReportForm
-   *
-   * @see QuizQuestionResponse#getReportForm()
-   */
-  public function getReportForm() {
-    return array(
-      '#no_report' => TRUE,
-    );
-  }
-}
-
-
-
-
diff --git a/question_types/quiz_directions/quiz_directions.info b/question_types/quiz_directions/quiz_directions.info
deleted file mode 100644
index a93f26c..0000000
--- a/question_types/quiz_directions/quiz_directions.info
+++ /dev/null
@@ -1,10 +0,0 @@
-name = Quiz Direction
-package = Quiz Question
-description = Provide a slot to add directions for a quiz or some sub-portion of a quiz.
-core = 7.x
-
-dependencies[] = quiz
-dependencies[] = quiz_question
-
-files[] = quiz_directions.classes.inc
-
diff --git a/question_types/quiz_directions/quiz_directions.install b/question_types/quiz_directions/quiz_directions.install
deleted file mode 100644
index 16225e7..0000000
--- a/question_types/quiz_directions/quiz_directions.install
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-/**
- * @file
- * The installer file for quiz directions module.
- */
-
-/**
- * Implements hook_install().
- */
-function quiz_directions_install() {
-  // Add body field to quiz directions node
-  quiz_question_add_body_field('quiz_directions');
-}
diff --git a/question_types/quiz_directions/quiz_directions.module b/question_types/quiz_directions/quiz_directions.module
deleted file mode 100644
index 7cfbeb4..0000000
--- a/question_types/quiz_directions/quiz_directions.module
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-/**
- * The main file for quiz_directions.
- *
- * Directions are implemented as a question type. However, they do not affect the score.
- * @file
- */
-
-/**
- * Implements hook_help().
- */
-function quiz_directions_help($path, $args) {
-  if ($path == 'admin/help#quiz_directions') {
-    return t('Provides directions which can be inserted alongside questions in a quiz.');
-  }
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function quiz_directions_quiz_question_info() {
-  return array(
-    'quiz_directions' => array(
-      'name' => t('@quiz directions', array('@quiz' => QUIZ_NAME)),
-      'description' => t('@quiz directions allow you to drop in directions anywhere in a quiz. You can also use them to insert a confirmation step at the end of the quiz.', array('@quiz' => QUIZ_NAME)),
-      'question provider' => 'QuizDirectionsQuestion',
-      'response provider' => 'QuizDirectionsResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function quiz_directions_quiz_question_config() {
-  return FALSE;
-}
-
-/**
- * Implements hook_theme().
- */
-function quiz_directions_theme() {
-  return array(
-    'quiz_directions_answering_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'quiz_directions') . '/theme',
-      'template' => 'quiz-directions-answering-form',
-    ),
-  );
-}
diff --git a/question_types/quiz_directions/theme/quiz-directions-answering-form.tpl.php b/question_types/quiz_directions/theme/quiz-directions-answering-form.tpl.php
deleted file mode 100644
index b55d6e0..0000000
--- a/question_types/quiz_directions/theme/quiz-directions-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the quiz_directions answering form.
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render($form);
-
-?>
\ No newline at end of file
diff --git a/question_types/quiz_page/QuizPageTestCase.test b/question_types/quiz_page/QuizPageTestCase.test
deleted file mode 100644
index 520159c..0000000
--- a/question_types/quiz_page/QuizPageTestCase.test
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-
-class QuizPageTestCase extends QuizQuestionTestCase {
-
-  var $question_node_type = 'quiz_page';
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz page'),
-      'description' => t('Unit test for quiz pages'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
-    $modules[] = 'quiz_page';
-    $modules[] = 'truefalse';
-    parent::setUp($modules, $admin_permissions, $user_permissions);
-  }
-
-  /**
-   * Test that question parentage saves.
-   */
-  function testQuizPageParentage() {
-    $this->drupalLogin($this->admin);
-
-    // Create Quiz with review of score.
-    $quiz_node = $this->drupalCreateQuiz();
-
-    // Create the questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 1 body text')))));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node); // QNR ID 1
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 2 body text')))));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node); // QNR ID 2
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 3 body text')))));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);  // QNR ID 3
-
-    // Create the pages.
-    $page_node1 = $this->drupalCreateNode(array('type' => 'quiz_page'));
-    $this->linkQuestionToQuiz($page_node1, $quiz_node); // QNR ID 4
-    $page_node2 = $this->drupalCreateNode(array('type' => 'quiz_page'));
-    $this->linkQuestionToQuiz($page_node2, $quiz_node); // QNR ID 5
-
-    // Go to the manage questions form.
-    $this->drupalGet("node/$quiz_node->nid/quiz/questions");
-    $post = array(
-      // Make the questions have parents.
-      "qnr_pids[{$question_node1->nid}-{$question_node1->vid}]" => 4,
-      "qnr_pids[{$question_node2->nid}-{$question_node2->vid}]" => 4,
-      "qnr_pids[{$question_node3->nid}-{$question_node3->vid}]" => 5,
-      // Mirror what JS would have done by adjusting the weights.
-      "weights[{$page_node1->nid}-{$page_node1->vid}]" => 2,
-      "weights[{$question_node1->nid}-{$question_node1->vid}]" => 3,
-      "weights[{$question_node2->nid}-{$question_node2->vid}]" => 4,
-      "weights[{$page_node2->nid}-{$page_node2->vid}]" => 3,
-      "weights[{$question_node3->nid}-{$question_node3->vid}]" => 4,
-    );
-    $this->drupalPost(NULL, $post, t('Submit'));
-
-    $sql = "SELECT * FROM {quiz_node_relationship}";
-    $data = db_query($sql)->fetchAllAssoc('qnr_id');
-    // Check the relationships properly saved.
-    foreach ($data as $qnr_id => $rel) {
-      switch ($qnr_id) {
-        case 1:
-        case 2:
-          $this->assertEqual($rel->qnr_pid, 4);
-          break;
-        case 3:
-          $this->assertEqual($rel->qnr_pid, 5);
-          break;
-        case 4:
-        case 5:
-          $this->assertNull($rel->qnr_pid);
-          break;
-      };
-    }
-
-    // Take the quiz. Ensure the pages are correct.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    // Questions 1 and 2 are present. Question 3 is hidden.
-    $this->assertFieldByName("question[{$question_node1->nid}]");
-    $this->assertFieldByName("question[{$question_node2->nid}]");
-    $this->assertNoFieldByName("question[{$question_node3->nid}]");
-    $this->drupalPost(NULL, array(
-      "question[{$question_node1->nid}]" => 1,
-      "question[{$question_node2->nid}]" => 1,
-      ), t('Next'));
-    // Questions 1 and 2 are gone. Question 3 is present.
-    $this->assertNoFieldByName("question[{$question_node1->nid}]");
-    $this->assertNoFieldByName("question[{$question_node2->nid}]");
-    $this->assertFieldByName("question[{$question_node3->nid}]");
-    $this->drupalPost(NULL, array(
-      "question[{$question_node3->nid}]" => 1,
-      ), t('Finish'));
-
-    // Check that the results page contains all the questions.
-    $this->assertText('You got 3 of 3 possible points.');
-    $this->assertText('TF 1 body text');
-    $this->assertText('TF 2 body text');
-    $this->assertText('TF 3 body text');
-  }
-
-}
diff --git a/question_types/quiz_page/quiz_page.classes.inc b/question_types/quiz_page/quiz_page.classes.inc
deleted file mode 100644
index a20b3a8..0000000
--- a/question_types/quiz_page/quiz_page.classes.inc
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-
-/**
- * @file quiz_page.classes.inc
- * Support class for Quiz pages.
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class QuizPagesQuestion extends QuizQuestion {
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validateNode($form)
-   */
-  public function validateNode(array &$form) {
-
-  }
-
-  /**
-   * Implementation of saveNodeProperties
-   *
-   * @see QuizQuestion#saveNodeProperties($is_new)
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-
-  }
-
-  /**
-   * Implementation of getBodyFieldTitle
-   *
-   * @see QuizQuestion#getBodyFieldTitle()
-   */
-  public function getBodyFieldTitle() {
-    return t('Page');
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm($form_state)
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    return array();
-  }
-
-  /**
-   * Implementation of getMaximumScore
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    return 0;
-  }
-
-  public function isGraded() {
-    return FALSE;
-  }
-
-  function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $element = array(
-      '#type' => 'hidden',
-    );
-    return $element;
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class QuizPagesResponse extends QuizQuestionResponse {
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-
-  }
-
-  /**
-   * Implementation of isCorrect
-   *
-   * @see QuizQuestionResponse#isCorrect()
-   */
-  public function isCorrect() {
-    return TRUE;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer;
-  }
-
-  /**
-   * Implementation of getReportForm
-   *
-   * @see QuizQuestionResponse#getReportForm()
-   */
-  public function getReportForm() {
-    return array(
-      '#no_report' => TRUE,
-    );
-  }
-
-}
diff --git a/question_types/quiz_page/quiz_page.info b/question_types/quiz_page/quiz_page.info
deleted file mode 100644
index 84715f9..0000000
--- a/question_types/quiz_page/quiz_page.info
+++ /dev/null
@@ -1,8 +0,0 @@
-name = Quiz pages
-package = Quiz
-description = Provides support for pages of questions.
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-files[] = quiz_page.classes.inc
-files[] = QuizPageTestCase.test
diff --git a/question_types/quiz_page/quiz_page.install b/question_types/quiz_page/quiz_page.install
deleted file mode 100644
index f134a69..0000000
--- a/question_types/quiz_page/quiz_page.install
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-/**
- * @file
- * The installer file for quiz pages module.
- */
-
-/**
- * Implements hook_install().
- */
-function quiz_page_install() {
-  // Add body field to quiz pages node
-  quiz_question_add_body_field('quiz_page');
-}
diff --git a/question_types/quiz_page/quiz_page.module b/question_types/quiz_page/quiz_page.module
deleted file mode 100644
index 577bd19..0000000
--- a/question_types/quiz_page/quiz_page.module
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-/**
- * @file quiz_page.module
- *
- * Pages are implemented as a question type. They do not affect the score.
- */
-
-/**
- * Implements hook_help().
- */
-function quiz_page_help($path, $args) {
-  if ($path == 'admin/help#quiz_pages') {
-    return t('Provides pages which can contain questions in a quiz.');
-  }
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function quiz_page_quiz_question_info() {
-  return array(
-    'quiz_page' => array(
-      'name' => t('@quiz page', array('@quiz' => QUIZ_NAME)),
-      'description' => t('Quiz pages allow you display questions across multiple pages.'),
-      'question provider' => 'QuizPagesQuestion',
-      'response provider' => 'QuizPagesResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function quiz_page_quiz_question_config() {
-  return FALSE;
-}
diff --git a/question_types/quiz_question/quiz_question.core.inc b/question_types/quiz_question/quiz_question.core.inc
deleted file mode 100644
index c8e2263..0000000
--- a/question_types/quiz_question/quiz_question.core.inc
+++ /dev/null
@@ -1,888 +0,0 @@
-<?php
-
-/**
- * Classes used in the Quiz Question module.
- *
- * The core of the Quiz Question module is a set of abstract classes that
- * can be used to quickly and efficiently create new question types.
- *
- * Why OO?
- * Drupal has a long history of avoiding many of the traditional OO structures
- * and metaphors. However, with PHP 5, there are many good reasons to use OO
- * principles more broadly.
- *
- * The case for Quiz question types is that question types all share common
- * structure and logic. Using the standard hook-only Drupal metaphor, we are
- * forced to copy and paste large amounts of repetitive code from question
- * type to question type. By using OO principles and construction, we can
- * easily encapsulate much of that logic, while still making it easy to
- * extend the existing content.
- *
- * Where do I start?
- * To create a new question type, check out the multichoice question type for instance.
- *
- * @file
- */
-
-/**
- * A base implementation of a quiz_question, adding a layer of abstraction between the
- * node API, quiz API and the question types.
- *
- * It is required that Question types extend this abstract class.
- *
- * This class has default behaviour that all question types must have. It also handles the node API, but
- * gives the question types oppurtunity to save, delete and provide data specific to the question types.
- *
- * This abstract class also declares several abstract functions forcing question-types to implement required
- * methods.
- */
-abstract class QuizQuestion {
-  /*
-   * QUESTION IMPLEMENTATION FUNCTIONS
-   *
-   * This part acts as a contract(/interface) between the question-types and the rest of the system.
-   *
-   * Question types are made by extending these generic methods and abstract methods.
-   */
-
-  /**
-   * The current node for this question.
-   */
-  public $node = NULL;
-  // Extra node properties
-  public $nodeProperties = NULL;
-
-  /**
-   * QuizQuestion constructor stores the node object.
-   *
-   * @param $node
-   *   The node object
-   */
-  public function __construct(stdClass &$node) {
-    $this->node = $node;
-  }
-
-  /**
-   * Allow question types to override the body field title
-   *
-   * @return
-   *  The title for the body field
-   */
-  public function getBodyFieldTitle() {
-    return t('Question');
-  }
-
-  /**
-   * Returns a node form to quiz_question_form
-   *
-   * Adds default form elements, and fetches question type specific elements from their
-   * implementation of getCreationForm
-   *
-   * @param array $form_state
-   * @return unknown_type
-   */
-  public function getNodeForm(array &$form_state = NULL) {
-    global $user;
-    $form = array();
-
-    // mark this form to be processed by quiz_form_alter. quiz_form_alter will among other things
-    // hide the revion fieldset if the user don't have permission to controll the revisioning manually.
-    $form['#quiz_check_revision_access'] = TRUE;
-
-    // Allow user to set title?
-    if (user_access('edit question titles')) {
-      $form['helper']['#theme'] = 'quiz_question_creation_form';
-      $form['title'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Title'),
-        '#maxlength' => 255,
-        '#default_value' => $this->node->title,
-        '#required' => FALSE,
-        '#description' => t('Add a title that will help distinguish this question from other questions. This will not be seen during the @quiz.', array('@quiz' => QUIZ_NAME)),
-      );
-    }
-    else {
-      $form['title'] = array(
-        '#type' => 'value',
-        '#value' => $this->node->title,
-      );
-    }
-
-    // Store quiz id in the form
-    $form['quiz_nid'] = array(
-      '#type' => 'hidden',
-    );
-    $form['quiz_vid'] = array(
-      '#type' => 'hidden',
-    );
-
-    if (isset($_GET['quiz_nid']) && isset($_GET['quiz_vid'])) {
-      $form['quiz_nid']['#value'] = intval($_GET['quiz_nid']);
-      $form['quiz_vid']['#value'] = intval($_GET['quiz_vid']);
-    }
-
-    // Identify this node as a quiz question type so that it can be recognized by other modules effectively.
-    $form['is_quiz_question'] = array(
-      '#type' => 'value',
-      '#value' => TRUE
-    );
-
-    if (!empty($this->node->nid)) {
-      if ($properties = entity_load('quiz_question', FALSE, array('nid' => $this->node->nid, 'vid' => $this->node->vid))) {
-        $quiz_question = reset($properties);
-      }
-    }
-    $form['feedback'] = array(
-      '#type' => 'text_format',
-      '#title' => t('Question feedback'),
-      '#default_value' => !empty($quiz_question->feedback) ? $quiz_question->feedback : '',
-      '#format' => !empty($quiz_question->feedback_format) ? $quiz_question->feedback_format : filter_default_format(),
-      '#description' => t('This feedback will show when configured and the user answers a question, regardless of correctness.'),
-    );
-
-    //Add question type specific content
-    $form = array_merge($form, $this->getCreationForm($form_state));
-
-    if ($this->hasBeenAnswered()) {
-      $log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
-      $this->node->revision = 1;
-      $this->node->log = $log;
-    }
-    return $form;
-  }
-
-  /**
-   * Retrieve information relevant for viewing the node.
-   *
-   * (This data is generally added to the node's extra field.)
-   *
-   * @return
-   *  Content array
-   */
-  public function getNodeView() {
-    $type = node_type_get_type($this->node);
-    $content['question_type'] = array(
-      '#markup' => '<div class="question_type_name">' . $type->name . '</div>',
-      '#weight' => -2,
-    );
-    /*
-      $question_body = field_get_items('node', $this->node, 'body');
-      $content['question'] = array(
-      '#markup' => '<div class="question-body">' . $question_body[0]['safe_value'] . '</div>',
-      '#weight' => -1,
-      );
-     */
-    return $content;
-  }
-
-  /**
-   * Getter function returning properties to be loaded when the node is loaded.
-   *
-   * @see load hook in quiz_question.module (quiz_question_load)
-   *
-   * @return array
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props['max_score'] = db_query('SELECT max_score
-            FROM {quiz_question_properties}
-            WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchField();
-    $props['is_quiz_question'] = TRUE;
-    $this->nodeProperties = $props;
-    return $props;
-  }
-
-  /**
-   * Responsible for handling insert/update of question-specific data.
-   * This is typically called from within the Node API, so there is no need
-   * to save the node.
-   *
-   * The $is_new flag is set to TRUE whenever the node is being initially
-   * created.
-   *
-   * A save function is required to handle the following three situations:
-   * - A new node is created ($is_new is TRUE)
-   * - A new node *revision* is created ($is_new is NOT set, because the
-   *   node itself is not new).
-   * - An existing node revision is modified.
-   *
-   * @see hook_update and hook_insert in quiz_question.module
-   *
-   * @param $is_new
-   *  TRUE when the node is initially created.
-   */
-  public function save($is_new = FALSE) {
-    // We call the abstract function saveNodeProperties to save type specific data
-    $this->saveNodeProperties($is_new);
-
-    db_merge('quiz_question_properties')
-      ->key(array(
-        'nid' => $this->node->nid,
-        'vid' => $this->node->vid,
-      ))
-      ->fields(array(
-        'nid' => $this->node->nid,
-        'vid' => $this->node->vid,
-        'max_score' => $this->getMaximumScore(),
-        'feedback' => !empty($this->node->feedback['value']) ? $this->node->feedback['value'] : '',
-        'feedback_format' => !empty($this->node->feedback['format']) ? $this->node->feedback['format'] : filter_default_format(),
-      ))
-      ->execute();
-
-
-
-    // Save what quizzes this question belongs to.
-    $quizzes_kept = $this->saveRelationships();
-    if ($quizzes_kept && $this->node->revision) {
-      if (user_access('manual quiz revisioning') && !variable_get('quiz_auto_revisioning', 1)) {
-        unset($_GET['destination']);
-        unset($_REQUEST['edit']['destination']);
-        drupal_goto('quiz_question/' . $this->node->nid . '/' . $this->node->vid . '/revision_actions');
-      }
-      // For users without the 'manual quiz revisioning' permission we submit the revision_actions form
-      // silently with its default values set.
-      else {
-        $form_state = array();
-        $form_state['values']['op'] = t('Submit');
-        require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'quiz_question') . '/quiz_question.pages.inc';
-        drupal_form_submit('quiz_question_revision_actions', $form_state, $this->node->nid, $this->node->vid);
-      }
-    }
-  }
-
-  /**
-   * Delete question data from the database.
-   *
-   * Called by quiz_question_delete (hook_delete).
-   * Child classes must call super
-   *
-   * @param $only_this_version
-   *  If the $only_this_version flag is TRUE, then only the particular
-   *  nid/vid combo should be deleted. Otherwise, all questions with the
-   *  current nid can be deleted.
-   */
-  public function delete($only_this_version = FALSE) {
-    // Delete answeres
-    $delete = db_delete('quiz_node_results_answers')
-      ->condition('question_nid', $this->node->nid);
-    if ($only_this_version) {
-      $delete->condition('question_vid', $this->node->vid);
-    }
-    $delete->execute();
-
-    // Delete properties
-    $delete = db_delete('quiz_question_properties')
-      ->condition('nid', $this->node->nid);
-    if ($only_this_version) {
-      $delete->condition('vid', $this->node->vid);
-    }
-    $delete->execute();
-  }
-
-  /**
-   * Provides validation for question before it is created.
-   *
-   * When a new question is created and initially submited, this is
-   * called to validate that the settings are acceptible.
-   *
-   * @param $form
-   *  The processed form.
-   */
-  abstract public function validateNode(array &$form);
-
-  /**
-   * Get the form through which the user will answer the question.
-   *
-   * @param $form_state
-   *  The FAPI form_state array
-   * @param $result_id
-   *  The result id.
-   * @return
-   *  Must return a FAPI array.
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $form = array();
-    $form['#element_validate'] = array('quiz_question_element_validate');
-    return $form;
-  }
-
-  /**
-   * Get the form used to create a new question.
-   *
-   * @param
-   *  FAPI form state
-   * @return
-   *  Must return a FAPI array.
-   */
-  abstract public function getCreationForm(array &$form_state = NULL);
-
-  /**
-   * Get the maximum possible score for this question.
-   */
-  abstract public function getMaximumScore();
-
-  /**
-   * Save question type specific node properties
-   */
-  abstract public function saveNodeProperties($is_new = FALSE);
-
-  /**
-   * Save this Question to the specified Quiz.
-   */
-  function saveRelationships() {
-    if (!empty($this->node->quiz_nid) && !empty($this->node->quiz_vid)) {
-      $quiz_node = node_load($this->node->quiz_nid, $this->node->quiz_vid);
-      $nid_vid[0] = $quiz_node->nid;
-      $nid_vid[1] = $quiz_node->vid;
-
-      if (quiz_has_been_answered($quiz_node)) {
-        // We need to revise the quiz node if it has been answered
-        $quiz_node->revision = 1;
-        $quiz_node->auto_created = TRUE;
-        node_save($quiz_node);
-        $nid_vid[0] = $quiz_node->nid;
-        $nid_vid[1] = $quiz_node->vid;
-        drupal_set_message(t('New revision has been created for the @quiz %n', array('%n' => $quiz_node->title, '@quiz' => QUIZ_NAME)));
-      }
-
-      $nid = $this->node->nid;
-
-      $insert_values[$nid]['parent_nid'] = $quiz_node->nid;
-      $insert_values[$nid]['parent_vid'] = $quiz_node->vid;
-      $insert_values[$nid]['child_nid'] = $this->node->nid;
-      $insert_values[$nid]['child_vid'] = $this->node->vid;
-      $insert_values[$nid]['max_score'] = $this->getMaximumScore();
-      $insert_values[$nid]['auto_update_max_score'] = $this->autoUpdateMaxScore() ? 1 : 0;
-      $insert_values[$nid]['weight'] = 1 + db_query('SELECT MAX(weight) FROM {quiz_node_relationship} WHERE parent_vid = :vid', array(':vid' => $nid_vid[1]))->fetchField();
-      $randomization = db_query('SELECT randomization FROM {quiz_node_properties} WHERE nid = :nid AND vid = :vid', array(':nid' => $nid_vid[0], ':vid' => $nid_vid[1]))->fetchField();
-      $insert_values[$nid]['question_status'] = $randomization == 2 ? QUESTION_RANDOM : QUESTION_ALWAYS;
-
-      $insert_qnr = db_insert('quiz_node_relationship');
-      $insert_qnr->fields(array('parent_nid', 'parent_vid', 'child_nid', 'child_vid', 'max_score', 'weight', 'question_status', 'auto_update_max_score'));
-      foreach ($insert_values as $insert_value) {
-        $insert_qnr->values($insert_value);
-      }
-      $insert_qnr->execute();
-
-      // Update max_score for relationships if auto update max score is enabled
-      // for question
-      $quizzes_to_update = array();
-      $result = db_query(
-        'SELECT parent_vid as vid from {quiz_node_relationship} where child_nid = :nid and child_vid = :vid and auto_update_max_score=1', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-      foreach ($result as $record) {
-        $quizzes_to_update[] = $record->vid;
-      }
-
-      db_update('quiz_node_relationship')
-        ->fields(array('max_score' => $this->getMaximumScore()))
-        ->condition('child_nid', $this->node->nid)
-        ->condition('child_vid', $this->node->vid)
-        ->condition('auto_update_max_score', 1)
-        ->execute();
-
-      quiz_update_max_score_properties($quizzes_to_update);
-      quiz_update_max_score_properties(array($quiz_node->vid));
-    }
-  }
-
-  /**
-   * Finds out if a question has been answered or not
-   *
-   * This function also returns TRUE if a quiz that this question belongs to have been answered.
-   * Even if the question itself haven't been answered. This is because the question might have
-   * been rendered and a user is about to answer it...
-   *
-   * @return
-   *   true if question has been answered or is about to be answered...
-   */
-  public function hasBeenAnswered() {
-    if (!isset($this->node->vid)) {
-      return FALSE;
-    }
-    $answered = db_query_range('SELECT 1 FROM {quiz_node_results} qnres
-            JOIN {quiz_node_relationship} qnrel ON (qnres.vid = qnrel.parent_vid)
-            WHERE qnrel.child_vid = :child_vid', 0, 1, array(':child_vid' => $this->node->vid))->fetch();
-    return $answered ? TRUE : FALSE;
-  }
-
-  /**
-   * Determines if the user can view the correct answers
-   *
-   * @todo grabbing the node context here probably isn't a great idea
-   *
-   * @return boolean
-   *   true iff the view may include the correct answers to the question
-   */
-  public function viewCanRevealCorrect() {
-    global $user;
-    $quiz_node = node_load(arg(1));
-
-    $reveal_correct[] = user_access('view any quiz question correct response');
-    $reveal_correct[] = ($user->uid == $this->node->uid);
-    if (array_filter($reveal_correct)) {
-      return TRUE;
-    }
-  }
-
-  /**
-   * Utility function that returns the format of the node body
-   */
-  protected function getFormat() {
-    $node = isset($this->node) ? $this->node : $this->question;
-    $body = field_get_items('node', $node, 'body');
-    return isset($body[0]['format']) ? $body[0]['format'] : NULL;
-  }
-
-  /**
-   * This may be overridden in subclasses. If it returns true,
-   * it means the max_score is updated for all occurrences of
-   * this question in quizzes.
-   */
-  protected function autoUpdateMaxScore() {
-    return false;
-  }
-
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-
-  }
-
-  /**
-   * Is this question graded?
-   *
-   * Questions like Quiz Directions, Quiz Page, and Scale are not.
-   *
-   * By default, questions are expected to be gradeable
-   *
-   * @return bool
-   */
-  public function isGraded() {
-    return TRUE;
-  }
-
-}
-
-/**
- * Each question type must store its own response data and be able to calculate a score for
- * that data.
- */
-abstract class QuizQuestionResponse {
-
-  // Result id
-  protected $result_id = 0;
-  protected $is_correct = FALSE;
-  protected $evaluated = TRUE;
-  // The question node(not a quiz question instance)
-  public $question = NULL;
-  public $quizQuestion = NULL;
-  protected $answer = NULL;
-  protected $score;
-  public $is_skipped;
-  public $is_doubtful;
-
-  /**
-   * Create a new user response.
-   *
-   * @param $result_id
-   *  The result ID for the user's result set. There is one result ID per time
-   *  the user takes a quiz.
-   * @param $question_node
-   *  The question node.
-   * @param $answer
-   *  The answer (dependent on question type).
-   */
-  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
-    $this->result_id = $result_id;
-    $this->question = $question_node;
-    $this->quizQuestion = _quiz_question_get_instance($question_node);
-    $this->answer = $answer;
-    $result = db_query('SELECT is_skipped, is_doubtful FROM {quiz_node_results_answers}
-            WHERE result_id = :result_id AND question_nid = :question_nid AND question_vid = :question_vid', array(':result_id' => $result_id, ':question_nid' => $question_node->nid, ':question_vid' => $question_node->vid))->fetch();
-    if (is_object($result)) {
-      $this->is_doubtful = $result->is_doubtful;
-      $this->is_skipped = $result->is_skipped;
-    }
-  }
-
-  /**
-   *
-   * @return QuizQuestion
-   */
-  function getQuizQuestion() {
-    return $this->quizQuestion;
-  }
-
-  /**
-   * Used to refresh this instances question node in case drupal has changed it.
-   *
-   * @param $newNode
-   *  Question node
-   */
-  public function refreshQuestionNode($newNode) {
-    $this->question = $newNode;
-  }
-
-  /**
-   * Indicate whether the response has been evaluated (scored) yet.
-   * Questions that require human scoring (e.g. essays) may need to manually
-   * toggle this.
-   */
-  public function isEvaluated() {
-    return (bool) $this->evaluated;
-  }
-
-  /**
-   * Check to see if the answer is marked as correct.
-   *
-   * This default version returns TRUE iff the score is equal to the maximum possible score.
-   */
-  function isCorrect() {
-    return ($this->getMaxScore() == $this->getScore());
-  }
-
-  /**
-   * Returns stored score if it exists, if not the score is calculated and returned.
-   *
-   * @param $weight_adjusted
-   *  If the returned score shall be adjusted according to the max_score the question has in a quiz
-   * @return
-   *  Score(int)
-   */
-  function getScore($weight_adjusted = TRUE) {
-    if ($this->is_skipped) {
-      return 0;
-    }
-    if (!isset($this->score)) {
-      $this->score = $this->score();
-    }
-    if (isset($this->question->score_weight) && $weight_adjusted) {
-      return round($this->score * $this->question->score_weight);
-    }
-    return $this->score;
-  }
-
-  /**
-   * Returns stored max score if it exists, if not the max score is calculated and returned.
-   *
-   * @param $weight_adjusted
-   *  If the returned max score shall be adjusted according to the max_score the question has in a quiz
-   * @return
-   *  Max score(int)
-   */
-  public function getMaxScore($weight_adjusted = TRUE) {
-    if (!isset($this->question->max_score)) {
-      $this->question->max_score = $this->question->getMaximumScore();
-    }
-    if (isset($this->question->score_weight) && $weight_adjusted) {
-      return round($this->question->max_score * $this->question->score_weight);
-    }
-    return $this->question->max_score;
-  }
-
-  /**
-   * Represent the response as a stdClass object.
-   *
-   * Convert data to an object that has the following properties:
-   * - $score
-   * - $result_id
-   * - $nid
-   * - $vid
-   * - $is_correct
-   */
-  function toBareObject() {
-    $obj = new stdClass();
-    $obj->score = $this->getScore(); // This can be 0 for unscored.
-    $obj->nid = $this->question->nid;
-    $obj->vid = $this->question->vid;
-    $obj->result_id = $this->result_id;
-    $obj->is_correct = (int) $this->isCorrect();
-    $obj->is_evaluated = $this->isEvaluated();
-    $obj->is_skipped = 0;
-    $obj->is_doubtful = isset($_POST['is_doubtful']) ? $_POST['is_doubtful'] : 0;
-    $obj->is_valid = $this->isValid();
-    return $obj;
-  }
-
-  /**
-   * Validates response from a quiz taker. If the response isn't valid the quiz taker won't be allowed to proceed.
-   *
-   * @return
-   *  True if the response is valid.
-   *  False otherwise
-   */
-  public function isValid() {
-    return TRUE;
-  }
-
-  /**
-   * Get data suitable for reporting a user's score on the question.
-   * This expects an object with the following attributes:
-   *
-   *  answer_id; // The answer ID
-   *  answer; // The full text of the answer
-   *  is_evaluated; // 0 if the question has not been evaluated, 1 if it has
-   *  score; // The score the evaluator gave the user; this should be 0 if is_evaluated is 0.
-   *  question_vid
-   *  question_nid
-   *  result_id
-   */
-  public function getReport() {
-    // Basically, we encode internal information in a
-    // legacy array format for Quiz.
-    $report = array(
-      'answer_id' => 0, // <-- Stupid vestige of multichoice.
-      'answer' => $this->answer,
-      'is_evaluated' => $this->isEvaluated(),
-      'is_correct' => $this->isCorrect(),
-      'score' => $this->getScore(),
-      'question_vid' => $this->question->vid,
-      'question_nid' => $this->question->nid,
-      'result_id' => $this->result_id,
-    );
-    return $report;
-  }
-
-  /**
-   * Creates the report form for the admin pages, and for when a user gets feedback after answering questions.
-   *
-   * The report is a form to allow editing scores and the likes while viewing the report form
-   *
-   * @return $form
-   *  Drupal form array
-   */
-  public function getReportForm() {
-    /*
-     * Add general data, and data from the question type implementation
-     */
-    $form = array();
-    $form['nid'] = array(
-      '#type' => 'value',
-      '#value' => $this->question->nid,
-    );
-    $form['vid'] = array(
-      '#type' => 'value',
-      '#value' => $this->question->vid,
-    );
-    $form['result_id'] = array(
-      '#type' => 'value',
-      '#value' => $this->result_id,
-    );
-    if (quiz_access_to_score() && $submit = $this->getReportFormSubmit()) {
-      $form['submit'] = array(
-        '#type' => 'value',
-        '#value' => $submit,
-      );
-    }
-    $form['question'] = $this->getReportFormQuestion();
-
-    if (quiz_access_to_score()) {
-      $form['answer_feedback'] = $this->getReportFormAnswerFeedback();
-    }
-
-    $form['max_score'] = array(
-      '#type' => 'value',
-      '#value' => ($this->canReview('score')) ? $this->getMaxScore() : '?',
-    );
-
-    $rows = array();
-
-    $labels = array(
-      'attempt' => t('Your answer'),
-      'choice' => t('Choice'),
-      'correct' => t('Correct?'),
-      'score' => t('Score'),
-      'answer_feedback' => t('Feedback'),
-      'solution' => t('Correct answer'),
-    );
-    drupal_alter('quiz_feedback_labels', $labels);
-
-    foreach ($this->getReportFormResponse() as $idx => $row) {
-      foreach ($labels as $reviewType => $label) {
-        if (in_array($reviewType, array('choice')) || (isset($row[$reviewType]) && $this->canReview($reviewType))) {
-          $rows[$idx][$reviewType] = $row[$reviewType];
-        }
-      }
-    }
-
-    if ($this->isEvaluated()) {
-      $score = $this->getScore();
-      if ($this->isCorrect()) {
-        $class = 'q-correct';
-      }
-      else {
-        $class = 'q-wrong';
-      }
-    }
-    else {
-      $score = t('?');
-      $class = 'q-waiting';
-    }
-
-    if (quiz_access_to_score() && $submit) {
-      $form['score'] = $this->getReportFormScore();
-    }
-
-    if ($this->canReview('score') || quiz_access_to_score()) {
-      $form['score_display']['#markup'] = theme('quiz_question_score', array('score' => $score, 'max_score' => $this->getMaxScore(), 'class' => $class));
-    }
-
-    $headers = array_intersect_key($labels, $rows[0]);
-    $type = $this->getQuizQuestion()->node->type;
-    $form['response']['#markup'] = theme('quiz_question_feedback__' . $type, array('labels' => $headers, 'data' => $rows));
-
-    if ($this->canReview('question_feedback')) {
-      if ($properties = entity_load('quiz_question', FALSE, array('nid' => $this->quizQuestion->node->nid, 'vid' => $this->quizQuestion->node->vid))) {
-        $quiz_question = reset($properties);
-        $form['question_feedback']['#markup'] = check_markup($quiz_question->feedback, $quiz_question->feedback_format);
-      }
-    }
-
-    $form['#theme'] = $this->getReportFormTheme();
-    return $form;
-  }
-
-  /**
-   * get the question part of the reportForm
-   *
-   * @return
-   *  FAPI form array holding the question
-   */
-  public function getReportFormQuestion() {
-    $node = node_load($this->question->nid);
-    $node->no_answer_form = TRUE;
-    node_build_content($node, 'question');
-    return $node->content;
-  }
-
-  /**
-   * Get the response part of the report form
-
-   * @return
-   *  Array of choices
-   */
-  public function getReportFormResponse() {
-    $data = array();
-
-    $data[] = array(
-      'choice' => 'True',
-      'attempt' => 'Did the user choose this?',
-      'correct' => 'Was their answer correct?',
-      'score' => 'Points earned for this answer',
-      'answer_feedback' => 'Feedback specific to the answer',
-      'question_feedback' => 'General question feedback for any answer',
-      'solution' => 'Is this choice the correct solution?',
-      'quiz_feedback' => 'Quiz feedback at this time',
-    );
-
-    return $data;
-  }
-
-  public function getReportFormAnswerFeedback() {
-    return FALSE;
-  }
-
-  /**
-   * Get the submit function for the reportForm
-   *
-   * @return
-   *  Submit function as a string, or FALSE if no submit function
-   */
-  public function getReportFormSubmit() {
-    return FALSE;
-  }
-
-  /**
-   * Get the validate function for the reportForm
-   *
-   * @return
-   *  Validate function as a string, or FALSE if no validate function
-   */
-  public function getReportFormValidate(&$element, &$form_state) {
-    return FALSE;
-  }
-
-  /**
-   * Get the theme key for the reportForm
-   *
-   * @return
-   *  Theme key as a string, or FALSE if no submit function
-   */
-  public function getReportFormTheme() {
-    return FALSE;
-  }
-
-  /**
-   * Saves the quiz result. This should only be called when an answer is
-   * provided.
-   */
-  public function saveResult() {
-    $this->save();
-  }
-
-  /**
-   * Utility function that returns the format of the node body
-   */
-  protected function getFormat() {
-    $body = field_get_items('node', $this->question, 'body');
-    return ($body ? $body[0]['format'] : NULL);
-  }
-
-  /**
-   * Save the current response.
-   */
-  abstract public function save();
-
-  /**
-   * Delete the response.
-   */
-  abstract public function delete();
-
-  /**
-   * Calculate the score for the response.
-   */
-  abstract public function score();
-
-  /**
-   * Get the user's response.
-   */
-  abstract public function getResponse();
-
-  /**
-   * Can the quiz taker view the requested review?
-   */
-  public function canReview($option) {
-    $quiz_result = quiz_result_load($this->result_id);
-    return quiz_feedback_can_review($option, $quiz_result);
-  }
-
-  /**
-   * Implementation of getReportFormScore
-   *
-   * @see QuizQuestionResponse#getReportFormScore()
-   */
-  public function getReportFormScore() {
-    $score = ($this->isEvaluated()) ? $this->getScore() : '';
-    return array(
-      '#title' => 'Enter score',
-      '#type' => 'textfield',
-      '#default_value' => $score,
-      '#size' => 3,
-      '#maxlength' => 3,
-      '#attributes' => array('class' => array('quiz-report-score')),
-      '#element_validate' => array('element_validate_integer'),
-      '#required' => TRUE,
-      '#field_suffix' => '/ ' . $this->getMaxScore(),
-    );
-  }
-
-  /**
-   * Set the target result ID for this Question response.
-   *
-   * Useful for cloning entire result sets.
-   */
-  public function setResultId($result_id) {
-    $this->result_id = $result_id;
-  }
-
-}
diff --git a/question_types/quiz_question/quiz_question.info b/question_types/quiz_question/quiz_question.info
deleted file mode 100644
index bbbc1c6..0000000
--- a/question_types/quiz_question/quiz_question.info
+++ /dev/null
@@ -1,8 +0,0 @@
-name = Quiz Question
-package = Quiz Core
-description = Helper module for all quiz question types
-core = 7.x
-dependencies[] = quiz
-
-files[] = quiz_question.core.inc
-files[] = quiz_question.test
diff --git a/question_types/quiz_question/quiz_question.install b/question_types/quiz_question/quiz_question.install
deleted file mode 100644
index 04579c0..0000000
--- a/question_types/quiz_question/quiz_question.install
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-
-/**
- * The installer file for quiz_question.
- * @file
- */
-
-/**
- * Implements hook_install().
- */
-function quiz_question_install() {
-
-}
-
-/**
- * Implements hook_schema().
- */
-function quiz_question_schema() {
-  $schema = array();
-  $schema['quiz_question_properties'] = array(
-    'description' => 'Properties common for all quizzes',
-    'fields' => array(
-      'qqp_id' => array(
-        'type' => 'serial',
-      ),
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'max_score' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'default' => 0,
-      ),
-      'feedback' => array(
-        'type' => 'text',
-        'not null' => FALSE,
-      ),
-      'feedback_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => FALSE,
-      ),
-    ),
-    'primary key' => array('qqp_id'),
-    'indexes' => array('question_id' => array('vid', 'nid')),
-  );
-  $schema['quiz_question_latest_quizzes'] = array(
-    'description' => 'Stores the latest quizzes each user has been involved in',
-    'fields' => array(
-      'quiz_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'uid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'id',
-    ),
-  );
-  return $schema;
-}
-
-/**
- * Implements hook_uninstall().
- */
-function quiz_question_uninstall() {
-
-}
-
-/**
- * Implements hook_update_N()
- */
-
-/**
- * Make the question body visible by default for the question view mode
- */
-function quiz_question_update_7401() {
-  $question_types = array_keys(_quiz_question_get_implementations());
-
-  foreach ($question_types as $question_type) {
-    $instance = field_read_instance('node', 'body', $question_type);
-
-    $instance['display']['question'] = array(
-      'label' => 'hidden',
-      'type' => 'text_default',
-      'weight' => 1,
-      'settings' => array(),
-      'module' => 'text',
-    );
-
-    field_update_instance($instance);
-  }
-}
-
-/**
- * Add fields for general question feedback.
- */
-function quiz_question_update_7500() {
-  db_drop_primary_key('quiz_question_properties');
-
-  db_add_field('quiz_question_properties', 'qqp_id', array(
-    'type' => 'serial',
-    ), array('primary key' => array('qqp_id')));
-
-  db_add_field('quiz_question_properties', 'feedback', array(
-    'type' => 'text',
-    'not null' => FALSE,
-  ));
-
-  db_add_field('quiz_question_properties', 'feedback_format', array(
-    'type' => 'varchar',
-    'length' => 255,
-    'not null' => FALSE,
-  ));
-
-  // Migrate the truefalse feedback, since we have generic feedback now.
-  $filter = filter_default_format();
-  db_query("UPDATE {quiz_question_properties} qqp
-    INNER JOIN {quiz_truefalse_node} qtn ON (qtn.vid = qqp.vid)
-    SET qqp.feedback = qtn.feedback, qqp.feedback_format = :format", array(':format' => $filter));
-
-  // Drop old field.
-  db_drop_field('quiz_truefalse_node', 'feedback');
-
-  return t('Added global quiz question feedback fields and migrated true/false feedback.');
-}
diff --git a/question_types/quiz_question/quiz_question.module b/question_types/quiz_question/quiz_question.module
deleted file mode 100644
index 7de1925..0000000
--- a/question_types/quiz_question/quiz_question.module
+++ /dev/null
@@ -1,1173 +0,0 @@
-<?php
-
-/**
- * Quiz Question module.
- * This module provides the basic facilities for adding quiz question types to a quiz.
- * @file
- */
-/*
- * The system remembers what quizzes a user has been involved in lately. This constant determines
- * how many quizzes the system will remember for each user
- */
-define('QUIZ_QUESTION_NUM_LATEST', 10);
-
-/**
- * Implements hook_help().
- */
-function quiz_question_help($path, $args) {
-  if ($path == 'admin/help#quiz_quesion') {
-    return t('Support for Quiz question types.');
-  }
-}
-
-/**
- * Implements hook_menu().
- */
-function quiz_question_menu() {
-  $items = array();
-  $items['quiz_question/%/%/revision_actions'] = array(
-    'title' => 'Revision actions',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('quiz_question_revision_actions', 1, 2),
-    'access arguments' => array('manual quiz revisioning'),
-    'file' => 'quiz_question.pages.inc',
-    'type' => MENU_NORMAL_ITEM,
-  );
-  // Menu items for admin view of each question type.
-  $items['admin/quiz/settings/questions_settings'] = array(
-    'title' => 'Question configuration',
-    'description' => 'Configure the question types.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('quiz_question_config'),
-    'access arguments' => array('administer quiz configuration'),
-    'type' => MENU_NORMAL_ITEM,
-  );
-  return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function quiz_question_theme() {
-  $hooks = array(
-    'quiz_question_creation_form' => array(
-      'render element' => 'form',
-      'file' => 'quiz_question.theme.inc',
-    ),
-    'quiz_question_navigation_form' => array(
-      'render element' => 'form',
-      'file' => 'quiz_question.theme.inc',
-    ),
-  );
-
-  $hooks['quiz_question_feedback'] = array(
-    'variables' => NULL,
-    'pattern' => 'quiz_question_feedback__',
-  );
-
-  return $hooks;
-}
-
-/**
- * Implements hook_node_info().
- */
-function quiz_question_node_info() {
-  $node_info = array();
-  foreach (_quiz_question_get_implementations(NULL, TRUE) as $type => $definition) {
-    $node_info[$type] = array(
-      'name' => $definition['name'],
-      'base' => 'quiz_question',
-      'description' => $definition['description']
-    );
-    //$info[$type] = $node_info + $defaults;
-  }
-  return $node_info;
-}
-
-/**
- * Figure out if a user has access to score a certain result
- *
- * @param $vid
- *  Question version id
- * @param $result_id
- *  Result id
- * @return
- *  True if the user has access to score the result
- */
-function quiz_question_access_to_score($vid, $result_id) {
-  global $user;
-  $sql = 'SELECT * FROM {quiz_node_results_answers} WHERE result_id = :result_id AND question_vid = :question_vid';
-  $answer = db_query($sql, array(':result_id' => $result_id, ':question_vid' => $vid))->fetch();
-  if (!$answer) {
-    return FALSE;
-  }
-  if (user_access('score any quiz')) {
-    return TRUE;
-  }
-  if (user_access('score taken quiz answer')) {
-    $uid = db_query('SELECT uid from {quiz_node_results} qnr WHERE qnr.result_id = :result_id', array(':result_id' => $result_id))->fetchField();
-    if ($uid == $user->uid) {
-      return TRUE;
-    }
-  }
-  if (user_access('score own quiz')) {
-    return db_query('SELECT r.uid FROM {node_revision} r
-            JOIN {quiz_node_results} qnr ON (r.nid = qnr.nid)
-            WHERE qnr.result_id = :result_id
-            ', array(':result_id' => $result_id))->fetchField() == $user->uid;
-  }
-}
-
-/**
- * Implements hook_form().
- */
-function quiz_question_form(&$node, &$form_state) {
-  $question = _quiz_question_get_instance($node);
-  $form = $question->getNodeForm($form_state);
-  return $form;
-}
-
-/**
- * Implements hook_validate().
- */
-function quiz_question_validate($node, &$form) {
-  // Check to make sure that there is a question.
-  if (empty($node->body)) {
-    form_set_error('body', t('Question text is empty.'));
-  }
-  _quiz_question_get_instance($node)->validateNode($form);
-}
-
-/**
- * Get the form to show to the quiz taker.
- *
- * @param $nodes
- *   A list of question nodes to get answers from.
- * @param $result_id
- *   The result ID for this attempt.
- */
-function quiz_question_answering_form($form, $form_state, $nodes, $result_id) {
-  $quiz_result = quiz_result_load($result_id);
-  $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-
-  if (!is_array($nodes)) {
-    // One single question (or page?)
-    if ($nodes->type == 'quiz_page') {
-      foreach ($quiz_result->layout as $question) {
-        if ($question['nid'] == $nodes->nid) {
-          // Found a page
-          $nodes = array(node_load($nodes->nid));
-          foreach ($quiz_result->layout as $question2) {
-            if ($question2['qnr_pid'] == $question['qnr_id']) {
-              // This question belongs in the requested page.
-              $nodes[] = node_load($question2['nid']);
-            }
-          }
-          break;
-        }
-      }
-    }
-    else {
-      $nodes = array($nodes->nid => $nodes);
-    }
-  }
-
-  $form['#attributes']['class'] = array('answering-form');
-
-  foreach ($nodes as $node) {
-    $question = _quiz_question_get_instance($node);
-    $class = drupal_html_class('quiz-question-' . $node->type);
-    // Element for a single question
-    $element = $question->getAnsweringForm($form_state, $result_id);
-
-    // Hide the label of the body field if this is an informative question.
-    $options = !$question->isGraded() ? array('label' => 'hidden') : array();
-
-    node_build_content($node, 'question');
-    unset($node->content['answers']);
-    $form['questions'][$node->nid] = array(
-      '#attributes' => array('class' => array($class)),
-      '#type' => 'container',
-      'header' => $node->content,
-      'question' => array('#tree' => TRUE, $node->nid => $element),
-    );
-
-    if (!$quiz->allow_change) {
-      if (quiz_result_is_question_answered($quiz_result, $node)) {
-        // This question was already answered, and not skipped.
-        $form['questions'][$node->nid]['#disabled'] = TRUE;
-      }
-    }
-
-    if ($quiz->mark_doubtful) {
-      $form['is_doubtful'] = array(
-        '#type' => 'checkbox',
-        '#title' => t('doubtful'),
-        '#weight' => 1,
-        '#prefix' => '<div class="mark-doubtful checkbox enabled"><div class="toggle"><div></div></div>',
-        '#suffix' => '</div>',
-        '#default_value' => 0,
-        '#attached' => array(
-          'js' => array(drupal_get_path('module', 'quiz') . '/theme/quiz_take.js'),
-        ),
-      );
-      if (isset($node->result_id)) {
-        $form['is_doubtful']['#default_value'] = db_query('SELECT is_doubtful FROM {quiz_node_results_answers} WHERE result_id = :result_id AND question_nid = :question_nid AND question_vid = :question_vid', array(':result_id' => $node->result_id, ':question_nid' => $node->nid, ':question_vid' => $node->vid))->fetchField();
-      }
-    }
-  }
-
-  $is_last = _quiz_show_finish_button($quiz);
-
-  $form['navigation']['#type'] = 'actions';
-  $form['navigation']['#theme'] = 'quiz_question_navigation_form';
-
-  if (!empty($quiz->backwards_navigation) && (arg(3) != 1)) {
-    // Backwards navigation enabled, and we are looking at not the first
-    // question. @todo detect when on the first page.
-    $form['navigation']['back'] = array(
-      '#weight' => 10,
-      '#type' => 'submit',
-      '#value' => t('Back'),
-      '#submit' => array('quiz_question_answering_form_submit_back'),
-      '#limit_validation_errors' => array(),
-    );
-    if ($is_last) {
-      $form['navigation']['#last'] = TRUE;
-      $form['navigation']['last_text'] = array(
-        '#weight' => 0,
-        '#markup' => '<p><em>' . t('This is the last question. Press Finish to deliver your answers') . '</em></p>',
-      );
-    }
-  }
-
-  $form['navigation']['submit'] = array(
-    '#weight' => 30,
-    '#type' => 'submit',
-    '#value' => $is_last ? t('Finish') : t('Next'),
-  );
-
-  if ($is_last && $quiz->backwards_navigation && !$quiz->repeat_until_correct) {
-    // Display a confirmation dialogue if this is the last question and a user
-    // is able to navigate backwards but not forced to answer correctly.
-    $form['#attributes']['class'][] = 'quiz-answer-confirm';
-    $form['#attributes']['data-confirm-message'] = t("By proceeding you won't be able to go back and edit your answers.");
-    $form['#attached'] = array(
-      'js' => array(drupal_get_path('module', 'quiz') . '/theme/quiz_confirm.js'),
-    );
-  }
-  if ($quiz->allow_skipping) {
-    $form['navigation']['skip'] = array(
-      '#weight' => 20,
-      '#type' => 'submit',
-      '#value' => $is_last ? t('Leave blank and finish') : t('Leave blank'),
-      '#access' => ($node->type == 'quiz_directions') ? FALSE : TRUE,
-      '#submit' => array('quiz_question_answering_form_submit_blank'),
-      '#limit_validation_errors' => array(),
-    );
-  }
-
-  return $form;
-}
-
-/**
- * Submit action for "leave blank".
- */
-function quiz_question_answering_form_submit_blank($form, &$form_state) {
-  $quiz_result = quiz_result_load($_SESSION['quiz'][arg(1)]['result_id']);
-  $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-
-  foreach (array_keys($form_state['input']['question']) as $nid) {
-    // Loop over all question inputs provided, and record them as skipped.
-    $question = node_load($nid);
-
-    // Delete the user's answer.
-    _quiz_question_response_get_instance($quiz_result->result_id, $question)->delete();
-
-    // Mark our question attempt as skipped, reset the correct and points flag.
-    $qra = quiz_result_answer_load($quiz_result->result_id, $question->nid, $question->vid);
-    $qra->is_skipped = 1;
-    $qra->is_correct = 0;
-    $qra->points_awarded = 0;
-    $qra->answer_timestamp = REQUEST_TIME;
-    entity_save('quiz_result_answer', $qra);
-    quiz_question_goto($quiz, $_SESSION['quiz'][$quiz->nid]['current'] + 1);
-  }
-
-  // Advance to next question.
-  $form_state['redirect'] = "node/{$quiz->nid}/take/" . $_SESSION['quiz'][$quiz->nid]['current'];
-
-  if (!isset($quiz_result->layout[$_SESSION['quiz'][$quiz->nid]['current']])) {
-    // If this is the last question, finalize the quiz.
-    quiz_question_answering_form_finalize($form, $form_state);
-  }
-}
-
-/**
- * Form for teaser display
- *
- * @param $node
- *  The question node
- * @return
- *  Content array
- */
-function _quiz_question_teaser_content($node) {
-  $content['question_type'] = array(
-    '#markup' => '<div class="question_type_name">' . node_type_get_type($node)->name . '</div>',
-    '#weight' => -100,
-  );
-  return $content;
-}
-
-/**
- * Update the session for this quiz to the active question.
- *
- * @param type $quiz
- *   A Quiz node.
- * @param type $question_number
- *   Question number starting at 1.
- */
-function quiz_question_goto($quiz, $question_number) {
-  $_SESSION['quiz'][$quiz->nid]['current'] = $question_number;
-}
-
-/**
- * Implementation of hook_get_report().
- *
- * @return
- *  Node containing all of the items from the question plus the user's answer.
- */
-function quiz_question_get_report($nid, $vid, $result_id) {
-  $response_instance = _quiz_question_response_get_instance($result_id, NULL, NULL, $nid, $vid);
-  if (!$response_instance) {
-    drupal_set_message(t('Unable to load question with nid %nid and vid %vid', array('%nid' => $nid, '%vid' => $vid)), 'error');
-    return FALSE;
-  }
-  $result = $response_instance->getReport();
-  $response_instance->question->answers[$result['answer_id']] = $result;
-  $response_instance->question->correct = $result['is_correct'];
-  return $response_instance->question;
-}
-
-/**
- * Check if a question has already been answered by anyone.
- *
- * This is to see if a new revision of a question should be made when saving.
- *
- * @return boolean
- *  TRUE if a user has submitted an answer for this question.
- */
-function quiz_question_has_been_answered($node) {
-  $question_instance = _quiz_question_get_instance($node, true);
-  return $question_instance->hasBeenAnswered();
-}
-
-/**
- * Implements hook_quiz_question_score().
- */
-function quiz_question_quiz_question_score($quiz, $question_nid, $question_vid = NULL, $result_id = NULL) {
-  if (!isset($quiz) && !isset($result_id)) {
-    return quiz_question_get_max_score($question_nid, $question_vid);
-  }
-
-  // We avoid using node_load to increase performance...
-  $dummy_node = new stdClass();
-  $dummy_node->nid = $question_nid;
-  $dummy_node->vid = $question_vid;
-
-  $question = _quiz_question_get_instance($dummy_node, TRUE);
-  if (!$question) {
-    return FALSE;
-  }
-
-  $score = new stdClass();
-  $score->possible = $question->getMaximumScore();
-  $score->question_nid = $question->node->nid;
-  $score->question_vid = $question->node->vid;
-
-  if (isset($result_id)) {
-    $response = _quiz_question_response_get_instance($result_id, $question->node);
-    $score->attained = $score->possible > 0 ? $response->getScore() : 0;
-    $score->possible = $response->getMaxScore();
-    $score->is_evaluated = $response->isEvaluated();
-  }
-  return $score;
-}
-
-/**
- * Implements hook_delete_result().
- *
- * @param $result_id
- *  Result id
- * @param $nid
- *  Question node id
- * @param $vid
- *  Question node version id
- */
-function quiz_question_delete_result($result_id, $nid, $vid) {
-  $response = _quiz_question_response_get_instance($result_id, NULL, NULL, $nid, $vid);
-  if ($response) {
-    $response->delete();
-  }
-  else {
-    drupal_set_message(t('Unable to delete result. A constructor could not be found for the question-type'), 'error');
-  }
-}
-
-/**
- * Get the configuration form for all enabled question types.
- */
-function quiz_question_config($form, $context) {
-  $q_types = _quiz_question_get_implementations();
-  $form = array();
-  $form['#validate'] = array();
-  // Go through all question types and merge their config forms
-  foreach ($q_types as $type => $values) {
-    $function = $type . '_quiz_question_config';
-    if ($admin_form = $function()) {
-      $form[$type] = $admin_form;
-      $form[$type]['#type'] = 'fieldset';
-      $form[$type]['#title'] = $values['name'];
-      $form[$type]['#collapsible'] = TRUE;
-      $form[$type]['#collapsed'] = TRUE;
-      if (isset($admin_form['#validate']) && is_array($admin_form['#validate'])) {
-        $form['#validate'] = array_merge($form['#validate'], $admin_form['#validate']);
-        unset($form[$type]['#validate']);
-      }
-    }
-  }
-  return system_settings_form($form);
-}
-
-// NODE API
-
-/**
- * Implements hook_node_revision_delete().
- */
-function quiz_question_node_revision_delete($node) {
-  $q_types = _quiz_question_get_implementations();
-  foreach ($q_types as $q_type => $info) {
-    if ($node->type == $q_type) {
-      _quiz_delete_question($node, TRUE); // true for only this version
-    }
-  }
-}
-
-/**
- * Implements hook_node_presave().
- */
-function quiz_question_node_presave($node) {
-  $q_types = _quiz_question_get_implementations();
-  foreach ($q_types as $q_type => $info) {
-    if ($node->type == $q_type) {
-      if (drupal_strlen($node->title) == 0 || !user_access('edit question titles')) {
-        $body = field_get_items('node', $node, 'body');
-        $markup = strip_tags(check_markup($body[0]['value'], $body[0]['format']));
-        if (drupal_strlen($markup) > variable_get('quiz_autotitle_length', 50)) {
-          $node->title = drupal_substr($markup, 0, variable_get('quiz_autotitle_length', 50) - 3) . '...';
-        }
-        else {
-          $node->title = $markup;
-        }
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_node_insert().
- */
-function quiz_question_node_insert($node) {
-  // Make sure the latest quizzes table is maintained when a quiz changes
-  if ($node->type == 'quiz') {
-    quiz_question_refresh_latest_quizzes($node->nid);
-  }
-}
-
-/**
- * Implements hook_node_update().
- */
-function quiz_question_node_update($node) {
-  // Make sure the latest quizzes table is maintained when a quiz changes
-  if ($node->type == 'quiz') {
-    quiz_question_refresh_latest_quizzes($node->nid);
-  }
-}
-
-/**
- * Implements hook_node_delete().
- */
-function quiz_question_node_delete($node) {
-  // Make sure the latest quizzes table is maintained when a quiz changes
-  if ($node->type == 'quiz') {
-    quiz_question_remove_latest_quizzes($node->nid);
-  }
-}
-
-/**
- * Implements hook_insert().
- */
-function quiz_question_insert(stdClass $node) {
-  _quiz_question_get_instance($node)->save(TRUE);
-  if (isset($node->quiz_nid) && $node->quiz_nid > 0) {
-    quiz_question_refresh_latest_quizzes($node->quiz_nid);
-  }
-}
-
-/**
- * Implements hook_view().
- */
-//function quiz_question_view($node, $teaser = FALSE, $page = FALSE) {
-function quiz_question_view($node, $view_mode) {
-  if ($view_mode == 'search_index' && !variable_get('quiz_index_questions', 1)) {
-    $node->body = '';
-    $node->content = array();
-    $node->title = '';
-    $node->taxonomy = array();
-    return $node;
-  }
-  $content = '';
-
-  if ($view_mode == 'teaser') {
-    $node->content['question_teaser'] = _quiz_question_teaser_content($node);
-  }
-  else {
-    // normal node view
-    //$question = _quiz_question_get_instance($node, TRUE);
-    $content = _quiz_question_get_instance($node, TRUE)->getNodeView();
-  }
-
-  // put it into the node->content
-  if (!empty($content)) {
-    $node->content = (isset($node->content)) ? $node->content + $content : $content;
-  }
-  return $node;
-}
-
-/**
- * Implements hook_update().
- */
-function quiz_question_update($node) {
-  _quiz_question_get_instance($node)->save();
-}
-
-/**
- * Implements hook_delete().
- */
-function quiz_question_delete(&$node) {
-  _quiz_delete_question($node, FALSE);
-}
-
-/**
- * Delete the question node from the db, and mark its identifiers in the quiz
- * linking table as "NEVER".  This is safer than deleting them
- * and allows for same tracing of what's happened if a question was deleted unintentionally.
- *
- * @param $node the question node
- * @param $only_this_version whether to delete only the specific revision of the question
- */
-function _quiz_delete_question(&$node, $only_this_version) {
-  // let each question class delete its own stuff
-  _quiz_question_get_instance($node, TRUE)->delete($only_this_version);
-
-  // FIXME QuizQuestion class makes these relationships, so it should handle their 'deletion' too
-  // FIXME alternately, move the relationship handling out of QuizQuestion class
-  // @todo reconsider this QUESTION_NEVER status, since the node is actually gone
-  // then remove it from {quiz_node_relationship} linking table
-  //$base_sql = "UPDATE {quiz_node_relationship} SET question_status = " . QUESTION_NEVER;
-  $select_sql = 'SELECT parent_vid FROM {quiz_node_relationship}';
-  if ($only_this_version) {
-    $select_sql .= ' WHERE child_nid = :child_nid AND child_vid = :child_vid';
-    $filter_arg = array(':child_nid' => $node->nid, ':child_vid' => $node->vid);
-  }
-  else {
-    $select_sql .= ' WHERE child_nid = :child_nid';
-    $filter_arg = array(':child_nid' => $node->nid);
-  }
-  //$res = db_query($select_sql . $filter_sql, $node->nid, $node->vid);
-  $res = db_query($select_sql, $filter_arg);
-
-  //db_query($base_sql . $filter_sql, $node->nid, $node->vid);
-  $update = db_update('quiz_node_relationship')
-    ->fields(array('question_status' => QUESTION_NEVER))
-    ->condition('child_nid', $node->nid);
-  if ($only_this_version) {
-    $update = $update->condition('child_vid', $node->vid);
-  }
-  $update->execute();
-
-  $quizzes_to_update = array();
-  while ($quizzes_to_update[] = $res->fetchField()) {
-
-  }
-  quiz_update_max_score_properties($quizzes_to_update);
-}
-
-/**
- * Implements hook_load().
- */
-function quiz_question_load($nodes) {
-  foreach ($nodes as $nid => &$node) {
-    $node_additions = _quiz_question_get_instance($node, TRUE)->getNodeProperties();
-    foreach ($node_additions as $property => &$value) {
-      $node->$property = $value;
-    }
-  }
-}
-
-// END NODE API
-
-/**
- * Get an instance of a quiz question.
- *
- * Get information about the class and use it to construct a new
- * object of the appropriate type.
- *
- * @param $node
- *  Question node
- * @param $use_cached
- *  Can we use a cached version of the node?
- * @return
- *  The appropriate QuizQuestion extension instance
- */
-function _quiz_question_get_instance(&$node, $use_cached = FALSE) {
-  // We use static caching to improve performance
-  static $question_instances = array();
-
-  $using_dummy_node = FALSE;
-  if (is_object($node)) {
-    $vid = isset($node->vid) ? $node->vid : 0;
-    if ($use_cached && isset($question_instances[$vid])) {
-      // We just return a cached instance of the QuizQuestion
-      return $question_instances[$vid];
-    }
-    // If $node don't have a type it is a dummy node
-    if (!isset($node->type)) {
-      // To substanitally improve performance(especially on the result page) we avoid node_load()...
-      // @todo why is this? consider getting rid of this as nodes are better
-      // cached/retrieved in d7. adding the UID here to get rid of a notice.
-      $sql = 'SELECT n.type, r.nid, r.vid, r.title, p.max_score, n.uid
-              FROM {node_revision} r
-              JOIN {node} n ON r.nid = n.nid
-              JOIN {quiz_question_properties} p ON r.vid = p.vid
-              WHERE r.vid = :vid';
-      $node = db_query($sql, array(':vid' => $node->vid))->fetch();
-      $using_dummy_node = TRUE;
-    }
-    $name = $node->type;
-  }
-  elseif (is_array($node)) {
-    $name = $node['type'];
-    $vid = $node['vid'];
-    if ($use_cached && isset($question_instances[$vid])) {
-      // We return a cached instance of the appropriate QuizQuestion
-      return $question_instances[$vid];
-    }
-  }
-  // No cached instance of QuizQuestion has been returned. We construct a new instance
-  $info = _quiz_question_get_implementations();
-  $constructor = $info[$name]['question provider'];
-  if (empty($constructor)) {
-    return FALSE;
-  }
-
-  // We create a new instance of QuizQuestion
-  $to_return = new $constructor($node);
-  if (!($to_return instanceof QuizQuestion)) {
-    // Make sure the constructor is creating an extension of QuizQuestion
-    drupal_set_message(t('The question-type %name isn\'t a QuizQuestion. It needs to extend the QuizQuestion class.', array('%name' => $name)), 'error', FALSE);
-  }
-  // If we're using a dummy node we have to run getNodeProperties, and populate the node with those properties
-  if ($using_dummy_node) {
-    $props = $to_return->getNodeProperties();
-    foreach ($props as $key => $value) {
-      $to_return->node->{$key} = $value;
-    }
-  }
-  // Cache the node
-  $question_instances[$vid] = $to_return;
-
-  return $to_return;
-}
-
-/**
- * Get an instance of a quiz question responce.
- *
- * Get information about the class and use it to construct a new
- * object of the appropriate type.
- *
- * @param $result_id
- *  Result id
- * @param $question
- *  The question node(not a QuizQuestion instance)
- * @param $answer
- *  Resonce to the answering form.
- * @param $nid
- *  Question node id
- * @param $vid
- *  Question node version id
- * @return
- *  The appropriate QuizQuestionResponce extension instance
- */
-function _quiz_question_response_get_instance($result_id, $question, $answer = NULL, $nid = NULL, $vid = NULL) {
-  // We cache responses to improve performance
-  static $quiz_responses = array();
-  if (is_object($question) && isset($quiz_responses[$result_id][$question->vid])) {
-    // We refresh the question node in case it has been changed since we cached the response
-    $quiz_responses[$result_id][$question->vid]->refreshQuestionNode($question);
-    if ($quiz_responses[$result_id][$question->vid]->is_skipped !== FALSE) {
-      return $quiz_responses[$result_id][$question->vid];
-    }
-  }
-  elseif (isset($quiz_responses[$result_id][$vid])) {
-    if ($quiz_responses[$result_id][$vid]->is_skipped !== FALSE) {
-      return $quiz_responses[$result_id][$vid];
-    }
-  }
-  if (!isset($quiz_responses[$result_id])) {
-    // Prepare to cache responses for this result id
-    $quiz_responses[$result_id] = array();
-  }
-  // If the question node isn't set we fetch it from the QuizQuestion instance this responce belongs to
-  if (!isset($question)) {
-    $question_node = node_load($nid, $vid);
-    $question = _quiz_question_get_instance($question_node, TRUE)->node;
-  }
-  if (!$question) {
-    return FALSE;
-  }
-  $info = _quiz_question_get_implementations();
-  $constructor = $info[$question->type]['response provider'];
-  $to_return = new $constructor($result_id, $question, $answer);
-  // All responce classes must extend QuizQuestionResponse
-  if (!($to_return instanceof QuizQuestionResponse)) {
-    drupal_set_message(t('The question-response isn\'t a QuizQuestionResponse. It needs to extend the QuizQuestionResponse interface, or extend the abstractQuizQuestionResponse class.'), 'error', FALSE);
-  }
-  // Cache the responce instance
-  $quiz_responses[$result_id][$question->vid] = $to_return;
-
-  return $to_return;
-}
-
-/**
- * Get the information about various implementations of quiz questions.
- *
- * @param $reset
- *  If this is true, the cache will be reset.
- * @return
- *  An array of information about quiz question implementations.
- * @see quiz_question_quiz_question_info() for an example of a quiz question info hook.
- */
-function _quiz_question_get_implementations($name = NULL, $reset = FALSE) {
-  static $info = array();
-  if (empty($info) || $reset) {
-    $qtypes = module_invoke_all('quiz_question_info');
-    foreach ($qtypes as $type => $definition) {
-      // We only want the ones with classes.
-      if (!empty($definition['question provider'])) {
-        // Cache the info
-        $info[$type] = $definition;
-      }
-    }
-    drupal_alter('quiz_question_info', $info);
-  }
-  return $info;
-}
-
-/**
- * Refreshes the quiz_question_latest_quizzes table when a user has modified a new quiz.
- *
- * The latest quizzes table is used to know what quizzes the user has been using lately.
- *
- * @param $nid
- *   nid of the last quiz the current user modified
- */
-function quiz_question_refresh_latest_quizzes($nid) {
-  global $user;
-  // Delete entry if it allready exists
-  db_delete('quiz_question_latest_quizzes')
-    ->condition('uid', $user->uid)
-    ->condition('quiz_nid', $nid)
-    ->execute();
-
-  // Inserts as new entry to get new id. Latest quizzes are ordered by id(descending)
-  $id = db_insert('quiz_question_latest_quizzes')
-    ->fields(array(
-      'uid' => $user->uid,
-      'quiz_nid' => $nid,
-    ))
-    ->execute();
-
-  // If we have to many entries for current user, delete the oldest entries...
-  $min_id = db_select('quiz_question_latest_quizzes', 'lq')
-    ->fields('lq', array('id'))
-    ->condition('uid', $user->uid)
-    ->orderBy('id', 'DESC')
-    ->range(QUIZ_QUESTION_NUM_LATEST - 1, 1)
-    ->execute()
-    ->fetchField();
-
-  // Delete all table entries older than the nth row, if nth row was found.
-  if ($min_id) {
-    db_delete('quiz_question_latest_quizzes')
-      ->condition('id', $min_id, '<')
-      ->condition('uid', $user->uid)
-      ->execute();
-  }
-}
-
-/**
- * Removes a quiz from the quiz_question_latest_quizzes table.
- *
- * @param $nid
- *   the nid of a quiz that shall be removed
- */
-function quiz_question_remove_latest_quizzes($nid) {
-  db_delete('quiz_question_latest_quizzes')
-    ->condition('quiz_nid', $nid)
-    ->execute();
-}
-
-/**
- * Get the max score for a question
- *
- * @param $nid
- *  Question node id
- * @param $vid
- *  Question node version id
- * @return
- *  Max score(int)
- */
-function quiz_question_get_max_score($nid, $vid) {
-  return db_query('SELECT max_score
-          FROM {quiz_question_properties}
-          WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->fetchField();
-}
-
-/**
- * Returns a result report for a question response.
- *
- * The retaurned value is a form array because in some contexts the scores in the form
- * is editable
- *
- * @param $question
- *  The question node
- * @return
- *  FAPI form array
- */
-function quiz_question_report_form($question) {
-  $answer = $question->answers[0];
-  $response_instance = _quiz_question_response_get_instance($answer['result_id'], $question, $answer);
-  // If need to specify the score weight if it isn't already specified.
-  if (!isset($response_instance->question->score_weight)) {
-    $vid = db_query('SELECT vid FROM {quiz_node_results}
-      WHERE result_id = :rid', array(':rid' => $answer['result_id']))
-      ->fetchField();
-
-    $qnr_max_score = db_query('SELECT qnr.max_score FROM {quiz_node_relationship} qnr
-      WHERE qnr.child_vid = :child_vid AND qnr.parent_vid = :parent_vid', array(':child_vid' => $question->vid, ':parent_vid' => $vid))
-      ->fetchField();
-
-    if ($qnr_max_score === FALSE) {
-      $qnr_max_score = db_query('SELECT qt.max_score FROM {quiz_node_results} qnr
-         JOIN {quiz_node_results_answers} qnra ON (qnr.result_id = qnra.result_id)
-         JOIN {quiz_terms} qt ON (qt.vid = qnr.vid AND qt.tid = qnra.tid)
-         WHERE qnr.result_id = :rid AND qnra.question_nid = :qnid AND qnra.question_vid = :qvid', array(':rid' => $answer['result_id'], ':qnid' => $question->nid, ':qvid' => $question->vid))
-        ->fetchField();
-    }
-
-    $response_instance->question->score_weight = ($qnr_max_score == 0 || $response_instance->question->max_score == 0) ? 0 : ($qnr_max_score / $response_instance->question->max_score);
-  }
-
-  return $response_instance->getReportForm();
-}
-
-/**
- * Add body field to quiz_question nodes.
- */
-function quiz_question_add_body_field($type) {
-  node_types_rebuild();
-  $node_type = node_type_get_type($type);
-  if (!$node_type) {
-    watchdog('quiz', 'Attempt to add body field was failed as question content type %type is not defined.', array('%type' => $type), WATCHDOG_ERROR);
-    watchdog('quiz', '<pre>' . print_r(node_type_get_types(), 1), array(), WATCHDOG_ERROR);
-    return;
-  }
-  node_add_body_field($node_type, 'Question');
-  // Override default weight to make body field appear first
-  $instance = field_read_instance('node', 'body', $type);
-  $instance['widget']['weight'] = -10;
-  $instance['widget']['settings']['rows'] = 6;
-
-  // Make the question body visible by default for the question view mode
-  $instance['display']['question'] = array(
-    'label' => 'hidden',
-    'type' => 'text_default',
-    'weight' => 1,
-    'settings' => array(),
-    'module' => 'text',
-  );
-
-  field_update_instance($instance);
-}
-
-/**
- * Return a form with question feedback.
- */
-function quiz_question_feedback($quiz, $question) {
-
-  if (empty($_SESSION['quiz'][$quiz->nid]['result_id'])) {
-    $result_id = $_SESSION['quiz']['temp']['result_id'];
-  }
-  else {
-    $result_id = $_SESSION['quiz'][$quiz->nid]['result_id'];
-  }
-
-  $types = quiz_get_question_types();
-  $module = $types[$question->type]['module'];
-  // Invoke hook_get_report().
-  $report = module_invoke($module, 'get_report', $question->nid, $question->vid, $result_id);
-  $path = drupal_get_path('module', 'quiz');
-  require_once DRUPAL_ROOT . '/' . $path . '/quiz.pages.inc';
-  if ($report) {
-    $report_form = drupal_get_form('quiz_report_form', array($report));
-    return $report_form;
-  }
-}
-
-/**
- * Submit handler for the question answering form.
- *
- * There is no validation code here, but there may be feedback code for
- * correct feedback.
- */
-function quiz_question_answering_form_submit(&$form, &$form_state) {
-  $quiz_result = quiz_result_load($_SESSION['quiz'][arg(1)]['result_id']);
-  $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-  $time_reached = $quiz->time_limit && (REQUEST_TIME > ($quiz_result->time_start + $quiz->time_limit));
-
-  if ($time_reached) {
-    // Too late.
-    // @todo move to quiz_question_answering_form_validate(), and then put all
-    // the "quiz end" logic in a sharable place. We just need to not fire the
-    // logic that saves all the users answers.
-    drupal_set_message(t('The last answer was not submitted, as the time ran out.'), 'error');
-  }
-  else {
-    $questions = $quiz_result->layout;
-
-    if (!empty($form_state['values']['question'])) {
-      foreach ($form_state['values']['question'] as $nid => $answer) {
-        $current_question = node_load($nid);
-        foreach ($questions as $question) {
-          if ($question['nid'] == $current_question->nid) {
-            $question_array = $question;
-          }
-        }
-        $qi_instance = _quiz_question_response_get_instance($_SESSION['quiz'][$quiz->nid]['result_id'], $current_question, $form_state['values']['question'][$current_question->nid]);
-        $qi_instance->delete();
-        $qi_instance->saveResult();
-        $result = $qi_instance->toBareObject();
-        quiz_store_question_result($quiz, $result, array('set_msg' => TRUE, 'question_data' => $question_array));
-
-        // Increment the counter.
-        quiz_question_goto($quiz, $_SESSION['quiz'][$quiz->nid]['current'] + 1);
-      }
-    }
-  }
-
-  // Wat do?
-  if (!empty($quiz->review_options['question']) && array_filter($quiz->review_options['question'])) {
-    // We have question feedback.
-    $form_state['redirect'] = "node/$quiz->nid/take/" . ($_SESSION['quiz'][$quiz->nid]['current'] - 1) . '/feedback';
-  }
-  else {
-    // No question feedback. Go to next question.
-    $form_state['redirect'] = "node/$quiz->nid/take/" . ($_SESSION['quiz'][$quiz->nid]['current']);
-  }
-
-  if ($time_reached || !isset($quiz_result->layout[$_SESSION['quiz'][$quiz->nid]['current']])) {
-    // If this is the last question, finalize the quiz.
-    quiz_question_answering_form_finalize($form, $form_state);
-  }
-}
-
-/**
- * Helper function to finalize a quiz attempt.
- * @see quiz_question_answering_form_submit()
- * @see quiz_question_answering_form_submit_blank()
- */
-function quiz_question_answering_form_finalize(&$form, &$form_state) {
-  $quiz_result = quiz_result_load($_SESSION['quiz'][arg(1)]['result_id']);
-  $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-
-  // No more questions. Score quiz.
-  $score = quiz_end_scoring($_SESSION['quiz'][$quiz->nid]['result_id']);
-
-  if (empty($quiz->review_options['question']) || !array_filter($quiz->review_options['question'])) {
-    // Only redirect to question results if there is not question feedback.
-    $form_state['redirect'] = "node/{$quiz->nid}/quiz-results/{$quiz_result->result_id}/view";
-  }
-
-  quiz_end_actions($quiz, $score, $_SESSION['quiz'][$quiz->nid]);
-
-  // Remove all information about this quiz from the session.
-  // @todo but for anon, we might have to keep some so they could access
-  // results
-  // When quiz is completed we need to make sure that even though the quiz has
-  // been removed from the session, that the user can still access the
-  // feedback for the last question, THEN go to the results page.
-  $_SESSION['quiz']['temp']['result_id'] = $quiz_result->result_id;
-  unset($_SESSION['quiz'][$quiz->nid]);
-}
-
-/**
- * Implements hook_quiz_result_update().
- */
-function quiz_quiz_result_update($quiz_result) {
-  if ($quiz_result->original->is_evaluated && !$quiz_result->is_evaluated) {
-    // Quiz is finished!
-    $quiz = node_load($quiz_result->nid);
-    // Delete old results if necessary.
-    _quiz_maintain_results($quiz, $quiz_result->result_id);
-  }
-}
-
-/**
- * Submit handler for "back".
- */
-function quiz_question_answering_form_submit_back(&$form, &$form_state) {
-  // Back a question.
-  $quiz = node_load(arg(1));
-  quiz_question_goto($quiz, $_SESSION['quiz'][$quiz->nid]['current'] - 1);
-  $quiz_result = quiz_result_load($_SESSION['quiz'][$quiz->nid]['result_id']);
-  $question = $quiz_result->layout[$_SESSION['quiz'][$quiz->nid]['current']];
-  if (!empty($question['qnr_pid'])) {
-    foreach ($quiz_result->layout as $question2) {
-      if ($question2['qnr_id'] == $question['qnr_pid']) {
-        quiz_question_goto($quiz, $question2['number']);
-      }
-    }
-  }
-
-  $form_state['redirect'] = "node/$quiz->nid/take/" . ($_SESSION['quiz'][$quiz->nid]['current']);
-}
-
-/**
- * Validation callback for quiz question submit.
- */
-function quiz_question_answering_form_validate(&$form, &$form_state) {
-  $quiz = node_load(arg(1));
-  $quiz_result = quiz_result_load($_SESSION['quiz'][arg(1)]['result_id']);
-  $time_reached = $quiz->time_limit && (REQUEST_TIME > ($quiz_result->time_start + $quiz->time_limit));
-
-  if ($time_reached) {
-    // Let's not validate anything, because the input won't get saved in submit
-    // either.
-    return;
-  }
-
-  foreach ($form_state['values']['question'] as $nid => $answer) {
-    $current_question = node_load($nid);
-
-    if ($current_question) {
-      // There was an answer submitted.
-      $quiz_question = _quiz_question_get_instance($current_question);
-      $quiz_question->getAnsweringFormValidate($form, $form_state);
-    }
-  }
-}
-
-/**
- * Implements hook_node_access_records().
- */
-function quiz_question_node_access_records($node) {
-  $grants = array();
-  // Restricting view access to question nodes outside quizzes.
-  $question_types = _quiz_question_get_implementations();
-  $question_types = array_keys($question_types);
-  if (in_array($node->type, $question_types)) {
-    // This grant is for users having 'view quiz question outside of a quiz'
-    // permission. We set a priority of 2 because OG has a 1 priority and we
-    // want to get around it.
-    $grants[] = array(
-      'realm' => 'quiz_question',
-      'gid' => 1,
-      'grant_view' => 1,
-      'grant_update' => 0,
-      'grant_delete' => 0,
-      'priority' => 2,
-    );
-  }
-  return $grants;
-}
-
-/**
- * Implements hook_node_grants().
- */
-function quiz_question_node_grants($account, $op) {
-  $grants = array();
-  if ($op == 'view') {
-    if (user_access('view quiz question outside of a quiz')) {
-      // Granting view access
-      $grants['quiz_question'][] = 1;
-    }
-  }
-  return $grants;
-}
-
-/**
- * Element validator (for repeat until correct).
- */
-function quiz_question_element_validate(&$element, &$form_state) {
-  $quiz = node_load(arg(1));
-  $question_nid = $element['#array_parents'][1];
-  $answer = $form_state['values']['question'][$question_nid];
-  $current_question = node_load($question_nid);
-
-  // There was an answer submitted.
-  $result = _quiz_question_response_get_instance($_SESSION['quiz'][$quiz->nid]['result_id'], $current_question, $answer);
-  if ($quiz->repeat_until_correct && !$result->isCorrect()) {
-    form_set_error('', t('The answer was incorrect. Please try again.'));
-
-    $feedback = quiz_question_feedback($quiz, $current_question);
-    $element['feedback'] = array(
-      '#weight' => 100,
-      '#markup' => drupal_render($feedback),
-    );
-  }
-}
-
-/**
- * Theme the feedback for any question type.
- */
-function theme_quiz_question_feedback($variables) {
-  $rows = $variables['data'];
-  $headers = array_intersect_key($variables['labels'], $rows[0]);
-
-  return theme('table', array('header' => $headers, 'rows' => $rows));
-}
-
-/**
- * Helper function to faciliate icon display, like "correct" or "selected".
- */
-function quiz_icon($icon) {
-  return theme('quiz_answer_result', array('type' => $icon));
-}
-
-/**
- * Implements hook_entity_info().
- */
-function quiz_question_entity_info() {
-  return array(
-    'quiz_question' => array(
-      'label' => t('Quiz question properties'),
-      'controller class' => 'EntityAPIController',
-      'base table' => 'quiz_question_properties',
-      'entity keys' => array(
-        'id' => 'qqp_id',
-      ),
-      'views controller class' => 'EntityDefaultViewsController',
-    ),
-  );
-}
diff --git a/question_types/quiz_question/quiz_question.pages.inc b/question_types/quiz_question/quiz_question.pages.inc
deleted file mode 100644
index 6cf4da6..0000000
--- a/question_types/quiz_question/quiz_question.pages.inc
+++ /dev/null
@@ -1,247 +0,0 @@
-<?php
-
-/**
- * @file
- * User page callbacks for the quiz_question module.
- */
-
-/**
- * Create the form for the revision actions page
- *
- * Form for deciding what to do with the quizzes a question is member of when the question is
- * revised
- *
- * @param $form_state
- * @param $nid
- *  Question node id
- * @param $vid
- *  Question node version id
- * @return
- *  FAPI form array
- */
-function quiz_question_revision_actions($form, $form_state, $nid, $vid) {
-  // If no questions were kept we shouldn't get here...
-  if (!isset($_SESSION['quiz_question_kept'])) {
-    drupal_goto('node/' . $nid);
-  }
-
-  $form = array();
-  $form['q_nid'] = array('#type' => 'value', '#value' => intval($nid));
-  $form['q_vid'] = array('#type' => 'value', '#value' => intval($vid));
-
-  // Fetch data for all the quizzes that was kept
-  $quiz_nids = array();
-  $quiz_vids = array();
-  foreach ($_SESSION['quiz_question_kept'] as $nid_vid) {
-    $temp = explode('-', $nid_vid);
-    if (_quiz_is_int($temp[0], 0) && _quiz_is_int($temp[1], 0)) {
-      $quiz_nids[] = $temp[0];
-      $quiz_vids[] = $temp[1];
-    }
-  }
-  $quizzes = array();
-  $sql = 'SELECT nr.nid, nr.vid, nr.title, n.status FROM {node_revision} nr
-          JOIN {node} n ON n.nid = nr.nid
-          WHERE nr.vid IN (:vids)';
-  $res = db_query($sql, array(':vids' => $quiz_vids));
-  //while ($res_o = db_fetch_object($res)) {
-  foreach ($res as $res_o) {
-    $res_o->answered = quiz_has_been_answered($res_o);
-    $quizzes[] = $res_o;
-  }
-
-  $text = t('You have created a new revision of a question that belongs to %num quizzes. Choose what you want to do with the different quizzes.', array('%num' => count($quizzes)));
-  $form['intro'] = array(
-    '#markup' => $text,
-  );
-  $form['quizzes'] = array();
-  // Create a form element for each quiz
-  foreach ($quizzes as $quiz) {
-    $published = $quiz->status == 1 ? t('published') : t('unpublished');
-    $answered = $quiz->answered ? t('answered') : t('unanswered');
-
-    // We fetch the revision options from a helper function
-    $options = _quiz_question_revision_options($quiz->status == 1, $quiz->answered);
-
-    $form['quizzes'][$quiz->nid . '-' . $quiz->vid . '-' . $quiz->status . '-' . ($quiz->answered ? '1' : '0')] = array(
-      '#type' => 'radios',
-      '#title' => check_plain($quiz->title) . ' (' . $published . ' ' . t('and') . ' ' . $answered . ')',
-      '#default_value' => $options['default'],
-      '#options' => $options['options'],
-    );
-    $form['quizzes'][$quiz->vid]['#quiz_title'] = $quiz->title;
-  }
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Submit'),
-  );
-  $form['#submit'] = array('quiz_question_revision_actions_submit');
-
-  // Help texts
-  $form['update_expl'] = array(
-    '#type' => 'item',
-    '#title' => t('Update'),
-    '#value' => t('Replace the old revision of the question with the new revision.'),
-  );
-  $form['revise_expl'] = array(
-    '#type' => 'item',
-    '#title' => t('Revise'),
-    '#value' => t('If a quiz has been answered you should make a new revision to ensure that existing answer statistics and reports remain correct.'),
-    '#description' => t('If the new revision of the question only correct spelling errors etc. you don\'t need to revise.'),
-  );
-  return $form;
-}
-
-/**
- * Get revision options for the revision actions page
- *
- * Returns revision options and default option depending on the published and answered status
- * for a quiz
- *
- * @param $published
- *  Publish status for a quiz
- * @param $answered
- *  Has the quiz been answered?
- * @return
- *  Array with values for the #options and #default_value part of a form item
- */
-function _quiz_question_revision_options($published, $answered) {
-  // We create a data structure holding the different options for the different quiz states
-  $struct = array(
-    'published' => array(
-      'answered' => array(
-        'options' => array(
-          // The key is in the form [update][revise][publish]
-          '110' => t('Update, revise and unpublish'),
-          '111' => t('Update and revise'),
-          '101' => t('Update'),
-          '001' => t('Do nothing'),
-        ),
-        'default' => '111',
-      ),
-      'unanswered' => array(
-        'options' => array(
-          '100' => t('Update and Unpublish'),
-          '101' => t('Update'),
-          '001' => t('Do nothing'),
-        ),
-        'default' => '101',
-      ),
-    ),
-    'unpublished' => array(
-      'answered' => array(
-        'options' => array(
-          '111' => t('Update, revise and publish'),
-          '110' => t('Update and revise'),
-          '101' => t('Update and publish'),
-          '100' => t('Update'),
-          '000' => t('Do nothing'),
-        ),
-        'default' => '110',
-      ),
-      'unanswered' => array(
-        'options' => array(
-          '101' => t('Update and publish'),
-          '100' => t('Update'),
-          '000' => t('Do nothing'),
-        ),
-        'default' => '100',
-      ),
-    ),
-  );
-  $published = $published ? 'published' : 'unpublished';
-  $answered = $answered ? 'answered' : 'unanswered';
-  return $struct[$published][$answered];
-}
-
-/**
- * Submit callback for the revision actions page
- */
-function quiz_question_revision_actions_submit($form, &$form_state) {
-  // TODO: Add date check here?
-  // We should't be able to revisit the revision actions page after this.
-  unset($_SESSION['quiz_question_kept']);
-
-  foreach ($form_state['values'] as $key => $value) {
-    if (!preg_match('/^[0-9]+-[0-9]+-[0,1]-[0,1]$/', $key)) {
-      continue;
-    }
-    list($update, $revise, $publish) = str_split($value); //FORMAT: Update[0,1], revise[0,1] and publish[0,1]
-    list($nid, $vid, $published, $answered) = explode('-', $key); //FORMAT: nid(int), vid(int), published[0,1] and answered[0,1]
-    //If we are to revise the quiz we need to do that first...
-    if ($revise == '1') {
-      $quiz_node = node_load((int) $nid, (int) $vid);
-      $quiz_node->revision = 1;
-    }
-    if (!isset($quiz_node) && $publish != $published) {
-      $quiz_node = node_load((int) $nid, (int) $vid);
-    }
-
-    // If the quiz node is to be revised and/or (un)published we save it now
-    if (isset($quiz_node)) {
-      $quiz_node->auto_created = TRUE;
-      $quiz_node->status = $publish;
-      node_save($quiz_node);
-      $quiz_nid = $quiz_node->nid;
-      $quiz_vid = $quiz_node->vid;
-    }
-    else {
-      $quiz_nid = (int) $nid;
-      $quiz_vid = (int) $vid;
-    }
-
-    if ($update == '1') {
-      $res_relationship = db_query('SELECT max_score, auto_update_max_score FROM {quiz_node_relationship}
-              WHERE parent_vid = :parent_vid AND child_nid = :child_nid', array(':parent_vid' => $quiz_vid, ':child_nid' => $form_state['values']['q_nid']))->fetch();
-
-      $auto_update_max_score = isset($res_relationship->auto_update_max_score) ? $res_relationship->auto_update_max_score : 0;
-
-      if ($res_relationship) {
-        $max_score = $res_relationship->max_score;
-      }
-
-      if (!$res_relationship || $auto_update_max_score) {
-        $res = db_query('SELECT max_score FROM {quiz_question_properties}
-                WHERE vid = :vid', array(':vid' => $form_state['values']['q_vid']));
-        $max_score = $res->fetchField();
-      }
-
-      $res = db_query('SELECT weight, question_status FROM {quiz_node_relationship}
-              WHERE parent_nid = :parent_nid AND parent_vid = :parent_vid AND child_nid = :child_nid', array(':parent_nid' => $quiz_nid, ':parent_vid' => $quiz_vid, ':child_nid' => $form_state['values']['q_nid']));
-      if ($res_o = $res->fetch()) {
-        // Remove old revsions of the question from the quiz
-        db_delete('quiz_node_relationship')
-          ->condition('parent_nid', $quiz_nid)
-          ->condition('parent_vid', $quiz_vid)
-          ->condition('child_nid', $form_state['values']['q_nid'])
-          ->execute();
-        $weight = $res_o->weight;
-        $question_status = $res_o->question_status;
-      }
-      else {
-        $weight = 1 + db_query(
-            "SELECT MAX(weight)
-          FROM {quiz_node_relationship}
-          WHERE parent_vid = :parent_vid", array(':parent_vid' => $quiz_vid))->fetchField();
-        $quiz_randomization = db_query(
-          "SELECT randomization
-          FROM {quiz_node_properties}
-          WHERE nid = :nid AND vid = :vid", array(':nid' => $quiz_nid, ':vid' => $quiz_vid))->fetchField();
-        $question_status = $quiz_randomization == 2 ? QUESTION_RANDOM : QUESTION_ALWAYS;
-      }
-      // Insert the newest revision of the question into the quiz
-      $entity = (object) array(
-          'parent_nid' => $quiz_nid,
-          'parent_vid' => $quiz_vid,
-          'child_nid' => $form_state['values']['q_nid'],
-          'child_vid' => $form_state['values']['q_vid'],
-          'max_score' => $max_score,
-          'weight' => $weight,
-          'question_status' => $question_status,
-          'auto_update_max_score' => $auto_update_max_score,
-      );
-      entity_save('quiz_question_relationship', $entity);
-    }
-  }
-}
diff --git a/question_types/quiz_question/quiz_question.test b/question_types/quiz_question/quiz_question.test
deleted file mode 100644
index 047d98e..0000000
--- a/question_types/quiz_question/quiz_question.test
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the quiz question Module.
- */
-
-/**
- * Base test class for Quiz questions.
- */
-abstract class QuizQuestionTestCase extends QuizTestCase {
-
-   var $question_node_type = 'quiz_question';
-
-  /**
-   * Set up a question test case.
-   *
-   * @param array $modules
-   *   Array of modules to enable.
-   * @param array $admin_permissions
-   *   Array of admin permissions to add.
-   * @param array $user_permissions
-   *   Array of user permissions to add.
-   */
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
-    $modules[] = 'quiz_question';
-    $admin_permissions[] = "create {$this->question_node_type} content";
-    $admin_permissions[] = "edit any {$this->question_node_type} content";
-
-    $user_permissions[] = 'access quiz';
-
-    parent::setUp($modules, $admin_permissions, $user_permissions);
-  }
-
-  /**
-   * Test the subclass's quiz question implementation.
-   */
-  public function testQuizQuestionImplementation() {
-    $qq = _quiz_question_get_implementations();
-    $this->assertTrue(isset($qq[$this->question_node_type]), t('Check that the question implementation is correct.'));
-  }
-
-}
diff --git a/question_types/quiz_question/quiz_question.theme.inc b/question_types/quiz_question/quiz_question.theme.inc
deleted file mode 100644
index 67fd963..0000000
--- a/question_types/quiz_question/quiz_question.theme.inc
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/*
- * @file
- * Themes for the quiz question module.
- */
-
-/**
- * Theme the question creation form
- *
- * @param $form
- *  Question creation form
- */
-function theme_quiz_question_creation_form($variables) {
-  $form = $variables['form'];
-  module_load_include('inc', 'quiz', 'quiz.admin');
-  quiz_set_auto_title();
-}
-
-/**
- * Theme the question navigation area.
- */
-function theme_quiz_question_navigation_form($variables) {
-
-}
diff --git a/question_types/scale/README.txt b/question_types/scale/README.txt
deleted file mode 100644
index 264909a..0000000
--- a/question_types/scale/README.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-
-/**
- * @file
- * README file for Quiz Scale.
- */
-
-Quiz Scale
-Allows quiz creators to make a special question type for surveys.
-
-Sponsored by: Norwegian Centre for Telemedicine
-Code: falcon
-
-
-CONTENTS
---------
-
-1.  Introduction
-2.  Installation
-3.  Configuration
-
-----
-1. Introduction
-
-This module is an attempt to make it easy to create simple surveys using the quiz module.
-
-The Scale module lets the quiz creator store preset answer collections, enabling rapid
-creation of survey questions.
-
-----
-2. Installation
-
-To install, unpack the module to your modules folder, and simply enable the module at Admin > Build > Modules.
-
-Several database tables prefixed with quiz_scale are installed to have a separate storage for this module.
-
-----
-3.  Configuration
-Settings are to be found here: admin/quiz/questions_settings
-
-Preset answer collections can be configured at: scale/collection/manage
-Here you can change and delete presets(answer collections).
-
-You can give roles the 'Edit global presets' permission in the permission administration(admin/user/permissions)
-With this permission users can make presets available to all users, and not only themselves.
\ No newline at end of file
diff --git a/question_types/scale/scale.classes.inc b/question_types/scale/scale.classes.inc
deleted file mode 100644
index dda3ede..0000000
--- a/question_types/scale/scale.classes.inc
+++ /dev/null
@@ -1,635 +0,0 @@
-<?php
-
-/**
- * The main classes for the scale question type.
- *
- * These extend code found in quiz_question.classes.inc.
- *
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * Based on:
- * Other question types in the quiz framework.
- *
- *
- *
- * @file
- * Question type, enabling rapid creation of simple surveys using the quiz framework.
- */
-// @todo: We mix the names answer_collection and alternatives. Use either alternative or answer consistently
-
-/**
- * Extension of QuizQuestion.
- */
-class ScaleQuestion extends QuizQuestion {
-
-  // $util will be set to true if an instance of this class is used only as a utility
-  protected $util = FALSE;
-  // (answer)Collection id
-  protected $col_id = NULL;
-
-  /**
-   * Tells the instance that it is beeing used as a utility.
-   *
-   * @param $c_id - answer collection id
-   */
-  public function initUtil($c_id) {
-    $this->util = TRUE;
-    $this->col_id = $c_id;
-  }
-
-  /**
-   * Implementation of saveNodeProperties
-   *
-   * @see QuizQuestion#saveNodeProperties()
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    $is_new_node = $is_new || $this->node->revision == 1;
-    $answer_collection_id = $this->saveAnswerCollection($is_new_node);
-    // Save the answer collection as a preset if the save preset option is checked
-    if (!empty($this->node->save)) {
-      $this->setPreset($answer_collection_id);
-    }
-    if ($is_new_node) {
-      $id = db_insert('quiz_scale_node_properties')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'answer_collection_id' => $answer_collection_id,
-        ))
-        ->execute();
-    }
-    else {
-      db_update('quiz_scale_node_properties')
-        ->fields(array(
-          'answer_collection_id' => $answer_collection_id,
-        ))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-  }
-
-  /**
-   * Add a preset for the current user.
-   *
-   * @param $col_id - answer collection id of the collection this user wants to have as a preset
-   */
-  private function setPreset($col_id) {
-    db_merge('quiz_scale_user')
-      ->key(array(
-        'uid' => $GLOBALS['user']->uid,
-        'answer_collection_id' => $col_id
-      ))
-      ->fields(array(
-        'uid' => $GLOBALS['user']->uid,
-        'answer_collection_id' => $col_id
-      ))
-      ->execute();
-  }
-
-  /**
-   * Stores the answer collection to the database, or identifies an existing collection.
-   *
-   * We try to reuse answer collections as much as possible to minimize the amount of rows in the database,
-   * and thereby improving performance when surveys are beeing taken.
-   *
-   * @param $is_new_node - the question is beeing inserted(not updated)
-   * @param $alt_input - the alternatives array to be saved.
-   * @param $preset - 1 | 0 = preset | not preset
-   * @return
-   *  Answer collection id
-   */
-  public function saveAnswerCollection($is_new_node, array $alt_input = NULL, $preset = NULL) {
-    global $user;
-    if (!isset($alt_input)) {
-      $alt_input = get_object_vars($this->node);
-    }
-    if (!isset($preset) && isset($this->node->save)) {
-      $preset = $this->node->save;
-    }
-
-    $alternatives = array();
-    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
-      if (isset($alt_input['alternative' . $i]) && drupal_strlen($alt_input['alternative' . $i]) > 0) {
-        $alternatives[] = $alt_input['alternative' . $i];
-      }
-    }
-
-    // If an identical answer collection already exists
-    if ($answer_collection_id = $this->existingCollection($alternatives)) {
-      if ($preset == 1) {
-        $this->setPreset($answer_collection_id);
-      }
-      if (!$is_new_node || $this->util) {
-        $col_to_delete = $this->util ? $this->col_id : $this->node->{0}->answer_collection_id;
-        if ($col_to_delete != $answer_collection_id) {
-          // We try to delete the old answer collection
-          $this->deleteCollectionIfNotUsed($col_to_delete, 1);
-        }
-      }
-      return $answer_collection_id;
-    }
-    // Register a new answer collection
-    $answer_collection_id = db_insert('quiz_scale_answer_collection')
-      ->fields(array('for_all' => 1))
-      ->execute();
-
-    // Save as preset if checkbox for preset has been checked
-    if ($preset == 1) {
-      $id = db_insert('quiz_scale_user')
-        ->fields(array(
-          'uid' => $user->uid,
-          'answer_collection_id' => $answer_collection_id,
-        ))
-        ->execute();
-    }
-    // Save the alternatives in the answer collection
-    //db_lock_table('quiz_scale_answer');
-    for ($i = 0; $i < count($alternatives); $i++) {
-      $this->saveAlternative($alternatives[$i], $answer_collection_id);
-    }
-    //db_unlock_tables();
-    return $answer_collection_id;
-  }
-
-  /**
-   * Saves one alternative to the database
-   *
-   * @param $alternative - the alternative(String) to be saved.
-   * @param $answer_collection_id - the id of the answer collection this alternative shall belong to.
-   */
-  private function saveAlternative($alternative, $answer_collection_id) {
-    $id = db_insert('quiz_scale_answer')
-      ->fields(array(
-        'answer_collection_id' => $answer_collection_id,
-        'answer' => $alternative,
-      ))
-      ->execute();
-  }
-
-  /**
-   * Deletes an answer collection if it isn't beeing used.
-   *
-   * @param $answer_collection_id
-   * @param $accept
-   *  If collection is used more than this many times we keep it.
-   * @return
-   *  true if deleted, false if not deleted.
-   */
-  public function deleteCollectionIfNotUsed($answer_collection_id, $accept = 0) {
-    // Check if the collection is someones preset. If it is we can't delete it.
-    $count = db_query('SELECT COUNT(*) FROM {quiz_scale_user} WHERE answer_collection_id = :acid', array(':acid' => $answer_collection_id))->fetchField();
-    if ($count > 0) {
-      return FALSE;
-    }
-
-    // Check if the collection is a global preset. If it is we can't delete it.
-    $for_all = db_query('SELECT for_all FROM {quiz_scale_answer_collection} WHERE id = :id', array(':id' => $answer_collection_id))->fetchField();
-    if ($for_all == 1) {
-      return FALSE;
-    }
-
-    // Check if the collection is used in an existing question. If it is we can't delete it.
-    $count = db_query('SELECT COUNT(*) FROM {quiz_scale_node_properties} WHERE answer_collection_id = :acid', array(':acid' => $answer_collection_id))->fetchField();
-
-    // We delete the answer collection if it isnt beeing used by enough questions
-    if ($count <= $accept) {
-      db_delete('quiz_scale_answer_collection')
-        ->condition('id', $answer_collection_id)
-        ->execute();
-
-      db_delete('quiz_scale_answer')
-        ->condition('answer_collection_id', $answer_collection_id)
-        ->execute();
-      return TRUE;
-    }
-    return FALSE;
-  }
-
-  /**
-   * Finds out if a collection already exists.
-   *
-   * @param $alternatives
-   *  This is the collection that will be compared with the database.
-   * @param $answer_collection_id
-   *  If we are matching a set of alternatives with a given collection that exists in the database.
-   * @param $last_id - The id of the last alternative we compared with.
-   * @return
-   *  TRUE if the collection exists
-   *  FALSE otherwise
-   */
-  private function existingCollection(array $alternatives, $answer_collection_id = NULL, $last_id = NULL) {
-    $my_alts = isset($answer_collection_id) ? $alternatives : array_reverse($alternatives);
-
-    // Find all answers identical to the next answer in $alternatives
-    $sql = 'SELECT id, answer_collection_id FROM {quiz_scale_answer} WHERE answer = :answer';
-    $args[':answer'] = array_pop($my_alts);
-    // Filter on collection id
-    if (isset($answer_collection_id)) {
-      $sql .= ' AND answer_collection_id = :acid';
-      $args[':acid'] = $answer_collection_id;
-    }
-    // Filter on alternative id(If we are investigating a specific collection, the alternatives needs to be in a correct order)
-    if (isset($last_id)) {
-      $sql .= ' AND id = :id';
-      $args[':id'] = $last_id + 1;
-    }
-    $res = db_query($sql, $args);
-    if (!$res_o = $res->fetch()) {
-      return FALSE;
-    }
-
-    /*
-     * If all alternatives has matched make sure the collection we are comparing against in the database
-     * doesn't have more alternatives.
-     */
-    if (count($my_alts) == 0) {
-      $res_o2 = db_query('SELECT * FROM {quiz_scale_answer}
-              WHERE answer_collection_id = :answer_collection_id
-              AND id = :id', array(':answer_collection_id' => $answer_collection_id, ':id' => ($last_id + 2)))->fetch();
-      return ($res_o2) ? FALSE : $answer_collection_id;
-    }
-
-    // Do a recursive call to this function on all answer collection candidates
-    do {
-      $col_id = $this->existingCollection($my_alts, $res_o->answer_collection_id, $res_o->id);
-      if ($col_id) {
-        return $col_id;
-      }
-    } while ($res_o = $res->fetch());
-    return FALSE;
-  }
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validate()
-   */
-  public function validateNode(array &$form) {
-
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete()
-   */
-  public function delete($only_this_version = FALSE) {
-    parent::delete($only_this_version);
-    if ($only_this_version) {
-      db_delete('quiz_scale_user_answers')
-        ->condition('question_nid', $this->node->nid)
-        ->condition('question_vid', $this->node->vid)
-        ->execute();
-
-      db_delete('quiz_scale_node_properties')
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-    else {
-      db_delete('quiz_scale_user_answers')
-        ->condition('question_nid', $this->node->nid)
-        ->execute();
-
-      db_delete('quiz_scale_node_properties')
-        ->condition('nid', $this->node->nid)
-        ->execute();
-    }
-    $this->deleteCollectionIfNotUsed($this->node->{0}->answer_collection_id, 0);
-  }
-
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-
-    $res = db_query('SELECT id, answer, a.answer_collection_id
-            FROM {quiz_scale_node_properties} p
-            JOIN {quiz_scale_answer} a ON (p.answer_collection_id = a.answer_collection_id)
-            WHERE nid = :nid AND vid = :vid
-            ORDER BY a.id', array(':nid' => $this->node->nid, ':vid' => $this->node->vid));
-    foreach ($res as $res_o) {
-      $props[] = $res_o;
-    }
-    $this->nodeProperties = $props;
-    return $props;
-  }
-
-  /**
-   * Implementation of getNodeView
-   *
-   * @see QuizQuestion#view()
-   */
-  public function getNodeView() {
-    $content = parent::getNodeView();
-    $alternatives = array();
-    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
-      if (isset($this->node->{$i}->answer) && drupal_strlen($this->node->{$i}->answer) > 0) {
-        $alternatives[] = check_plain($this->node->{$i}->answer);
-      }
-    }
-    $content['answer'] = array(
-      '#markup' => theme('scale_answer_node_view', array('alternatives' => $alternatives)),
-      '#weight' => 2,
-    );
-    return $content;
-  }
-
-  /**
-   * Implementation of getAnsweringForm
-   *
-   * @see getAnsweringForm($form_state, $result_id)
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $form = parent::getAnsweringForm($form_state, $result_id);
-    //$form['#theme'] = 'scale_answering_form';
-    $options = array();
-    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
-      if (isset($this->node->{$i}) && drupal_strlen($this->node->{$i}->answer) > 0) {
-        $options[$this->node->{$i}->id] = check_plain($this->node->{$i}->answer);
-      }
-    }
-
-    $form = array(
-      '#type' => 'radios',
-      '#title' => t('Choose one'),
-      '#options' => $options,
-    );
-    if (isset($result_id)) {
-      $response = new ScaleResponse($result_id, $this->node);
-      $form['#default_value'] = $response->getResponse();
-    }
-    return $form;
-  }
-
-  /**
-   * Question response validator.
-   */
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-    if ($form_state['values']['question'][$this->node->nid] == '') {
-      form_set_error('', t('You must provide an answer.'));
-    }
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm()
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    $form = array();
-    /*
-     * Getting presets from the database
-     */
-    $collections = $this->getPresetCollections(TRUE);
-    $options = $this->makeOptions($collections);
-    $options['d'] = '-'; // Default
-    // We need to add the available preset collections as javascript so that the alternatives can be populated instantly
-    // when a
-    $jsArray = $this->makeJSArray($collections);
-
-    $form['answer'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Answer'),
-      '#description' => t('Provide alternatives for the user to answer.'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#weight' => -4,
-    );
-    $form['answer']['#theme'][] = 'scale_creation_form';
-    $form['answer']['presets'] = array(
-      '#type' => 'select',
-      '#title' => t('Presets'),
-      '#options' => $options,
-      '#default_value' => 'd',
-      '#description' => t('Select a set of alternatives'),
-      '#attributes' => array('onchange' => 'refreshAlternatives(this)'),
-    );
-    $max_num_alts = variable_get('scale_max_num_of_alts', 10);
-    // TODO: use drupal_add_js($path, 'settings');
-    $form['jsArray'] = array('#markup' => "<script type='text/javascript'>$jsArray var scale_max_num_of_alts = $max_num_alts;</script>");
-    $form['answer']['alternatives'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Alternatives'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    for ($i = 0; $i < $max_num_alts; $i++) {
-      $form['answer']['alternatives']["alternative$i"] = array(
-        '#type' => 'textfield',
-        '#title' => t('Alternative !i', array('!i' => ($i + 1))),
-        '#size' => 60,
-        '#maxlength' => 256,
-        '#default_value' => isset($this->node->{$i}->answer) ? $this->node->{$i}->answer : '',
-        '#required' => $i < 2,
-      );
-    }
-    $form['answer']['alternatives']['save'] = array(// @todo: Rename save to save_as_preset or something
-      '#type' => 'checkbox',
-      '#title' => t('Save as a new preset'),
-      '#description' => t('Current alternatives will be saved as a new preset'),
-      '#default_value' => FALSE,
-    );
-    $form['answer']['manage'] = array('#markup' => l(t('Manage presets'), 'scale/collection/manage'));
-    return $form;
-  }
-
-  /**
-   * Get all available presets for the current user.
-   *
-   * @param $with_defaults
-   * @return
-   *  array holding all the preset collections as an array of objects.
-   *  each object in the array has the following properties:
-   *   ->alternatives(array)
-   *   ->name(string)
-   *   ->for_all(int, 0|1)
-   */
-  public function getPresetCollections($with_defaults = FALSE) {
-    global $user;
-    $collections = array(); // array holding data for each collection
-    $scale_element_names = array();
-    $sql = 'SELECT DISTINCT ac.id AS answer_collection_id, a.answer, ac.for_all
-            FROM {quiz_scale_user} au
-            JOIN {quiz_scale_answer_collection} ac ON(au.answer_collection_id = ac.id)
-            JOIN {quiz_scale_answer} a ON(a.answer_collection_id = ac.id)
-            WHERE au.uid = :uid';
-    if ($with_defaults) {
-      $sql .= ' OR ac.for_all = 1';
-    }
-    $sql .= ' ORDER BY au.answer_collection_id, a.id';
-    $res = db_query($sql, array(':uid' => $user->uid));
-    $col_id = NULL;
-
-    // Populate the $collections array
-    while (true) {
-      if (!($res_o = $res->fetch()) || ($res_o->answer_collection_id != $col_id)) {
-        /*
-         * We have gone through all elements for one answer collection,
-         * and needs to store the answer collection name and id in the options array...
-         */
-        if (isset($col_id)) {
-          $num_scale_elements = count($collections[$col_id]->alternatives);
-          $collections[$col_id]->name = check_plain($collections[$col_id]->alternatives[0] . ' - ' . $collections[$col_id]->alternatives[$num_scale_elements - 1] . ' (' . $num_scale_elements . ')');
-        }
-        // Break the loop if there are no more answer collections to process
-        if (!$res_o) {
-          break;
-        }
-
-        // Init the next collection in the $collections array
-        $col_id = $res_o->answer_collection_id;
-        if (!isset($collections[$col_id])) {
-          $collections[$col_id] = new stdClass();
-          $collections[$col_id]->alternatives = array();
-          $collections[$col_id]->for_all = $res_o->for_all;
-        }
-      }
-      $collections[$col_id]->alternatives[] = check_plain($res_o->answer);
-    }
-    return $collections;
-  }
-
-  /**
-   * Makes options array for form elements.
-   *
-   * @param $collections
-   *  collections array, from getPresetCollections() for instance...
-   * @return
-   *  #options array.
-   */
-  private function makeOptions(array $collections = NULL) {
-    $options = array();
-    foreach ($collections as $col_id => $obj) {
-      $options[$col_id] = $obj->name;
-    }
-    return $options;
-  }
-
-  /**
-   * Makes a javascript constructing an answer collection array.
-   *
-   * @param $collections
-   *  collections array, from getPresetCollections() for instance...
-   * @return
-   *  javascript(string)
-   */
-  private function makeJSArray(array $collections = NULL) {
-    $jsArray = 'scaleCollections = new Array();';
-    foreach ($collections as $col_id => $obj) {
-      if (is_array($collections[$col_id]->alternatives)) {
-        $jsArray .= "scaleCollections[$col_id] = new Array();";
-        foreach ($collections[$col_id]->alternatives as $alt_id => $text) {
-          $jsArray .= "scaleCollections[$col_id][$alt_id] = '" . check_plain($text) . "';";
-        }
-      }
-    }
-    return $jsArray;
-  }
-
-  /**
-   * Implementation of getMaximumScore.
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    // In some use-cases we want to reward users for answering a survey question.
-    // This is why 1 is returned and not zero.
-    return 1;
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class ScaleResponse extends QuizQuestionResponse {
-
-  /**
-   * ID of the answer.
-   */
-  protected $answer_id = 0;
-
-  /**
-   * Constructor
-   */
-  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
-    parent::__construct($result_id, $question_node, $answer);
-
-    if (isset($answer)) {
-      $this->answer_id = intval($answer);
-    }
-    else {
-      $this->answer_id = db_query('SELECT answer_id FROM {quiz_scale_user_answers} WHERE result_id = :rid AND question_nid = :qnid AND question_vid = :qvid', array(':rid' => $result_id, ':qnid' => $this->question->nid, ':qvid' => $this->question->vid))->fetchField();
-    }
-    $answer = db_query('SELECT answer FROM {quiz_scale_answer} WHERE id = :id', array(':id' => $this->answer_id))->fetchField();
-    $this->answer = check_plain($answer);
-  }
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-    $id = db_insert('quiz_scale_user_answers')
-      ->fields(array(
-        'answer_id' => $this->answer_id,
-        'result_id' => $this->result_id,
-        'question_vid' => $this->question->vid,
-        'question_nid' => $this->question->nid,
-      ))
-      ->execute();
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-    db_delete('quiz_scale_user_answers')
-      ->condition('result_id', $this->result_id)
-      ->condition('question_nid', $this->question->nid)
-      ->condition('question_vid', $this->question->vid)
-      ->execute();
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    return $this->isValid() ? 1 : 0;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer_id;
-  }
-
-  /**
-   * Implmenets QuizQuestionResponse::getReportFormResponse().
-   */
-  public function getReportFormResponse() {
-    $data = array();
-    $data[] = array(
-      'choice' => $this->answer,
-    );
-    return $data;
-  }
-
-}
diff --git a/question_types/scale/scale.info b/question_types/scale/scale.info
deleted file mode 100644
index 2bf2403..0000000
--- a/question_types/scale/scale.info
+++ /dev/null
@@ -1,9 +0,0 @@
-name = Scale
-package = Quiz Question
-description = Scale question type for quiz.
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-
-files[] = scale.classes.inc
-files[] = scale.test
diff --git a/question_types/scale/scale.install b/question_types/scale/scale.install
deleted file mode 100644
index ab544ab..0000000
--- a/question_types/scale/scale.install
+++ /dev/null
@@ -1,193 +0,0 @@
-<?php
-
-/**
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * @file
- * Scale Install (a quiz question type)
- */
-
-
-/**
- * Implements hook_install().
- */
-function scale_install() {
-  // Add body field to scale node
-  quiz_question_add_body_field('scale');
-
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_install_schema('scale')
-  _scale_insert_collection(array('Always', 'Very often', 'Some times', 'Rarely', 'Very rarely', 'Never'));
-  _scale_insert_collection(array('Excellent', 'Very good', 'Good', 'Ok', 'Poor', 'Very poor'));
-  _scale_insert_collection(array('Totally agree', 'Agree', 'Not sure', 'Disagree', 'Totally disagree'));
-  _scale_insert_collection(array('Very important', 'Important', 'Moderately important', 'Less important', 'Least important'));
-  variable_set('node_options_scale', array('status'));
-}
-
-/**
- * Implements hook_schema().
- */
-function scale_schema() {
-  $schema['quiz_scale_node_properties'] = array(
-    'description' => 'Properties specific to this question type. Holds information about what answer collection this node uses',
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer_collection_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'nid', 'vid',
-    ),
-  );
-  // Stores the users answers to a question.
-  $schema['quiz_scale_user_answers'] = array(
-    'description' => 'Store the users answers',
-    'fields' => array(
-      'answer_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'result_id', 'question_nid', 'question_vid'
-    ),
-  );
-
-  $schema['quiz_scale_answer_collection'] = array(
-    'description' => 'Answer collection ids and properties',
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'for_all' => array(
-        'description' => '1 for global presets, 0 otherwise',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'id',
-    ),
-  );
-
-  $schema['quiz_scale_user'] = array(
-    'description' => 'User settings, store what answer collections the user have as presets',
-    'fields' => array(
-      'uid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer_collection_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array(
-      'uid', 'answer_collection_id'
-    ),
-  );
-  /*
-   * Holds each answer in the answer_collections.
-   */
-  $schema['quiz_scale_answer'] = array(
-    'description' => 'Holds all the possible answers and what answer collections they belong to',
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer_collection_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer' => array(
-        'type' => 'text',
-      ),
-    ),
-    'primary key' => array(
-      'id',
-    ),
-  );
-  return $schema;
-}
-
-/**
- * Implements hook_uninstall().
- */
-function scale_uninstall() {
-  variable_del('scale_max_num_of_alts');
-  cache_clear_all('variables', 'cache');
-  drupal_set_message(t('The Scale module has been uninstalled. Scale nodes may still exist, but they will not function properly.'));
-}
-
-/**
- * Inserts answer collections into the database
- *
- * @param $answers
- *  Array of answers to include in the answer collection
- */
-function _scale_insert_collection($answers) {
-  // Save the collection as a global preset
-  $collection_id = db_insert('quiz_scale_answer_collection')
-    ->fields(array(
-      'for_all' => 1
-    ))
-    ->execute();
-
-  // Save the collections as a preset collection for user 1
-  db_insert('quiz_scale_user')
-    ->fields(array(
-      'answer_collection_id' => $collection_id,
-      'uid' => 1,
-    ))
-    ->execute();
-
-  $insert = db_insert('quiz_scale_answer')->fields(array('answer_collection_id', 'answer'));
-  $values = array();
-  // Save the answers belonging to the collection
-  for ($i = 0; $i < count($answers); $i++) {
-    $values = array(
-      'answer_collection_id' => $collection_id,
-      'answer' => $answers[$i],
-    );
-    $insert->values($values);
-  }
-  $insert->execute();
-}
diff --git a/question_types/scale/scale.js b/question_types/scale/scale.js
deleted file mode 100644
index 2e9e8b4..0000000
--- a/question_types/scale/scale.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @file
- * Javascript functions for the scale question type.
- */
-
-/**
- * Refreshes alternatives when a preset is selected.
- *
- * @param selection
- *  The select item used to select answer collection
- */
-function refreshAlternatives(selection) {
-  clearAlternatives();
-  var colId = selection.options[selection.selectedIndex].value;
-  var numberOfOptions = scaleCollections[colId].length;
-  for(var i = 0; i<numberOfOptions;i++){
-	jQuery('#edit-alternative' + (i)).val(scaleCollections[colId][i]);
-  }
-}
-
-/**
- * Clears all the alternatives on the scale node form
- */
-function clearAlternatives() {
-  for ( var i = 0; i < scale_max_num_of_alts; i++) {
-	jQuery('#edit-alternative' + (i)).val('');
-  }
-}
diff --git a/question_types/scale/scale.module b/question_types/scale/scale.module
deleted file mode 100644
index fb98e21..0000000
--- a/question_types/scale/scale.module
+++ /dev/null
@@ -1,391 +0,0 @@
-<?php
-
-/**
- * The main file for scale.
- *
- * Sponsored by: Norwegian Centre for Telemedicine
- * Code: falcon
- *
- * @file
- * Scale question type for the Quiz module.
- *
- * Allows the creation of scale questions (ex: likert scale)
- */
-
-/**
- * Implements hook_help().
- */
-function scale_help($path, $args) {
-  if ($path == 'admin/help#scale') {
-    return t('This module provides a scale question type for Quiz. It may be used to construct surveys.');
-  }
-}
-
-/**
- * Implements hook_menu().
- */
-function scale_menu() {
-  $items['scale/collection/manage'] = array(
-    'title' => 'Manage your preset collections',
-    'access callback' => 'node_access',
-    'access arguments' => array('create', 'scale'),
-    'type' => MENU_SUGGESTED_ITEM,
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('scale_manage_collection_form'),
-  );
-  return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function scale_theme($existing, $type, $theme, $path) {
-  $module_path = drupal_get_path('module', 'scale');
-  return array(
-    'scale_creation_form' => array(
-      'render element' => 'form',
-      'path' =>  $module_path . '/theme',
-      'file' => 'scale.theme.inc',
-    ),
-    'scale_response_form' => array(
-      'render element' => 'form',
-      'path' => $module_path . '/theme',
-      'file' => 'scale.theme.inc',
-    ),
-    'scale_answer_node_view' => array(
-      'variables' => array(
-        'alternatives' => NULL,
-      ),
-      'path' => $module_path . '/theme',
-      'file' => 'scale.theme.inc',
-    ),
-    'scale_answering_form' => array(
-      'render element' => 'form',
-      'path' => $module_path . '/theme',
-      'template' => 'scale-answering-form',
-    ),
-  );
-}
-
-/**
- * Implements hook_permission().
- */
-function scale_permission() {
-  return array(
-    'Edit global presets' => array(
-      'title' => t('Administer presets'),
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function scale_quiz_question_info() {
-  return array(
-    'scale' => array(
-      'name' => t('Scale question'),
-      'description' => t('Quiz questions that allow a user to choose from a scale.'),
-      'question provider' => 'ScaleQuestion',
-      'response provider' => 'ScaleResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function scale_quiz_question_config() {
-  $form['manage'] = array('#markup' => l(t('Manage presets'), 'scale/collection/manage'));
-  $form['scale_max_num_of_alts'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Maximum number of alternatives allowed'),
-    '#default_value' => variable_get('scale_max_num_of_alts', 10),
-  );
-  $form['#validate'][] = 'scale_config_validate';
-  return $form;
-}
-
-/**
- * Validate the scale config form values
- */
-function scale_config_validate($form, $form_state) {
-  if (!_quiz_is_int($form_state['values']['scale_max_num_of_alts'], 2, 50)) {
-    form_set_error('scale_max_num_of_alts', t('The number of alternatives must be between 2 and 50'));
-  }
-}
-
-/**
- * Implements hook_get_answer().
- */
-function scale_get_answer($question_nid, $question_vid, $result_id) {
-  $results = db_query('SELECT answer_id, question_vid, question_nid, result_id
-    FROM {quiz_scale_user_answers}
-    WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(':qnid' => $question_nid, ':qvid' => $question_vid, ':rid' => $result_id))->fetchAssoc();
-  return $results ? $results : FALSE;
-}
-
-/**
- * Implements hook_user_cancel().
- */
-function scale_user_cancel($edit, $account, $method) {
-  $results = db_query('SELECT id FROM {quiz_scale_answer_collection} ac
-              JOIN {quiz_scale_user} u ON(ac.id = u.answer_collection_id)
-              WHERE uid = :uid
-              AND ac.for_all = :for_all
-              AND ac.id NOT IN
-                (SELECT answer_collection_id
-                 FROM {quiz_scale_node_properties})
-              AND ac.id NOT IN
-                (SELECT answer_collection_id
-                 FROM {quiz_scale_user}
-                 WHERE NOT uid = :uid)', array(':uid' => $account->uid, ':for_all' => 0));
-  foreach ($results as $result) {
-    db_delete('quiz_scale_answer')
-      ->condition('answer_collection_id', $result->id)
-      ->execute();
-    db_delete('quiz_scale_answer_collection')
-      ->condition('id', $result->id)
-      ->execute();
-  }
-
-  db_delete('quiz_scale_user')
-    ->condition('uid', $account->uid)
-    ->execute();
-}
-
-/**
- * Form for changing and deleting the current users preset answer collections.
- *
- * Users with the Edit global presets permissions can also add new global
- * presets here.
- *
- * @param $form_state
- * @return form
- */
-function scale_manage_collection_form($form, &$form_state) {
-  global $user;
-
-  $edit_globals_access = user_access('Edit global presets');
-  // We create an instance of ScaleQuestion. We want to use some of its methods.
-  $scale_question = new ScaleQuestion(new stdClass());
-  $collections = $scale_question->getPresetCollections($edit_globals_access);
-
-  // If user is allowed to edit global answer collections he is also allowed to add new global presets
-  if ($edit_globals_access) {
-    $new_col = new stdClass();
-    $new_col->for_all = 1;
-    $new_col->name = t('New global collection(available to all users)');
-    $collections['new'] = $new_col;
-  }
-
-  $form = array();
-  if (count($collections) == 0) {
-    $form['no_col'] = array(
-      '#markup' => t("You don't have any preset collections."),
-    );
-    return $form;
-  }
-  // Populate the form
-  foreach ($collections as $col_id => $obj) {
-    $form["collection$col_id"] = array(
-      '#type' => 'fieldset',
-      '#title' => $collections[$col_id]->name,
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      '#group' => 'scale_manage_collection_form',
-    );
-    $alternatives = isset($collections[$col_id]->alternatives) ? $collections[$col_id]->alternatives : array();
-    for ($i = 0; $i < variable_get('scale_max_num_of_alts', 10); $i++) {
-      $form["collection$col_id"]["alternative$i"] = array(
-        '#title' => t('Alternative !i', array('!i' => ($i + 1))),
-        '#size' => 60,
-        '#maxlength' => 256,
-        '#type' => 'textfield',
-        '#default_value' => isset($alternatives[$i]) ? $alternatives[$i] : '',
-        '#required' => (($i < 2) && $col_id != 'new'),
-      );
-    }
-    if ($col_id != 'new') {
-      if ($edit_globals_access) {
-        $form["collection$col_id"]['for_all'] = array(
-          '#type' => 'checkbox',
-          '#title' => t('Available to all users'),
-          '#default_value' => $collections[$col_id]->for_all,
-        );
-      }
-      $form["collection$col_id"]['to-do'] = array(
-        '#type' => 'radios',
-        '#title' => t('What will you do?'),
-        '#default_value' => '0',
-        '#options' => array(
-          t('Save changes, do not change questions using this preset'),
-          t('Save changes, and change your own questions who uses this preset'),
-          t('Delete this preset(This will not affect existing questions)')),
-      );
-    }
-    else {
-      $form["collection$col_id"]["to-do"] = array(
-        '#type' => 'value',
-        '#value' => 3,
-      );
-      $form["collection$col_id"]["for_all"] = array(
-        '#type' => 'value',
-        '#value' => 1,
-      );
-    }
-  }
-  $form['process'] = array(
-    '#type' => 'submit',
-    '#value' => t('Submit'),
-  );
-  $form['#submit'][] = 'scale_collection_form_submit';
-  if ($edit_globals_access) {
-    $form['#validate'][] = 'scale_collection_form_validate';
-  }
-  $form['#tree'] = TRUE;
-  return $form;
-}
-
-/**
- * Handles the scale collection form.
- *
- * @param $form
- * @param $form_state
- */
-function scale_collection_form_submit($form, &$form_state) {
-  global $user;
-
-  $changed = 0;
-  $deleted = 0;
-
-  foreach ($form_state['values'] as $key => $alternatives) {
-    if ($col_id = _scale_get_col_id($key)) {
-      $s_q = new ScaleQuestion(new stdClass());
-      $s_q->initUtil($col_id);
-      switch ($alternatives['to-do']) { // @todo: Rename to-do to $op
-        case 0: //Save, but don't change
-        case 1: //Save and change existing questions
-          $new_col_id = $s_q->saveAnswerCollection(FALSE, $alternatives, 1);
-          if (isset($alternatives['for_all'])) {
-            _scale_set_for_all($new_col_id, $alternatives['for_all']);
-          }
-          if ($new_col_id == $col_id) {
-            break;
-          }
-          $changed++;
-          // We save the changes, but don't change existing questions
-          if ($alternatives['to-do'] == 0) {
-            // The old version of the collection shall not be a preset anymore
-            _scale_unpreset_collection($col_id, $user->uid);
-            // If the old version of the collection doesn't belong to any questions it is safe to delete it.
-            $s_q->deleteCollectionIfNotUsed($col_id);
-
-            if (isset($alternatives['for_all'])) {
-              _scale_set_for_all($new_col_id, $alternatives['for_all']);
-            }
-          }
-          elseif ($alternatives['to-do'] == 1) {
-            // Update all the users questions where the collection is used
-            $nids = db_query('SELECT nid FROM {node} WHERE uid = :uid', array(':uid' => 1))->fetchCol();
-            db_update('quiz_scale_node_properties')
-              ->fields(array('answer_collection_id' => $new_col_id))
-              ->condition('answer_collection_id', $nids, 'IN')
-              ->execute();
-
-            db_delete('quiz_scale_user')
-              ->condition('answer_collection_id', $col_id)
-              ->condition('uid', $user->uid)
-              ->execute();
-            $s_q->deleteCollectionIfNotUsed($col_id);
-          }
-          break;
-        case 2: //Delete
-          $got_deleted = $s_q->deleteCollectionIfNotUsed($col_id);
-          if (!$got_deleted) {
-            _scale_unpreset_collection($col_id, $user->uid);
-          }
-          $deleted++;
-          break;
-        case 3: //New
-          if (drupal_strlen($alternatives['alternative0']) > 0) {
-            $new_col_id = $s_q->saveAnswerCollection(FALSE, $alternatives, 1);
-            _scale_set_for_all($new_col_id, $alternatives['for_all']);
-            drupal_set_message(t('New preset has been added'));
-          }
-          break;
-      }
-    }
-  }
-  if ($changed > 0) {
-    drupal_set_message(t('!changed presets have been changed.', array('!changed' => $changed)));
-  }
-  if ($deleted > 0) {
-    drupal_set_message(t('!deleted presets have been deleted.', array('!deleted' => $deleted)));
-  }
-}
-
-/**
- * Validates the scale collection form
- */
-function scale_collection_form_validate($form, &$form_state) {
-  // If the user is trying to create a new collection
-  if (drupal_strlen($form_state['values']['collectionnew']['alternative0']) > 0) {
-    // If the new collection don't have two alternatives
-    if (drupal_strlen($form_state['values']['collectionnew']['alternative1']) == 0) {
-      // This can't be replaced by adding #required to the form elements. If we did so we would always have to
-      // create a new collection when we press submit
-      form_set_error('collectionnew][alternative1', t('New preset must have atleast two alternatives.'));
-    }
-  }
-}
-
-/**
- * Searches a string for the answer collection id
- *
- * @param $string
- * @return answer collection id
- */
-function _scale_get_col_id($string) {
-  $res = array();
-  $success = preg_match('/^collection([0-9]{1,}|new)$/', $string, $res);
-  return ($success > 0) ? $res[1] : FALSE;
-}
-
-/**
- * Make sure an answer collection isn't a preset for a given user.
- *
- * @param $col_id
- *  Answer_collection_id
- * @param $user_id
- */
-function _scale_unpreset_collection($col_id, $user_id) {
-  db_delete('quiz_scale_user')
-    ->condition('answer_collection_id', $col_id)
-    ->condition('uid', $user_id)
-    ->execute();
-  if (user_access('Edit global presets')) {
-    db_update('quiz_scale_answer_collection')
-      ->fields(array('for_all' =>  0))
-      ->execute();
-  }
-}
-
-/**
- * Make an answer collection (un)available for all question creators.
- *
- * @param $new_col_id
- *  Answer collection id
- * @param $for_all
- *  0 if not for all,
- *  1 if for all
- */
-function _scale_set_for_all($new_col_id, $for_all) {
-  db_update('quiz_scale_answer_collection')
-    ->fields(array(
-      'for_all' => $for_all,
-    ))
-    ->condition('id', $new_col_id)
-    ->execute();
-}
diff --git a/question_types/scale/scale.test b/question_types/scale/scale.test
deleted file mode 100644
index c7dca86..0000000
--- a/question_types/scale/scale.test
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-
-/**
- * @file
- * Test suite for scale question type module.
- */
-
-/**
- * Test class for scale questions.
- */
-class ScaleTestCase extends QuizQuestionTestCase {
-
-  var $question_node_type = 'scale';
-
-  /**
-   * The getInfo() method provides information about the test.
-   * In order for the test to be run, the getInfo() method needs
-   * to be implemented.
-   */
-  public static function getInfo() {
-    return array(
-      'name' => t('Scale'),
-      'description' => t('Unit test for scale question type.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  /**
-   * Implementing setUp() to enable scale module testing
-   */
-  function setUp() {
-    parent::setUp(array('scale'));
-  }
-
-  /**
-   * Create a new question with default settings.
-   */
-  public function testCreateQuizQuestion() {
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    $settings = array();
-    $settings['type'] = 'scale';
-
-
-    $question_node = $this->drupalCreateNode(array(
-      'type' => 'scale',
-      'title' => 'Scale 1 title',
-      'alternative0' => 'This is alternative 1',
-      'alternative1' => 'This is alternative 2',
-      'alternative2' => 'This is alternative 3',
-      'alternative3' => 'This is alternative 4',
-      'alternative4' => 'This is alternative 5',
-      'body' => array(LANGUAGE_NONE => array(array('value' => 'Scale 1 body text'))),
-    ));
-
-    return $question_node;
-  }
-
-  function testTakeQuestion() {
-    $question_node = $this->testCreateQuizQuestion();
-
-    // Link the question.
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    // Test that question appears in lists.
-    $this->drupalGet("node/$quiz_node->nid/quiz/questions");
-    $this->assertText('Scale 1 title');
-
-    // Login as non-admin.
-    $this->drupalLogin($this->user);
-
-    // Take the quiz.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->assertNoText('Scale 1 title');
-    $this->assertText('Scale 1 body text');
-    $this->assertText('This is alternative 1');
-    $this->assertText('This is alternative 2');
-
-    // Test validation.
-    $this->drupalPost(NULL, array(), t('Finish'));
-    $this->assertText('You must provide an answer.');
-
-    // Test any question.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      // Alternative #2 is actually 24 (because of the presets).
-      "question[$question_node->nid]" => 24,
-      ), t('Finish'));
-    $this->assertText('You got 1 of 1 possible points.');
-  }
-
-}
diff --git a/question_types/scale/theme/scale-answering-form.tpl.php b/question_types/scale/theme/scale-answering-form.tpl.php
deleted file mode 100644
index 2ff1c2c..0000000
--- a/question_types/scale/theme/scale-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the scale answering form.
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render($form);
-
-?>
\ No newline at end of file
diff --git a/question_types/scale/theme/scale.theme.inc b/question_types/scale/theme/scale.theme.inc
deleted file mode 100644
index 27305ff..0000000
--- a/question_types/scale/theme/scale.theme.inc
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-/**
- * The theme file for scale.
- *
- * @file
- * Theming functions for the scale question type.
- */
-
-/**
- * Theme function for the answer part of the node view
- *
- * @param $alternatives
- *  Array of alternatives as text
- */
-function theme_scale_answer_node_view($variables) {
-  $alternatives = $variables['alternatives'];
-  $header = array(t('Alternatives'));
-  $rows = array();
-  foreach ($alternatives as $alternative) {
-    $rows[] = array($alternative);
-  }
-  return theme('table', array('header' => $header, 'rows' => $rows));
-}
-
-/**
- * Theme the scale response form
- *
- * @param $form
- *  The response form
- */
-function theme_scale_response_form($variables) {
-  $form = $variables['form'];
-  return drupal_render_children($form);
-}
-
-/**
- * Theme function for the scale creation form.
- *
- * This function only adds js to the page. Js should be added to forms through theme functions
- * like this one. Js can't be added to the page from the form function because sometimes the form
- * is returned from cache.
- *
- * @param $form
- *  The scale creation form
- */
-function theme_scale_creation_form($form) {
-  drupal_add_js(drupal_get_path('module', 'scale') .'/scale.js');
-}
diff --git a/question_types/short_answer/short_answer.admin.inc b/question_types/short_answer/short_answer.admin.inc
deleted file mode 100644
index b0bdc5d..0000000
--- a/question_types/short_answer/short_answer.admin.inc
+++ /dev/null
@@ -1,174 +0,0 @@
-<?php
-/**
- * short_answer.admin
- *
- * @file
- */
-
-/**
- * Generate a view of all unscored short_answer questions.
- *
- * @see theme_short_answer_view_unscored()
- */
-function short_answer_view_unscored() {
-  $unscored = ShortAnswerResponse::fetchAllUnscoredAnswers();
-  return theme('short_answer_view_unscored', array('unscored' => $unscored));
-}
-
-/**
- * Page handler for displaying a scoring form.
- * This function is called directly from the menu router. It generates a form for
- * scoring a quiz.
- *
- * @param $vid
- *  The VID of the question and answer to load.
- * @param $result_id
- *  The result ID of the answer to load.
- * @return
- *  Text to display.
- */
-function short_answer_edit_score($vid, $result_id) {
-  $nid = db_query('SELECT nid FROM {node_revision} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
-  if (!$nid) {
-    drupal_not_found();
-    return;
-  }
-
-  $node = node_load($nid, $vid);
-  if (!$node || $node->type != 'short_answer') {
-    drupal_not_found();
-    return;
-  }
-
-  $answer = (object) short_answer_get_answer($node->nid, $node->vid, $result_id);
-  if (!$answer) {
-    drupal_not_found();
-    return;
-  }
-
-  if ($node->max_score > 0) {
-    $answer->rel_score = round($answer->score * $answer->rel_max_score / $node->max_score);
-  }
-  else {
-    $answer->rel_score = 0;
-  }
-
-  drupal_set_title(t('Score answer to "@title"', array('@title' => $node->title)), PASS_THROUGH);
-
-  return drupal_get_form('short_answer_score_form', $node, $answer);
-}
-
-/**
- * Build a form for scoring long-answer questions.
- *
- * @param $node
- *  The question node.
- * @param $answer
- *  An object containing an answer to the question. This form is for scoring that answer.
- * @return
- *  The form (as a FAPI array).
- */
-function short_answer_score_form($form, $context, $node, $answer) {
-
-  if (!$node || $node->type != 'short_answer' || !$answer) {
-    drupal_not_found();
-    return;
-  }
-  // Set up the form
-  $form['question'] = array(
-    '#type' => 'item',
-    '#title' => t('Question'),
-    '#markup' => check_markup($node->body['und'][0]['value'], $node->body['und'][0]['format']),
-  );
-  $form['answer'] = array(
-    '#type' => 'item',
-    '#title' => t('Answer'),
-    '#markup' => check_markup($answer->answer, variable_get('short_answer_markup_filter')),
-  );
-  $form['show_max_score'] = array(
-    '#type' => 'item',
-    '#title' => t('Maximum Score'),
-    '#markup' => (int) $answer->rel_max_score,
-  );
-  $form['score'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Score'),
-    '#description' => t('The score for this essay, between 0 and @max', array('@max' => $answer->rel_max_score)),
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#default_value' => (int) $answer->rel_score,
-    '#required' => TRUE,
-  );
-  $form['answer_feedback'] = array(
-    '#title' => t('Feedback'),
-    '#type' => 'text_format',
-    '#description' => t('The text entered here would be shown to attendees'),
-    '#default_value' => $answer->answer_feedback,
-    '#format' => isset($answer->answer_feedback_format) ? $answer->answer_feedback_format : NULL 
-  );
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save this score'),
-  );
-
-  // Save some work by keeping these.
-  $form['max_score'] = array(
-    '#type' => 'value',
-    '#value' => $node->max_score,
-  );
-  $form['rel_max_score'] = array(
-    '#type' => 'value',
-    '#value' => $answer->rel_max_score,
-  );
-  $form['question_nid'] = array(
-    '#type' => 'value',
-    '#value' => $answer->question_nid,
-  );
-  $form['question_vid'] = array(
-    '#type' => 'value',
-    '#value' => $answer->question_vid,
-  );
-  $form['result_id'] = array(
-    '#type' => 'value',
-    '#value' => $answer->result_id,
-  );
-
-  return $form;
-}
-
-/**
- * Validate the score form
- */
-function short_answer_score_form_validate($form, $form_state) {
-  // Check to make sure that entered score is not higher than max allowed score.
-  $max = (int) $form_state['values']['rel_max_score'];
-  $given = (int) $form_state['values']['score'];
-
-  if (!_quiz_is_int($form_state['values']['score'], 0, $max)) {
-    $args = array('@score' => $given, '@max' => $max);
-    form_error($form['score'], t('The score needs to be a number between 0 and @max', $args));
-  }
-}
-
-/**
- * Submit the score form
- */
-function short_answer_score_form_submit($form, &$form_state) {
-  $result = db_query('SELECT nid, vid FROM {quiz_node_results} WHERE result_id = :result_id', array(':result_id' => $form_state['values']['result_id']))->fetchobject();
-  $result = short_answer_score_an_answer(array(
-    'quiz' => node_load($result->nid, $result->vid),
-    'nid' => $form_state['values']['question_nid'],
-    'vid' => $form_state['values']['question_vid'],
-    'result_id' => $form_state['values']['result_id'],
-    'score' => $form_state['values']['score'],
-    'max_score' => $form_state['values']['rel_max_score'],
-    'answer_feedback' => $form_state['values']['answer_feedback']
-  ));
-  if ($result == 1) {
-    drupal_set_message("The score has been saved.");
-    $form_state['redirect'] = 'admin/quiz/reports/score-short-answer';
-  }
-  else {
-    drupal_set_message(t('Error saving the score. The selected answer was not scored.'), 'error');
-  }
-}
diff --git a/question_types/short_answer/short_answer.classes.inc b/question_types/short_answer/short_answer.classes.inc
deleted file mode 100644
index 64df33b..0000000
--- a/question_types/short_answer/short_answer.classes.inc
+++ /dev/null
@@ -1,482 +0,0 @@
-<?php
-
-/**
- * The main classes for the short answer question type.
- *
- * These inherit or implement code found in quiz_question.classes.inc.
- *
- * If you are developing your own question type, the easiest place to start is with
- * multichoice.classes.inc. short_answer and long_answer are good for understanding
- * question types that involve textual answers.
- *
- * @file
- */
-
-/**
- * Extension of QuizQuestion.
- *
- * This could have extended long answer, except that that would have entailed
- * adding long answer as a dependency.
- */
-class ShortAnswerQuestion extends QuizQuestion {
-
-  // Constants for answer matching options
-  const ANSWER_MATCH = 0;
-  const ANSWER_INSENSITIVE_MATCH = 1;
-  const ANSWER_REGEX = 2;
-  const ANSWER_MANUAL = 3;
-
-  /**
-   * Implementation of saveNodeProperties
-   *
-   * @see QuizQuestion#saveNodeProperties($is_new)
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    if ($is_new || $this->node->revision == 1) {
-      $id = db_insert('quiz_short_answer_node_properties')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'correct_answer' => $this->node->correct_answer,
-          'correct_answer_evaluation' => $this->node->correct_answer_evaluation,
-        ))
-        ->execute();
-    }
-    else {
-
-      db_update('quiz_short_answer_node_properties')
-        ->fields(array(
-          'correct_answer' => $this->node->correct_answer,
-          'correct_answer_evaluation' => $this->node->correct_answer_evaluation,
-        ))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-  }
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validateNode($form)
-   */
-  public function validateNode(array &$form) {
-    if ($this->node->correct_answer_evaluation != self::ANSWER_MANUAL && empty($this->node->correct_answer)) {
-      form_set_error('correct_answer', t('An answer must be specified for any evaluation type other than manual scoring.'));
-    }
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete($only_this_version)
-   */
-  public function delete($only_this_version = FALSE) {
-    parent::delete($only_this_version);
-    $delete_ans = db_delete('quiz_short_answer_user_answers');
-    $delete_ans->condition('question_nid', $this->node->nid);
-
-    $delete_node = db_delete('quiz_short_answer_node_properties');
-    $delete_node->condition('nid', $this->node->nid);
-
-    if ($only_this_version) {
-      $delete_ans->condition('question_vid', $this->node->vid);
-      $delete_node->condition('vid', $this->node->vid);
-    }
-
-    $delete_ans->execute();
-    $delete_node->execute();
-  }
-
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-    $res_a = db_query('SELECT correct_answer, correct_answer_evaluation FROM {quiz_short_answer_node_properties}
-      WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
-    $this->nodeProperties = (is_array($res_a)) ? array_merge($props, $res_a) : $props;
-    return $this->nodeProperties;
-  }
-
-  /**
-   * Implementation of getNodeView
-   *
-   * @see QuizQuestion#getNodeView()
-   */
-  public function getNodeView() {
-    $content = parent::getNodeView();
-    if ($this->viewCanRevealCorrect()) {
-      $content['answers'] = array(
-        '#markup' => '<div class="quiz-solution">' . check_plain($this->node->correct_answer) . '</div>',
-        '#weight' => 2,
-      );
-    }
-    else {
-      $content['answers'] = array(
-        '#markup' => '<div class="quiz-answer-hidden">Answer hidden</div>',
-        '#weight' => 2,
-      );
-    }
-    return $content;
-  }
-
-  /**
-   * Implementation of getAnsweringForm
-   *
-   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $form = parent::getAnsweringForm($form_state, $result_id);
-    //$form['#theme'] = 'short_answer_answering_form';
-
-    $form = array(
-      '#type' => 'textfield',
-      '#title' => t('Answer'),
-      '#description' => t('Enter your answer here'),
-      '#default_value' => '',
-      '#size' => 60,
-      '#maxlength' => 256,
-      '#required' => FALSE,
-      '#attributes' => array('autocomplete' => 'off'),
-    );
-
-    if (isset($result_id)) {
-      $response = new ShortAnswerResponse($result_id, $this->node);
-      $form['#default_value'] = $response->getResponse();
-    }
-
-    return $form;
-  }
-
-  /**
-   * Question response validator.
-   */
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-    if ($form_state['values']['question'][$this->node->nid] == '') {
-      form_set_error('', t('You must provide an answer.'));
-    }
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm($form_state)
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    $form['answer'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Answer'),
-      '#description' => t('Provide the answer and the method by which the answer will be evaluated.'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#weight' => -4,
-    );
-
-    $options = array(
-      self::ANSWER_MATCH => t('Automatic and case sensitive'),
-      self::ANSWER_INSENSITIVE_MATCH => t('Automatic. Not case sensitive'),
-    );
-    $access_regex = user_access('use regex for short answer');
-    if ($access_regex) {
-      $options[self::ANSWER_REGEX] = t('Match against a regular expression (answer must match the supplied regular expression)');
-    }
-    $options[self::ANSWER_MANUAL] = t('Manual');
-
-    $form['answer']['correct_answer_evaluation'] = array(
-      '#type' => 'radios',
-      '#title' => t('Pick an evaluation method'),
-      '#description' => t('Choose how the answer shall be evaluated.'),
-      '#options' => $options,
-      '#default_value' => isset($this->node->correct_answer_evaluation) ? $this->node->correct_answer_evaluation : self::ANSWER_INSENSITIVE_MATCH,
-      '#required' => FALSE,
-    );
-    if ($access_regex) {
-      $form['answer']['regex_box'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('About regular expressions'),
-        '#collapsible' => TRUE,
-        '#collapsed' => TRUE,
-      );
-
-      $form['answer']['regex_box']['regex_help'] = array(
-        '#markup' => '<p>' .
-        t('Regular expressions are an advanced syntax for pattern matching. They allow you to create a concise set of rules that must be met before a value can be considered a match.') .
-        '</p><p>' .
-        t('For more on regular expression syntax, visit !url.', array('!url' => l('the PHP regular expressions documentation', 'http://www.php.net/manual/en/book.pcre.php'))) .
-        '</p>',
-      );
-    }
-
-    $form['answer']['correct_answer'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Correct answer'),
-      '#description' => t('Specify the answer. If this question is manually scored, no answer needs to be supplied.'),
-      '#default_value' => isset($this->node->correct_answer) ? $this->node->correct_answer : '',
-      '#size' => 60,
-      '#maxlength' => 256,
-      '#required' => FALSE,
-    );
-    return $form;
-  }
-
-  /**
-   * Implementation of getMaximumScore
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    return variable_get('short_answer_default_max_score', 5);
-  }
-
-  /**
-   * Evaluate the correctness of an answer based on the correct answer and evaluation method.
-   */
-  public function evaluateAnswer($user_answer) {
-    $score = 0;
-
-    // Ignore white spaces for correct answer and user's answer.
-    $user_answer = trim($user_answer);
-    $this->node->correct_answer = trim($this->node->correct_answer);
-
-    switch ($this->node->correct_answer_evaluation) {
-      case self::ANSWER_MATCH:
-        if ($user_answer == $this->node->correct_answer) {
-          $score = $this->node->max_score;
-        }
-        break;
-      case self::ANSWER_INSENSITIVE_MATCH:
-        if (drupal_strtolower($user_answer) == drupal_strtolower($this->node->correct_answer)) {
-          $score = $this->node->max_score;
-        }
-        break;
-      case self::ANSWER_REGEX:
-        if (preg_match($this->node->correct_answer, $user_answer) > 0) {
-          $score = $this->node->max_score;
-        }
-        break;
-    }
-    return $score;
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class ShortAnswerResponse extends QuizQuestionResponse {
-
-  /**
-   * Get all quiz scores that haven't been evaluated yet.
-   *
-   * @param $count
-   *  Number of items to return (default: 50).
-   * @param $offset
-   *  Where in the results we should start (default: 0).
-   *
-   * @return
-   *  Array of objects describing unanswered questions. Each object will have result_id, question_nid, and question_vid.
-   */
-  public static function fetchAllUnscoredAnswers($count = 50, $offset = 0) {
-    global $user;
-    $query = db_select('quiz_short_answer_user_answers', 'a');
-    $query->fields('a', array('result_id', 'question_nid', 'question_vid', 'answer_feedback', 'answer_feedback_format'));
-    $query->fields('r', array('title'));
-    $query->fields('qnr', array('time_end', 'time_start', 'uid'));
-    $query->join('node_revision', 'r', 'a.question_vid = r.vid');
-    $query->join('quiz_node_results', 'qnr', 'a.result_id = qnr.result_id');
-    $query->join('node', 'n', 'qnr.nid = n.nid');
-    $query->condition('a.is_evaluated', 0);
-
-    if (user_access('score own quiz') && user_access('score taken quiz answer')) {
-      $query->condition(db_or()->condition('n.uid', $user->uid)->condition('qnr.uid', $user->uid));
-    }
-    else if (user_access('score own quiz')) {
-      $query->condition('n.uid', $user->uid);
-    }
-    else if (user_access('score taken quiz answer')) {
-      $query->condition('qnr.uid', $user->uid);
-    }
-
-    $results = $query->execute();
-    $unscored = array();
-    foreach ($results as $row) {
-      $unscored[] = $row;
-    }
-    return $unscored;
-  }
-
-  /**
-   * Given a question, return a list of all of the unscored answers.
-   *
-   * @param $nid
-   *  Node ID for the question to check.
-   * @param $vid
-   *  Version ID for the question to check.
-   * @param $count
-   *  Number of items to return (default: 50).
-   * @param $offset
-   *  Where in the results we should start (default: 0).
-   *
-   * @return
-   *  Indexed array of result IDs that need to be scored.
-   */
-  public static function fetchUnscoredAnswersByQuestion($nid, $vid, $count = 50, $offset = 0) {
-    return db_query('SELECT result_id FROM {quiz_short_answer_user_answers}
-      WHERE is_evaluated = :is_evaluated AND question_nid = :question_nid AND question_vid = :question_vid', array(':is_evaluated' => 0, ':question_nid' => $nid, ':question_vid' => $vid)
-      )->fetchCol();
-  }
-
-  /**
-   * ID of the answer.
-   */
-  protected $answer_id = 0;
-
-  /**
-   * Constructor
-   */
-  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
-    parent::__construct($result_id, $question_node, $answer);
-    if (!isset($answer)) {
-      $r = db_query('SELECT answer_id, answer, is_evaluated, score, question_vid, question_nid, result_id, answer_feedback, answer_feedback_format
-        FROM {quiz_short_answer_user_answers}
-        WHERE question_nid = :qnid AND question_vid = :qvid AND result_id = :rid', array(':qnid' => $question_node->nid, ':qvid' => $question_node->vid, ':rid' => $result_id)
-        )->fetch();
-
-      if (!empty($r)) {
-        $this->answer = $r->answer;
-        $this->score = $r->score;
-        $this->evaluated = $r->is_evaluated;
-        $this->answer_id = $r->answer_id;
-        $this->answer_feedback = $r->answer_feedback;
-        $this->answer_feedback_format = $r->answer_feedback_format;
-      }
-    }
-    else {
-      if (is_array($answer)) {
-        $this->answer = $answer['answer'];
-      }
-      else {
-        $this->answer = $answer;
-        $this->evaluated = $this->question->correct_answer_evaluation != ShortAnswerQuestion::ANSWER_MANUAL;
-      }
-    }
-  }
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-    // We need to set is_evaluated depending on whether the type requires evaluation.
-    $this->is_evaluated = (int) ($this->question->correct_answer_evaluation != ShortAnswerQuestion::ANSWER_MANUAL);
-    $this->answer_id = db_insert('quiz_short_answer_user_answers')
-      ->fields(array(
-        'answer' => $this->answer,
-        'question_nid' => $this->question->nid,
-        'question_vid' => $this->question->vid,
-        'result_id' => $this->result_id,
-        'score' => $this->getScore(FALSE),
-        'is_evaluated' => $this->is_evaluated,
-      ))
-      ->execute();
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-    db_delete('quiz_short_answer_user_answers')
-      ->condition('question_nid', $this->question->nid)
-      ->condition('question_vid', $this->question->vid)
-      ->condition('result_id', $this->result_id)
-      ->execute();
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    // Manual scoring means we go with what is in the DB.
-    if ($this->question->correct_answer_evaluation == ShortAnswerQuestion::ANSWER_MANUAL) {
-      $score = db_query('SELECT score FROM {quiz_short_answer_user_answers} WHERE result_id = :result_id AND question_vid = :question_vid', array(':result_id' => $this->result_id, ':question_vid' => $this->question->vid))->fetchField();
-      if (!$score) {
-        $score = 0;
-      }
-    }
-    // Otherwise, we run the scoring automatically.
-    else {
-      $shortAnswer = new ShortAnswerQuestion($this->question);
-      $score = $shortAnswer->evaluateAnswer($this->getResponse());
-    }
-    return $score;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer;
-  }
-
-  /**
-   * Implementation of getReportFormResponse
-   */
-  public function getReportFormResponse() {
-    $data = array();
-
-    $data[] = array(
-      'choice' => '',
-      'attempt' => $this->answer,
-      'correct' => !$this->evaluated ? t('This answer has not yet been scored.') : '',
-      'score' => $this->getScore(),
-      'answer_feedback' => check_markup($this->answer_feedback, $this->answer_feedback_format),
-      'question_feedback' => '',
-      'solution' => '',
-    );
-
-    return $data;
-  }
-
-  public function getReportFormAnswerFeedback() {
-    return array(
-      '#title' => t('Enter feedback'),
-      '#type' => 'text_format',
-      '#default_value' => $this->answer_feedback,
-      '#format' => isset($this->answer_feedback_format) ? $this->answer_feedback_format : filter_default_format(),
-      '#attributes' => array('class' => array('quiz-report-score')),
-    );
-  }
-
-  /**
-   * Implementation of getReportFormSubmit
-   *
-   * @see QuizQuestionResponse#getReportFormSubmit()
-   */
-  public function getReportFormSubmit() {
-    return 'short_answer_report_submit';
-  }
-
-  /**
-   * Implements QuizQuestionResponse::getReportFormValidate().
-   */
-  public function getReportFormValidate(&$element, &$form_state) {
-    // Check to make sure that entered score is not higher than max allowed score.
-    if ($element['score']['#value'] > $this->question->max_score) {
-      form_error($element['score'], t('The score needs to be a number between 0 and @max', array('@max' => $this->question->max_score)));
-    }
-  }
-
-}
diff --git a/question_types/short_answer/short_answer.info b/question_types/short_answer/short_answer.info
deleted file mode 100644
index c090cad..0000000
--- a/question_types/short_answer/short_answer.info
+++ /dev/null
@@ -1,9 +0,0 @@
-name = Short Answer
-package = Quiz Question
-description = Short answer question type for Quiz.
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-
-files[] = short_answer.classes.inc
-files[] = short_answer.test
diff --git a/question_types/short_answer/short_answer.install b/question_types/short_answer/short_answer.install
deleted file mode 100644
index 8e2a6c0..0000000
--- a/question_types/short_answer/short_answer.install
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-
-/**
- * The installer file for short_answer.
- * @file
- */
-
-/**
- * Implements hook_install().
- */
-function short_answer_install() {
-  // Add body field to short answer node
-  quiz_question_add_body_field('short_answer');
-  variable_set('node_options_short_answer', array('status'));
-}
-
-/**
- * Implements hook_uninstall().
- */
-function short_answer_uninstall() {
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_uninstall_schema('short_answer')
-}
-
-/**
- * Implements hook_schema().
- */
-function short_answer_schema() {
-  // Properties for a question nodes go in here:
-  $schema['quiz_short_answer_node_properties'] = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'maximum_score' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      // One of (0) textfield and (1) textare
-      'text_entry_type' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      // One of exact (0), case insensitive (1), regex (2), manual (3)
-      'correct_answer_evaluation' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      // Correct answer
-      'correct_answer' => array(
-        'type' => 'text'
-      ),
-      // Feedback if answer is correct
-      'feedback_correct'  => array(
-        'type' => 'text'
-      ),
-      // Feedback if answer is incorrect
-      'feedback_incorrect'  => array(
-        'type' => 'text'
-      ),
-    ),
-    'primary key' => array('nid', 'vid'),
-  );
-
-  // User answers go in here.
-  $schema['quiz_short_answer_user_answers'] = array(
-    'fields' => array(
-      'answer_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'score' => array(
-        'type' => 'float',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      // This may be set to false if manual scoring is used.
-      'is_evaluated' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'size' => 'tiny',
-        'default' => 0,
-        'not null' => TRUE,
-      ),
-      'answer' => array(
-        'type' => 'text'
-      ),
-      'answer_feedback' => array(
-        'type' => 'text'
-      ),
-      'answer_feedback_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-      ),
-    ),
-    'primary key' => array('answer_id'),
-    'unique keys' => array(
-      'ids' => array('result_id', 'question_nid', 'question_vid'),
-    ),
-  );
-  return $schema;
-}
-
-
-/**
- * Adding feedback field to table {quiz_short_answer_user_answers}
- */
-function short_answer_update_7401() {
-  if (!db_field_exists('quiz_short_answer_user_answers', 'answer_feedback')) {
-    $spec = array(
-      'type' => 'text',
-      'not null' => FALSE,
-    );
-    db_add_field('quiz_short_answer_user_answers', 'answer_feedback', $spec);
-  }
-}
-
-/**
- * Adding feedback format field to table {quiz_short_answer_user_answers}
- */
-function short_answer_update_7402() {
-  if (!db_field_exists('quiz_short_answer_user_answers', 'answer_feedback_format')) {
-    $spec = array(
-        'type' => 'varchar',
-        'length' => 255,
-    );
-    db_add_field('quiz_short_answer_user_answers', 'answer_feedback_format', $spec);
-  }
-}
diff --git a/question_types/short_answer/short_answer.module b/question_types/short_answer/short_answer.module
deleted file mode 100644
index ee2fb62..0000000
--- a/question_types/short_answer/short_answer.module
+++ /dev/null
@@ -1,252 +0,0 @@
-<?php
-
-/**
- * The main file for short_answer.
- *
- * Short answer is structurally similar to long answer. However, the module
- * mechanism makes it very difficult for these two modules (either one of
- * which may be disabled) to effectively share code.
- * @file
- */
-
-/**
- * Implements hook_help().
- */
-function short_answer_help($path, $args) {
-  if ($path == 'admin/help#short_answer') {
-    return t('This module provides a short answer question type for Quiz.');
-  }
-}
-
-/**
- * Implements hook_permission().
- */
-function short_answer_permission() {
-  return array(
-    'use regex for short answer' => array(
-      'title' => t('use regex for short answer'),
-      'description' => t('Use PHP "regular expressions" the advanced option for automated response evaluation.'),
-      'restrict access' => TRUE,
-    ),
-  );
-}
-
-/**
- * Implements hook_menu().
- */
-function short_answer_menu() {
-  $items['admin/quiz/reports/score-short-answer'] = array(
-    'title' => 'Score short answer questions',
-    'description' => 'Score the answers from quizzes that use short answer questions.',
-    'page callback' => 'short_answer_view_unscored',
-    'access arguments' => array('score any quiz', 'score own quiz', 'score taken quiz answer'),
-    'access callback' => 'quiz_access_multi_or',
-    'type' => MENU_NORMAL_ITEM,
-    'file' => 'short_answer.admin.inc',
-  );
-  // Pass vid and rid to this path.
-  $items['admin/quiz/reports/score-short-answer/%/%'] = array(
-    'title' => 'Score short answer response',
-    'description' => 'Score a response to a short answer question.',
-    'page callback' => 'short_answer_edit_score',
-    'page arguments' => array(4, 5),
-    'type' => MENU_NORMAL_ITEM,
-    'access arguments' => array(4, 5),
-    'access callback' => 'quiz_question_access_to_score',
-    'file' => 'short_answer.admin.inc'
-  );
-
-  return $items;
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function short_answer_quiz_question_info() {
-  return array(
-    'short_answer' => array(
-      'name' => t('Short answer question'),
-      'description' => t('Quiz questions that allow a user to enter a line of text.'),
-      'question provider' => 'ShortAnswerQuestion',
-      'response provider' => 'ShortAnswerResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function short_answer_quiz_question_config() {
-  $form['short_answer_default_max_score'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default max score'),
-    '#description' => t('Choose the default maximum score for a short answer question.'),
-    '#default_value' => variable_get('short_answer_default_max_score', 5),
-  );
-  $form['#validate'][] = 'short_answer_config_validate';
-  return $form;
-}
-
-/**
- * Validate the long_answer config form values
- */
-function short_answer_config_validate($form, $form_state) {
-  if ($form_state['values']['short_answer_default_max_score'] <= 0) {
-    form_set_error('short_answer_default_max_score', t('The default max score must be greater than 0'));
-  }
-}
-
-/**
- * Implements hook_theme().
- */
-function short_answer_theme($existing, $type, $theme, $path) {
-  $module_path = drupal_get_path('module', 'short_answer');
-  return array(
-    'short_answer_view_unscored' => array(
-      'variables' => array('unscored' => array()),
-      'path' => $module_path . '/theme',
-      'file' => 'short_answer.theme.inc',
-    ),
-    'short_answer_response_form' => array(
-      'render element' => 'form',
-      'path' => $module_path . '/theme',
-      'file' => 'short_answer.theme.inc',
-    ),
-    'short_answer_user_answer' => array(
-      'variables' => array('answer' => NULL, 'correct' => NULL),
-      'path' => $module_path . '/theme',
-      'file' => 'short_answer.theme.inc',
-    ),
-    'short_answer_answering_form' => array(
-      'render element' => 'form',
-      'path' => $module_path . '/theme',
-      'template' => 'short-answer-answering-form',
-    ),
-  );
-}
-
-/**
- * Set a score for a short answer question.
- *
- * This stores a score for a short answer question and marks that question as having been evaluated.
- * The function updates all of the necessary data sources so that the individual answer results should be
- * reflected in the total scoring table.
- *
- * @param $quiz
- *  Quiz node.
- * @param $nid
- *  Node ID of question.
- * @param $vid
- *  Version ID of question.
- * @param $result_id
- *  Result ID for the quiz results.
- * @param $score
- *  The numeric score to assign the result.
- * @param $update_total
- *  Shall the total score for a quiz be updated?
- *
- * @return int
- *  Number of scores adjusted. If a change was made, this should be 1.
- */
-function short_answer_score_an_answer($values, $update_total = TRUE) {
-  extract($values);
-  // When we set the score we make sure that the max score in the quiz the question belongs to is considered
-  $question_max_score = db_query('SELECT max_score FROM {quiz_question_properties} WHERE nid = :nid AND vid = :vid', array(':nid' => $nid, ':vid' => $vid))->FetchField();
-
-  $quiz_max_score = db_query('SELECT max_score FROM {quiz_node_relationship} WHERE parent_vid = :pvid AND child_vid = :cvid', array(':pvid' => $quiz->vid, ':cvid' => $vid))->fetchField();
-
-  $changed = db_update('quiz_short_answer_user_answers')
-    ->fields(array(
-      'score' => $score * $question_max_score / $quiz_max_score,
-      'is_evaluated' => 1,
-      'answer_feedback' => empty($answer_feedback['value']) ? '' : $answer_feedback['value'],
-      'answer_feedback_format' => empty($answer_feedback['format']) ? '' : $answer_feedback['format'],
-    ))
-    ->condition('question_nid', $nid)
-    ->condition('question_vid', $vid)
-    ->condition('result_id', $result_id)
-    ->execute();
-  // Now the short answer user data has been updated. We also need to update the data in the quiz tables
-  if ($changed > 0) {
-    $max = db_query('SELECT max_score FROM {quiz_question_properties} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
-    if ($max <= 0) {
-      $is_correct = 0;
-      $points_awarded = 0;
-    }
-    else {
-      $is_correct = ($score / $max > 0.5) ? 1 : 0;
-      $points_awarded = $score;
-    }
-
-    db_update('quiz_node_results_answers')
-      ->fields(array(
-        'points_awarded' => $points_awarded,
-        'is_correct' => $is_correct,
-      ))
-      ->condition('question_vid', $vid)
-      ->condition('result_id', $result_id)
-      ->execute();
-
-    // Third, we update the main quiz results table
-    if ($update_total) {
-      quiz_update_total_score($quiz, $result_id);
-    }
-  }
-  return $changed;
-}
-
-/**
- * Submit the result report for short answer
- */
-function short_answer_report_submit($values) {
-  short_answer_score_an_answer($values, FALSE);
-}
-
-/**
- * Get the answer for a question.
- *
- * This stores a score for a short answer question and marks that question as having been evaluated.
- * @param $question_nid
- *  Node ID of question.
- * @param $question_vid
- *  Version ID of question.
- * @param $result_id
- *  Result ID for the quiz results.
- *
- * @return Assoc array
- *  An array if successful, or FALSE if no result could be found. The array contains the following properties:
- *  <code>
- *  answer_id; // The answer ID
- *  answer; // The full text of the answer
- *  is_evaluated; // 0 if the question has not been evaluated, 1 if it has
- *  score; // The score the evaluator gave the user; this should be 0 if is_evaluated is 0.
- *  question_vid
- *  question_nid
- *  result_id
- *  </code>
- */
-function short_answer_get_answer($question_nid, $question_vid, $result_id) {
-  $results = db_query('SELECT sa.answer_id, sa.answer, sa.is_evaluated, sa.score, sa.question_vid, sa.question_nid, sa.result_id, sa.answer_feedback, sa.answer_feedback_format, rel.max_score AS rel_max_score
-    FROM {quiz_short_answer_user_answers} sa
-    JOIN {quiz_node_results} qnr ON (sa.result_id = qnr.result_id)
-    JOIN {quiz_node_relationship} rel ON (qnr.vid = rel.parent_vid AND rel.child_vid = sa.question_vid)
-    WHERE sa.question_nid = :qnid AND sa.question_vid = :qvid AND sa.result_id = :rid', array(':qnid' => $question_nid, ':qvid' => $question_vid, ':rid' => $result_id))->fetchAssoc();
-  return $results ? $results : FALSE;
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function short_answer_field_extra_fields() {
-  $extra['node']['short_answer'] = array(
-    'form' => array(
-      'answer' => array(
-        'label' => t('Answer'),
-        'description' => t('Provide the answer and the method by which the answer will be evaluated.'),
-        'weight' => -4,
-      ),
-    ),
-  );
-  return $extra;
-}
diff --git a/question_types/short_answer/short_answer.test b/question_types/short_answer/short_answer.test
deleted file mode 100644
index df1d3ec..0000000
--- a/question_types/short_answer/short_answer.test
+++ /dev/null
@@ -1,220 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the short_answer Module.
- */
-
-/**
- * Test class for short answer.
- */
-class ShortAnswerTestCase extends QuizQuestionTestCase {
-
-  var $question_node_type = 'short_answer';
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Short answer'),
-      'description' => t('Unit test for Short answers question type.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  function setUp() {
-    parent::setUp(array('short_answer'), array('score any quiz'));
-  }
-
-  /**
-   * Test case insensitive graded questions.
-   */
-  public function testGradeAnswerInsensitive() {
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    $question_node = $this->testCreateQuizQuestion(array(
-      'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_INSENSITIVE_MATCH,
-      'correct_answer' => 'the Zero One Infinity rule',
-    ));
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    // Test incorrect.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'This is an incorrect answer.',
-      ), t('Finish'));
-    $this->assertText('Your score: 0%');
-
-    // Test correct.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'the Zero One Infinity rule',
-      ), t('Finish'));
-    $this->assertText('Your score: 100%');
-
-    // Test correct.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'the zero one Infinity rule',
-      ), t('Finish'));
-    $this->assertText('Your score: 100%');
-  }
-
-  /**
-   * Test case sensitive graded questions.
-   */
-  public function testGradeAnswerSensitive() {
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'review_options' => array('end' => array('score' => 'score')),
-    ));
-
-    $question_node = $this->testCreateQuizQuestion(array(
-      'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_MATCH,
-      'correct_answer' => 'the Zero One Infinity rule',
-    ));
-    $this->linkQuestionToQuiz($question_node, $quiz_node);
-
-    // Test incorrect.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'the zero one Infinity rule',
-      ), t('Finish'));
-    $this->assertText('Your score: 0%');
-
-    // Test correct.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'the Zero One Infinity rule',
-      ), t('Finish'));
-    $this->assertText('Your score: 100%');
-  }
-
-  /**
-   * Test regex graded questions.
-   */
-  public function testGradeAnswerRegex() {
-    $this->drupalLogin($this->admin);
-
-    $quiz_node = $this->drupalCreateQuiz();
-
-    $question_node = $this->testCreateQuizQuestion(array(
-      'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_REGEX,
-      'correct_answer' => '/Zero One Infinity/i',
-    ));
-    $this->linkQuestionToQuiz($question_node, $quiz_node);
-
-    // Test incorrect.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'um some rule, I forget',
-      ), t('Finish'));
-    $this->assertText('Your score: 0%');
-
-    // Test correct.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 'the answer is the zero one infinity rule',
-      ), t('Finish'));
-    $this->assertText('Your score: 100%');
-  }
-
-  /**
-   * Test manually graded questions.
-   *
-   * Note: we use two questions here to make sure the grading form is handled
-   * correctly.
-   */
-  public function testGradeAnswerManualFeedback() {
-    $this->drupalLogin($this->admin);
-
-    $quiz_node = $this->drupalCreateQuiz();
-
-    $question_node1 = $this->testCreateQuizQuestion(array(
-      'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_MANUAL,
-      'correct_answer' => 'the Zero One Infinity rule',
-    ));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-
-    $question_node2 = $this->testCreateQuizQuestion(array(
-      'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_MANUAL,
-      'correct_answer' => 'The number two is ridiculous and cannot exist',
-    ));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-
-    // Test correct.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 'the answer is the zero one infinity rule',
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 'the number two really is ridiculous',
-      ), t('Finish'));
-    $this->assertText('Your score: 0%');
-    // Strange behavior - extra spaces in HTML.
-    //$this->assertText('Score ? of 10');
-    $this->assertText('This answer has not yet been scored.');
-    $this->assertNoFieldByName('0[score]');
-    $this->assertNoFieldByName('1[score]');
-    $this->assertNoFieldByName('0[answer_feedback][value]');
-    $this->assertNoFieldByName('1[answer_feedback][value]');
-    $this->assertNoRaw(t('Save score'));
-    $url_of_result = $this->getUrl();
-
-    // Test grading the question.
-    $this->drupalLogin($this->admin);
-    $this->drupalGet($url_of_result);
-    $this->assertFieldByName('0[score]');
-    $this->assertFieldByName('1[score]');
-    // Admin has to provide feedback.
-    $this->drupalLogin($this->admin);
-    $this->drupalGet($url_of_result);
-    $this->drupalPost(NULL, array(
-      "0[score]" => 2,
-      "1[score]" => 3,
-      "0[answer_feedback][value]" => 'Feedback for answer 1.',
-      "1[answer_feedback][value]" => 'Feedback for answer 2.',
-      "0[answer_feedback][format]" => 'filtered_html',
-      "1[answer_feedback][format]" => 'filtered_html',
-      ), t('Save score'));
-    $this->assertText('The scoring data you provided has been saved.');
-
-    // Test the score is visible to the user.
-    $this->drupalLogin($this->user);
-    $this->drupalGet($url_of_result);
-    $this->assertText('You got 5 of 10 possible points.');
-    $this->assertText('Your score: 50%');
-    // Strange behavior - extra spaces in HTML.
-    //$this->assertText('Score 2 of 5');
-    //$this->assertText('Score 3 of 5');
-    $this->assertText('Feedback for answer 1.');
-    $this->assertText('Feedback for answer 2.');
-  }
-
-  /**
-   * Test adding and taking a truefalse question.
-   */
-  function testCreateQuizQuestion($settings = NULL) {
-    if (!$settings) {
-      $settings = array(
-        'correct_answer_evaluation' => ShortAnswerQuestion::ANSWER_INSENSITIVE_MATCH,
-        'correct_answer' => 'SA 1 correct answer.',
-      );
-    }
-
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    $question_node = $this->drupalCreateNode(array(
-      'type' => $this->question_node_type,
-      'title' => 'SA 1 title',
-      'body' => array(LANGUAGE_NONE => array(array('value' => 'SA 1 body text.'))),
-      ) + $settings);
-
-    return $question_node;
-  }
-
-}
diff --git a/question_types/short_answer/theme/short-answer-answering-form.tpl.php b/question_types/short_answer/theme/short-answer-answering-form.tpl.php
deleted file mode 100644
index 4a54b09..0000000
--- a/question_types/short_answer/theme/short-answer-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the short_answer answering form.
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render($form);
-
-?>
\ No newline at end of file
diff --git a/question_types/short_answer/theme/short_answer.theme.inc b/question_types/short_answer/theme/short_answer.theme.inc
deleted file mode 100644
index 35b8d92..0000000
--- a/question_types/short_answer/theme/short_answer.theme.inc
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/**
- * @file
- * Theme functions for short_answer.
- */
-
-/**
- * Theme the list of unscored short answer questions.
- *
- * @param $unscored
- *  An array of objects with information about the unscored questions
- */
-function theme_short_answer_view_unscored($variables) {
-  $unscored = $variables['unscored'];
-  $output = '';
-  $header = array(
-    t('Question'),
-    t('Time Finished'),
-    t('Action')
-  );
-  $rows = array();
-
-  foreach ($unscored as $item) {
-    if ($item->time_end > 0) {
-      $rows[] = array(
-        $item->title,
-        date('Y-m-d H:i', $item->time_end),
-        l(t('score this response'), 'admin/quiz/reports/score-short-answer/' . $item->question_vid . '/' . $item->result_id),
-      );
-    }
-  }
-
-  if (!empty($rows)) {
-    $output .= theme('table', array('header' => $header, 'rows' => $rows));
-  }
-  else {
-    $output .= t('There are no unscored essays.');
-  }
-  return $output;
-}
-
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function theme_short_answer_user_answer($variables) {
-  $answer = $variables['answer'];
-  $correct = $variables['correct'];
-  $header = array(t('Correct Answer'), t('User Answer'));
-  $row = array(array($correct, $answer));
-  return theme('table', array('header' => $header, 'rows' => $row));
-}
-
-/**
- * Theme the short_answer response form
- *
- * @param $form
- *  The response form
- */
-function theme_short_answer_response_form($variables) {
-  $form = $variables['form'];
-  return drupal_render_children($form);
-}
diff --git a/question_types/truefalse/theme/truefalse-answering-form.tpl.php b/question_types/truefalse/theme/truefalse-answering-form.tpl.php
deleted file mode 100644
index 4d26eac..0000000
--- a/question_types/truefalse/theme/truefalse-answering-form.tpl.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Handles the layout of the truefalse answering form. 
- *
- *
- * Variables available:
- * - $form
- */
-print drupal_render($form);
-
-?>
\ No newline at end of file
diff --git a/question_types/truefalse/truefalse.classes.inc b/question_types/truefalse/truefalse.classes.inc
deleted file mode 100644
index 0fa02c5..0000000
--- a/question_types/truefalse/truefalse.classes.inc
+++ /dev/null
@@ -1,309 +0,0 @@
-<?php
-
-/**
- * Defines the classes necessary for a True/False quiz.
- *
- * @file
- */
-
-/**
- * Extension of QuizQuestion.
- */
-class TrueFalseQuestion extends QuizQuestion {
-
-  /**
-   * Implementation of saveNodeProperties
-   *
-   * @see QuizQuestion#saveNodeProperties($is_new)
-   */
-  public function saveNodeProperties($is_new = FALSE) {
-    if ($is_new || $this->node->revision == 1) {
-      $id = db_insert('quiz_truefalse_node')
-        ->fields(array(
-          'nid' => $this->node->nid,
-          'vid' => $this->node->vid,
-          'correct_answer' => $this->node->correct_answer,
-        ))
-        ->execute();
-    }
-    else {
-      db_update('quiz_truefalse_node')
-        ->fields(array(
-          'correct_answer' => (int) $this->node->correct_answer,
-        ))
-        ->condition('nid', $this->node->nid)
-        ->condition('vid', $this->node->vid)
-        ->execute();
-    }
-  }
-
-  /**
-   * Implementation of validateNode
-   *
-   * @see QuizQuestion#validateNode($form)
-   */
-  public function validateNode(array &$form) {
-    // This space intentionally left blank. :)
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestion#delete($only_this_version)
-   */
-  public function delete($only_this_version = FALSE) {
-    parent::delete($only_this_version);
-
-    $delete_ans = db_delete('quiz_truefalse_user_answers');
-    $delete_ans->condition('question_nid', $this->node->nid);
-
-    $delete_node = db_delete('quiz_truefalse_node');
-    $delete_node->condition('nid', $this->node->nid);
-
-    if ($only_this_version) {
-      $delete_ans->condition('question_vid', $this->node->vid);
-      $delete_node->condition('vid', $this->node->vid);
-    }
-
-    $delete_ans->execute();
-    $delete_node->execute();
-  }
-
-  /**
-   * Implementation of getNodeProperties
-   *
-   * @see QuizQuestion#getNodeProperties()
-   */
-  public function getNodeProperties() {
-    if (isset($this->nodeProperties)) {
-      return $this->nodeProperties;
-    }
-    $props = parent::getNodeProperties();
-
-    $res_a = db_query('SELECT correct_answer FROM {quiz_truefalse_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchAssoc();
-
-    if (is_array($res_a)) {
-      $props = array_merge($props, $res_a);
-    }
-    $this->nodeProperties = $props;
-    return $props;
-  }
-
-  /**
-   * Implementation of getNodeView
-   *
-   * @see QuizQuestion#getNodeView()
-   */
-  public function getNodeView() {
-    $content = parent::getNodeView();
-    if ($this->viewCanRevealCorrect()) {
-      $answer = ($this->node->correct_answer) ? t('True') : t('False');
-      $content['answers']['#markup'] = '<div class="quiz-solution">' . $answer . '</div>';
-      $content['answers']['#weight'] = 2;
-    }
-    else {
-      $content['answers'] = array(
-        '#markup' => '<div class="quiz-answer-hidden">' . t('Answer hidden') . '</div>',
-        '#weight' => 2,
-      );
-    }
-    return $content;
-  }
-
-  /**
-   * Implementation of getAnsweringForm
-   *
-   * @see QuizQuestion#getAnsweringForm($form_state, $result_id)
-   */
-  public function getAnsweringForm(array $form_state = NULL, $result_id) {
-    $element = parent::getAnsweringForm($form_state, $result_id);
-    //$form['#theme'] = 'truefalse_answering_form';
-    // 'tries' is unfortunately required by quiz.module
-    $element += array(
-      '#type' => 'radios',
-      '#title' => t('Choose one'),
-      '#options' => array(
-        1 => t('True'),
-        0 => t('False')
-      ),
-      '#default_value' => NULL, // prevent default value set to NULL
-    );
-
-    if (isset($result_id)) {
-      $response = new TrueFalseResponse($result_id, $this->node);
-      $default = $response->getResponse();
-      $element['#default_value'] = is_null($default) ? NULL : $default;
-    }
-    return $element;
-  }
-
-  /**
-   * Question response validator.
-   */
-  public function getAnsweringFormValidate(array &$form, array &$form_state = NULL) {
-    if (is_null($form_state['values']['question'][$this->node->nid])) {
-      form_set_error('', t('You must provide an answer.'));
-    }
-  }
-
-  /**
-   * Implementation of getBodyFieldTitle
-   *
-   * @see QuizQuestion#getBodyFieldTitle()
-   */
-  public function getBodyFieldTitle() {
-    return t('True/false statement');
-  }
-
-  /**
-   * Implementation of getCreationForm
-   *
-   * @see QuizQuestion#getCreationForm($form_state)
-   */
-  public function getCreationForm(array &$form_state = NULL) {
-    $form['correct_answer'] = array(
-      '#type' => 'radios',
-      '#title' => t('Correct answer'),
-      '#options' => array(
-        1 => t('True'),
-        0 => t('False'),
-      ),
-      '#default_value' => isset($this->node->correct_answer) ? $this->node->correct_answer : 1,
-      '#required' => TRUE,
-      '#weight' => -4,
-      '#description' => t('Choose if the correct answer for this question is "true" or "false".')
-    );
-    return $form;
-  }
-
-  /**
-   * Implementation of getMaximumScore
-   *
-   * @see QuizQuestion#getMaximumScore()
-   */
-  public function getMaximumScore() {
-    return 1;
-  }
-
-  /**
-   * Get the answer to this question.
-   *
-   * This is a utility function. It is not defined in the interface.
-   */
-  public function getCorrectAnswer() {
-    return db_query('SELECT correct_answer FROM {quiz_truefalse_node} WHERE nid = :nid AND vid = :vid', array(':nid' => $this->node->nid, ':vid' => $this->node->vid))->fetchField();
-  }
-
-}
-
-/**
- * Extension of QuizQuestionResponse
- */
-class TrueFalseResponse extends QuizQuestionResponse {
-
-  /**
-   * Constructor
-   */
-  public function __construct($result_id, stdClass $question_node, $answer = NULL) {
-    parent::__construct($result_id, $question_node, $answer);
-    if (!isset($answer)) {
-      // Get from DB
-      $r = $this->getUserAnswer();
-      if (!empty($r)) {
-        $this->answer = $r->answer;
-        $this->score = $r->score;
-      }
-    }
-    else {
-      // Load from input
-      $this->answer = $answer;
-    }
-  }
-
-  /**
-   * Implementation of save
-   *
-   * @see QuizQuestionResponse#save()
-   */
-  public function save() {
-    db_insert('quiz_truefalse_user_answers')
-      ->fields(array(
-        'question_nid' => $this->question->nid,
-        'question_vid' => $this->question->vid,
-        'result_id' => $this->result_id,
-        'answer' => (int) $this->answer,
-        'score' => (int) $this->getScore(),
-      ))
-      ->execute();
-  }
-
-  /**
-   * Implementation of delete
-   *
-   * @see QuizQuestionResponse#delete()
-   */
-  public function delete() {
-    db_delete('quiz_truefalse_user_answers')
-      ->condition('question_nid', $this->question->nid)
-      ->condition('question_vid', $this->question->vid)
-      ->condition('result_id', $this->result_id)
-      ->execute();
-  }
-
-  /**
-   * Implementation of score
-   *
-   * @see QuizQuestionResponse#score()
-   */
-  public function score() {
-    $tfQuestion = new TrueFalseQuestion($this->question);
-    return ($this->getResponse() == $tfQuestion->getCorrectAnswer()) ? 1 : 0;
-  }
-
-  /**
-   * Implementation of getResponse
-   *
-   * @see QuizQuestionResponse#getResponse()
-   */
-  public function getResponse() {
-    return $this->answer;
-  }
-
-  /**
-   * Implementation of getCorrectAnswer
-   */
-  public function getUserAnswer() {
-    return db_query('SELECT answer, score FROM {quiz_truefalse_user_answers} WHERE question_vid = :qvid AND result_id = :rid', array(':qvid' => $this->question->vid, ':rid' => $this->result_id))->fetch();
-  }
-
-  /**
-   * Implementation of getReportFormResponse
-   */
-  public function getReportFormResponse() {
-
-    $answer = $this->question->answers[0]['answer'];
-    $correct_answer = $this->question->correct_answer;
-
-    $data = array();
-    $data[] = array(
-      'choice' => 'True',
-      'attempt' => $answer ? quiz_icon('selected') : '',
-      'correct' => $answer == 1 ? quiz_icon($correct_answer ? 'correct' : 'incorrect') : '',
-      'score' => intval($correct_answer == 1 && $answer == 1),
-      'answer_feedback' => '',
-      'solution' => $correct_answer == 1 ? quiz_icon('should') : '',
-    );
-
-    $data[] = array(
-      'choice' => 'False',
-      'attempt' => !$answer ? quiz_icon('selected') : '',
-      'correct' => $answer == 0 ? (quiz_icon(!$correct_answer ? 'correct' : 'incorrect')) : '',
-      'score' => intval($correct_answer == 0 && $answer == 0),
-      'answer_feedback' => '',
-      'solution' => $correct_answer == 0 ? quiz_icon('should') : '',
-    );
-
-    return $data;
-  }
-
-}
diff --git a/question_types/truefalse/truefalse.info b/question_types/truefalse/truefalse.info
deleted file mode 100644
index 7df8197..0000000
--- a/question_types/truefalse/truefalse.info
+++ /dev/null
@@ -1,9 +0,0 @@
-name = True False
-package = Quiz Question
-description = Module to create True or False Kind of Questions
-core = 7.x
-dependencies[] = quiz
-dependencies[] = quiz_question
-
-files[] = truefalse.classes.inc
-files[] = truefalse.test
diff --git a/question_types/truefalse/truefalse.install b/question_types/truefalse/truefalse.install
deleted file mode 100644
index cef1283..0000000
--- a/question_types/truefalse/truefalse.install
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-
-
-/**
- * @file
- * The installer file for truefalse question type. *
- */
-
-/**
- * Implements hook_install().
- */
-function truefalse_install() {
-  // Add body field to true false node
-  quiz_question_add_body_field('truefalse');
-
-  if (!db_table_exists('quiz_truefalse_node')) {
-    // TODO The drupal_(un)install_schema functions are called automatically in D7.
-    // drupal_install_schema('truefalse')
-  }
-  else {
-    // TODO Please review the conversion of this statement to the D7 database API syntax.
-    /* db_query("UPDATE {system} SET name = 'truefalse' WHERE name = 'true_false'") */
-    db_update('system')
-      ->fields(array(
-        'name' =>  'truefalse',
-      ))
-      ->condition('name', 'true_false')
-      ->execute();
-    // TODO Please review the conversion of this statement to the D7 database API syntax.
-    /* db_query("UPDATE {node} SET type = 'truefalse' WHERE type = 'true_false'") */
-    db_update('node')
-      ->fields(array(
-        'type' =>  'truefalse',
-      ))
-      ->condition('type', 'true_false')
-      ->execute();
-    // TODO Please review the conversion of this statement to the D7 database API syntax.
-    /* db_query("UPDATE {system} SET type = 'truefalse' WHERE type = 'true_false'") */
-    db_update('system')
-      ->fields(array(
-        'type' =>  'truefalse',
-      ))
-      ->condition('type', 'true_false')
-      ->execute();
-    // TODO Please review the conversion of this statement to the D7 database API syntax.
-    /* db_query("UPDATE {taxonomy_vocabulary_node_type} SET type = 'truefalse' WHERE type = 'true_false'") */
-    /*
-    db_update('taxonomy_vocabulary_node_type')
-  ->fields(array(
-    'type' =>  'truefalse',
-  ))
-  ->condition('type', 'true_false')
-  ->execute();
-  */
-  }
-  variable_set('node_options_truefalse', array('status'));
-}
-
-/**
- * Implements hook_uninstall().
- */
-function truefalse_uninstall() {
-  // TODO The drupal_(un)install_schema functions are called automatically in D7.
-  // drupal_uninstall_schema('truefalse')
-}
-
-/**
- * Implements hook_schema().
- */
-function truefalse_schema() {
-
-  //////////////////////
-  // TRUE/FALSE tables.
-  //////////////////////
-
-  // Extensions to nodes for T/F
-  $schema['quiz_truefalse_node'] = array(
-    'fields' => array(
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'correct_answer' => array(
-        'description' => 'The correct answer for the user to give. 1 for true.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'size' => 'tiny',
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'vid', 'nid'
-    ),
-  );
-
-  // Place to store user answers for T/F questions
-  $schema['quiz_truefalse_user_answers'] = array(
-    'fields' => array(
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'answer' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'size' => 'tiny',
-        'default' => 0,
-      ),
-      'score' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array(
-      'result_id', 'question_nid', 'question_vid',
-    ),
-  );
-
-  return $schema;
-}
diff --git a/question_types/truefalse/truefalse.module b/question_types/truefalse/truefalse.module
deleted file mode 100644
index 2aa2d35..0000000
--- a/question_types/truefalse/truefalse.module
+++ /dev/null
@@ -1,95 +0,0 @@
-<?php
-
-/**
- * @file
- * TrueFalse question type for quiz module
- *
- * Allows the creation of "True or False" questions
- */
-
-
-/**
- * Implements hook_help().
- */
-function truefalse_help($path, $args) {
-  switch ($path) {
-    case 'admin/modules#description':
-      return t('TrueFalse question type for the quiz module.');
-    case 'node/add#truefalse':
-    case 'admin/help#truefalse':
-      return t('A question type for the quiz module. A simplified version of multichoice where "true" and "false" are the alternatives.');
-    default:
-      break;
-  }
-}
-
-/**
- * Implements hook_quiz_question_info().
- */
-function truefalse_quiz_question_info() {
-  return array(
-    'truefalse' => array(
-      'name' => t('True/false question'),
-      'description' => t('Quiz questions that allow a user to select "true" or "false" as his response to a statement.'),
-      'question provider' => 'TrueFalseQuestion',
-      'response provider' => 'TrueFalseResponse',
-      'module' => 'quiz_question', // All wrapper functions are in that module.
-    ),
-  );
-}
-
-/**
- * Implements hook_quiz_question_config().
- */
-function truefalse_quiz_question_config() {
-  return FALSE;
-}
-
-/**
- * Implements hook_theme().
- */
-function truefalse_theme() {
-  return array(
-    'truefalse_answering_form' => array(
-      'render element' => 'form',
-      'path' => drupal_get_path('module', 'truefalse') . '/theme',
-      'template' => 'truefalse-answering-form',
-    ),
-    'truefalse_response' => array(
-      'variables' => array(
-        'metadata' => array(),
-        'data' => array(),
-      ),
-    ),
-  );
-}
-
-/**
- * Theme the response part of the response report
- *
- * @param $metadata
- *  Can be used as a table header
- * @param $data
- *  Can be used as table rows
- */
-function theme_truefalse_response($variables) {
-  $metadata = $variables['metadata'];
-  $data = $variables['data'];
-  return theme('table', array('header' => $metadata, 'rows' => $data));
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function truefalse_field_extra_fields() {
-  $extra['node']['truefalse'] = array(
-    'form' => array(
-      'correct_answer' => array(
-        'label' => t('Correct answer'),
-        'description' => t('The answer for this question.'),
-        'weight' => -4,
-      ),
-    ),
-  );
-  return $extra;
-}
diff --git a/question_types/truefalse/truefalse.test b/question_types/truefalse/truefalse.test
deleted file mode 100644
index 5121a6d..0000000
--- a/question_types/truefalse/truefalse.test
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the truefalse Module.
- */
-
-/**
- * Test class for true false questions.
- */
-class TrueFalseTestCase extends QuizQuestionTestCase {
-
-  var $question_node_type = 'truefalse';
-
-  public static function getInfo() {
-    return array(
-      'name' => t('True/False'),
-      'description' => t('Unit test for true or false question type.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  function setUp() {
-    parent::setUp(array('truefalse'));
-  }
-
-  /**
-   * Test adding and taking a truefalse question.
-   */
-  function testCreateQuizQuestion() {
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    $question_node = $this->drupalCreateNode(array(
-      'type' => 'truefalse',
-      'title' => 'TF 1 title',
-      'correct_answer' => 1,
-      'body' => array(LANGUAGE_NONE => array(array('value' => 'TF 1 body text'))),
-    ));
-
-    return $question_node;
-  }
-
-  function testTakeQuestion() {
-    $question_node = $this->testCreateQuizQuestion();
-
-    // Create the quiz and question.
-    $quiz_node = $this->drupalCreateQuiz();
-
-    // Link the question.
-    $question_node->quiz_nid = $quiz_node->nid;
-    $question_node->quiz_vid = $quiz_node->vid;
-    $qq = _quiz_question_get_instance($question_node);
-    $qq->saveRelationships();
-
-    // Test that question appears in lists.
-    $this->drupalGet("node/$quiz_node->nid/quiz/questions");
-    $this->assertText('TF 1 title');
-
-    // Login as non-admin.
-    $this->drupalLogin($this->user);
-
-    // Take the quiz.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->assertNoText('TF 1 title');
-    $this->assertText('TF 1 body text');
-    $this->assertText('True');
-    $this->assertText('False');
-
-    // Test validation.
-    $this->drupalPost(NULL, array(), t('Finish'));
-    $this->assertText('You must provide an answer.');
-
-    // Test correct question.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 1,
-      ), t('Finish'));
-    $this->assertText('You got 1 of 1 possible points.');
-
-    // Test incorrect question.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 0,
-      ), t('Finish'));
-    $this->assertText('You got 0 of 1 possible points.');
-  }
-
-  function testQuestionFeedback() {
-    // Login as our privileged user.
-    $this->drupalLogin($this->admin);
-
-    // Create the quiz and question.
-    $question_node = $this->testCreateQuizQuestion();
-
-    // Link the question.
-    $quiz_node = $this->linkQuestionToQuiz($question_node);
-
-    // Test incorrect question with all feedbacks on.
-
-    // Login as non-admin.
-    $this->drupalLogin($this->user);
-    // Take the quiz.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 1,
-      ), t('Finish'));
-    $this->assertRaw('quiz-score-icon correct');
-    $this->assertRaw('quiz-score-icon should');
-    // Take the quiz.
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 0,
-      ), t('Finish'));
-    $this->assertRaw('quiz-score-icon incorrect');
-    $this->assertRaw('quiz-score-icon should');
-  }
-
-}
diff --git a/quiz.admin.inc b/quiz.admin.inc
deleted file mode 100644
index 7adca9f..0000000
--- a/quiz.admin.inc
+++ /dev/null
@@ -1,1588 +0,0 @@
-<?php
-
-/**
- * Administrator interface for Quiz module.
- *
- * @file
- */
-// QUIZ ADMIN
-// Quiz Admin Settings
-
-/**
- * This builds the main settings form for the quiz module.
- */
-function quiz_admin_settings($form, &$form_state) {
-  $form = array();
-
-  $form['quiz_global_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Global configuration'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#description' => t('Control aspects of the Quiz module\'s display'),
-  );
-
-  $form['quiz_global_settings']['quiz_auto_revisioning'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Auto revisioning'),
-    '#default_value' => variable_get('quiz_auto_revisioning', 1),
-    '#description' => t('It is strongly recommended that auto revisioning is always on. It makes sure that when a question or quiz is changed a new revision is created if the current revision has been answered. If this feature is switched off result reports might be broken because a users saved answer might be connected to a wrong version of the quiz and/or question she was answering. All sorts of errors might appear.'),
-  );
-
-  $form['quiz_global_settings']['quiz_durod'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Delete results when a user is deleted'),
-    '#default_value' => variable_get('quiz_durod', 0),
-    '#description' => t('When a user is deleted delete any and all results for that user.'),
-  );
-
-  $form['quiz_global_settings']['quiz_index_questions'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Index questions'),
-    '#default_value' => variable_get('quiz_index_questions', 1),
-    '#description' => t('If you turn this off, questions will not show up in search results.'),
-  );
-
-  $form['quiz_global_settings']['quiz_default_close'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Default number of days before a @quiz is closed', array('@quiz' => QUIZ_NAME)),
-    '#default_value' => variable_get('quiz_default_close', 30),
-    '#size' => 4,
-    '#maxlength' => 4,
-    '#description' => t('Supply a number of days to calculate the default close date for new quizzes.'),
-  );
-
-  $form['quiz_global_settings']['quiz_use_passfail'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Allow quiz creators to set a pass/fail option when creating a @quiz.', array('@quiz' => strtolower(QUIZ_NAME))),
-    '#default_value' => variable_get('quiz_use_passfail', 1),
-    '#description' => t('Check this to display the pass/fail options in the @quiz form. If you want to prohibit other quiz creators from changing the default pass/fail percentage, uncheck this option.', array('@quiz' => QUIZ_NAME)),
-  );
-
-  $form['quiz_global_settings']['quiz_max_result_options'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Maximum result options'),
-    '#description' => t('Set the maximum number of result options (categorizations for scoring a quiz). Set to 0 to disable result options.'),
-    '#default_value' => variable_get('quiz_max_result_options', 5),
-    '#size' => 2,
-    '#maxlength' => 2,
-    '#required' => FALSE,
-  );
-
-  $form['quiz_global_settings']['quiz_remove_partial_quiz_record'] = array(
-    '#type' => 'select',
-    '#title' => t('Remove incomplete quiz records (older than)'),
-    '#options' => quiz_remove_partial_quiz_record_value(),
-    '#description' => t('Number of days to keep incomplete quiz attempts.'),
-    '#default_value' => variable_get('quiz_remove_partial_quiz_record', quiz_remove_partial_quiz_record_value()),
-  );
-
-  $form['quiz_global_settings']['quiz_autotitle_length'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Length of automatically set question titles'),
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#description' => t('Integer between 0 and 128. If the question creator doesn\'t set a question title the system will make a title automatically. Here you can decide how long the autotitle can be.'),
-    '#default_value' => variable_get('quiz_autotitle_length', 50),
-  );
-
-  $form['quiz_global_settings']['quiz_pager_start'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Pager start'),
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#description' => t('If a quiz has this many questions, a pager will be displayed instead of a select box.'),
-    '#default_value' => variable_get('quiz_pager_start', 100),
-  );
-
-  $form['quiz_global_settings']['quiz_pager_siblings'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Pager siblings'),
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#description' => t('Number of siblings to show.'),
-    '#default_value' => variable_get('quiz_pager_siblings', 5),
-  );
-
-  $target = array(
-    'attributes' => array(
-      'target' => '_blank'
-    ),
-  );
-
-  $links = array(
-    '!jquery_countdown' => l(t('JQuery Countdown'), 'http://drupal.org/project/jquery_countdown', $target),
-    '!userpoints' => l(t('UserPoints'), 'http://drupal.org/project/userpoints', $target),
-  );
-
-  $form['quiz_addons'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Addons configuration'),
-    '#description' => t('Quiz has built in integration with some other modules. Disabled checkboxes indicates that modules are not enabled.', $links),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $form['quiz_addons']['quiz_has_userpoints'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('User Points'),
-    '#default_value' => variable_get('quiz_has_userpoints', 0),
-    '#description' => t('!userpoints is an <strong>optional</strong> module for Quiz. It provides ways for users to gain or lose points for performing certain actions on your site like completing a Quiz.', $links),
-    '#disabled' => !module_exists('userpoints'),
-  );
-
-  $form['quiz_addons']['quiz_has_timer'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Display timer'),
-    '#default_value' => variable_get('quiz_has_timer', 0),
-    '#description' => t("!jquery_countdown is an <strong>optional</strong> module for Quiz. It is used to display a timer when taking a quiz. Without this timer, the user will not know how much time they have left to complete the Quiz", $links),
-    '#disabled' => !function_exists('jquery_countdown_add'),
-  );
-
-  $form['quiz_look_feel'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Look and feel'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#description' => t('Control aspects of the Quiz module\'s display'),
-  );
-
-  $form['quiz_look_feel']['quiz_name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Display name'),
-    '#default_value' => QUIZ_NAME,
-    '#description' => t('Change the name of the quiz type. Do you call it <em>test</em> or <em>assessment</em> instead? Change the display name of the module to something else. By default, it is called <em>Quiz</em>.'),
-    '#required' => TRUE,
-  );
-
-  $form['quiz_email_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Notifications'),
-    '#description' => t('Send results to quiz author/attendee via email. Configure email subject and body.'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $form['quiz_email_settings']['taker'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Email for Quiz takers'),
-    '#collapsible' => FALSE,
-  );
-
-  $form['quiz_email_settings']['taker']['quiz_email_results'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Email results to quiz takers'),
-    '#default_value' => variable_get('quiz_email_results', 0),
-    '#description' => t('Check this to send users their results at the end of a quiz.')
-  );
-
-  $form['quiz_email_settings']['taker']['quiz_email_results_subject_taker'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Configure email subject'),
-    '#description' => t('This format will be used when sending quiz results at the end of a quiz.'),
-    '#default_value' => variable_get('quiz_email_results_subject_taker', quiz_email_results_format('subject', 'taker')),
-  );
-
-  $form['quiz_email_settings']['taker']['quiz_email_results_body_taker'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Configure email format'),
-    '#description' => t('This format will be used when sending quiz results at the end of a quiz. !title(quiz title), !sitename, !taker(quiz takers username), !date(time when quiz was finished), !minutes(How many minutes the quiz taker spent taking the quiz), !desc(description of the quiz), !correct(points attained), !total(max score for the quiz), !percentage(percentage score), !url(url to the result page) and !author are placeholders.'),
-    '#default_value' => variable_get('quiz_email_results_body_taker', quiz_email_results_format('body', 'taker')),
-  );
-
-  $form['quiz_email_settings']['author'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Email for Quiz authors'),
-    '#collapsible' => FALSE,
-  );
-
-  $form['quiz_email_settings']['author']['quiz_results_to_quiz_author'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Email all results to quiz author'),
-    '#default_value' => variable_get('quiz_results_to_quiz_author', 0),
-    '#description' => t('Check this to send quiz results for all users to the quiz author.'),
-  );
-
-  $form['quiz_email_settings']['author']['quiz_email_results_subject'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Configure email subject'),
-    '#description' => t('This format will be used when sending quiz results at the end of a quiz. Authors and quiz takers gets the same format.'),
-    '#default_value' => variable_get('quiz_email_results_subject', quiz_email_results_format('subject', 'author')),
-  );
-
-  $form['quiz_email_settings']['author']['quiz_email_results_body'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Configure email format'),
-    '#description' => t('This format will be used when sending @quiz results at the end of a quiz. !title(quiz title), !sitename, !taker(quiz takers username), !date(time when quiz was finished), !minutes(How many minutes the quiz taker spent taking the quiz), !desc(description of the quiz), !correct(points attained), !total(max score for the quiz), !percentage(percentage score), !url(url to the result page) and !author are placeholders.'),
-    '#default_value' => variable_get('quiz_email_results_body', quiz_email_results_format('body', 'author')),
-  );
-
-  $form['#validate'][] = 'quiz_settings_form_validate';
-  $form['#submit'][] = 'quiz_settings_form_submit';
-
-  return system_settings_form($form);
-}
-
-/**
- * Validation of the Form Settings form.
- *
- * Checks the values for the form administration form for quiz settings.
- */
-function quiz_settings_form_validate($form, &$form_state) {
-  if (!_quiz_is_int($form_state['values']['quiz_default_close'])) {
-    form_set_error('quiz_default_close', t('The default number of days before a quiz is closed must be a number greater than 0.'));
-  }
-
-  if (!_quiz_is_int($form_state['values']['quiz_autotitle_length'], 0, 128)) {
-    form_set_error('quiz_autotitle_length', t('The autotitle length value must be an integer between 0 and 128.'));
-  }
-
-  if (!_quiz_is_int($form_state['values']['quiz_max_result_options'], 0, 100)) {
-    form_set_error('quiz_max_result_options', t('The number of resultoptions must be an integer between 0 and 100.'));
-  }
-
-  if (!_quiz_is_plain($form_state['values']['quiz_name'])) {
-    form_set_error('quiz_name', t('The quiz name must be plain text.'));
-  }
-
-  /* if (!_quiz_is_plain($form_state['values']['quiz_action_type']))
-    form_set_error('quiz_action_type', t('The action type must be plain text.')); */
-}
-
-/**
- * Submit the admin settings form
- */
-function quiz_settings_form_submit($form, &$form_state) {
-  if (QUIZ_NAME != $form_state['values']['quiz_name']) {
-    variable_set('quiz_name', $form_state['values']['quiz_name']);
-    define(QUIZ_NAME, $form_state['values']['quiz_name']);
-    menu_rebuild();
-  }
-}
-
-/**
- * Renders the quiz node form for the admin pages
- *
- * This form is used to configure default values for the quiz node form
- */
-function quiz_admin_node_form($form, &$form_state) {
-  // Create a dummy node to use as input for quiz_form
-  $dummy_node = new stdClass();
-  // def_uid is the uid of the default user holding the default values for the node form(no real user with this uid exists)
-  $settings = quiz_get_defaults();
-  foreach ($settings as $key => $value) {
-    if (!isset($dummy_node->$key)) {
-      $dummy_node->{$key} = $value;
-    }
-  }
-  $form = quiz_form($dummy_node, $form_state);
-  $form['direction'] = array(
-    '#markup' => t('Here you can change the default @quiz settings for new users.', array('@quiz' =>  QUIZ_NAME)),
-    '#weight' => -10,
-  );
-  // unset values we can't or won't let the user edit default values for
-  unset(
-    $form['#quiz_check_revision_access'],
-    $form['title'], $form['body_field'],
-    $form['taking']['aid'], $form['taking']['addons'],
-    $form['quiz_availability']['quiz_open'],
-    $form['quiz_availability']['quiz_close'],
-    $form['resultoptions'],
-    $form['number_of_random_questions'],
-    $form['remember_global']
-  );
-
-  $form['remember_settings']['#type'] = 'value';
-  $form['remember_settings']['#default_value'] = TRUE;
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-    '#submit' => array('quiz_admin_node_form_submit'),
-  );
-  return $form;
-}
-
-/**
- * Validation function for the quiz_admin_node_form form
- */
-function quiz_admin_node_form_validate($form, &$form_state) {
-  // Create dummy node for quiz_validate
-  $dummy_node = new stdClass();
-  foreach ($form_state['values'] as $key => $value) {
-    $dummy_node->{$key} = $value;
-  }
-  $dummy_node->resultoptions = array();
-
-  // We use quiz_validate to validate the default values
-  quiz_validate($dummy_node);
-}
-
-/**
- * Submit function for quiz_admin_node_form
- *
- * The default values are saved as the user settings for the "default user"
- * The default user is created when quiz is installed. He has a unique uid, but doesn't exist
- * as a real user.
- *
- * Why?
- * Default user settings can be loaded and saved using the same code and
- * database tables as any other user settings, making the code a lot easier to maintain.
- * Ref: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
- */
-function quiz_admin_node_form_submit($form, &$form_state) {
-  // We add the uid for the "default user"
-  // Generate the node object:
-  $node = (object) $form_state['values'];
-  $node->uid = 0;
-  $node->nid = 0;
-  $node->vid = 0;
-
-  quiz_update_defaults($node);
-
-  $form_state['node'] = $node;
-}
-
-// QUIZ RESULTS ADMIN
-
-/**
- * Quiz result report page for the quiz admin section
- *
- * @param $quiz
- *   The quiz node
- * @param $result_id
- *   The result id
- */
-function quiz_admin_results($quiz, $result_id) {
-  $breadcrumb = drupal_get_breadcrumb();
-  $breadcrumb[] = l(t('Quiz Results'), 'admin/quiz/reports/results');
-
-  // Make sure we have the right version of the quiz
-  $result = db_query('SELECT vid, uid FROM {quiz_node_results} WHERE result_id = :result_id', array(':result_id' => $result_id))->fetchObject();
-  if ($quiz->vid != $result->vid) {
-    $quiz = node_load($quiz->nid, $result->vid);
-  }
-
-  // Get all the data we need.
-  $questions = _quiz_get_answers($quiz, $result_id);
-  $score = quiz_calculate_score($quiz, $result_id);
-  $summary = _quiz_get_summary_text($quiz, $score);
-
-  // Lets add the quiz title to the breadcrumb array.
-  $breadcrumb[] = l($quiz->title, 'admin/quiz/reports/results/' . $quiz->nid);
-  //drupal_set_breadcrumb($breadcrumb);
-  $data = array(
-    'quiz' => $quiz,
-    'questions' => $questions,
-    'score' => $score,
-    'summary' => $summary,
-    'result_id' => $result_id,
-    'account' => user_load($result->uid),
-  );
-  return theme('quiz_result', $data);
-}
-
-// MANAGE QUESTIONS
-
-/**
- * Creates a form for quiz questions.
- *
- * Handles the manage questions tab.
- *
- * @param $node
- *   The quiz node we are managing questions for.
- * @return
- *   String containing the form.
- */
-function quiz_questions($node) {
-  // Set page title.
-  drupal_set_title($node->title);
-  if ($node->randomization < 3) {
-    $mq_form = drupal_get_form('quiz_questions_form', $node);
-    $manage_questions = drupal_render($mq_form);
-    $question_bank = views_get_view('quiz_question_bank')->preview();
-
-    // Insert into vert tabs
-    $form['vert_tabs'] = array(
-      '#type' => 'vertical_tabs',
-      '#weight' => 0,
-    );
-    $form['vert_tabs']['question_admin'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Manage questions'),
-      '#value' => $manage_questions,
-    );
-    $form['vert_tabs']['global_questions'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Question bank'),
-      '#value' => $question_bank,
-    );
-  }
-  else {
-    $form = drupal_get_form('quiz_categorized_form', $node);
-  }
-  return $form;
-}
-
-/**
- * Form for managing what questions should be added to a quiz with categorized random questions.
- *
- * @param array $form_state
- *  The form state array
- * @param object $quiz
- *  The quiz node
- */
-function quiz_categorized_form($form, $form_state, $quiz) {
-  $form = array();
-  _quiz_categorized_existing_terms_form($form, $form_state, $quiz);
-  _quiz_categorized_new_term_form($form, $form_state, $quiz);
-  $form['nid'] = array(
-    '#type' => 'value',
-    '#value' => $quiz->nid,
-  );
-  $form['vid'] = array(
-    '#type' => 'value',
-    '#value' => $quiz->vid,
-  );
-  $form['tid'] = array(
-    '#type' => 'value',
-    '#value' => NULL,
-  );
-  // Give the user the option to create a new revision of the quiz
-  _quiz_add_revision_checkbox($form, $quiz);
-  // Timestamp is needed to avoid multiple users editing the same quiz at the same time.
-  $form['timestamp'] = array('#type' => 'hidden', '#default_value' => REQUEST_TIME);
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Submit'),
-  );
-  $form['#tree'] = TRUE;
-  return $form;
-}
-
-function _quiz_categorized_existing_terms_form(&$form, $form_state, $quiz) {
-  $terms = _quiz_get_terms($quiz->vid);
-  if ($terms) {
-    if (empty($form_state['input']) && !_quiz_build_categorized_question_list($quiz)) {
-      drupal_set_message(t('There are not enough questions in the requested categories.'), 'error');
-    }
-  }
-  foreach ($terms as $term) {
-    $form[$term->tid]['name'] = array(
-      '#markup' => check_plain($term->name),
-    );
-    $form[$term->tid]['number'] = array(
-      '#type' => 'textfield',
-      '#size' => 3,
-      '#default_value' => $term->number,
-    );
-    $form[$term->tid]['max_score'] = array(
-      '#type' => 'textfield',
-      '#size' => 3,
-      '#default_value' => $term->max_score,
-    );
-    $form[$term->tid]['remove'] = array(
-      '#type' => 'checkbox',
-      '#default_value' => 0,
-    );
-    $form[$term->tid]['weight'] = array(
-      '#type' => 'textfield',
-      '#size' => 3,
-      '#default_value' => $term->weight,
-      '#attributes' => array(
-        'class' => array('term-weight')
-      ),
-    );
-  }
-}
-
-/**
- * Form for adding new terms to a quiz
- *
- * @see quiz_categorized_form
- */
-function _quiz_categorized_new_term_form(&$form, $form_state, $quiz) {
-  $form['new'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Add category'),
-    '#collapsible' => FALSE,
-    '#collapsed' => FALSE,
-    '#tree' => FALSE,
-  );
-  $form['new']['term'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Category'),
-    '#description' => t('Type in the name of the term you would like to add questions from.'),
-    '#autocomplete_path' => "node/$quiz->nid/questions/term_ahah",
-    '#field_suffix' => '<a id="browse-for-term" href="javascript:void(0)">' . t('browse') . '</a>',
-  );
-  $form['new']['number'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Number of questions'),
-    '#description' => t('How many questions would you like to draw from this term?'),
-  );
-  $form['new']['max_score'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Max score for each question'),
-    '#description' => t('The number of points a user will be awarded for each question he gets correct.'),
-    '#default_value' => 1,
-  );
-}
-
-/**
- * Validate the categorized form
- */
-function quiz_categorized_form_validate($form, &$form_state) {
-  if (_quiz_is_int(arg(1))) {
-    if (node_last_changed(arg(1)) > $form_state['values']['timestamp']) {
-      form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
-    }
-  }
-  else {
-    form_set_error('changed', t('A critical error has occured. Please report error code 28 on the quiz project page.'));
-    return;
-  }
-  if (!empty($form_state['values']['term'])) {
-    $tid = _quiz_get_id_from_string($form_state['values']['term']);
-    if ($tid === FALSE) {
-      $terms = _quiz_search_terms($form_state['values']['term']);
-      $num_terms = count($terms);
-      if ($num_terms == 1) {
-        $tid = key($terms);
-      }
-      elseif ($num_terms > 1) {
-        form_set_error('term', t('You need to be more specific, or use the autocomplete feature. The term name you entered matches several terms: %terms', array('%terms' => implode(', ', $terms))));
-      }
-      elseif ($num_terms == 0) {
-        form_set_error('term', t("The term name you entered doesn't match any registered question terms."));
-      }
-    }
-    if (in_array($tid, array_keys($form))) {
-      form_set_error('term', t('The category you are trying to add has already been added to this quiz.'));
-    }
-    else {
-      form_set_value($form['tid'], $tid, $form_state);
-    }
-
-    if (!_quiz_is_int($form_state['values']['number'])) {
-      form_set_error('number', t('The number of questions needs to be a positive integer'));
-    }
-    if (!_quiz_is_int($form_state['values']['max_score'], 0)) {
-      form_set_error('max_score', t('The max score needs to be a positive integer or 0'));
-    }
-  }
-}
-
-/**
- * Submit the categorized form
- */
-function quiz_categorized_form_submit($form, $form_state) {
-  $quiz = node_load($form_state['values']['nid'], $form_state['values']['vid']);
-  $quiz->number_of_random_questions = 0;
-  // Update the refresh latest quizzes table so that we know what the users latest quizzes are
-  if (variable_get('quiz_auto_revisioning', 1)) {
-    $is_new_revision = quiz_has_been_answered($quiz);
-  }
-  else {
-    $is_new_revision = (bool) $form_state['values']['new_revision'];
-  }
-  if (!empty($form_state['values']['tid'])) {
-    $quiz->number_of_random_questions += _quiz_categorized_add_term($form, $form_state);
-  }
-  $quiz->number_of_random_questions += _quiz_categorized_update_terms($form, $form_state);
-  if ($is_new_revision) {
-    $quiz->revision = 1;
-  }
-  // We save the node to update its timestamp and let other modules react to the update.
-  // We also do this in case a new revision is required...
-  node_save($quiz);
-}
-
-/**
- * Update the categoriez belonging to a quiz with categorized random questions.
- *
- * Helper function for quiz_categorized_form_submit
- */
-function _quiz_categorized_update_terms(&$form, &$form_state) {
-  $ids = array('weight', 'max_score', 'number');
-  $changed = array();
-  $removed = array();
-  $num_questions = 0;
-  foreach ($form_state['values'] as $key => $existing) {
-    if (!is_numeric($key)) {
-      continue;
-    }
-    if (!$existing['remove']) {
-      $num_questions += $existing['number'];
-    }
-    foreach ($ids as $id) {
-      if ($existing[$id] != $form[$key][$id]['#default_value'] && !$existing['remove']) {
-        $existing['nid'] = $form_state['values']['nid'];
-        $existing['vid'] = $form_state['values']['vid'];
-        $existing['tid'] = $key;
-        if (empty($existing['weight'])) {
-          $existing['weight'] = 1;
-        }
-        $changed[] = $form[$key]['name']['#markup'];
-        drupal_write_record('quiz_terms', $existing, array('vid', 'tid'));
-        break;
-      }
-      elseif ($existing['remove']) {
-        db_delete('quiz_terms')
-          ->condition('tid', $key)
-          ->condition('vid', $form_state['values']['vid'])
-          ->execute();
-        $removed[] = $form[$key]['name']['#markup'];
-        break;
-      }
-    }
-  }
-  if (!empty($changed)) {
-    drupal_set_message(t('Updates were made for the following terms: %terms', array('%terms' => implode(', ', $changed))));
-  }
-  if (!empty($removed)) {
-    drupal_set_message(t('The following terms were removed: %terms', array('%terms' => implode(', ', $removed))));
-  }
-  return $num_questions;
-}
-
-/**
- * Adds a term to a categorized quiz
- *
- * This is a helper function for the submit function.
- */
-function _quiz_categorized_add_term($form, $form_state) {
-  drupal_set_message(t('The term was added'));
-  // Needs to be set to avoid error-message from db:
-  $form_state['values']['weight'] = 0;
-  drupal_write_record('quiz_terms', $form_state['values']);
-  return $form_state['values']['number'];
-}
-
-/**
- * Searches for an id in the end of a string.
- *
- * Id should be written like "(id:23)"
- *
- * @param string $string
- *  The string where we will search for an id
- * @return int
- *  The matched integer
- */
-function _quiz_get_id_from_string($string) {
-  $matches = array();
-  preg_match('/\(id:(\d+)\)$/', $string, $matches);
-  return isset($matches[1]) ? (int) $matches[1] : FALSE;
-}
-
-/**
- * Ahah function for finding terms...
- *
- * @param string $start
- *  The start of the string we are looking for
- */
-function quiz_categorized_term_ahah($start) {
-  $terms = _quiz_search_terms($start, $start == '*');
-  $to_json = array();
-  foreach ($terms as $key => $value) {
-    $to_json["$value (id:$key)"] = $value;
-  }
-  drupal_json_output($to_json);
-}
-
-/**
- * Helper function for finding terms...
- *
- * @param string $start
- *  The start of the string we are looking for
- */
-function _quiz_search_terms($start, $all = FALSE) {
-  $terms = array();
-  $sql_args = array_keys(_quiz_get_vocabularies());
-  if (empty($sql_args)) {
-    return $terms;
-  }
-  $query = db_select('taxonomy_term_data', 't')
-    ->fields('t', array('name', 'tid'))
-    ->condition('t.vid', $sql_args, 'IN');
-  if (!$all) {
-    $query->condition('t.name', '%' . $start . '%', 'LIKE');
-  }
-  $res = $query->execute();
-  // TODO Don't user db_fetch_object
-  while ($res_o = $res->fetch()) {
-    $terms[$res_o->tid] = $res_o->name;
-  }
-  return $terms;
-}
-
-/**
- * Handles "manage questions" tab.
- *
- * Displays form which allows questions to be assigned to the given quiz.
- *
- * This function is not used if the question assignment type "categorized random questions" is chosen
- *
- * @param $form_state
- *  The form state variable
- * @param $quiz
- *  The quiz node.
- * @return
- *  HTML output to create page.
- */
-function quiz_questions_form($form, $form_state, $quiz) {
-
-  $types = quiz_get_question_types();
-
-  _quiz_add_fields_for_creating_questions($form, $types, $quiz);
-
-  // Display questions in this quiz.
-  $form['question_list'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Questions in this @quiz', array('@quiz' => QUIZ_NAME)),
-    '#theme' => 'question_selection_table',
-    '#collapsible' => TRUE,
-    '#attributes' => array('id' => 'mq-fieldset'),
-    'question_status' => array('#tree' => TRUE),
-  );
-
-  // Add randomization settings if this quiz allows randomized questions
-  _quiz_add_fields_for_random_quiz($form, $quiz);
-
-  $include_random = $quiz->randomization == 2;
-  // @todo deal with $include_random
-  $questions = quiz_get_questions($quiz->nid, $quiz->vid);
-
-  if (empty($questions)) {
-    $form['question_list']['no_questions'] = array(
-      '#markup' => '<div id = "no-questions">' . t('There are currently no questions in this @quiz. Assign existing questions by using the question browser below. You can also use the links above to create new questions.', array('@quiz' => QUIZ_NAME)) . '</div>',
-    );
-  }
-
-  // We add the questions to the form array
-  _quiz_add_questions_to_form($form, $questions, $quiz, $types);
-
-  // Show the number of questions in the table header.
-  $always_count = isset($form['question_list']['titles']) ? count($form['question_list']['titles']) : 0;
-  $form['question_list']['#title'] .= ' (' . $always_count . ')';
-
-  // Give the user the option to create a new revision of the quiz
-  _quiz_add_revision_checkbox($form, $quiz);
-
-  // Timestamp is needed to avoid multiple users editing the same quiz at the same time.
-  $form['timestamp'] = array('#type' => 'hidden', '#default_value' => REQUEST_TIME);
-
-  $form['actions']['#type'] = 'actions';
-
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Submit'),
-    '#submit' => array('quiz_questions_form_submit'),
-  );
-  return $form;
-}
-
-/**
- * Fields for creating new questions are added to the quiz_questions_form
- *
- * @param $form
- *   FAPI form(array)
- * @param $types
- *   All the question types(array)
- * @param $quiz
- *   The quiz node
- */
-function _quiz_add_fields_for_creating_questions(&$form, &$types, &$quiz) {
-  // Display links to create other questions.
-  $form['additional_questions'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Create new question'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-
-  $url_query = drupal_get_destination();
-  $url_query['quiz_nid'] = $quiz->nid;
-  $url_query['quiz_vid'] = $quiz->vid;
-  $create_question = FALSE;
-
-  foreach ($types as $type => $info) {
-
-    $url_type = str_replace('_', '-', $type);
-    $options = array(
-      'attributes' => array('title' => t('Create @name', array('@name' => $info['name']))),
-      'query' => $url_query,
-    );
-    $access = node_access('create', $type);
-    if ($access) {
-      $create_question = TRUE;
-    }
-    $form['additional_questions'][$type] = array(
-      '#markup' => '<div class="add-questions">' . l($info['name'], "node/add/$url_type", $options) . '</div>',
-      '#access' => $access,
-    );
-  }
-  if (!$create_question) {
-    $form['additional_questions']['create'] = array(
-      '#type' => 'markup',
-      '#markup' => t('You have not enabled any question type module or no has permission been given to create any question.'),
-      // @todo revisit UI text
-    );
-  }
-}
-
-/**
- * Add fields for random quiz to the quiz_questions_form
- *
- * @param $form
- *   FAPI form array
- * @param $quiz
- *   The quiz node(object)
- */
-function _quiz_add_fields_for_random_quiz(&$form, $quiz) {
-  if ($quiz->randomization != 2) {
-    return;
-  }
-  $form['question_list']['random_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Settings for random questions'),
-    '#collapsible' => TRUE,
-  );
-  $form['question_list']['random_settings']['num_random_questions'] = array(
-    '#type' => 'textfield',
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#weight' => -5,
-    '#title' => t('Number of random questions'),
-    '#description' => t('The number of questions to be randomly selected each time someone takes this quiz'),
-    '#default_value' => isset($quiz->number_of_random_questions) ? $quiz->number_of_random_questions : 10,
-  );
-  $form['question_list']['random_settings']['max_score_for_random'] = array(
-    '#type' => 'textfield',
-    '#size' => 3,
-    '#maxlength' => 3,
-    '#weight' => -5,
-    '#title' => t('Max score for each random question'),
-    '#default_value' => isset($quiz->max_score_for_random) ? $quiz->max_score_for_random : 1,
-  );
-  if ($quiz->randomization == 3) {
-    $terms = _quiz_taxonomy_select($quiz->tid);
-    if (!empty($terms) && function_exists('taxonomy_get_vocabularies')) {
-      $form['question_list']['random_settings']['random_term_id'] = array(
-        '#type' => 'select',
-        '#title' => t('Terms'),
-        '#size' => 1,
-        '#options' => _quiz_taxonomy_select($quiz->tid),
-        '#default_value' => $quiz->tid,
-        '#description' => t('Randomly select from questions with this term, or choose from the question pool below'),
-        '#weight' => -4,
-      );
-    }
-  }
-}
-
-/**
- * Adds the questions in the $questions array to the form
- *
- * @param $form
- *   FAPI form(array)
- * @param $questions
- *   The questions to be added to the question list(array)
- * @param $quiz
- *   The quiz node(object)
- * @param $question_types
- *   array of all available question types
- */
-function _quiz_add_questions_to_form(&$form, &$questions, &$quiz, &$question_types) {
-  $form['question_list']['weights'] = array('#tree' => TRUE);
-  $form['question_list']['qnr_ids'] = array('#tree' => TRUE);
-  $form['question_list']['qnr_pids'] = array('#tree' => TRUE);
-  $form['question_list']['max_scores'] = array('#tree' => TRUE);
-  $form['question_list']['auto_update_max_scores'] = array('#tree' => TRUE);
-  $form['question_list']['stayers'] = array('#tree' => TRUE);
-  $form['question_list']['revision'] = array('#tree' => TRUE);
-  if ($quiz->randomization == 2) {
-    $form['question_list']['compulsories'] = array('#tree' => TRUE);
-  }
-
-  foreach ($questions as $question) {
-    // @todo replace entire form with usage of question instance
-    $question_node = node_load($question->nid, $question->vid);
-    $instance = _quiz_question_get_instance($question_node);
-    $fieldset = 'question_list';
-    $id = $question->nid . '-' . $question->vid;
-
-    $form[$fieldset]['weights'][$id] = array(
-      '#type' => 'textfield',
-      '#size' => 3,
-      '#maxlength' => 4,
-      '#default_value' => isset($question->weight) ? $question->weight : 0,
-    );
-
-    $form[$fieldset]['qnr_pids'][$id] = array(
-      '#type' => 'textfield',
-      '#size' => 3,
-      '#maxlength' => 4,
-      '#default_value' => $question->qnr_pid,
-    );
-
-    $form[$fieldset]['qnr_ids'][$id] = array(
-      '#type' => 'textfield',
-      '#size' => 3,
-      '#maxlength' => 4,
-      '#default_value' => $question->qnr_id,
-    );
-
-    // Quiz directions don't have scoring...
-    $form[$fieldset]['max_scores'][$id] = array(
-      '#type' => $instance->isGraded() ? 'textfield' : 'hidden',
-      '#size' => 2,
-      '#maxlength' => 2,
-      '#disabled' => isset($question->auto_update_max_score) ? $question->auto_update_max_score : FALSE,
-      '#default_value' => isset($question->max_score) ? $question->max_score : 0,
-      '#states' => array(
-        'disabled' => array(
-          "#edit-auto-update-max-scores-$id" => array('checked' => TRUE),
-        )
-      ),
-    );
-
-    $form[$fieldset]['auto_update_max_scores'][$id] = array(
-      '#type' => $instance->isGraded() ? 'checkbox' : 'hidden',
-      '#default_value' => isset($question->auto_update_max_score) ? $question->auto_update_max_score : 0,
-    );
-
-    // Add checkboxes to remove questions in js disabled browsers...
-    $form[$fieldset]['stayers'][$id] = array(
-      '#type' => 'checkbox',
-      '#default_value' => 0,
-      '#attributes' => array('class' => array('q-staying')),
-    );
-
-    //Add checkboxes to mark compulsory questions for randomized quizzes.
-    if ($quiz->randomization == 2) {
-      $form[$fieldset]['compulsories'][$id] = array(
-        '#type' => 'checkbox',
-        '#default_value' => isset($question->question_status) ? ($question->question_status == QUESTION_ALWAYS) ? 1 : 0 : 0,
-        '#attributes' => array('class' => array('q-compulsory')),
-      );
-    }
-
-    if (user_access('view quiz question outside of a quiz')) {
-      $link_options = array(
-        'attributes' => array('class' => array('handle-changes')),
-      );
-      $question_titles = l($question->title, 'node/' . $question->nid, $link_options);
-    }
-    else {
-      $question_titles = check_plain($question->title);
-    }
-
-    $form[$fieldset]['titles'][$id] = array('#markup' => $question_titles);
-
-
-    $form[$fieldset]['types'][$id] = array(
-      '#markup' => $question_types[$question->type]['name'],
-      '#question_type' => $question->type,
-    );
-
-    $form[$fieldset]['view_links'][$id] = array(
-      '#markup' => l(
-        t('Edit'), 'node/' . $question->nid . '/edit', array(
-        'query' => drupal_get_destination(),
-        'attributes' => array('class' => array('handle-changes')),
-        )
-      ),
-      '#access' => node_access('update', node_load($question->nid, $question->vid)),
-    );
-    // For js enabled browsers questions are removed by pressing a remove link
-    $form[$fieldset]['remove_links'][$id] = array(
-      '#markup' => '<a href="#" class="rem-link">' . t('Remove') . '</a>',
-    );
-    // Add a checkbox to update to the latest revision of the question
-    if ($question->vid == $question->latest_vid) {
-      $update_cell = array(
-        '#markup' => t('<em>Up to date</em>'),
-      );
-    }
-    else {
-      $update_cell = array(
-        '#type' => 'checkbox',
-        '#title' => (l(t('Latest'), 'node/' . $question->nid . '/revisions/' . $question->latest_vid . '/view')
-        . ' of ' .
-        l(t('revisions'), 'node/' . $question->nid . '/revisions')
-        ),
-      );
-    }
-    $form[$fieldset]['revision'][$id] = $update_cell;
-  }
-}
-
-/**
- * Adds checkbox for creating new revision. Checks it by default if answers exists.
- *
- * @param $form
- *   FAPI form(array)
- * @param $quiz
- *   Quiz node(object)
- */
-function _quiz_add_revision_checkbox(&$form, &$quiz) {
-  // Recomend and preselect to create the quiz as a new revision if it already has been answered
-  if (quiz_has_been_answered($quiz)) {
-    $rev_default = TRUE;
-    $rev_description = t('This quiz has been answered. To maintain correctnes of existing answer reports changes should be saved as a new revision.');
-  }
-  else {
-    $rev_default = in_array('revision', variable_get('node_options_quiz', array()));
-    $rev_description = t('Allow question status changes to create a new revision of the quiz?');
-  }
-  if (user_access('manual quiz revisioning') && !variable_get('quiz_auto_revisioning', 1)) {
-    $form['new_revision'] = array(
-      '#type' => 'checkbox',
-      '#default_value' => $rev_default,
-      '#title' => t('New revision'),
-      '#description' => $rev_description,
-    );
-  }
-  else {
-    $form['new_revision'] = array(
-      '#type' => 'value',
-      '#value' => $rev_default,
-    );
-  }
-}
-
-/**
- * Validate that the supplied questions are real.
- */
-function quiz_questions_form_validate($form, $form_state) {
-  if (_quiz_is_int(arg(1))) {
-    if (node_last_changed(intval(arg(1))) > $form_state['values']['timestamp']) {
-      form_set_error('changed', t('This content has been modified by another user, changes cannot be saved.'));
-    }
-  }
-  else {
-    form_set_error('changed', t('A critical error has occured. Please report error code 28 on the quiz project page.'));
-    return;
-  }
-
-  $already_checked = array();
-  $weight_map = $form_state['values']['weights'];
-
-  // Make sure the number of random questions is a positive number
-  if (isset($form_state['values']['num_random_questions']) && !_quiz_is_int($form_state['values']['num_random_questions'], 0)) {
-    form_set_error('num_random_questions', 'The number of random questions needs to be a positive number');
-  }
-
-  // Make sure the max score for random questions is a positive number
-  if (isset($form_state['values']['max_score_for_random']) && !_quiz_is_int($form_state['values']['max_score_for_random'], 0)) {
-    form_set_error('max_score_for_random', 'The max score for random questions needs to be a positive number');
-  }
-
-  if (empty($weight_map)) {
-    form_set_error('none', 'No questions were included.');
-    return;
-  }
-
-  $question_types = array_keys(quiz_get_question_types());
-
-  foreach ($weight_map as $id => $weight) {
-
-    list($nid, $vid) = explode('-', $id, 2);
-
-    // If a node isn't one of the questionstypes we remove it from the question list
-    $has_questions = (Boolean) db_select('node', 'n')
-        ->fields('n', array('nid'))
-        ->condition('type', $question_types, 'IN')
-        ->addTag('node_access')
-        ->condition('n.nid', $nid)
-        ->execute()
-        ->fetchField();
-    if (!$has_questions) {
-      form_set_error('none', 'One of the supplied questions was invalid. It has been removed from the quiz.');
-      unset($form_state['values']['weights'][$id]);
-    }
-
-    // We also make sure that we don't have duplicate questions in the quiz.
-    elseif (in_array($nid, $already_checked)) {
-      form_set_error('none', 'A duplicate question has been removed. You can only ask a question once per quiz.');
-      unset($form_state['values']['weights'][$id]);
-    }
-    else {
-      $already_checked[] = $nid;
-    }
-  }
-
-  // We make sure max score is a positive number
-  $max_scores = $form_state['values']['max_scores'];
-  foreach ($max_scores as $id => $max_score) {
-    if (!_quiz_is_int($max_score, 0)) {
-      form_set_error("max_scores][$id", t('Max score needs to be a positive number'));
-    }
-  }
-}
-
-/**
- * Update a quiz set of items with new weights and membership
- * @param $quiz
- *   The quiz node
- * @param $weight_map
- *   Weights for each question(determines the order in which the question will be taken by the quiz taker)
- * @param $max_scores
- *   Array of max scores for each question
- * @param $is_new_revision
- *   Array of boolean values determining if the question is to be updated to the newest revision
- * @param $refreshes
- *   True if we are creating a new revision of the quiz
- * @param $stayers
- *   Questions added to the quiz
- * @param $compulsories
- *   Array of boolean values determining if the question is compulsory or not.
- * @return array set of questions after updating
- */
-function _quiz_update_items($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qnr_ids, $qnr_pids, $compulsories = NULL) {
-  $questions = array();
-  foreach ($weight_map as $id => $weight) {
-    if ($stayers[$id]) {
-      continue;
-    }
-    list($nid, $vid) = explode('-', $id, 2);
-    $nid = (int) $nid;
-    $vid = (int) $vid;
-    $question = new stdClass();
-    $question->nid = $nid;
-    $question->vid = $vid;
-    if (isset($compulsories)) {
-      if ($compulsories[$id] == 1) {
-        $question->state = QUESTION_ALWAYS;
-      }
-      else {
-        $question->state = QUESTION_RANDOM;
-        $max_scores[$id] = $quiz->max_score_for_random;
-      }
-    }
-    else {
-      $question->state = QUESTION_ALWAYS;
-    }
-    $question->weight = $weight;
-    $question->max_score = $max_scores[$id];
-    $question->auto_update_max_score = $auto_update_max_scores[$id];
-    $question->qnr_pid = $qnr_pids[$id] > 0 ? $qnr_pids[$id] : NULL;
-    $question->qnr_id = $qnr_ids[$id] > 0 ? $qnr_ids[$id] : NULL;
-    $question->refresh = (isset($refreshes[$id]) && $refreshes[$id] == 1);
-
-    // Add item as an object in the questions array.
-    $questions[] = $question;
-  }
-
-  // Save questions.
-  quiz_set_questions($quiz, $questions, $is_new_revision);
-
-  return $questions;
-}
-
-/**
- * Submit function for quiz_questions.
- *
- * Updates from the "manage questions" tab.
- */
-function quiz_questions_form_submit($form, &$form_state) {
-  // Load the quiz node
-  $quiz = node_load(intval(arg(1)));
-  // Update the refresh latest quizzes table so that we know what the users latest quizzes are
-  if (variable_get('quiz_auto_revisioning', 1)) {
-    $is_new_revision = quiz_has_been_answered($quiz);
-  }
-  else {
-    $is_new_revision = (bool) $form_state['values']['new_revision'];
-  }
-
-  _quiz_question_browser_submit($form, $form_state);
-
-  $weight_map = $form_state['values']['weights'];
-  $qnr_pids_map = $form_state['values']['qnr_pids'];
-  $qnr_ids_map = $form_state['values']['qnr_ids'];
-  $max_scores = $form_state['values']['max_scores'];
-  $auto_update_max_scores = $form_state['values']['auto_update_max_scores'];
-  $refreshes = isset($form_state['values']['revision']) ? $form_state['values']['revision'] : NULL;
-  $stayers = $form_state['values']['stayers'];
-  $compulsories = isset($form_state['values']['compulsories']) ? $form_state['values']['compulsories'] : NULL;
-
-  $num_random = isset($form_state['values']['num_random_questions']) ? $form_state['values']['num_random_questions'] : 0;
-
-
-  $quiz->max_score_for_random = isset($form_state['values']['max_score_for_random']) ? $form_state['values']['max_score_for_random'] : 1;
-  $term_id = isset($form_state['values']['random_term_id']) ? (int) $form_state['values']['random_term_id'] : 0;
-
-
-  // Store what questions belong to the quiz
-  $questions = _quiz_update_items($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qnr_ids_map, $qnr_pids_map, $compulsories, $stayers);
-
-  // If using random questions and no term ID is specified, make sure we have enough.
-  if (empty($term_id)) {
-    $assigned_random = 0;
-
-    foreach ($questions as $question) {
-      if ($question->state == QUESTION_RANDOM) {
-        ++$assigned_random;
-      }
-    }
-
-    // Adjust number of random questions downward to match number of selected questions..
-    if ($num_random > $assigned_random) {
-      $num_random = $assigned_random;
-      drupal_set_message(t('The number of random questions for this @quiz have been lowered to %anum to match the number of questions you assigned.', array('@quiz' => QUIZ_NAME, '%anum' => $assigned_random), array('langcode' => 'warning')));
-    }
-  }
-  else {
-    // Warn user if not enough questions available with this term_id.
-    $available_random = count(_quiz_get_random_taxonomy_question_ids($term_id, $num_random));
-    if ($num_random > $available_random) {
-      $num_random = $available_random;
-      drupal_set_message(t('There are currently not enough questions assigned to this term (@random). Please lower the number of random quetions or assign more questions to this taxonomy term before taking this @quiz.', array('@random' => $available_random, '@quiz' => QUIZ_NAME)), 'error');
-    }
-  }
-
-  if ($quiz->type == 'quiz') {
-    // Update the quiz node properties.
-    $success = db_update('quiz_node_properties')
-      ->fields(array(
-        'number_of_random_questions' => $num_random ? $num_random : 0,
-        'max_score_for_random' => $quiz->max_score_for_random,
-        'tid' => $term_id,
-      ))
-      ->condition('vid', $quiz->vid)
-      ->condition('nid', $quiz->nid)
-      ->execute();
-
-    // Get sum of max_score
-    $query = db_select('quiz_node_relationship', 'qnr');
-    $query->addExpression('SUM(max_score)', 'sum');
-    $query->condition('parent_vid', $quiz->vid);
-    $query->condition('question_status', QUESTION_ALWAYS);
-    $score = $query->execute()->fetchAssoc();
-
-    $success2 = db_update('quiz_node_properties')
-      ->expression('max_score', 'max_score_for_random * number_of_random_questions + :sum', array(':sum' => (int) $score['sum']))
-      ->condition('vid', $quiz->vid)
-      ->execute();
-  }
-
-  if (isset($success) && isset($success2)) {
-    drupal_set_message(t('Questions updated successfully.'));
-  }
-  else {
-    drupal_set_message(t('There was an error updating the @quiz.', array('@quiz' => QUIZ_NAME)), 'error');
-  }
-}
-
-/**
- * Takes care of the browser part of the submitted form values.
- *
- * This function changes the form_state to reflect questions added via the browser.
- * (Especially if js is disabled)
- *
- *
- * @param $form
- *   FAPI form(array)
- * @param $form_state
- *   FAPI form_state(array)
- */
-function _quiz_question_browser_submit($form, &$form_state) {
-  // Find the biggest weight:
-  $next_weight = max($form_state['values']['weights']);
-
-  // If a question is chosen in the browser, add it to the question list if it isn't already there
-  if (isset($form_state['values']['browser']['table']['titles'])) {
-    foreach ($form_state['values']['browser']['table']['titles'] as $id) {
-      if ($id !== 0) {
-        $matches = array();
-        preg_match('/([0-9]+)-([0-9]+)/', $id, $matches);
-        $nid = $matches[1];
-        $vid = $matches[2];
-        $form_state['values']['weights'][$id] = ++$next_weight;
-        $form_state['values']['max_scores'][$id] = quiz_question_get_max_score($nid, $vid);
-        $form_state['values']['stayers'][$id] = 1;
-      }
-    }
-  }
-}
-
-// THEME FUNCTIONS
-
-/**
- * Theme the quiz node form
- *
- * Adds js to enhance the ux.
- *
- * @param $form
- *   FAPI form array
- */
-function theme_quiz_node_form($variables) {
-  $form = $variables['form'];
-  $path = drupal_get_path('module', 'quiz') . '/theme/quiz_node_form.js';
-  drupal_add_js($path);
-  return theme('node_form', array('form' => $form));
-}
-
-/**
- * Theme a question selection table, adding drag and drop support.
- */
-function theme_question_selection_table($variables) {
-  $form = $variables['form'];
-  drupal_add_tabledrag('question-list', 'match', 'parent', 'qnr-pid', 'qnr-pid', 'qnr-id', TRUE, 1);
-  drupal_add_tabledrag('question-list', 'order', 'sibling', 'question-list-weight');
-
-  // Building headers
-  $headers = array(t('Question'), t('Type'), t('Actions'), t('Update'), t('Max score'), t('Auto update max score'), t('Delete'));
-  if (isset($form['compulsories'])) {
-    $headers[] = t('Compulsory');
-  }
-  $headers[] = t('Weight');
-  $headers[] = t('Parent ID');
-  $headers[] = array(
-    'data' => t('ID'),
-    'class' => array('tabledrag-hide'),
-  );
-
-  // Building table body
-  $rows = array();
-  if (!empty($form['titles'])) {
-    foreach (element_children($form['titles']) as $id) {
-      $form['weights'][$id]['#attributes']['class'] = array('question-list-weight');
-      $form['qnr_ids'][$id]['#attributes']['class'] = array('qnr-id');
-      $form['qnr_pids'][$id]['#attributes']['class'] = array('qnr-pid');
-      $rows[] = _quiz_get_question_row($form, $id);
-    }
-    // Make sure the same fields aren't rendered twice
-    unset($form['types'], $form['view_links'], $form['remove_links'], $form['stayers']);
-    unset($form['max_scores'], $form['auto_update_max_scores'], $form['revision'], $form['weights'], $form['titles'], $form['compulsories'], $form['qnr_ids'], $form['qnr_pids']);
-  }
-  $html_attr = array('id' => 'question-list');
-
-  // We hide the table if no questions have been added so that jQuery can show it the moment the first question is beeing added.
-  if (isset($form['no_questions'])) {
-    $html_attr['style'] = "display:none;";
-  }
-
-  $table = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => $html_attr));
-
-  return drupal_render($form['random_settings'])
-    . $table
-    . drupal_render_children($form);
-}
-
-/**
- * Delete a single result, or all results for a given user and a given quiz.
- *
- * @param $result_id
- *   result if for the result to be deleted
- * @param $nid
- *   Node id for the quiz the result belongs to. If set all the users results for this quiz will be deleted.
- */
-function _quiz_delete_results($result_id, $nid = NULL) {
-  $result_ids = array();
-
-  // We are to delete all results for a certain user on a certain quiz.
-  if (isset($nid)) {
-    // TODO: We should be able to rewrite this one to use the DBTNG.
-    $res = db_query('SELECT result_id
-            FROM {quiz_node_results}
-            WHERE nid = :nid AND uid = (
-              SELECT uid
-              FROM {quiz_node_results}
-              WHERE result_id = :result_id
-            )', array(':nid' => $nid, ':result_id' => $result_id));
-
-    while ($result = $res->fetchField()) {
-      $result_ids[] = $result;
-    }
-  }
-  // We are only to delete one specific result
-  else {
-    $result_ids[] = $result_id;
-  }
-  entity_delete_multiple('quiz_result', $result_ids);
-}
-
-// HELPER FUNCTIONS
-
-/**
- * Helper function for theme_question_selection_table
- *
- * TODO: DELETE
- *
- * @see quiz_questions_form()
- * @see theme_question_selection_table()
- *
- * @param $sub_form
- *   Form definition array for a filtered questions list
- * @param $id
- *   Identifier used in $sub_form
- * @return table row
- *   Array defining a table row
- */
-function _quiz_get_question_row($sub_form, $id) {
-  $question_types = quiz_get_question_types();
-  $type = $sub_form['types'][$id]['#markup'];
-
-  $action = theme('item_list', array(
-    'items' => array(
-      drupal_render($sub_form['view_links'][$id]),
-      '<SPAN CLASS="q-remove" STYLE="display:none">' . drupal_render($sub_form['remove_links'][$id]) . '</SPAN>',
-    ),
-    'attributes' => array(
-      'class' => array('links', 'inline'),
-    ),
-  ));
-  $qnr_pid = $sub_form['qnr_pids'][$id]['#default_value'];
-  $data_array = array(
-    // The checkbox and the title
-    ($qnr_pid ? theme('indentation', array('size' => 1)) : NULL) . drupal_render($sub_form['titles'][$id]),
-    $type,
-    $action,
-    isset($sub_form['revision'][$id]) ? drupal_render($sub_form['revision'][$id]) : t("Up to date"),
-    drupal_render($sub_form['max_scores'][$id]),
-    drupal_render($sub_form['auto_update_max_scores'][$id]),
-    drupal_render($sub_form['stayers'][$id]),
-  );
-  if (isset($sub_form['compulsories'])) {
-    $data_array[] = drupal_render($sub_form['compulsories'][$id]);
-  }
-  $data_array[] = drupal_render($sub_form['weights'][$id]);
-  $data_array[] = drupal_render($sub_form['qnr_pids'][$id]);
-  $data_array[] = array(
-    'class' => array('tabledrag-hide'),
-    'data' => drupal_render($sub_form['qnr_ids'][$id]),
-  );
-
-  $leaf_class = $sub_form['types'][$id]['#question_type'] != 'quiz_page' ? 'tabledrag-leaf' : '';
-
-  return array(
-    'class' => array('q-row', 'draggable', $leaf_class),
-    'id' => 'q-' . $id,
-    'data' => $data_array
-  );
-}
-
-/**
- * Helper function returning number of days as values and corresponding
- * number of milliseconds as array keys.
- *
- * @return
- *   array of options for when we want to delete partial quiz record values.
- */
-function quiz_remove_partial_quiz_record_value() {
-  return array(
-    '0' => t('Never'),
-    '86400' => t('1 Day'),
-    '172800' => t('2 Days'),
-    '259200' => t('3 Days'),
-    '345600' => t('4 Days'),
-    '432000' => t('5 Days'),
-    '518400' => t('6 Days'),
-    '604800' => t('7 Days'),
-    '691200' => t('8 Days'),
-    '777600' => t('9 Days'),
-    '864000' => t('10 Days'),
-    '950400' => t('11 Days'),
-    '1036800' => t('12 Days'),
-    '1123200' => t('13 Days'),
-    '1209600' => t('14 Days'),
-    '1296000' => t('15 Days'),
-    '1382400' => t('16 Days'),
-    '1468800' => t('17 Days'),
-    '1555200' => t('18 Days'),
-    '1641600' => t('19 Days'),
-    '1728000' => t('20 Days'),
-    '1814400' => t('21 Days'),
-    '1900800' => t('22 Days'),
-    '1987200' => t('23 Days'),
-    '2073600' => t('24 Days'),
-    '2160000' => t('25 Days'),
-    '2246400' => t('26 Days'),
-    '2332800' => t('27 Days'),
-    '2419200' => t('28 Days'),
-    '2505600' => t('29 Days'),
-    '2592000' => t('30 Days'),
-    '3024000' => t('35 Days'),
-    '3456000' => t('40 Days'),
-    '3888000' => t('45 Days'),
-    '4320000' => t('50 Days'),
-    '4752000' => t('55 Days'),
-    '5184000' => t('60 Days'),
-    '5616000' => t('65 Days'),
-    '6048000' => t('70 Days'),
-    '6480000' => t('75 Days'),
-    '6912000' => t('80 Days'),
-    '7344000' => t('85 Days'),
-    '7776000' => t('90 Days'),
-    '8208000' => t('95 Days'),
-    '8640000' => t('100 Days'),
-    '9072000' => t('105 Days'),
-    '9504000' => t('110 Days'),
-    '9936000' => t('115 Days'),
-    '10368000' => t('120 Days'),
-  );
-}
-
-/**
- * Adds inline js to automatically set the question's node title.
- */
-function quiz_set_auto_title() {
-  $max_length = variable_get('quiz_autotitle_length', 50);
-  drupal_add_js(array('quiz_max_length' => $max_length), array('type' => 'setting'));
-  drupal_add_js(drupal_get_path('module', 'quiz') . '/js/quiz.auto-title.js');
-}
-
-/**
- * Themes a categorized quiz form
- */
-function theme_quiz_categorized_form($variables) {
-  $form = $variables['form'];
-  $output = '';
-  $rows = array();
-  foreach ($form as $key => &$existing) {
-    if (!is_numeric($key)) {
-      continue;
-    }
-    $cols = array();
-    $cols[] = drupal_render($existing['name']);
-    $cols[] = drupal_render($existing['number']);
-    $cols[] = drupal_render($existing['max_score']);
-    $cols[] = drupal_render($existing['remove']);
-    $cols[] = drupal_render($existing['weight']);
-    $rows[] = array(
-      'data' => $cols,
-      'class' => array('draggable'),
-    );
-  }
-  if (!empty($rows)) {
-    $header = array(
-      t('Category'),
-      t('Number of questions'),
-      t('Max score per question'),
-      t('Remove'),
-      t('Weight'),
-    );
-    drupal_add_tabledrag('existing-terms', 'order', 'sibling', 'term-weight', NULL, NULL, TRUE);
-    $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'existing-terms')));
-  }
-  $output .= drupal_render_children($form);
-  drupal_add_js(
-    "(function ($) {
-     Drupal.behaviors.quiz_categorized = {
-     attach: function(context) {
-      $('#browse-for-term:not(.quiz-processed)').click(function(event) {
-        event.preventDefault();
-        $('#edit-term').focus().val('*').trigger('keyup');
-      }).addClass('quiz-processed');
-      $('#edit-term').click(function(){
-        if ($(this).val() == '*') {
-          $(this).val('');
-        }
-      });
-    }};}(jQuery));"
-    , array('type' => 'inline', 'group' => JS_DEFAULT));
-  return $output;
-}
diff --git a/quiz.api.php b/quiz.api.php
index db6d837..483679c 100644
--- a/quiz.api.php
+++ b/quiz.api.php
@@ -6,27 +6,42 @@
  *
  * These entity types provided by Quiz also have entity API hooks.
  *
- * quiz (settings for quiz nodes)
+ * quiz (settings for quiz entities)
  * quiz_result (quiz attempt/result)
  * quiz_result_answer (answer to a specific question in a quiz result)
- * quiz_node_relationship (relationship from quiz to question)
+ * quiz_relationship (relationship from quiz to question)
  *
  * So for example
  *
  * hook_quiz_result_presave(&$course_report)
  *   - Runs before a result is saved to the DB.
- * hook_quiz_node_relationship_insert($course_object_fulfillment)
+ * hook_quiz_relationship_insert($course_object_fulfillment)
  *  - Runs when a new question is added to a quiz.
  *
  * Enjoy :)
  */
 
 /**
+ * Implements hook_quiz_question_info().
+ */
+function hook_quiz_question_info() {
+  return array(
+      'long_answer' => array(
+          'name'              => t('Example question type'),
+          'description'       => t('An example question type that does something.'),
+          'question provider' => 'ExampleAnswerQuestion',
+          'response provider' => 'ExampleAnswerResponse',
+          'module'            => 'quiz_question',
+      ),
+  );
+}
+
+/**
  * Implements hook_quiz_begin().
  *
  * Fired when a new quiz result is created.
  */
-function hook_quiz_begin($quiz, $result_id) {
+function hook_quiz_begin(Drupal\quiz\Entity\QuizEntity $quiz, $result_id) {
 
 }
 
@@ -35,7 +50,7 @@ function hook_quiz_begin($quiz, $result_id) {
  *
  * Fired after the last question is submitted.
  */
-function hook_quiz_finished($quiz, $score, $data) {
+function hook_quiz_finished(Drupal\quiz\Entity\QuizEntity $quiz, $score, $data) {
 
 }
 
@@ -44,21 +59,6 @@ function hook_quiz_finished($quiz, $score, $data) {
  *
  * Fired when a quiz is evaluated.
  */
-function hook_quiz_scored($quiz, $score, $result_id) {
+function hook_quiz_scored(Drupal\quiz\Entity\QuizEntity $quiz, $score, $result_id) {
 
 }
-
-/**
- * Implements hook_quiz_question_info().
- */
-function hook_quiz_question_info() {
-  return array(
-    'long_answer' => array(
-      'name' => t('Example question type'),
-      'description' => t('An example question type that does something.'),
-      'question provider' => 'ExampleAnswerQuestion',
-      'response provider' => 'ExampleAnswerResponse',
-      'module' => 'quiz_question',
-    ),
-  );
-}
diff --git a/quiz.css b/quiz.css
deleted file mode 100644
index 328d480..0000000
--- a/quiz.css
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Definitions that apply while viewing questions
- */
-.question-type-name {
-  font-style: italic;
-}
-
-/*
- * Definitions that apply while taking the quiz.
- */
-#quiz-progress {
-  font-style: italic;
-  font-size: 80%;
-}
-
-#quiz-score-possible, #quiz-score-percent {
-  font-weight: bold;
-}
-
-td.quiz-summary-qcell {
-  vertical-align: top;
-  padding: 1em 1em 0em 0em;
-}
-
-td.quiz-summary-qcell table tr {
-  background: transparent;
-}
-
-td.quiz-summary-qcell table td {
-  vertical-align: top;
-  padding: .5em;
-}
-
-.quiz-answer-feedback {
-  font-style: italic;
-}
-
-.quiz-summary-text {
-
-}
-
-.feedback-icon {
-  display: inline;
-}
-
-.add-questions {
-  background: transparent url(images/add.png) no-repeat scroll 0% 10%;
-  padding: 0 0 1em 2em;
-}
-
-.hidden-question {
-  display: none;
-}
-
-.quiz-question-browser-row.odd {
-  background-color: #e7e7e7;
-}
-
-.quiz-question-browser-row.even {
-  background-color: #fbfbfb;
-}
-
-#quiz-question-browser-filters {
-  /*background-color: black;*/
-  /* Black bg in seven theme looks horrible. */
-  background: none repeat scroll 0 0 #E1E2DC;
-  border-color: #BEBFB9;
-  border-style: solid;
-  border-width: 1px;
-  font-weight: normal;
-  padding: 3px 10px;
-  text-transform: uppercase;
-}
-
-#quiz-question-browser-filters td.active {
-  background-color: #666;
-}
-
-.quiz-question-browser-row.odd td.active {
-  background-color: #e2e2f9;
-}
-
-.quiz-question-browser-row.even td.active {
-  background-color: #f8f8ff;
-}
-
-tr.selected.quiz-question-browser-row td, tr.selected.quiz-question-browser-row td.active {
-  background: none repeat scroll 0 0 #505050;
-  color: #FFF;
-}
-
-.browser-table th {
-  border-bottom: 0px solid #666666;
-}
-
-.q-staying {
-  display: inline;
-  float: left;
-  margin-right: 8px;
-}
-
-
-/*
- * Definitions that apply on the quiz report pages.
- */
-
-div.quiz-report {
-  margin: 0;
-  padding: 0;
-}
-
-div.quiz-report .dt {
-  background: #eee;
-  border-top: solid 1px gray;
-  padding-left: 5px;
-}
-
-div.quiz-report .dt div.quiz-report-score-container {
-  border-right: solid 1px gray;
-  border-left: solid 1px gray;
-  border-bottom: solid 1px gray;
-  float: right;
-  padding: 4px 6px;
-}
-
-div.quiz-report .dt div.quiz-report-score-container span div.form-item {
-  display: inline;
-}
-
-div.quiz-score-submit {
-  float: right;
-}
-
-.quiz-report-skipped {
-  float: right;
-}
-
-.q-correct {
-  background-color: #DDFFDD;
-}
-
-.q-wrong {
-  background-color: #FFCCCC;
-}
-
-.q-waiting {
-  background-color: #FFFFDD;
-}
-
-input#edit-submit-hidden {
-  height: 0;
-  width: 0;
-  overflow: hidden;
-  padding: 0;
-  border: none;
-}
-
-.quiz-passed {
-  color: #00dd00;
-}
-
-.quiz-failed {
-  color: #dd0000;
-}
-
-#no-questions {
-  font-style: italic;
-}
-#quiz-view-table {
-  width: auto;
-  border-collapse: separate;
-  border: thin solid #555555;
-  padding: 0px;
-  margin: 0px;
-  margin-bottom: 20px;
-}
-#quiz-start-quiz-button-form div {
-  padding: 0px;
-  margin: 0px;
-}
-
-#quiz-start-quiz-button-form input {
-  margin: 0px;
-}
-
-#quiz-view-table td {
-  padding: 0px 3px 0px 3px;
-  margin: 0px;
-  line-height: 110%;
-}
-.quiz-view-table-title {
-
-}
-.quiz-view-table-data {
-
-}
-
-#browser-pager .pager-last {
-  display: none;
-}
-#browser-pager .pager-first {
-  display: none;
-}
-
-.matching-tbl {
-  width: auto;
-  border-collapse: separate;
-  padding: 0px;
-  margin: 0px;
-  border: 0px;
-  line-height: 100%;
-}
-
-.matching-tbl p, .matching-tbl select, .matching-tbl div {
-  padding: 0px;
-  margin: 0px;
-  line-height: 100%;
-}
-
-.matching-tbl td {
-  padding: 2px;
-  margin: 2px;
-  line-height: 100%;
-}
-
-.quiz-solution {
-  border-left: solid 5px #00ff00;
-  padding-left: 3px;
-}
-
-.quiz-answer-hidden {
-  font-style: italic;
-}
-
-#quiz-jumper-form, #quiz-jumper-form div, #quiz-jumper-form select {
-  display: inline;
-}
-
-#quiz-progress .item-list .pager {
-  text-align: left;
-}
-
-#quiz-categorized-form #autocomplete {
-  overflow: auto;
-  max-height: 300px;
-}
-
-.mark-doubtful .toggle {
-  margin: auto;
-  position: relative;
-  width: 100px;
-  overflow: hidden;
-  height: 18px;
-  line-height: 18px;
-  font-size: 11px;
-  text-align: center;
-  cursor: pointer;
-  border: 1px solid #ccc;
-  -moz-border-radius: 3px;
-  -webkit-border-radius: 3px;
-  -khtml-border-radius: 3px;
-  border-radius: 3px;
-  -moz-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
-  -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
-  box-shadow: 0 0 10px rgba(0,0,0,0.50) inset;
-}
-
-.mark-doubtful .toggle.off {
-  background-clip: padding-box;
-  background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, red), color-stop(50%, white), color-stop(100%, white));
-  background-image: -moz-linear-gradient(left, red 50%, white 50%, white 100%);
-  background-image: linear-gradient(left, red 50%, white 50%, white 100%);
-}
-
-.mark-doubtful .toggle {
-  background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(50%, white), color-stop(50%, green), color-stop(100%, green));
-  background-image: -moz-linear-gradient(left, white 50%, green 50%, green 100%);
-  background-image: linear-gradient(left, white 50%, green 50%, green 100%);
-}
-
-.mark-doubtful .toggle div {
-  position: relative;
-  color: #777;
-  width: 52px;
-  -moz-border-radius: 2px;
-  -webkit-border-radius: 2px;
-  -khtml-border-radius: 2px;
-  border-radius: 2px;
-  background: white;
-  text-shadow: 1px 1px 0 white;
-  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #FEFEFE), color-stop(100%, #EAEAEA));
-  background-image: -moz-linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%);
-  background-image: linear-gradient(top, #FEFEFE 0%, #EAEAEA 100%);
-  -webkit-transition: left 0.2s;
-  -mox-transition: left 0.2s;
-  -o-transition: left 0.2s;
-  transition: left 0.2s;
-}
-
-
-.mark-doubtful .toggle div:after,
-.mark-doubtful .toggle div:before {
-  color: white;
-  text-shadow: none;
-  width: 50px;
-  position: absolute;
-  top: 0;
-  font-size: 9px;
-  font-weight: bold;
-}
-
-.mark-doubtful .toggle div:before {
-  content: "Doubtful";
-  left: -48px;
-}
-
-.mark-doubtful .toggle div:after {
-  content: "Clear";
-  right: -48px;
-}
-.mark-doubtful .toggle.off div {
-  left: 48px;
-}
-
-/*
-  Icons
-*/
-.quiz-score-icon {
-  display: inline-block;
-  width: 32px;
-  height: 32px;
-  background-size: cover
-}
-.quiz-score-icon.correct {
-  background-image: url('images/check_008000_64.png');
-}
-.quiz-score-icon.incorrect {
-  background-image: url('images/times_ff0000_64.png');
-}
-.quiz-score-icon.unknown {
-  background-image: url('images/question_808080_64.png');
-}
-.quiz-score-icon.should {
-  background-image: url('images/check_808080_64.png');
-}
-.quiz-score-icon.should-not {
-  background-image: url('images/times_808080_64.png');
-}
-.quiz-score-icon.almost {
-  background-image: url('images/check_ffff00_64.png');
-}
-.quiz-score-icon.selected {
-  background-image: url('images/arrow-right_808080_64.png');
-}
-.quiz-score-icon.unselected {
-  background-image: url('images/circle-o_808080_64.png');
-}
-
-/*
-Fun
-
-#quiz-question-answering-form {
-  background-image: url('theme/question_bg.png');
-  background-repeat: no-repeat;
-  background-position: top left;
-}
-*/
diff --git a/quiz.help.inc b/quiz.help.inc
deleted file mode 100644
index c11f38a..0000000
--- a/quiz.help.inc
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-/**
- * Help text.
- *
- * @file
- * quiz help function
- */
-
-/**
- * Handle help calls.
- * @see hook_help()
- */
-function _quiz_help($path, $arg) {
-  switch ($path) {
-    case 'admin/help#quiz':
-      return t('
-
-<p>The quiz module allows users to administer a quiz, as a sequence of questions, and track the answers given. It allows for the creation of questions (and their answers), and organizes these questions into a quiz. Its target audience includes educational institutions, online training programs, employers, and people who just want to add a fun activity for their visitors to their Drupal site.</p>
-
-<p>The quiz module has a plethora of permission options. Unless you take care setting your permissions, the quiz module might not do everything you want it to do.</p>
-
-<p>For more information about quiz, and resources on how to use quiz, see the <a href="http://drupal.org/project/quiz">Quiz project website</a></p>
-
-    ', array('@quiz' => QUIZ_NAME, '@admin-access' => url('admin/user/permissions'), '@admin-modules' => url('admin/modules'), '@admin-taxonomy' => url('admin/structure/taxonomy'), '@create-content' => url('node/add'), '@multichoice' => url('node/add/multichoice'), '@create-quiz' => url('node/add/quiz')));
-    case 'node/add#quiz':
-      return t('A collection of questions designed to create interactive tests');
-    default:
-      break;
-  }
-}
diff --git a/quiz.hooks.inc b/quiz.hooks.inc
new file mode 100644
index 0000000..6ebf991
--- /dev/null
+++ b/quiz.hooks.inc
@@ -0,0 +1,561 @@
+<?php
+
+/**
+ * @file
+ * Quiz hook implementations and helper functions (not API).
+ */
+
+/**
+ * Implements hook_help().
+ */
+function quiz_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#quiz':
+      return _filter_autop(t('The quiz module allows users to administer a quiz, as a sequence of questions, and track the answers given. It allows for the creation of questions (and their answers), and organizes these questions into a quiz. Its target audience includes educational institutions, online training programs, employers, and people who just want to add a fun activity for their visitors to their Drupal site.
+
+The quiz module has a plethora of permission options. Unless you take care setting your permissions, the quiz module might not do everything you want it to do.
+
+For more information about quiz, and resources on how to use quiz, see the <a href="http://drupal.org/project/quiz">Quiz project website</a>'));
+    case 'quiz/add#quiz':
+      return t('A collection of questions designed to create interactive tests');
+  }
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function quiz_views_api() {
+  return array('api' => '3.0', 'path' => QUIZ_VIEWS_DIR);
+}
+
+/**
+ * Implements hook_ctools_plugin_directory() to let the system know
+ * where our task and task_handler plugins are.
+ */
+function quiz_ctools_plugin_directory($owner, $plugin_type) {
+  if ($owner === 'page_manager') {
+    return 'plugins/page_manager/' . $plugin_type;
+  }
+  if ($owner === 'ctools' && $plugin_type === 'content_types') {
+    return 'plugins/content_types';
+  }
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function quiz_ctools_plugin_api($module, $api) {
+  if ($module === 'page_manager' && $api === 'pages_default') {
+    return array('version' => 1);
+  }
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function quiz_entity_info() {
+  return quiz()->getHookImplementation()->getHookEntityInfo()->execute();
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function quiz_field_extra_fields() {
+  return quiz()->getHookImplementation()->getHookFieldExtraFields()->execute();
+}
+
+/**
+ * Implements hook_permission().
+ */
+function quiz_permission() {
+  return array(
+      // Configure quiz
+      'administer quiz configuration'           => array(
+          'title'           => t('Administer quiz configuration'),
+          'description'     => t('Control the various settings and behaviours of quiz'),
+          'restrict access' => TRUE,
+      ),
+      // Managing quizzes
+      'access quiz'                             => array(
+          'title'       => t('Take quiz'),
+          'description' => t('Can access (take) all quizzes.'),
+      ),
+      'create quiz content'                     => array(
+          'title' => t('Create quiz content'),
+      ),
+      'edit any quiz content'                   => array(
+          'title' => t('Edit any quiz content'),
+      ),
+      // viewing results:
+      'view any quiz results'                   => array(
+          'title'       => t('View any quiz results'),
+          'description' => t('Can view results for all quizzes and users.'),
+      ),
+      'view own quiz results'                   => array(
+          'title'       => t('View own quiz results'),
+          'description' => t('Quiz takers can view their own results, also when quiz is not passed.'),
+      ),
+      'view results for own quiz'               => array(
+          'title'       => t('View results for own quiz'),
+          'description' => t('Quiz makers can view results for their own quizzes.'),
+      ),
+      // deleting results:
+      'delete any quiz results'                 => array(
+          'title' => t('Delete any quiz results'),
+      ),
+      'delete results for own quiz'             => array(
+          'title' => t('Delete own quiz results'),
+      ),
+      // scoring:
+      'score any quiz'                          => array(
+          'title' => t('Score any quiz'),
+      ),
+      'score own quiz'                          => array(
+          'title' => t('Score own quiz'),
+      ),
+      'score taken quiz answer'                 => array(
+          'title'       => t('score taken quiz answer'),
+          'description' => t('Allows attendee to score questions needing manual evaluation.'),
+      ),
+      // Allow a quiz question to be viewed outside of a test.
+      'view quiz question outside of a quiz'    => array(
+          'title'       => t('View quiz question outside of a quiz'),
+          'description' => t('Questions can only be accessed through taking a quiz (not as individual nodes) unless this permission is given.'),
+      ),
+      // Allow the user to see the correct answer, when viewed outside a quiz
+      'view any quiz question correct response' => array(
+          'title'       => t('View any quiz question correct response'),
+          'description' => t('Allow the user to see the correct answer, when viewed outside a quiz.'),
+      ),
+      // Allows users to pick a name for their questions. Otherwise this is auto
+      // generated.
+      'edit question titles'                    => array(
+          'title'       => t('Edit question titles'),
+          'description' => t('Questions automatically get a title based on the question text. This allows titles to be set manually.'),
+      ),
+      // Control revisioning, only assign this permission to users who understand
+      // who permissions work. Note: If a quiz or question is changed and not
+      // revisioned you will also change existing result reports.
+      'manual quiz revisioning'                 => array(
+          'title'       => t('Manual quiz revisioning'),
+          'description' => t('Quizzes are revisioned automatically each time they are changed. This allows you to do revisions manually.'),
+      ),
+  );
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function quiz_admin_paths() {
+  $items = array();
+
+  if (variable_get('node_admin_theme', TRUE)) {
+    $items['quiz/add'] = TRUE;
+    $items['quiz/add/*'] = TRUE;
+    $items['quiz/*/edit'] = TRUE;
+    $items['quiz/*/edit/*'] = TRUE;
+    $items['quiz/*/delete'] = TRUE;
+  }
+
+  return $items;
+}
+
+/**
+ * Implements hook_action_info().
+ */
+function quiz_action_info() {
+  $actions = array();
+
+  $actions['quiz_add_question_to_quiz'] = array(
+      'type'             => 'node',
+      'label'            => t('Add questions to @quiz', array('@quiz' => QUIZ_NAME)),
+      'configurable'     => FALSE,
+      'vbo_configurable' => FALSE,
+  );
+
+  return $actions;
+}
+
+/**
+ * Implementation of hook_cron().
+ *
+ * @todo Please document this function.
+ * @see http://drupal.org/node/1354
+ */
+function quiz_cron() {
+  // Remove old quiz results that haven't been finished.
+  if ($rm_time = variable_get('quiz_remove_partial_quiz_record', '0')) { // $time = 0 for never.
+    db_delete('quiz_results')
+      ->condition('time_end', 0)
+      ->where('(:request_time - time_start) > :remove_time', array(
+          ':request_time' => REQUEST_TIME,
+          ':remove_time'  => $rm_time
+      ))
+      ->execute();
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function quiz_menu() {
+  return quiz()->getHookImplementation()->getHookMenu()->execute();
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ *
+ * Add action link to 'quiz/add' on 'admin/content/quiz' page.
+ */
+function quiz_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+  if ($root_path === 'admin/content/quiz') {
+    if (($item = menu_get_item('quiz/add')) && $item['access']) {
+      $data['actions']['output'][] = array('#theme' => 'menu_local_action', '#link' => $item);
+    }
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function quiz_theme($existing, $type, $theme, $path) {
+  return array(
+      'quiz_view_stats'          => array(
+          'variables' => array('quiz' => NULL),
+          'file'      => 'quiz.pages.inc'
+      ),
+      'quiz_categorized_form'    => array(
+          'render element' => 'form',
+          'file'           => 'quiz.pages.inc'
+      ),
+      'quiz_result'              => array(
+          'variables' => array(
+              'quiz'      => NULL,
+              'questions' => NULL,
+              'score'     => NULL,
+              'summary'   => NULL,
+              'result_id' => NULL),
+          'file'      => 'quiz.pages.inc',
+      ),
+      'quiz_progress'            => array(
+          'variables' => array(
+              'quiz'            => null,
+              'question_number' => NULL,
+              'num_questions'   => NULL,
+              'allow_jumping'   => NULL,
+              'time_limit'      => NULL),
+          'file'      => 'quiz.pages.inc',
+      ),
+      'quiz_no_feedback'         => array(
+          'file'      => 'quiz.pages.inc',
+          'variables' => array(),
+      ),
+      'question_selection_table' => array(
+          'file'           => 'quiz.pages.inc',
+          'render element' => 'form',
+      ),
+      'quiz_answer_result'       => array(
+          'file'      => 'quiz.pages.inc',
+          'variables' => array(),
+      ),
+      'quiz_report_form'         => array(
+          'render element' => 'form',
+          'path'           => $path . '/theme',
+          'template'       => 'quiz-report-form',
+      ),
+      'quiz_browser'             => array(
+          'render element' => 'form',
+          'file'           => 'quiz.pages.inc',
+      ),
+      'quiz_question_score'      => array(
+          'variables' => array('score' => NULL, 'max_score' => NULL),
+          'path'      => $path . '/theme',
+          'template'  => 'quiz-question-score',
+      ),
+      'quiz_pager'               => array(
+          'variables' => array('quiz' => null, 'total' => 0, 'current' => 0, 'siblings' => 0),
+      ),
+  );
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function quiz_form_alter(&$form, $form_state, $form_id) {
+  if (!isset($form['#quiz_check_revision_access'])) {
+    return;
+  }
+
+  // Remove revision fieldset if user don't have access to revise quiz manually.
+  if (!user_access('manual quiz revisioning') || variable_get('quiz_auto_revisioning', 1)) {
+    $form['revision_information']['revision']['#type'] = 'value';
+    $form['revision_information']['revision']['#value'] = $form['revision_information']['revision']['#default_value'];
+    $form['revision_information']['log']['#type'] = 'value';
+    $form['revision_information']['log']['#value'] = $form['revision_information']['log']['#default_value'];
+    $form['revision_information']['#access'] = FALSE;
+  }
+  unset($form['actions']['preview'], $form['actions']['preview_changes']);
+  $form['actions']['submit']['#access'] = TRUE;
+
+  // Quiz questions might want to add a cancel button.
+  if (isset($form['#cancel_button'])) {
+    $form['actions']['cancel'] = array('#weight' => 6, '#markup' => l(t('Cancel'), $form_state['redirect']));
+  }
+}
+
+/**
+ * Implements hook_validate().
+ */
+function quiz_validate($node) {
+  quiz()->getNodeHelper()->validate($node);
+}
+
+/**
+ * Implements hook_insert().
+ */
+function quiz_insert($node) {
+  quiz()->getNodeHelper()->insert($node);
+}
+
+/**
+ * Implements hook_update().
+ */
+function quiz_update($node) {
+  quiz()->getNodeHelper()->update($node);
+}
+
+/**
+ * Implementation of hook_node_presave().
+ */
+function quiz_node_presave($node) {
+  // Convert the action id to the actual id from the MD5 hash.
+  // Why the actions module does this I do not know? Maybe to prevent
+  // invalid values put into the options value="" field.
+  if (($node->type === 'quiz') && !empty($node->aid) && $aid = actions_function_lookup($node->aid)) {
+    $node->aid = $aid;
+  }
+
+  if (isset($node->is_quiz_question) && variable_get('quiz_auto_revisioning', 1)) {
+    $node->revision = (quiz_question_has_been_answered($node)) ? 1 : 0;
+  }
+}
+
+/**
+ * Implementation of hook_node_prepare().
+ */
+function quiz_node_prepare($node) {
+  if (isset($node->is_quiz_question) && variable_get('quiz_auto_revisioning', 1)) {
+    $node->revision = (quiz_question_has_been_answered($node)) ? 1 : 0;
+  }
+}
+
+/**
+ * Implements hook_user_cancel().
+ */
+function quiz_user_cancel($edit, $account, $method) {
+  quiz()->getHookImplementation()->getHookUserCancel($account, $method)->execute();
+}
+
+/**
+ * Implements hook_mail().
+ */
+function quiz_mail($key, &$message, $params) {
+  global $user;
+
+  switch ($key) {
+    case 'notice':
+      list($quiz, $score, $result_id, $target) = $params;
+      list($subject, $body) = quiz()->getMailHelper()->notice($user, $quiz, $score, $result_id, $target);
+      $message['subject'] = $subject;
+      $message['body'][] = $body;
+      break;
+  }
+}
+
+/**
+ * Implements hook_quiz_scored().
+ *
+ * Performs actions like sending quiz results over email at the end of quiz.
+ */
+function quiz_quiz_scored($quiz, $score, $result_id) {
+  return quiz()->getHookImplementation()->getHookQuizScored($quiz, $score, $result_id)->execute();
+}
+
+// ---------------------
+// Helper functions
+// ---------------------
+
+/**
+ * Shortcut to entity_load_single().
+ *
+ * @return \Drupal\quiz\Entity\Result
+ */
+function quiz_result_load($result_id) {
+  return entity_load_single('quiz_result', $result_id);
+}
+
+/**
+ * Workaround for menu_translate bug.
+ */
+function question_number_to_arg() {
+  return 0;
+}
+
+/**
+ * Result_id, menu argument loader.
+ */
+function quiz_rid_load($result_id) {
+  if ($result_id > 0) {
+    return $result_id;
+  }
+  return FALSE;
+}
+
+/**
+ * Workaround for a _menu_translate() bug.
+ */
+function quiz_rid_to_arg() {
+  return 0;
+}
+
+/**
+ * Helper function to check if the user has any of a given list of permissions.
+ *
+ * @param args
+ *   Any number of permissions.
+ * @return
+ *   TRUE if the user has access to any of the arguments given.
+ */
+function quiz_access_multi_or() {
+  foreach (func_get_args() as $perm) {
+    if (user_access($perm)) {
+      return TRUE;
+    }
+  }
+}
+
+/**
+ * Helper function to determine if a user has access to view a specific quiz result.
+ *
+ * @param \Drupal\quiz\Entity\Result|int $result
+ * @return boolean
+ *  True if access, false otherwise
+ */
+function quiz_access_my_result($result) {
+  global $user;
+
+  if (is_numeric($result) && !$result = quiz_result_load($result)) {
+    throw new RuntimeException('Invalid result ID.');
+  }
+
+  return $result->canAccessOwnResult($user);
+}
+
+/**
+ * Helper function to determine if a user has access to the different results
+ * pages.
+ *
+ * @param $quiz
+ *   The quiz entity.
+ * @param $result_id
+ *   The result id of a result we are trying to access.
+ * @return boolean
+ *   TRUE if user has permission.
+ */
+function quiz_access_results($quiz, $result_id = NULL) {
+  global $user;
+  return quiz()->getQuizHelper()->getAccessHelper()->canAccessResults($user, $quiz, $result_id);
+}
+
+/**
+ * Helper function to determine if a user has access to view his quiz results
+ *
+ * @param object $quiz
+ *  The Quiz entity
+ */
+function quiz_access_my_results($quiz) {
+  global $user;
+  return quiz()->getQuizHelper()->getAccessHelper()->canAccessMyResults($quiz, $user);
+}
+
+/**
+ * Does the current user have access to take the quiz?
+ *
+ * @param $quiz
+ *  The quiz entity
+ */
+function quiz_take_access($quiz) {
+  global $user;
+  return quiz()->getQuizHelper()->getAccessHelper()->canTakeQuiz($quiz, $user);
+}
+
+/**
+ * Menu check to see if user has access to take this question.
+ */
+function quiz_access_question($quiz, $page_number) {
+  return quiz()->getQuizHelper()->getAccessHelper()->canAccessQuestion($quiz, $page_number);
+}
+
+/**
+ * Access callback for quiz-type.
+ *
+ * @param string $op
+ * @param string $type
+ * @param stdClass $account
+ */
+function quiz_type_access($op, $type = NULL, $account = NULL) {
+  return user_access('administer quiz configuration', $account);
+}
+
+/**
+ * Access callback for quiz-entity.
+ * @param string $op
+ * @param string $type
+ * @param stdClass $account
+ */
+function quiz_entity_access_callback($op, $type = NULL, $account = NULL) {
+  switch ($op) {
+    case 'update':
+      return user_access('edit any quiz content', $account);
+    case 'view':
+      return user_access('access quiz', $account);
+  }
+  return TRUE;
+}
+
+/**
+ * @TODO: Access callback for /quiz/add.
+ *
+ * @return bool
+ *  TRUE only if user can create at least one quiz type.
+ */
+function quiz_can_create_quiz_entity() {
+  return TRUE;
+}
+
+/**
+ * Validate that a node is of type quiz, and that the user has access to it.
+ *
+ * @param stdClass $node
+ * @param string $op
+ * @return bool
+ */
+function quiz_type_confirm($node, $op = NULL) {
+  if ($node->type !== 'quiz') {
+    return FALSE;
+  }
+
+  if (isset($op)) {
+    $node_ops = array('view', 'update', 'delete', 'create');
+    return in_array($op, $node_ops) ? node_access($op, $node) : user_access($op);
+  }
+
+  return user_access('access quiz');
+}
+
+/**
+ * Menu access check for question feedback.
+ */
+function quiz_question_feedback_access($quiz, $question_number) {
+  return quiz()->getQuizHelper()->getFeedbackHelper()->canAccess($quiz, $question_number);
+}
diff --git a/quiz.info b/quiz.info
index 4fffd57..f1b6e22 100644
--- a/quiz.info
+++ b/quiz.info
@@ -2,15 +2,20 @@ name = Quiz
 package = Quiz Core
 description = Create interactive quizzes. This module must have at least one question type module enabled in order to function properly.
 core = 7.x
+
+dependencies[] = filter
 dependencies[] = ctools
 dependencies[] = entity
-dependencies[] = filter
 dependencies[] = views
 dependencies[] = views_bulk_operations
+dependencies[] = xautoload (>= 7.x-4.0)
+
 test_dependencies[] = taxonomy
 test_dependencies[] = views
-stylesheets[all][] = quiz.css
-files[] = includes/views/handlers/quiz_views_handler_argument_quiz_nid.inc
+
+stylesheets[all][] = misc/quiz.css
+
+files[] = includes/views/handlers/quiz_views_handler_argument_quiz_qid.inc
 files[] = includes/views/handlers/quiz_views_handler_argument_user_uid_nullable.inc
 files[] = includes/views/handlers/quiz_views_handler_field_number_questions.inc
 files[] = includes/views/handlers/quiz_views_handler_field_question_status.inc
@@ -20,16 +25,17 @@ files[] = includes/views/handlers/quiz_views_handler_field_node_link_take.inc
 files[] = includes/views/handlers/quiz_views_handler_field_time.inc
 files[] = includes/views/handlers/quiz_views_handler_field_user_quiz_state.inc
 files[] = includes/views/handlers/quiz_views_handler_filter_question_status.inc
-files[] = includes/views/handlers/quiz_views_handler_filter_quiz_nid.inc
+files[] = includes/views/handlers/quiz_views_handler_filter_quiz_qid.inc
 files[] = includes/views/handlers/quiz_views_handler_filter_user_nullable.inc
 files[] = includes/views/handlers/quiz_views_handler_filter_user_quiz_state.inc
-files[] = includes/views/handlers/quiz_views_handler_relationship_quiz_nid.inc
+files[] = includes/views/handlers/quiz_views_handler_relationship_quiz_qid.inc
 files[] = includes/views/handlers/quiz_views_handler_filter_quiz_question_type.inc
 files[] = includes/views/handlers/quiz_views_handler_filter_quiz_question.inc
-files[] = tests/QuizCreation.test
-files[] = tests/QuizNavigation.test
+
 files[] = tests/QuizTestCase.test
-files[] = tests/QuizResult.test
-files[] = tests/QuizTaking.test
-files[] = tests/QuizTimer.test
-files[] = tests/QuizFeedback.test
+files[] = tests/QuizEntityTestCase.test
+files[] = tests/QuizEntityTakingTestCase.test
+files[] = tests/QuizEntityFeedbackTestCase.test
+files[] = tests/QuizNavigationTestCase.test
+files[] = tests/QuizResultTestCase.test
+files[] = tests/QuizTimerTestCase.test
diff --git a/quiz.install b/quiz.install
index 2a76ec3..74aadd4 100644
--- a/quiz.install
+++ b/quiz.install
@@ -3,26 +3,22 @@
 /**
  * @file
  * Quiz install schema for installing the quiz module
- *
  */
 
 /**
  * Implements hook_install().
  */
 function quiz_install() {
-  node_types_rebuild();
-  $types = node_type_get_types();
-  node_add_body_field($types['quiz']);
-
-  // Default the "Show Author and Date" for quiz nodes to OFF.
-  $temp_array = variable_get('theme_settings', array());
-  $temp_array['toggle_node_info_quiz'] = 0;
-  variable_set('theme_settings', $temp_array);
-
-  // Default the comment settings to disabled.
-  variable_set('comment_quiz', '0');
+  // Create default quiz type. In case the module is installed via an
+  // installation profile, skip that.
+  if (!drupal_installation_attempted()) {
+    $quiz_type = entity_create('quiz_type', array('type' => 'quiz', 'label' => t('Quiz'), 'weight' => 0));
+    $quiz_type->save();
+  }
 
-  drupal_set_message(t('Quiz module has been enabled. To !create_a_quiz go to Create Content -> Quiz.', array('!create_a_quiz' => l(t('create a quiz'), 'node/add/quiz'))));
+  drupal_set_message(t('Quiz module has been enabled. To !create_a_quiz go to Create Content -> Quiz.', array(
+      '!create_a_quiz' => l(t('create a quiz'), 'quiz/add')
+  )));
 }
 
 /**
@@ -30,552 +26,189 @@ function quiz_install() {
  */
 function quiz_schema() {
   $schema = array();
-  /**
-   * Connect all the quiz specific properties to the correct version of a quiz.
-   */
-  // Create the quiz node properties table
-  $schema['quiz_node_properties'] = array(
-    'description' => 'The base table for quiz nodes',
-    'fields' => array(
-      'qnp_id' => array(
-        'type' => 'serial',
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'uid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'aid' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'number_of_random_questions' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'max_score_for_random' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'pass_rate' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'summary_pass' => array(
-        'type' => 'text',
-      ),
-      'summary_pass_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'summary_default' => array(
-        'type' => 'text',
-      ),
-      'summary_default_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'randomization' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'backwards_navigation' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'keep_results' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => QUIZ_KEEP_ALL,
-      ),
-      'repeat_until_correct' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'quiz_open' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'quiz_close' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'takes' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'show_attempt_stats' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'time_limit' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'quiz_always' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'tid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'has_userpoints' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'userpoints_tid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'time_left' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'max_score' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'allow_skipping' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'allow_resume' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'allow_jumping' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'allow_change' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'show_passed' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'mark_doubtful' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'review_options' => array(
-        'type' => 'text',
-        'serialize' => TRUE,
-      ),
-    ),
-    'primary key' => array('qnp_id'),
-    'indexes' => array('quiz_id' => array('vid', 'nid')),
+
+  $schema['quiz_type'] = array(
+      'description' => 'Stores information about all defined quiz types.',
+      'fields'      => array(
+          'id'          => array('type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique quiz type ID.'),
+          'type'        => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'description' => 'The machine-readable name of this quiz type.'),
+          'label'       => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The human-readable name of this quiz type.'),
+          'weight'      => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny', 'description' => 'The weight of this quiz type in relation to others.'),
+          'data'        => array('type' => 'text', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, 'description' => 'A serialized array of additional data related to this quiz type.'),
+          'status'      => array('type' => 'int', 'not null' => TRUE, 'default' => 0x01, 'size' => 'tiny', 'description' => 'The exportable status of the entity.'),
+          'module'      => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'description' => 'The name of the providing module if the entity has been defined in code.'),
+          'description' => array('type' => 'text', 'not null' => FALSE, 'size' => 'medium', 'translatable' => TRUE, 'description' => 'A brief description of this quiz type.'),
+          'help'        => array('type' => 'text', 'not null' => FALSE, 'size' => 'medium', 'translatable' => TRUE, 'description' => 'Help information shown to the user when creating a quiz entity of this type.'),
+      ),
+      'primary key' => array('id'),
+      'unique keys' => array('type' => array('type')),
   );
-  /*
-   * Both a quiz and a quiz question are nodes with versions.  A quiz is a parent node of a quiz question,
-   * making the quiz question the child.
-   *
-   * The quiz_node_relationship table stores this relationship in a way that allows a quiz question to be
-   * the child of multiple quizzes without losing version history.
-   *
-   * Future functionality will allow a quiz question to be a parent of another quiz question with the same
-   * data model.  This will make adaptive quiz functionality possible without redesign.
-   */
-  // Create the quiz node relationship table
-  $schema['quiz_node_relationship'] = array(
-    'description' => 'Table storing what questions belong to what quizzes',
-    'fields' => array(
-      'qnr_id' => array(
-        'type' => 'serial',
-        'size' => 'normal',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'qnr_pid' => array(
-        'type' => 'int',
-        'size' => 'normal',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-      ),
-      'parent_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'parent_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'child_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'child_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_status' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'weight' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'max_score' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'auto_update_max_score' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
+
+  $schema['quiz_entity'] = array(
+      'description' => 'Store quiz items',
+      'fields'      => array(
+          'qid'     => array('type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique quiz item ID.'),
+          'vid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'type'    => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '', 'description' => 'The {quiz_type}.type of this quiz.'),
+          'status'  => array('type' => 'int', 'not null' => TRUE, 'default' => 1, 'description' => 'Boolean indicating whether the quiz is published (visible to non-administrators).'),
+          'title'   => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The title of this quiz, always treated as non-markup plain text.'),
+          'created' => array('type' => 'int', 'not null' => FALSE, 'description' => 'The Unix timestamp when the quiz was created.'),
+          'changed' => array('type' => 'int', 'not null' => FALSE, 'description' => 'The Unix timestamp when the quiz was most recently saved.'),
+          'uid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+      ),
+      'primary key' => array('qid'),
+  );
+
+  $schema['quiz_entity_revision'] = array(
+      'description' => 'Entity revision table for quiz content with fields.',
+      'fields'      => array(
+          'qid'                        => array('type' => 'int', 'not null' => TRUE, 'description' => 'The id this revision belongs to'),
+          'vid'                        => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The primary identifier for this version.'),
+          'revision_uid'               => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'description' => 'Author of quiz revision.'),
+          'log'                        => array('type' => 'text', 'size' => 'big', 'description' => t('A log message associated with the revision.')),
+          'changed'                    => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The Unix timestamp when the quiz was most recently saved.'),
+          'aid'                        => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE),
+          'number_of_random_questions' => array('type' => 'int', 'size' => 'small', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'max_score_for_random'       => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 1),
+          'pass_rate'                  => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE),
+          'summary_pass'               => array('type' => 'text'),
+          'summary_pass_format'        => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE,),
+          'summary_default'            => array('type' => 'text',),
+          'summary_default_format'     => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE,),
+          'randomization'              => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0),
+          'backwards_navigation'       => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 1),
+          'keep_results'               => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => QUIZ_KEEP_ALL,),
+          'repeat_until_correct'       => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'quiz_open'                  => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'quiz_close'                 => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'takes'                      => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'show_attempt_stats'         => array('type' => 'int', 'size' => 'tiny', 'unsigned' => FALSE, 'not null' => TRUE, 'default' => 1),
+          'time_limit'                 => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'quiz_always'                => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+          'tid'                        => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,),
+          'has_userpoints'             => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'userpoints_tid'             => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'time_left'                  => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0),
+          'max_score'                  => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+          'allow_skipping'             => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0),
+          'allow_resume'               => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 1),
+          'allow_jumping'              => array('type' => 'int', 'size' => 'tiny', 'unsigned' => FALSE, 'not null' => TRUE, 'default' => 0),
+          'allow_change'               => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1),
+          'show_passed'                => array('type' => 'int', 'size' => 'tiny', 'unsigned' => FALSE, 'not null' => TRUE, 'default' => 1),
+          'mark_doubtful'              => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+          'review_options'             => array('type' => 'text', 'serialize' => TRUE),
+      ),
+      'primary key' => array('vid'),
+      'indexes'     => array('fpid' => array('qid', 'vid')),
+  );
+
+  $schema['quiz_relationship'] = array(
+      'description' => 'Table storing what questions belong to what quizzes aware of revision.',
+      'fields'      => array(
+          'qr_id'                 => array('type' => 'serial', 'size' => 'normal', 'unsigned' => TRUE, 'not null' => TRUE),
+          'quiz_qid'              => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'quiz_vid'              => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'qr_pid'                => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'default' => NULL, 'description' => 'ID of parent page (question node)'),
+          'question_nid'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'question_vid'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'question_status'       => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 1),
+          'weight'                => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+          'max_score'             => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+          'auto_update_max_score' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0),
+      ),
+      'primary key' => array('qr_id'),
+      'unique keys' => array(
+          'parent_child' => array('quiz_qid', 'quiz_vid', 'question_nid', 'question_vid'),
+      ),
+      'indexes'     => array(
+          'parent_id' => array('quiz_vid'),
+          'child_id'  => array('question_vid'),
       ),
-    ),
-    'primary key' => array('qnr_id'),
-    'unique keys' => array(
-      'parent_child' => array('parent_nid', 'parent_vid', 'child_nid', 'child_vid'),
-    ),
-    'indexes' => array(
-      'parent_id' => array('parent_vid'),
-      'child_id' => array('child_vid'),
-    ),
   );
 
   /**
    * Quiz specific options concerning  availability and access to scores.
    */
-  // Create the quiz node results table
-  $schema['quiz_node_results'] = array(
-    'description' => 'Table storing the total results for a quiz',
-    'fields' => array(
-      'result_id' => array(
-        'type' => 'serial',
-        'size' => 'normal',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'uid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'time_start' => array(
-        'type' => 'int',
-        'unsigned' => FALSE,
-      ),
-      'time_end' => array(
-        'type' => 'int',
-        'unsigned' => FALSE,
-      ),
-      'released' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'default' => 0,
+  // Create the quiz entity results table
+  $schema['quiz_results'] = array(
+      'description' => 'Table storing the total results for a quiz',
+      'fields'      => array(
+          'result_id'    => array('type' => 'serial', 'size' => 'normal', 'unsigned' => TRUE, 'not null' => TRUE,),
+          'quiz_qid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'ID of quiz entity'),
+          'quiz_vid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Version ID of quiz entity'),
+          'uid'          => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Author ID'),
+          'time_start'   => array('type' => 'int', 'unsigned' => FALSE),
+          'time_end'     => array('type' => 'int', 'unsigned' => FALSE),
+          'released'     => array('type' => 'int', 'unsigned' => TRUE, 'default' => 0),
+          'score'        => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
+          'is_invalid'   => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'is_evaluated' => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'time_left'    => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0),
+      ),
+      'primary key' => array('result_id'),
+      'indexes'     => array(
+          'user_results' => array('uid', 'quiz_vid', 'quiz_qid'),
+          'vid'          => array('quiz_vid'),
       ),
-      'score' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'is_invalid' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'is_evaluated' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'time_left' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'not null' => TRUE,
-        'default' => 0
-      ),
-    ),
-    'primary key' => array('result_id'),
-    'indexes' => array(
-      'user_results' => array('uid', 'vid', 'nid'),
-      'vid' => array('vid'),
-    ),
   );
 
   /**
    * Information about a particular question in a result
    */
-  $schema['quiz_node_results_answers'] = array(
-    'description' => 'Table storing information about the results for the questions',
-    'fields' => array(
-      'result_answer_id' => array(
-        'description' => 'The result answer ID',
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'result_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'question_vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'tid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-      ),
-      'is_correct' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'is_skipped' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0
-      ),
-      'points_awarded' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'answer_timestamp' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-      ),
-      'number' => array(
-        'type' => 'int',
-        'size' => 'small',
-        'unsigned' => FALSE,
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'is_doubtful' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-        'size' => 'tiny'
-      ),
-    ),
-    'primary key' => array('result_answer_id'),
-    'unique keys' => array(
-      'result_answer' => array('result_id', 'question_nid', 'question_vid'),
-    ),
-    'indexes' => array(
-      'result_id' => array('result_id'),
-    ),
+  $schema['quiz_results_answers'] = array(
+      'description' => 'Table storing information about the results for the questions',
+      'fields'      => array(
+          'result_answer_id' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The result answer ID',),
+          'result_id'        => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'question_nid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'question_vid'     => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+          'tid'              => array('type' => 'int', 'unsigned' => TRUE),
+          'is_correct'       => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,),
+          'is_skipped'       => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
+          'points_awarded'   => array('type' => 'int', 'size' => 'tiny', 'unsigned' => FALSE, 'not null' => TRUE, 'default' => 0,),
+          'answer_timestamp' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'default' => NULL),
+          'number'           => array('type' => 'int', 'size' => 'small', 'unsigned' => FALSE, 'not null' => TRUE, 'default' => 1,),
+          'is_doubtful'      => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'),
+      ),
+      'primary key' => array('result_answer_id'),
+      'unique keys' => array(
+          'result_answer' => array('result_id', 'question_nid', 'question_vid'),
+      ),
+      'indexes'     => array('result_id' => array('result_id')),
   );
 
-  /**
-   * Allows custom feedback based on the results of a user completing a quiz.
-   */
-  // Create the quiz node result options table
-  $schema['quiz_node_result_options'] = array(
-    'description' => 'Table storing result options for quizzes. Several result options may belong to a single quiz.',
-    'fields' => array(
-      'option_id' => array(
-        'type' => 'serial',
-        'size' => 'normal',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'option_name' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'option_summary' => array(
-        'type' => 'text',
-      ),
-      'option_summary_format' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-      ),
-      'option_start' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'default' => 0,
-      ),
-      'option_end' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array('option_id'),
-    'indexes' => array(
-      'quiz_id' => array('vid', 'nid'),
-    )
+  // Create the quiz entity result options table.
+  // Allows custom feedback based on the results of a user completing a quiz.
+  $schema['quiz_result_options'] = array(
+      'description' => 'Table storing result options for quizzes. Several result options may belong to a single quiz.',
+      'fields'      => array(
+          'option_id'             => array('type' => 'serial', 'size' => 'normal', 'unsigned' => TRUE, 'not null' => TRUE),
+          'quiz_qid'              => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'ID of quiz entity'),
+          'quiz_vid'              => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Version ID of quiz entity'),
+          'option_name'           => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
+          'option_summary'        => array('type' => 'text'),
+          'option_summary_format' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE),
+          'option_start'          => array('type' => 'int', 'unsigned' => TRUE, 'default' => 0),
+          'option_end'            => array('type' => 'int', 'unsigned' => TRUE, 'default' => 0),
+      ),
+      'primary key' => array('option_id'),
+      'indexes'     => array('quiz_id' => array('quiz_vid', 'quiz_qid'))
   );
 
   $schema['quiz_terms'] = array(
-    'description' => 'Table storing what terms belongs to what quiz for categorized random quizzes',
-    'fields' => array(
-      'nid' => array(
-        'description' => 'Node id',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'vid' => array(
-        'description' => 'Version id',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'weight' => array(
-        'description' => 'The terms weight decides the order of the terms',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'tid' => array(
-        'description' => 'Term id',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'max_score' => array(
-        'description' => 'Max score for each question marked with this term',
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'number' => array(
-        'description' => 'Number of questions to be drawn from this term',
-        'type' => 'int',
-        'size' => 'tiny',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-    ),
-    'primary key' => array('vid', 'tid'),
-    'indexes' => array(
-      'version' => array('vid'),
-    ),
+      'description' => 'Table storing what terms belongs to what quiz for categorized random quizzes',
+      'fields'      => array(
+          'nid'       => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Node ID'),
+          'vid'       => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Version ID'),
+          'weight'    => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The terms weight decides the order of the terms'),
+          'tid'       => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Term ID'),
+          'max_score' => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Max score for each question marked with this term'),
+          'number'    => array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Number of questions to be drawn from this term'),
+      ),
+      'primary key' => array('vid', 'tid'),
+      'indexes'     => array('version' => array('vid')),
   );
+
   return $schema;
 }
 
@@ -622,18 +255,18 @@ function quiz_update_7403() {
  */
 function quiz_update_7404() {
   db_change_field('quiz_node_properties', 'number_of_random_questions', 'number_of_random_questions', array(
-    'type' => 'int',
-    'size' => 'small',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-    'default' => 0,
+      'type'     => 'int',
+      'size'     => 'small',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+      'default'  => 0,
   ));
   db_change_field('quiz_node_results_answers', 'number', 'number', array(
-    'type' => 'int',
-    'size' => 'small',
-    'unsigned' => FALSE,
-    'not null' => TRUE,
-    'default' => 1,
+      'type'     => 'int',
+      'size'     => 'small',
+      'unsigned' => FALSE,
+      'not null' => TRUE,
+      'default'  => 1,
   ));
   return t('Increased the maximum quiz size.');
 }
@@ -643,9 +276,9 @@ function quiz_update_7404() {
  */
 function quiz_update_7405() {
   $spec = array(
-    'type' => 'int',
-    'unsigned' => FALSE,
-    'default' => 0,
+      'type'     => 'int',
+      'unsigned' => FALSE,
+      'default'  => 0,
   );
   db_change_field('quiz_node_results', 'time_start', 'time_start', $spec);
   db_change_field('quiz_node_results', 'time_end', 'time_end', $spec);
@@ -657,10 +290,10 @@ function quiz_update_7405() {
  */
 function quiz_update_7406(&$sandbox) {
   $spec = array(
-    'type' => 'int',
-    'not null' => TRUE,
-    'default' => 0,
-    'size' => 'tiny'
+      'type'     => 'int',
+      'not null' => TRUE,
+      'default'  => 0,
+      'size'     => 'tiny'
   );
   db_add_field('quiz_node_results_answers', 'is_doubtful', $spec);
   db_add_field('quiz_node_properties', 'mark_doubtful', $spec);
@@ -672,10 +305,10 @@ function quiz_update_7406(&$sandbox) {
  */
 function quiz_update_7407(&$sandbox) {
   $spec = array(
-    'type' => 'int',
-    'size' => 'tiny',
-    'not null' => TRUE,
-    'default' => 0,
+      'type'     => 'int',
+      'size'     => 'tiny',
+      'not null' => TRUE,
+      'default'  => 0,
   );
   db_add_field('quiz_node_relationship', 'auto_update_max_score', $spec);
   return t('Added new auto update max score field to the quiz_node_relationship table');
@@ -707,10 +340,10 @@ function quiz_uninstall() {
  */
 function quiz_update_7500() {
   $spec = array(
-    'serialize' => TRUE,
-    'type' => 'text',
-    'description' => "Serialized layout data.",
-    'size' => 'medium',
+      'serialize'   => TRUE,
+      'type'        => 'text',
+      'description' => "Serialized layout data.",
+      'size'        => 'medium',
   );
   db_add_field('quiz_node_results', 'layout', $spec);
   return t('Added new layout field to the quiz_node_results table');
@@ -723,10 +356,10 @@ function quiz_update_7501() {
   db_drop_primary_key('quiz_node_results_answers');
   db_add_unique_key('quiz_node_results_answers', 'result_answer', array('result_id', 'question_nid', 'question_vid'));
   $spec = array(
-    'description' => 'The result answer ID.',
-    'type' => 'serial',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
+      'description' => 'The result answer ID.',
+      'type'        => 'serial',
+      'unsigned'    => TRUE,
+      'not null'    => TRUE,
   );
   db_add_field('quiz_node_results_answers', 'result_answer_id', $spec, array('primary key' => array('result_answer_id')));
   return t('Added new result_answer_id field to the quiz_node_results_answers table.');
@@ -739,10 +372,10 @@ function quiz_update_7502() {
   db_drop_primary_key('quiz_node_relationship');
   db_add_unique_key('quiz_node_relationship', 'parent_child', array('parent_nid', 'parent_vid', 'child_nid', 'child_vid'));
   $spec = array(
-    'type' => 'serial',
-    'size' => 'normal',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
+      'type'     => 'serial',
+      'size'     => 'normal',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
   );
   db_add_field('quiz_node_relationship', 'qnr_id', $spec, array('primary key' => array('qnr_id')));
   return t('Added new qnr_id field to the quiz_node_relationship table.');
@@ -753,11 +386,11 @@ function quiz_update_7502() {
  */
 function quiz_update_7503() {
   $spec = array(
-    'type' => 'int',
-    'size' => 'normal',
-    'unsigned' => TRUE,
-    'not null' => FALSE,
-    'default' => NULL,
+      'type'     => 'int',
+      'size'     => 'normal',
+      'unsigned' => TRUE,
+      'not null' => FALSE,
+      'default'  => NULL,
   );
   db_add_field('quiz_node_relationship', 'qnr_pid', $spec);
   return t('Added new qnr_pid field to the quiz_node_relationship table.');
@@ -769,8 +402,8 @@ function quiz_update_7503() {
  */
 function quiz_update_7504() {
   $spec = array(
-    'type' => 'int',
-    'unsigned' => FALSE,
+      'type'     => 'int',
+      'unsigned' => FALSE,
   );
   db_change_field('quiz_node_results', 'time_start', 'time_start', $spec);
   db_change_field('quiz_node_results', 'time_end', 'time_end', $spec);
@@ -783,8 +416,8 @@ function quiz_update_7504() {
  */
 function quiz_update_7505() {
   db_add_field('quiz_node_properties', 'review_options', array(
-    'type' => 'text',
-    'serialize' => TRUE,
+      'type'      => 'text',
+      'serialize' => TRUE,
   ));
 
   drupal_get_schema(NULL, TRUE);
@@ -820,14 +453,14 @@ function quiz_update_7505() {
 function quiz_update_7506() {
   db_drop_primary_key('quiz_node_properties');
   db_add_field('quiz_node_properties', 'qnp_id', array(
-    'type' => 'serial',
+      'type' => 'serial',
     ), array('primary key' => array('qnp_id')));
 
   db_add_field('quiz_node_properties', 'uid', array(
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-    'default' => 0,
+      'type'     => 'int',
+      'unsigned' => TRUE,
+      'not null' => TRUE,
+      'default'  => 0,
   ));
 
   // We could do this, but we should really migrate user settings from 4.x.
@@ -840,10 +473,10 @@ function quiz_update_7506() {
  */
 function quiz_update_7507() {
   db_add_field('quiz_node_properties', 'allow_change', array(
-    'type' => 'int',
-    'size' => 'small',
-    'not null' => TRUE,
-    'default' => 1,
+      'type'     => 'int',
+      'size'     => 'small',
+      'not null' => TRUE,
+      'default'  => 1,
   ));
 }
 
@@ -853,12 +486,170 @@ function quiz_update_7507() {
  */
 function quiz_update_7509() {
   db_change_field('quiz_node_results_answers', 'answer_timestamp', 'answer_timestamp', array(
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not null' => FALSE,
-    'default' => NULL,
+      'type'     => 'int',
+      'unsigned' => TRUE,
+      'not null' => FALSE,
+      'default'  => NULL,
     )
   );
 
   db_drop_field('quiz_node_results', 'layout');
 }
+
+/**
+ * Update schema for quiz when it becomeds entity.
+ */
+function quiz_update_7510() {
+  db_rename_table('quiz_node_relationship', 'quiz_relationship');
+  db_change_field('quiz_relationship', 'qnr_id', 'qr_id', array('type' => 'serial', 'size' => 'normal', 'unsigned' => TRUE, 'not null' => TRUE));
+  db_change_field('quiz_relationship', 'parent_nid', 'quiz_qid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE));
+  db_change_field('quiz_relationship', 'parent_vid', 'quiz_vid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE));
+  db_change_field('quiz_relationship', 'child_nid', 'question_nid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE));
+  db_change_field('quiz_relationship', 'child_vid', 'question_vid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE));
+
+  db_rename_table('quiz_node_results', 'quiz_results');
+  db_change_field('quiz_results', 'nid', 'quiz_qid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'ID of quiz entity'));
+  db_change_field('quiz_results', 'vid', 'quiz_vid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Version ID of quiz entity'));
+
+  db_rename_table('quiz_node_result_options', 'quiz_result_options');
+  db_change_field('quiz_result_options', 'nid', 'quiz_qid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'ID of quiz entity'));
+  db_change_field('quiz_result_options', 'vid', 'quiz_vid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'Version ID of quiz entity'));
+  db_rename_table('quiz_node_results_answers', 'quiz_results_answers');
+}
+
+/**
+ * Convert quiz nodes to quiz entity
+ */
+function quiz_update_7511() {
+  // create temp columns quiz_revision.{node_nid, node_vid} to save old data
+  db_add_field('quiz_entity', 'node_nid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+  db_add_index('quiz_entity', 'quiz_node', array('node_qid'));
+  db_add_field('quiz_entity_revision', 'node_vid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
+
+  // create default quiz type
+  $quiz_type = entity_create('quiz_type', array('type' => 'quiz', 'label' => t('Quiz'), 'weight' => 0));
+  $quiz_type->save();
+
+  // migrate basic quiz
+  $select = db_select('node', 'n');
+  $select->innerJoin('node_revision', 'r', 'n.nid = r.nid');
+  $select->fields('r', array('nid', 'vid', 'uid', 'title', 'log', 'timestamp'));
+  $select->fields('n', array('created'));
+  $select->condition('n.type', 'quiz');
+  $select->orderBy('r.vid', 'ASC');
+  $result = $select->execute();
+  while ($row = $result->fetchAll()) {
+    _quiz_update_7511($row);
+  }
+}
+
+/**
+ * Create quiz revision with quiz-node basic data.
+ *
+ * @param stdClass $row
+ */
+function _quiz_update_7511($row) {
+  // find quiz revision
+  if ($quiz_id = db_query('SELECT qid FROM {quiz_entity} WHERE node_nid = :nid', array(':nid' => $row->nid))) {
+    $quiz = quiz_entity_single_load($quiz_id);
+  }
+  else {
+    $quiz = entity_create('quiz_entity', array(
+        'type'     => 'quiz',
+        'status'   => $row->status,
+        'uid'      => $row->uid,
+        'title'    => $row->title,
+        'created'  => $row->created,
+        'changed'  => $row->timestamp,
+        'node_nid' => $row->nid,
+        'node_vid' => $row->vid,
+    ));
+  }
+
+  $quiz->is_new_revision = 1;
+  $quiz->log = $row->log;
+  entity_save('quiz_entity', $quiz);
+}
+
+/**
+ * Convet quiz-node fields data to quiz entity
+ */
+function quiz_update_7512() {
+  // quiz node type is defined by quiz.module, but this modules no longer powers
+  // it. Reown to core 'node' module.
+  db_update("UDPATE {node_type} set module = 'node' WHERE type='quiz'");
+
+  $field_instances = field_info_instances('node', 'quiz');
+  $field_names = array_keys($field_instances);
+
+  // copy fields from quiz node to quiz entity
+  foreach ($field_instances as $name => $instance) {
+    if ('body' === $name) { // body field is auto created via internal API
+      continue;
+    }
+
+    unset($instance['id']);
+    $instance['entity_type'] = 'quiz_entity';
+    $instance['bundle'] = 'quiz';
+    field_create_instance($instance);
+  }
+
+  // migrate basic quiz
+  $select = db_select('node', 'n');
+  $select->innerJoin('node_revision', 'r', 'n.nid = r.nid');
+  $select->fields('r', array('nid', 'vid'));
+  $select->condition('n.type', 'quiz');
+  $result = $select->execute();
+  while ($row = $result->fetchAll()) {
+    $quiz_node = node_load($row->nid, $row->vid);
+    _quiz_update_7512($quiz_node, $field_names);
+  }
+}
+
+function _quiz_update_7512($quiz_node, $field_names) {
+  $sql = 'SELECT vid FROM {quiz_entity_revision} WHERE node_vid = :vid';
+  if ($quiz_vid = db_query($sql, array(':vid' => $quiz_node->vid))->fetchColumn()) {
+    $quiz = quiz_entity_single_load(NULL, $quiz_vid);
+    foreach ($field_names as $field_name) {
+      if (empty($quiz_node->{$field_name})) {
+        continue;
+      }
+
+      if ('body' === $field_name) {
+        $quiz->quiz_body = $quiz_node->body;
+      }
+      else {
+        $quiz->{$field_name} = $quiz_node->{$field_name};
+      }
+    }
+    entity_save('quiz_entity', $quiz);
+  }
+}
+
+/**
+ * Change path aliases from quiz-node to quiz-entity
+ */
+function quiz_update_7513() {
+  $select = db_select('quiz_entity');
+  $select->fields('quiz_entity', array('qid, node_nid'));
+  $result = $select->execute();
+  while ($row = $result->fetchAll()) {
+    if ($path = path_load("node/{$row->node_nid}")) {
+      $path['source'] = "quiz/{$row->qid}";
+      path_save($path);
+    }
+  }
+}
+
+/**
+ * Clean up data of when quiz was node.
+ */
+function quiz_update_7514() {
+  # db_drop_table('quiz_node_properties');
+  # delete all quiz nodes?
+  # delete quiz node type?
+
+  db_drop_index('quiz_entity', 'quiz_node');
+  db_drop_field('quiz_entity', 'node_nid');
+  db_drop_field('quiz_entity_revision', 'node_vid');
+}
diff --git a/quiz.module b/quiz.module
index 3a993ac..9a68906 100644
--- a/quiz.module
+++ b/quiz.module
@@ -1,31 +1,33 @@
 <?php
 
+use Drupal\quiz\Entity\QuizEntityType;
+use Drupal\quiz\Entity\Result;
+use Drupal\quiz\Quiz;
+
 /**
  * @file
  * Quiz Module
  *
  * This module allows the creation of interactive quizzes for site visitors.
+ *
+ * Where possible, user pages are located in quiz.pages.inc, and admin pages
+ * are in quiz.pages.inc. Most utility functions have been left here, even if they
+ * are only used by a function in one of the other files. quiz_datetime.inc holds
+ * some additional date/time functions.
+ *
+ * Themes are in quiz.pages.inc unless they clearly only apply to admin screens.
+ * Then they are in quiz.pages.inc.
+ *
+ * Views support is included in includes/views/quiz.views.inc
+ *
+ * Function starts with __quiz_ will be removed.
  */
-// This module is structured as follows:
-//
-// The main module file:
-// * Defines and general includes are at the top.
-// * Hook implementations come immediately after.
-// * Public functions come next.
-// * Private functions are at the bottom.
-//
-// Where possible, user pages are located in quiz.pages.inc, and admin pages
-// are in quiz.admin.inc. Most utility functions have been left here, even if they
-// are only used by a function in one of the other files. quiz_datetime.inc holds
-// some additional date/time functions.
-//
-// Themes are in quiz.pages.inc unless they clearly only apply to admin screens.
-// Then they are in quiz.admin.inc.
-//
-// Views support is included in includes/views/quiz.views.inc
 define('QUIZ_VIEWS_DIR', drupal_get_path('module', 'quiz') . '/includes/views');
 
-module_load_include('inc', 'quiz', 'quiz_datetime');
+/**
+ * Include hook implementations.
+ */
+require_once dirname(__FILE__) . '/quiz.hooks.inc';
 
 /*
  * Define question statuses...
@@ -47,3353 +49,69 @@ define('QUIZ_KEEP_LATEST', 1);
 define('QUIZ_KEEP_ALL', 2);
 
 /**
- * Implements hook_help().
- */
-function quiz_help($path, $arg) {
-  // This is moved on an experimental basis.
-  include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'quiz') . '/quiz.help.inc';
-  return _quiz_help($path, $arg);
-}
-
-/**
- * Implements hook_views_api().
- */
-function quiz_views_api() {
-  return array(
-    'api' => '3.0',
-    'path' => QUIZ_VIEWS_DIR,
-  );
-}
-
-/**
- * Implements hook_ctools_plugin_directory() to let the system know
- * where our task and task_handler plugins are.
- */
-function quiz_ctools_plugin_directory($owner, $plugin_type) {
-  if ($owner == 'page_manager') {
-    return 'plugins/page_manager/' . $plugin_type;
-  }
-  if ($owner == 'ctools' && $plugin_type == 'content_types') {
-    return 'plugins/content_types';
-  }
-}
-
-/**
- * Implements hook_ctools_plugin_api().
- */
-function quiz_ctools_plugin_api($module, $api) {
-
-  if ($module == 'page_manager' && $api == 'pages_default') {
-    return array('version' => 1);
-  }
-}
-
-/**
- * Implements hook_entity_info_alter().
- */
-function quiz_entity_info_alter(&$info) {
-  // Add a new view mode for displaying the question when taking a quiz
-  $info['node']['view modes'] += array(
-    'question' => array(
-      'label' => t('Question'),
-      'custom settings' => TRUE,
-    ),
-  );
-}
-
-/**
- * Implements hook_permission().
- */
-function quiz_permission() {
-  return array(
-    // Configure quiz:
-    'administer quiz configuration' => array(
-      'title' => t('Administer quiz configuration'),
-      'description' => t('Control the various settings and behaviours of quiz'),
-      'restrict access' => TRUE,
-    ),
-    // Managing quizzes:
-    'access quiz' => array(
-      'title' => t('Take quiz'),
-      'description' => t('Can access (take) all quizzes.'),
-    ),
-    // viewing results:
-    'view any quiz results' => array(
-      'title' => t('View any quiz results'),
-      'description' => t('Can view results for all quizzes and users.'),
-    ),
-    'view own quiz results' => array(
-      'title' => t('View own quiz results'),
-      'description' => t('Quiz takers can view their own results, also when quiz is not passed.'),
-    ),
-    'view results for own quiz' => array(
-      'title' => t('View results for own quiz'),
-      'description' => t('Quiz makers can view results for their own quizzes.'),
-    ),
-    // deleting results:
-    'delete any quiz results' => array(
-      'title' => t('Delete any quiz results'),
-    ),
-    'delete results for own quiz' => array(
-      'title' => t('Delete own quiz results'),
-    ),
-    // scoring:
-    'score any quiz' => array(
-      'title' => t('Score any quiz'),
-    ),
-    'score own quiz' => array(
-      'title' => t('Score own quiz'),
-    ),
-    'score taken quiz answer' => array(
-      'title' => t('score taken quiz answer'),
-      'description' => t('Allows attendee to score questions needing manual evaluation.'),
-    ),
-    // Allow a quiz question to be viewed outside of a test.
-    'view quiz question outside of a quiz' => array(
-      'title' => t('View quiz question outside of a quiz'),
-      'description' => t('Questions can only be accessed through taking a quiz (not as individual nodes) unless this permission is given.'),
-    ),
-    // Allow the user to see the correct answer, when viewed outside a quiz
-    'view any quiz question correct response' => array(
-      'title' => t('View any quiz question correct response'),
-      'description' => t('Allow the user to see the correct answer, when viewed outside a quiz.'),
-    ),
-    // Allows users to pick a name for their questions. Otherwise this is auto
-    // generated.
-    'edit question titles' => array(
-      'title' => t('Edit question titles'),
-      'description' => t('Questions automatically get a title based on the question text. This allows titles to be set manually.'),
-    ),
-    // Control revisioning, only assign this permission to users who understand
-    // who permissions work. Note: If a quiz or question is changed and not
-    // revisioned you will also change existing result reports.
-    'manual quiz revisioning' => array(
-      'title' => t('Manual quiz revisioning'),
-      'description' => t('Quizzes are revisioned automatically each time they are changed. This allows you to do revisions manually.'),
-    ),
-  );
-}
-
-/**
- * Implements hook_admin_paths().
- */
-function quiz_admin_paths() {
-  return array(
-    'node/*/quiz' => TRUE,
-    'node/*/quiz/*' => TRUE,
-  );
-}
-
-/**
- * Helper function to determine if a user has access to the different results
- * pages.
- *
- * @param $quiz
- *   The quiz node.
- * @param $result_id
- *   The result id of a result we are trying to access.
- * @return boolean
- *   TRUE if user has permission.
- */
-function quiz_access_results($quiz, $result_id = NULL) {
-  global $user;
-
-  if ($quiz->type !== 'quiz') {
-    return FALSE;
-  }
-  // If rid is set we must make sure the result belongs to the quiz we are
-  // viewing results for.
-  if (isset($result_id)) {
-    $res = db_query('SELECT qnr.nid, qnr.uid FROM {quiz_node_results} qnr WHERE result_id = :result_id', array(':result_id' => $result_id))->fetch();
-    if ($res && $res->nid != $quiz->nid) {
-      return FALSE;
-    }
-  }
-  if (user_access('view any quiz results')) {
-    return TRUE;
-  }
-  if (user_access('view results for own quiz') && $user->uid == $quiz->uid) {
-    return TRUE;
-  }
-  if (user_access('score taken quiz answer')) {
-    //check if the taken user is seeing his result
-    if (isset($result_id) && $res && $res->uid == $user->uid) {
-      return TRUE;
-    }
-  }
-}
-
-/**
- * Helper function to determine if a user has access to view his quiz results
- *
- * @param object $quiz
- *  The Quiz node
- */
-function quiz_access_my_results($quiz) {
-  global $user;
-  if ($quiz->type !== 'quiz') {
-    return FALSE;
-  }
-
-  $answered = db_query('SELECT 1 FROM {quiz_node_results} WHERE nid = :nid AND uid = :uid AND is_evaluated = :is_evaluated', array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1))->fetchField();
-  if ($answered) {
-    return TRUE;
-  }
-}
-
-/**
- * Helper function to determine if a user has access to view a specific quiz result.
- *
- * @param int $result_id
- *  Result id
- * @return boolean
- *  True if access, false otherwise
- */
-function quiz_access_my_result($result_id) {
-  global $user;
-
-  // Check if the quiz taking has been completed.
-  $quiz_result = quiz_result_load($result_id);
-  $node = node_load($quiz_result->nid, $quiz_result->vid);
-
-  if (quiz_access_to_score($node->uid) || ($quiz_result->time_end > 0 && $quiz_result->uid == $user->uid)) {
-    return TRUE;
-  }
-}
-
-/**
- * Helper function to determine if a user has access to score a quiz.
- *
- * @param $quiz_creator
- *   uid of the quiz creator.
- */
-function quiz_access_to_score($quiz_creator = NULL) {
-  global $user;
-  if ($quiz_creator == NULL && ($quiz = quiz_get_quiz_from_menu())) {
-    $quiz_creator = $quiz->uid;
-  }
-  if (user_access('score any quiz')) {
-    return TRUE;
-  }
-  if (user_access('score own quiz') && $user->uid == $quiz_creator) {
-    return TRUE;
-  }
-  if (user_access('score taken quiz answer')) {
-    return TRUE;
-  }
-}
-
-/**
- * Helper function to check if the user has any of a given list of permissions.
- *
- * @param args
- *   Any number of permissions.
- * @return
- *   TRUE if the user has access to any of the arguments given.
- */
-function quiz_access_multi_or() {
-  $perms = func_get_args();
-  foreach ($perms as $perm) {
-    if (user_access($perm)) {
-      return TRUE;
-    }
-  }
-}
-
-/**
- * Implements hook_node_info().
- */
-function quiz_node_info() {
-  return array(
-    'quiz' => array(
-      'name' => t('@quiz', array("@quiz" => QUIZ_NAME)),
-      'base' => 'quiz',
-      'description' => 'Create interactive quizzes for site visitors',
-    )
-  );
-}
-
-/*
-  function quiz_element_info() {
-  $types['advancedtable'] = array(
-  '#process' => array('form_process_fieldset', 'ajax_process_form'),
-  '#theme_wrappers' => array('quiz_table'),
-  );
-  $types['advancedtable_header'] = array(
-  '#process' => array('form_process_fieldset', 'ajax_process_form'),
-  '#theme_wrappers' => array('quiz_table_header'),
-  );
-  $types['advancedtable_body'] = array(
-  '#process' => array('form_process_fieldset', 'ajax_process_form'),
-  '#theme_wrappers' => array('quiz_table_body'),
-  );
-  return $types;
-  }
- */
-
-/*
- * Implementation of hook_cron().
- */
-
-/**
- * @todo Please document this function.
- * @see http://drupal.org/node/1354
- */
-function quiz_cron() {
-  // Remove old quiz results that haven't been finished.
-  $rm_time = variable_get('quiz_remove_partial_quiz_record', '0');
-
-  if ($rm_time) { // $time = 0 for never.
-    db_delete('quiz_node_results')
-      ->condition('time_end', 0)
-      ->where('(:request_time - time_start) > :remove_time', array(
-        ':request_time' => REQUEST_TIME,
-        ':remove_time' => $rm_time
-      ))
-      ->execute();
-  }
-}
-
-/**
- * Implements hook_menu().
- */
-function quiz_menu() {
-
-  // Take quiz.
-  $items['node/%quiz_menu/take'] = array(
-    'title' => 'Take',
-    'page callback' => 'quiz_take',
-    'page arguments' => array(1),
-    'access callback' => 'quiz_take_access',
-    'access arguments' => array(1),
-    'type' => MENU_LOCAL_TASK,
-  );
-
-  // Take question.
-  // @todo Thought - the 4th argument could be a "page" instead of a question
-  // number
-  $items['node/%quiz_menu/take/%question_number'] = array(
-    'title' => 'Take',
-    'page callback' => 'quiz_take_question',
-    'page arguments' => array(1, 3),
-    'access callback' => 'quiz_access_question',
-    'access arguments' => array(1, 3),
-  );
-
-  // Feedback
-  $items['node/%quiz_menu/take/%question_number/feedback'] = array(
-    'title' => 'Feedback',
-    'page callback' => 'quiz_take_question_feedback',
-    'page arguments' => array(1, 3),
-    'access callback' => 'quiz_question_feedback_access',
-    'access arguments' => array(1, 3),
-  );
-
-
-  // Admin pages.
-  $items['admin/quiz'] = array(
-    'title' => '@quiz',
-    'title arguments' => array('@quiz' => QUIZ_NAME),
-    'description' => 'View results, score answers, run reports and edit configurations.',
-    'page callback' => 'system_admin_menu_block_page',
-    'access arguments' => array('administer quiz configuration', 'score any quiz', 'score own quiz', 'view any quiz results', 'view results for own quiz'),
-    'access callback' => 'quiz_access_multi_or',
-    'type' => MENU_NORMAL_ITEM,
-    'file' => 'system.admin.inc',
-    'file path' => drupal_get_path('module', 'system'),
-  );
-
-  $items['admin/quiz/settings'] = array(
-    'title' => '@quiz settings',
-    'title arguments' => array('@quiz' => QUIZ_NAME),
-    'description' => 'Change settings for the all Quiz project modules.',
-    'page callback' => 'system_admin_menu_block_page',
-    'access arguments' => array('administer quiz configuration'),
-    'type' => MENU_NORMAL_ITEM,
-    'file' => 'system.admin.inc',
-    'file path' => drupal_get_path('module', 'system'),
-  );
-
-  $items['admin/quiz/settings/config'] = array(
-    'title' => '@quiz configuration',
-    'title arguments' => array('@quiz' => QUIZ_NAME),
-    'description' => 'Configure the Quiz module.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('quiz_admin_settings'),
-    'access arguments' => array('administer quiz configuration'),
-    'type' => MENU_NORMAL_ITEM, // optional
-    'file' => 'quiz.admin.inc',
-  );
-
-  $items['admin/quiz/settings/quiz-form'] = array(
-    'title' => '@quiz form configuration',
-    'title arguments' => array('@quiz' => QUIZ_NAME),
-    'description' => 'Configure default values for the quiz creation form.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('quiz_admin_node_form'),
-    'access arguments' => array('administer quiz configuration'),
-    'type' => MENU_NORMAL_ITEM, // optional
-    'file' => 'quiz.admin.inc',
-  );
-
-  $items['admin/quiz/reports'] = array(
-    'title' => '@quiz reports and scoring',
-    'title arguments' => array('@quiz' => QUIZ_NAME),
-    'description' => 'View reports and score answers.',
-    'page callback' => 'system_admin_menu_block_page',
-    'access arguments' => array('view any quiz results', 'view results for own quiz'),
-    'access callback' => 'quiz_access_multi_or',
-    'type' => MENU_NORMAL_ITEM,
-    'file' => 'system.admin.inc',
-    'file path' => drupal_get_path('module', 'system'),
-  );
-
-  $items['node/%quiz_menu/quiz-results/%quiz_rid/view'] = array(
-    'title' => 'View',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10,
-  );
-
-  $items['node/%quiz_menu/quiz-results/%quiz_rid'] = array(
-    'title' => 'User results',
-    'page callback' => 'quiz_user_results',
-    'page arguments' => array(3),
-    'access callback' => 'quiz_access_my_result',
-    'access arguments' => array(3),
-    'file' => 'quiz.pages.inc',
-  );
-
-  $items['node/%quiz_menu/quiz/results/%quiz_rid/view'] = array(
-    'title' => 'Results',
-    'page callback' => 'quiz_admin_results',
-    'page arguments' => array(1, 4),
-    'access callback' => 'quiz_access_results',
-    'access arguments' => array(1, 4),
-    'file' => 'quiz.admin.inc',
-  );
-
-  // Add questions to quiz.
-  $items['node/%quiz_menu/quiz/questions'] = array(
-    'title' => 'Manage questions',
-    'page callback' => 'quiz_questions',
-    'page arguments' => array(1),
-    'access callback' => 'quiz_type_confirm',
-    'access arguments' => array(1, 'update'),
-    'type' => MENU_LOCAL_TASK,
-    'file' => 'quiz.admin.inc',
-    'weight' => 2,
-  );
-
-  // User pages.
-
-  $items['user/%/quiz-results/%/view'] = array(
-    'title' => 'User results',
-    'page callback' => 'quiz_user_results',
-    'page arguments' => array(3),
-    'access arguments' => array(3),
-    'access callback' => 'quiz_access_my_result',
-    'type' => MENU_CALLBACK,
-    'file' => 'quiz.pages.inc',
-  );
-
-  $items['node/%quiz_menu/questions/term_ahah'] = array(//TODO: Add node access instead of user access...
-    'page callback' => 'quiz_categorized_term_ahah',
-    'type' => MENU_CALLBACK,
-    'access callback' => 'node_access',
-    'access arguments' => array('create', 'quiz'),
-    'file' => 'quiz.admin.inc',
-  );
-
-  return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function quiz_theme($existing, $type, $theme, $path) {
-  return array(
-    'quiz_view_stats' => array(
-      'variables' => array('node' => NULL),
-      'file' => 'quiz.pages.inc',
-    ),
-    'quiz_categorized_form' => array(
-      'render element' => 'form',
-      'file' => 'quiz.admin.inc'
-    ),
-    'quiz_result' => array(
-      'variables' => array('quiz' => NULL, 'questions' => NULL, 'score' => NULL, 'summary' => NULL, 'result_id' => NULL),
-      'file' => 'quiz.pages.inc',
-    ),
-    'quiz_progress' => array(
-      'variables' => array('question_number' => NULL, 'num_questions' => NULL, 'allow_jumping' => NULL, 'time_limit' => NULL),
-      'file' => 'quiz.pages.inc',
-    ),
-    'quiz_no_feedback' => array(
-      'file' => 'quiz.pages.inc',
-      'variables' => array(),
-    ),
-    'question_selection_table' => array(
-      'file' => 'quiz.admin.inc',
-      'render element' => 'form',
-    ),
-    'quiz_answer_result' => array(
-      'file' => 'quiz.pages.inc',
-      'variables' => array(),
-    ),
-    'quiz_report_form' => array(
-      'render element' => 'form',
-      'path' => $path . '/theme',
-      'template' => 'quiz-report-form',
-    ),
-    'quiz_node_form' => array(
-      'render element' => 'form',
-      'file' => 'quiz.admin.inc',
-    ),
-    'quiz_browser' => array(
-      'render element' => 'form',
-      'file' => 'quiz.admin.inc',
-    ),
-    'quiz_question_score' => array(
-      'variables' => array('score' => NULL, 'max_score' => NULL),
-      'path' => $path . '/theme',
-      'template' => 'quiz-question-score',
-    ),
-    'quiz_pager' => array(
-      'variables' => array('total' => 0, 'current' => 0, 'siblings' => 0),
-    ),
-  );
-}
-
-/**
- * Implements hook_form_alter().
- *
- * Override settings in some existing forms. For example, we remove the
- * preview button on a quiz.
- */
-function quiz_form_alter(&$form, $form_state, $form_id) {
-  // Set taking options the first vertical tab item
-  if ('quiz_node_form' == $form_id && isset($form['menu']['#weight'])) {
-    $form['menu']['#weight'] = 1;
-  }
-  // Remove revision fieldset if user don't have access to revise quiz manually.
-  if (isset($form['#quiz_check_revision_access'])) {
-    if (!user_access('manual quiz revisioning') || variable_get('quiz_auto_revisioning', 1)) {
-      $form['revision_information']['revision']['#type'] = 'value';
-      $form['revision_information']['revision']['#value'] = $form['revision_information']['revision']['#default_value'];
-      $form['revision_information']['log']['#type'] = 'value';
-      $form['revision_information']['log']['#value'] = $form['revision_information']['log']['#default_value'];
-      $form['revision_information']['#access'] = FALSE;
-    }
-    unset($form['actions']['preview'], $form['actions']['preview_changes']);
-    $form['actions']['submit']['#access'] = TRUE;
-    // Quiz questions might want to add a cancel button.
-    if (isset($form['#cancel_button'])) {
-      $form['actions']['cancel'] = array(
-        '#markup' => l(t('Cancel'), $form_state['redirect']),
-        '#weight' => 6,
-      );
-    }
-  }
-}
-
-/**
- * Implements hook_form_FORM_ID_alter() for the quiz node form.
- */
-function quiz_form_quiz_node_form_alter(&$form, $form_state) {
-  $form['actions']['submit']['#submit'][] = 'quiz_form_quiz_node_form_submit';
-}
-
-/**
- * Quiz node form submit callback.
- *
- * @see quiz_form_quiz_node_form_alter()
- */
-function quiz_form_quiz_node_form_submit($form, &$form_state) {
-  $node = $form_state['node'];
-
-  if (empty($form_state['values']['nid'])) {
-    drupal_set_message(t('You just created a new @quiz. Now you have to add questions to it. This page is for adding and managing questions. Here you can create new questions or add some of your already created questions. If you want to change the quiz settings, you can use the "edit" tab.'), array('@quiz' => QUIZ_NAME));
-    $form_state['redirect'] = "node/$node->nid/quiz/questions";
-  }
-  // If the quiz don't have any questions jump to the manage questions tab.
-  $res = db_query('SELECT child_vid FROM {quiz_node_relationship} WHERE parent_vid = :vid', array(':vid' => $node->vid))->fetchField();
-  if (!is_numeric($res)) {
-    $form_state['redirect'] = 'node/' . $node->nid . '/quiz/questions';
-  }
-}
-
-/**
- * Implements hook_insert().
- */
-function quiz_insert($node) {
-  global $user;
-
-  // Need to set max_score if this is a cloned node
-  $max_score = 0;
-
-  // Copy all the questions belonging to the quiz if this is a new translation.
-  if ($node->is_new && isset($node->translation_source)) {
-    quiz_copy_questions($node);
-  }
-
-  // Add references to all the questions belonging to the quiz if this is a cloned quiz (node_clone compatibility)
-  if ($node->is_new && isset($node->clone_from_original_nid)) {
-    $old_quiz = node_load($node->clone_from_original_nid, NULL, TRUE);
-
-    $max_score = $old_quiz->max_score;
-
-    $questions = quiz_get_questions($old_quiz->nid, $old_quiz->vid);
-
-    // Format the current questions for referencing
-    foreach ($questions as $question) {
-      $nid = $questions['nid'];
-      $questions[$nid]->state = $question->question_status;
-      $questions[$nid]->refresh = 0;
-    }
-
-    quiz_set_questions($node, $questions);
-  }
-
-  _quiz_common_presave_actions($node);
-
-  // If the quiz is saved as not randomized we have to make sure that questions belonging to the quiz are saved as not random
-  _quiz_check_num_random($node);
-  _quiz_check_num_always($node);
-
-  quiz_update_defaults($node);
-  _quiz_insert_resultoptions($node);
-}
-
-function quiz_update_defaults($node) {
-  global $user;
-
-  $entity = clone $node;
-  $entity->aid = !empty($entity->aid) ? $entity->aid : 0;
-  $entity->summary_pass = is_array($node->summary_pass) ? $node->summary_pass['value'] : $node->summary_pass;
-  $entity->summary_pass_format = is_array($node->summary_pass) ? $node->summary_pass['format'] : $node->summary_pass_format;
-  $entity->summary_default = is_array($node->summary_default) ? $node->summary_default['value'] : $node->summary_default;
-  $entity->summary_default_format = is_array($node->summary_default) ? $node->summary_default['format'] : $node->summary_default_format;
-  $entity->tid = (isset($entity->tid) ? $entity->tid : 0);
-
-  // Save the node values.
-  $quiz_props = clone $entity;
-  $quiz_props->uid = 0;
-  quiz_save_properties($quiz_props);
-
-  if (!empty($node->remember_settings)) {
-    // Save user defaults.
-    $user_defaults = clone $quiz_props;
-    $user_defaults->nid = 0;
-    $user_defaults->vid = 0;
-    $user_defaults->uid = $user->uid;
-    quiz_save_properties($user_defaults);
-  }
-
-  if (!empty($node->remember_global)) {
-    // Save global defaults.
-    $global_defaults = clone $quiz_props;
-    $global_defaults->uid = 0;
-    $global_defaults->nid = 0;
-    $global_defaults->vid = 0;
-    quiz_save_properties($global_defaults);
-  }
-}
-
-/**
- * Insert or update the quiz node properties accordingly.
- */
-function quiz_save_properties($entity) {
-  $sql = "SELECT qnp_id FROM {quiz_node_properties}
-    WHERE (nid = :nid AND nid > 0 AND vid = :vid AND vid > 0)
-    OR (uid = :uid and uid > 0)
-    OR (nid = :nid and uid = :uid and vid = :vid)";
-  $result = db_query($sql, array(':nid' => $entity->nid, ':uid' => $entity->uid, ':vid' => $entity->vid));
-  $entity->qnp_id = $result->fetchField();
-  entity_save('quiz', $entity);
-}
-
-/**
- * Implements hook_update().
- */
-function quiz_update($node) {
-  // Quiz node vid (revision) was updated.
-  if (isset($node->revision) && $node->revision) {
-    // Create new quiz-question relation entries in the quiz_node_relationship
-    // table.
-    quiz_update_quiz_question_relationship($node->old_vid, $node->vid, $node->nid);
-  }
-
-  // Update an existing row in the quiz_node_properties table.
-  _quiz_common_presave_actions($node);
-
-  quiz_update_defaults($node);
-  _quiz_update_resultoptions($node);
-
-  _quiz_check_num_random($node);
-  _quiz_check_num_always($node);
-  quiz_update_max_score_properties(array($node->vid));
-  drupal_set_message(t('Some of the updated settings may not apply to quiz being taken already. To see all changes in action you need to start again.'), 'warning');
-}
-
-/**
- * Implements hook_field_extra_fields().
- */
-function quiz_field_extra_fields() {
-  $extra['node']['quiz'] = array(
-    'display' => array(
-      'take' => array(
-        'label' => t('Take @quiz button', array('@quiz' => QUIZ_NAME)),
-        'description' => t('The take button.'),
-        'weight' => 10,
-      ),
-      'stats' => array(
-        'label' => t('@quiz summary', array('@quiz' => QUIZ_NAME)),
-        'description' => t('@quiz summary', array('@quiz' => QUIZ_NAME)),
-        'weight' => 9,
-      ),
-    ),
-    'form' => array(
-      'taking' => array(
-        'label' => t('Taking options'),
-        'description' => t('Fieldset for customizing how a @quiz is taken', array('@quiz' => QUIZ_NAME)),
-        'weight' => 0,
-      ),
-      'quiz_availability' => array(
-        'label' => t('Availability options'),
-        'description' => t('Fieldset for customizing when a @quiz is available', array('@quiz' => QUIZ_NAME)),
-        'weight' => 0,
-      ),
-      'summaryoptions' => array(
-        'label' => t('Summary options'),
-        'description' => t('Fieldset for customizing summaries in the @quiz reports', array('@quiz' => QUIZ_NAME)),
-        'weight' => 0,
-      ),
-      'resultoptions' => array(
-        'label' => t('Result options'),
-        'description' => t('Fieldset for customizing result comments in @quiz reports', array('@quiz' => QUIZ_NAME)),
-        'weight' => 0,
-      ),
-      'remember_settings' => array(
-        'label' => t('Remember settings'),
-        'description' => t('Checkbox for remembering @quiz settings', array('@quiz' => QUIZ_NAME)),
-        'weight' => 0,
-      ),
-      'remember_global' => array(
-        'label' => t('Remember as global'),
-        'description' => t('Checkbox for remembering @quiz settings', array('@quiz' => QUIZ_NAME)),
-        'weight' => 0,
-      ),
-    ),
-  );
-  return $extra;
-}
-
-/**
- * Common actions that need to be done before a quiz is inserted or updated
- *
- * @param $node
- *   Quiz node
- */
-function _quiz_common_presave_actions(&$node) {
-  quiz_translate_form_date($node, 'quiz_open');
-  quiz_translate_form_date($node, 'quiz_close');
-
-  if (empty($node->pass_rate)) {
-    $node->pass_rate = 0;
-  }
-  if ($node->randomization < 2) {
-    $node->number_of_random_questions = 0;
-  }
-}
-
-/**
- * Implements hook_delete().
- */
-function quiz_delete($node) {
-  $res = db_query('SELECT result_id FROM {quiz_node_results}
-          WHERE nid = :nid', array(':nid' => $node->nid));
-  $result_ids = array();
-  while ($result_id = $res->fetchField()) {
-    $result_ids[] = $result_id;
-  }
-  entity_delete_multiple('quiz_result', $result_ids);
-
-  // Remove quiz node records from table quiz_node_properties
-  db_delete('quiz_node_properties')
-    ->condition('nid', $node->nid)
-    ->execute();
-  // Remove quiz node records from table quiz_node_relationship
-  db_delete('quiz_node_relationship')
-    ->condition('parent_nid', $node->nid)
-    ->execute();
-  // Remove quiz node records from table quiz_node_results
-  db_delete('quiz_node_results')
-    ->condition('nid', $node->nid)
-    ->execute();
-  // Remove quiz node records from table quiz_node_result_options
-  db_delete('quiz_node_result_options')
-    ->condition('nid', $node->nid)
-    ->execute();
-}
-
-/**
- * Returns default values for all quiz settings.
- *
- * @todo also store this in the quiz_node_properties table
- *
- * @return
- *   Array of default values.
- */
-function _quiz_get_node_defaults() {
-  return (object) array(
-      'aid' => NULL,
-      'allow_jumping' => 0,
-      'allow_resume' => 1,
-      'allow_skipping' => 1,
-      'allow_change' => 1,
-      'always_available' => TRUE,
-      'backwards_navigation' => 1,
-      'has_userpoints' => 0,
-      'keep_results' => 2,
-      'mark_doubtful' => 0,
-      'max_score' => 0,
-      'max_score_for_random' => 1,
-      'number_of_random_questions' => 0,
-      'pass_rate' => 75,
-      'quiz_always' => 1,
-      'quiz_close' => 0,
-      'quiz_close' => _quiz_form_prepare_date(NULL, variable_get('quiz_default_close', 30)),
-      'quiz_open' => 0,
-      'quiz_open' => _quiz_form_prepare_date(),
-      'randomization' => 0,
-      'repeat_until_correct' => 0,
-      'review_options' => array('question' => array(), 'end' => array()),
-      'show_attempt_stats' => 1,
-      'show_passed' => 1,
-      'summary_default' => '',
-      'summary_default_format' => filter_fallback_format(),
-      'summary_pass' => '',
-      'summary_pass_format' => filter_fallback_format(),
-      'takes' => 0,
-      'tid' => 0,
-      'time_limit' => 0,
-      'userpoints_tid' => 0,
-  );
-}
-
-/**
- * Result_id, menu argument loader.
- */
-function quiz_rid_load($result_id) {
-  if ($result_id > 0) {
-    return $result_id;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-/**
- * Workaround for a _menu_translate() bug.
- */
-function quiz_rid_to_arg($result_id) {
-  return quiz_rid_load($result_id);
-}
-
-/**
- * Menu loader callback. Load a quiz node if the given node ID is a quiz.
- */
-function quiz_menu_load($nid) {
-  if (!is_numeric($nid)) {
-    return FALSE;
-  }
-  $node = node_load($nid);
-  if (!isset($node->type) || $node->type != 'quiz') {
-    return FALSE;
-  }
-  return $node;
-}
-
-/**
- * Implements hook_load().
- *
- * Load up quiz properties onto quiz nodes.
- */
-function quiz_load($nodes) {
-  foreach ($nodes as $node) {
-    $vids[] = $node->vid;
-  }
-
-  $quiz_properties = entity_load('quiz', FALSE, array('vid' => $vids));
-
-  foreach ($quiz_properties as $qnp_id => $props) {
-    foreach ($props as $name => $value) {
-      if (!in_array($name, array('uid', 'nid', 'vid'))) {
-        $nodes[$props->nid]->$name = $value;
-      }
-    }
-  }
-
-  // Fetching result options.
-  $query = db_select('quiz_node_result_options', 'qnro');
-  $query->fields('qnro');
-  $query->condition('vid', $vids);
-  $options = $query->execute();
-  foreach ($options as $option) {
-    $nodes[$option->nid]->resultoptions[$option->option_id] = (array) $option;
-  }
-}
-
-/**
- * Implements hook_view().
- */
-function quiz_view($node, $view_mode) {
-  drupal_alter('quiz_view', $node, $view_mode);
-  node_invoke($node, 'prepare');
-
-  // Number of questions is needed on the statistics page.
-  $node->number_of_questions = $node->number_of_random_questions + _quiz_get_num_always_questions($node->vid);
-
-  $node->content['stats'] = array(
-    '#markup' => theme('quiz_view_stats', array('node' => $node)),
-    '#weight' => -1,
-  );
-
-  $available = quiz_availability($node);
-  if ($available === TRUE) {
-    // Check the permission before displaying start button.
-    if (user_access('access quiz')) {
-      // Add a link to the take tab as a button if this isn't a teaser view.
-      if ($view_mode !== 'teaser') {
-        $quiz_form = drupal_get_form('quiz_start_quiz_button_form', $node);
-        $node->content['take'] = array(
-          '#markup' => drupal_render($quiz_form),
-          '#weight' => 2,
-        );
-      }
-      // Add a link to the take tab if this is a teaser view.
-      else {
-        $node->content['take'] = array(
-          '#markup' => l(t('Start @quiz', array('@quiz' => QUIZ_NAME)), 'node/' . $node->nid . '/take'),
-          '#weight' => 2,
-        );
-      }
-    }
-  }
-  else {
-    $node->content['take'] = array(
-      '#markup' => '<div class="quiz-not-available">' . $available . '</div>',
-      '#weight' => 2,
-    );
-  }
-
-  return $node;
-}
-
-/**
- * Helper function for hook_view().
- *
- * Returns a button to use as a link to start taking the quiz.
- *
- * @param $form_state
- *   Form state array.
- * @param $node
- *   The quiz node.
- * @return
- *   Form with a button linking to the take tab.
- */
-function quiz_start_quiz_button_form($form, &$form_state, $node) {
-  $form = array();
-  $form['#action'] = url("node/$node->nid/take");
-  $form['button'] = array(
-    '#type' => 'submit',
-    '#value' => t('Start @quiz', array('@quiz' => QUIZ_NAME)),
-  );
-  return $form;
-}
-
-/**
- * Primary quiz-taking view on 'Take' tab.
- */
-function quiz_take($node) {
-  //drupal_alter('quiz_take', $node);
-  if (isset($node->rendered_content)) {
-    return $node->rendered_content;
-  }
-  $to_be_rendered = quiz_take_quiz($node);
-  return drupal_render($to_be_rendered);
-}
-
-/**
- * Does the current user have access to take the quiz?
+ * Factory to create quiz helpers wrapper.
  *
- * @param $node
- *  The quiz node
+ * @staticvar Quiz $quiz
+ * @param string $className
+ * @return \Drupal\quiz\Quiz
  */
-function quiz_take_access($node) {
-  if ($node->type != 'quiz') {
-    return FALSE;
-  }
-  return node_access('view', $node) && user_access('access quiz') && quiz_availability($node) === TRUE;
+function quiz($className = 'Drupal\quiz\Quiz') {
+  static $obj;
+  return null === $obj ? ($obj = new $className) : $obj;
 }
 
 /**
- * Implements hook_form().
+ * Get controller for quiz entity.
  *
- * This is an admin form used to build a new quiz. It is called as part of the
- * node edit form.
+ * @return \Drupal\quiz\Entity\QuizEntityController
  */
-function quiz_form(&$node, &$form_state) {
-
-  $form = array();
-
-  // We tell quiz_form_alter to check for the manual revisioning permission.
-  $form['#quiz_check_revision_access'] = TRUE;
-
-  $form['title'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Title'),
-    '#default_value' => isset($node->title) ? $node->title : '',
-    '#description' => t('The name of this @quiz.', array('@quiz' => QUIZ_NAME)),
-    '#required' => TRUE,
-  );
-
-  $form['taking'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Taking options'),
-    '#collapsed' => isset($settings_loaded) ? $settings_loaded : FALSE,
-    '#collapsible' => TRUE,
-    '#attributes' => array('id' => 'taking-fieldset'),
-    '#group' => 'additional_settings',
-    '#weight' => -2,
-  );
-  $form['taking']['allow_resume'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Allow resume'),
-    '#default_value' => $node->allow_resume,
-    '#description' => t('Allow users to leave this @quiz incomplete and then resume it from where they left off.', array('@quiz' => QUIZ_NAME)),
-  );
-  $form['taking']['allow_skipping'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Allow skipping'),
-    '#default_value' => $node->allow_skipping,
-    '#description' => t('Allow users to skip questions in this @quiz.', array('@quiz' => QUIZ_NAME)),
-  );
-  $form['taking']['allow_jumping'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Allow jumping'),
-    '#default_value' => $node->allow_jumping,
-    '#description' => t('Allow users to jump to any question using a menu or pager in this @quiz.', array('@quiz' => QUIZ_NAME)),
-  );
-  $form['taking']['allow_change'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Allow changing answers'),
-    '#default_value' => $node->allow_change,
-    '#description' => t('If the user is able to visit a previous question, allow them to change the answer.'),
-  );
-  $form['taking']['backwards_navigation'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Backwards navigation'),
-    '#default_value' => $node->backwards_navigation,
-    '#description' => t('Allow users to go back and revisit questions already answered.'),
-  );
-  $form['taking']['repeat_until_correct'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Repeat until correct'),
-    '#default_value' => $node->repeat_until_correct,
-    '#description' => t('Require the user to retry the question until answered correctly.'),
-  );
-  $form['taking']['mark_doubtful'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Mark doubtful'),
-    '#default_value' => $node->mark_doubtful,
-    '#description' => t('Allow users to mark their answers as doubtful.'),
-  );
-  $form['taking']['show_passed'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Show passed status'),
-    '#default_value' => $node->show_passed,
-    '#description' => t('Show a message if the user has previously passed the @quiz.', array('@quiz' => QUIZ_NAME)),
-  );
-
-  $form['taking']['randomization'] = array(
-    '#type' => 'radios',
-    '#title' => t('Randomize questions'),
-    '#options' => array(
-      t('No randomization'),
-      t('Random order'),
-      t('Random questions'),
-      t('Categorized random questions'),
-    ),
-    '#description' => t('<strong>Random order</strong> - all questions display in random order')
-    . '<br/>' . t("<strong>Random questions</strong> - specific number of questions are drawn randomly from this @quiz's pool of questions", array('@quiz' => QUIZ_NAME))
-    . '<br/>' . t('<strong>Categorized random questions</strong> - specific number of questions are drawn from each specified taxonomy term'),
-    '#default_value' => $node->randomization,
-  );
-  $form['taking']['review_options'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Review options'),
-    '#collapsible' => FALSE,
-    '#collapsed' => FALSE,
-    '#tree' => TRUE,
-    '#description' => t('Control what feedback appears and when.'),
-  );
-
-  $review_options = quiz_get_feedback_options();
-
-  foreach (array('question' => 'After the question', 'end' => t('After the @quiz', array('@quiz' => QUIZ_NAME))) as $key => $when) {
-    $form['taking']['review_options'][$key] = array(
-      '#title' => $when,
-      '#type' => 'checkboxes',
-      '#options' => $review_options,
-      '#default_value' => isset($node->review_options[$key]) ? $node->review_options[$key] : array(),
-    );
-  }
-  $options = array(t('Unlimited'));
-  for ($i = 1; $i < 10; $i++) {
-    $options[$i] = $i;
-  }
-  $form['taking']['multiple_takes'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Multiple takes'),
-    '#collapsible' => FALSE,
-    '#collapsed' => FALSE,
-    '#attributes' => array('id' => 'multiple-takes-fieldset'),
-    '#description' => t('Allow users to take this @quiz multiple times.', array('@quiz' => QUIZ_NAME)),
-  );
-  $form['taking']['multiple_takes']['takes'] = array(
-    '#type' => 'select',
-    '#title' => t('Allowed number of attempts'),
-    '#default_value' => $node->takes,
-    '#options' => $options,
-    '#description' => t('The number of times a user is allowed to take this @quiz. <strong>Anonymous users are only allowed to take @quiz that allow an unlimited number of attempts.</strong>', array('@quiz' => QUIZ_NAME)),
-  );
-  $form['taking']['multiple_takes']['show_attempt_stats'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Display allowed number of attempts'),
-    '#default_value' => $node->show_attempt_stats,
-    '#description' => t('Display the allowed number of attempts on the starting page for this @quiz.', array('@quiz' => QUIZ_NAME)),
-  );
-
-  if (user_access('delete any quiz results') || user_access('delete results for own quiz')) {
-    $form['taking']['multiple_takes']['keep_results'] = array(
-      '#type' => 'radios',
-      '#title' => t('Store results'),
-      '#description' => t('These results should be stored for each user.'),
-      '#options' => array(
-        t('The best'),
-        t('The newest'),
-        t('All'),
-      ),
-      '#default_value' => $node->keep_results,
-    );
-  }
-  else {
-    $form['taking']['multiple_takes']['keep_results'] = array(
-      '#type' => 'value',
-      '#value' => $node->keep_results,
-    );
-  }
-
-  $form['taking']['time_limit'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Time limit'),
-    '#default_value' => isset($node->time_limit) ? $node->time_limit : 0,
-    '#description' => t('Set the maximum allowed time in seconds for this @quiz. Use 0 for no limit.', array('@quiz' => QUIZ_NAME))
-    . '<br/>' . t('It is recommended to install the !countdown module, and enable the option in !link to show the time left to the user.', array(
-      '!link' => l('Quiz configuration', 'admin/quiz/settings/config'),
-      '!countdown' => l('jquery_countdown', 'http://drupal.org/project/jquery_countdown'),
-  )));
-
-  if (function_exists('userpoints_userpointsapi') && variable_get('quiz_has_userpoints', 1)) {
-    $form['userpoints'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Userpoints'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#group' => 'additional_settings',
-    );
-    $form['userpoints']['has_userpoints'] = array(
-      '#type' => 'checkbox',
-      '#default_value' => (isset($node->has_userpoints) ? $node->has_userpoints : 1),
-      '#title' => t('Enable UserPoints Module Integration'),
-      '#description' => t('If checked, marks scored in this @quiz will be credited to userpoints. For each correct answer 1 point will be added to user\'s point.', array('@quiz' => QUIZ_NAME)),
-    );
-    $form['userpoints']['userpoints_tid'] = array(
-      '#type' => 'select',
-      '#options' => _quiz_userpoints_type(),
-      '#title' => t('Userpoints Category'),
-      '#states' => array(
-        'visible' => array(
-          ':input[name=has_userpoints]' => array('checked' => TRUE),
-        ),
-      ),
-      '#default_value' => isset($node->userpoints_tid) ? $node->userpoints_tid : 0,
-      '#description' => t('Select the category to which user points to be added. To add new category see <a href="!url">admin/structure/taxonomy/userpoints</a>', array('!url' => url('admin/structure/taxonomy/userpoints'))),
-    );
-  }
-
-  // Set up the availability options.
-  $form['quiz_availability'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Availability options'),
-    '#collapsed' => TRUE,
-    '#collapsible' => TRUE,
-    '#attributes' => array('id' => 'availability-fieldset'),
-    '#group' => 'additional_settings',
-  );
-  $form['quiz_availability']['quiz_always'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Always available'),
-    '#default_value' => $node->quiz_always,
-    '#description' => t('Ignore the open and close dates.'),
-  );
-  $form['quiz_availability']['quiz_open'] = array(
-    '#type' => 'date',
-    '#title' => t('Open date'),
-    '#default_value' => _quiz_form_prepare_date($node->quiz_open),
-    '#description' => t('The date this @quiz will become available.', array('@quiz' => QUIZ_NAME)),
-    '#after_build' => array('_quiz_limit_year_options'),
-  );
-  $form['quiz_availability']['quiz_close'] = array(
-    '#type' => 'date',
-    '#title' => t('Close date'),
-    '#default_value' => _quiz_form_prepare_date($node->quiz_close, variable_get('quiz_default_close', 30)),
-    '#description' => t('The date this @quiz will become unavailable.', array('@quiz' => QUIZ_NAME)),
-    '#after_build' => array('_quiz_limit_year_options'),
-  );
-
-  // Quiz summary options.
-  $form['summaryoptions'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Pass/fail options'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#attributes' => array('id' => 'summaryoptions-fieldset'),
-    '#group' => 'additional_settings',
-  );
-  // If pass/fail option is checked, present the form elements.
-  if (variable_get('quiz_use_passfail', 1)) {
-    $form['summaryoptions']['pass_rate'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Passing rate for @quiz (%)', array('@quiz' => QUIZ_NAME)),
-      '#default_value' => $node->pass_rate,
-      '#description' => t('Passing rate for this @quiz as a percentage score.', array('@quiz' => QUIZ_NAME)),
-      '#required' => FALSE,
-    );
-    $form['summaryoptions']['summary_pass'] = array(
-      '#type' => 'text_format',
-      '#base_type' => 'textarea',
-      '#title' => t('Summary text if passed'),
-      '#default_value' => $node->summary_pass,
-      '#cols' => 60,
-      '#description' => t("Summary text for when the user passes the @quiz. Leave blank to not give different summary text if passed, or if not using the \"percent to pass\" option above. If not using the \"percentage needed to pass\" field above, this text will not be used.", array('@quiz' => QUIZ_NAME)),
-      '#format' => isset($node->summary_pass_format) && !empty($node->summary_pass_format) ? $node->summary_pass_format : NULL,
-    );
-  }
-  // If the pass/fail option is unchecked, use the default and hide it.
-  else {
-    $form['summaryoptions']['pass_rate'] = array(
-      '#type' => 'hidden',
-      '#value' => $node->pass_rate,
-      '#required' => FALSE,
-    );
-  }
-  // We use a helper to enable the wysiwyg module to add an editor to the
-  // textarea.
-  $form['summaryoptions']['helper']['summary_default'] = array(
-    '#type' => 'text_format',
-    '#base_type' => 'textarea',
-    '#title' => t('Default summary text'),
-    '#default_value' => $node->summary_default,
-    '#cols' => 60,
-    '#description' => t("Default summary. Leave blank if you don't want to give a summary."),
-    '#format' => isset($node->summary_default_format) && !empty($node->summary_default_format) ? $node->summary_default_format : NULL,
-  );
-
-  // Number of random questions, max score and tid for random questions are set on
-  // the manage questions tab. We repeat them here so that they're not removed
-  // if the quiz is being updated.
-  $num_rand = (isset($node->number_of_random_questions)) ? $node->number_of_random_questions : 0;
-  $form['number_of_random_questions'] = array(
-    '#type' => 'value',
-    '#value' => $num_rand,
-  );
-  $max_score_for_random = (isset($node->max_score_for_random)) ? $node->max_score_for_random : 0;
-  $form['max_score_for_random'] = array(
-    '#type' => 'value',
-    '#value' => $max_score_for_random,
-  );
-  $tid = (isset($node->tid)) ? $node->tid : 0;
-  $form['tid'] = array(
-    '#type' => 'value',
-    '#value' => $tid,
-  );
-
-  $options = !empty($node->resultoptions) ? $node->resultoptions : array();
-  $num_options = max(count($options), variable_get('quiz_max_result_options', 5));
-
-  if ($num_options > 0) {
-    $form['resultoptions'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Result feedback'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      '#tree' => TRUE,
-      '#attributes' => array('id' => 'resultoptions-fieldset'),
-      '#group' => 'additional_settings',
-    );
-
-    for ($i = 0; $i < $num_options; $i++) {
-      $option = (count($options) > 0) ? array_shift($options) : NULL; // grab each option in the array
-      $form['resultoptions'][$i] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Result Option ') . ($i + 1),
-        '#collapsible' => TRUE,
-        '#collapsed' => FALSE,
-      );
-      $form['resultoptions'][$i]['option_name'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Range title'),
-        '#default_value' => isset($option['option_name']) ? $option['option_name'] : '',
-        '#maxlength' => 40,
-        '#size' => 40,
-        '#description' => 'e.g., "A" or "Passed"',
-      );
-      $form['resultoptions'][$i]['option_start'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Percentage low'),
-        '#description' => t('Show this result for scored @quiz in this range (0-100).', array('@quiz' => QUIZ_NAME)),
-        '#default_value' => isset($option['option_start']) ? $option['option_start'] : '',
-        '#size' => 5,
-      );
-      $form['resultoptions'][$i]['option_end'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Percentage high'),
-        '#description' => t('Show this result for scored @quiz in this range (0-100).', array('@quiz' => QUIZ_NAME)),
-        '#default_value' => isset($option['option_end']) ? $option['option_end'] : '',
-        '#size' => 5,
-      );
-      $form['resultoptions'][$i]['option_summary'] = array(
-        '#type' => 'text_format',
-        '#base_type' => 'textarea',
-        '#title' => t('Feedback'),
-        '#default_value' => isset($option['option_summary']) ? $option['option_summary'] : '',
-        '#description' => t("This is the text that will be displayed when the user's score falls in this range."),
-        '#format' => isset($option['option_summary_format']) ? $option['option_summary_format'] : NULL,
-      );
-      if (isset($option['option_id'])) {
-        $form['resultoptions'][$i]['option_id'] = array(
-          '#type' => 'hidden',
-          '#value' => isset($option['option_id']) ? $option['option_id'] : '',
-        );
-      }
-    }
-  }
-
-  $form['remember_settings'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Remember my settings'),
-    '#description' => t('If this box is checked most of the @quiz specific settings you have made will be remembered and will be your default settings next time you create a @quiz.', array('@quiz' => QUIZ_NAME)),
-    '#weight' => 49,
-  );
-
-  $form['remember_global'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Remember as global'),
-    '#description' => t('If this box is checked most of the @quiz specific settings you have made will be remembered and will be everyone\'s default settings next time they create a @quiz.', array('@quiz' => QUIZ_NAME)),
-    '#weight' => 49,
-    '#access' => user_access('administer quiz configuration'),
-  );
-
-  if (quiz_has_been_answered($node) && (!user_access('manual quiz revisioning') || variable_get('quiz_auto_revisioning', 1))) {
-    $node->revision = 1;
-    $node->log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
-  }
-  return $form;
-}
-
-function _quiz_userpoints_type() {
-  $userpoints_terms = taxonomy_get_tree(userpoints_get_vid());
-  $userpoints_tids = array(0 => t('Select'));
-  foreach ($userpoints_terms as $userpoints_term) {
-    $userpoints_tids[$userpoints_term->tid] = str_repeat('-', $userpoints_term->depth) . $userpoints_term->name;
-  }
-  return $userpoints_tids;
-}
-
-/**
- * Implements hook_validate().
- */
-function quiz_validate($node) {
-  // Don't check dates if the quiz is always available.
-  if (!$node->quiz_always) {
-    if (mktime(0, 0, 0, $node->quiz_open['month'], $node->quiz_open['day'], $node->quiz_open['year']) > mktime(0, 0, 0, $node->quiz_close['month'], $node->quiz_close['day'], $node->quiz_close['year'])) {
-      form_set_error('quiz_close', t('"Close date" must be later than the "open date".'));
-    }
-  }
-
-  if (!empty($node->pass_rate)) {
-    if (!_quiz_is_int($node->pass_rate, 0, 100)) {
-      form_set_error('pass_rate', t('"Passing rate" must be a number between 0 and 100.'));
-    }
-  }
-
-  if (isset($node->time_limit)) {
-    if (!_quiz_is_int($node->time_limit, 0)) {
-      form_set_error('time_limit', t('"Time limit" must be a positive number.'));
-    }
-  }
-
-  if (isset($node->resultoptions) && count($node->resultoptions) > 0) {
-    $taken_values = array();
-    $num_options = 0;
-    foreach ($node->resultoptions as $option) {
-      if (!empty($option['option_name'])) {
-        $num_options++;
-        if (empty($option['option_summary'])) {
-          form_set_error('option_summary', t('Range has no summary text.'));
-        }
-        if ($node->pass_rate && (isset($option['option_start']) || isset($option['option_end']))) {
-
-          // Check for a number between 0-100.
-          foreach (array('option_start' => 'start', 'option_end' => 'end') as $bound => $bound_text) {
-            if (!_quiz_is_int($option[$bound], 0, 100)) {
-              form_set_error($bound, t('The range %start value must be a number between 0 and 100.', array('%start' => $bound_text)));
-            }
-          }
-
-          // Check that range end >= start.
-          if ($option['option_start'] > $option['option_end']) {
-            form_set_error('option_start', t('The start must be less than the end of the range.'));
-          }
-
-          // Check that range doesn't collide with any other range.
-          $option_range = range($option['option_start'], $option['option_end']);
-          if ($intersect = array_intersect($taken_values, $option_range)) {
-            form_set_error('option_start', t('The ranges must not overlap each other. (%intersect)', array('%intersect' => implode(',', $intersect))));
-          }
-          else {
-            $taken_values = array_merge($taken_values, $option_range);
-          }
-        }
-      }
-      elseif (!_quiz_is_empty_html($option['option_summary']['value'])) {
-        form_set_error('option_summary', t('Range has a summary, but no name.'));
-      }
-    }
-  }
-  if ($node->allow_jumping && !$node->allow_skipping) {
-    // @todo when we have pages of questions, we have to check that jumping is
-    // not enabled, and randomization is not enabled unless there is only 1 page
-    form_set_error('allow_skipping', t('If jumping is allowed, skipping must also be allowed.'));
-  }
-}
-
-/**
- * Implementation of hook_node_presave().
- */
-function quiz_node_presave($node) {
-  if ($node->type == 'quiz') {
-    // Convert the action id to the actual id from the MD5 hash.
-    // Why the actions module does this I do not know? Maybe to prevent
-    // invalid values put into the options value="" field.
-    if (!empty($node->aid) && $aid = actions_function_lookup($node->aid)) {
-      $node->aid = $aid;
-    }
-
-    if (variable_get('quiz_auto_revisioning', 1)) {
-      $node->revision = (quiz_has_been_answered($node)) ? 1 : 0;
-    }
-
-    // If this is a programmatic save, ensure we use the defaults.
-    $defaults = quiz_get_defaults();
-    foreach ($defaults as $property => $value) {
-      if (!isset($node->$property)) {
-        $node->$property = $defaults->$property;
-      }
-    }
-  }
-  if (isset($node->is_quiz_question) && variable_get('quiz_auto_revisioning', 1)) {
-    $node->revision = (quiz_question_has_been_answered($node)) ? 1 : 0;
-  }
+function quiz_controller() {
+  return entity_get_controller('quiz_entity');
 }
 
 /**
- * Implementation of hook_node_prepare().
- */
-function quiz_node_prepare($node) {
-  if ($node->type == 'quiz' && !isset($node->nid)) {
-    // If this is a new node we apply the user defaults for the quiz settings.
-    if (arg(0) == 'node') {
-      drupal_set_message(t('You are making your first @quiz. On this page you set the attributes, most of which you may tell the system to remember as defaults for the future. On the next screen you can add questions.', array('@quiz' => QUIZ_NAME)));
-    }
-    $settings = quiz_get_defaults();
-    foreach ($settings as $key => $value) {
-      if (!isset($node->$key)) {
-        $node->$key = $value;
-      }
-    }
-  }
-
-  if (isset($node->is_quiz_question)) {
-    if (variable_get('quiz_auto_revisioning', 1)) {
-      $node->revision = (quiz_question_has_been_answered($node)) ? 1 : 0;
-    }
-  }
-}
-
-/**
- * Implements hook_user_cancel().
- */
-function quiz_user_cancel($edit, $account, $method) {
-  if (variable_get('quiz_durod', 0)) {
-    _quiz_delete_users_results($account->uid);
-  }
-}
-
-/**
- * Deletes all results associated with a given user.
- *
- * @param int $uid
- *  The users id
- */
-function _quiz_delete_users_results($uid) {
-  $res = db_query("SELECT result_id FROM {quiz_node_results} WHERE uid = :uid", array(':uid' => $uid));
-  $result_ids = array();
-  while ($result_id = $res->fetchField()) {
-    $result_ids[] = $result_id;
-  }
-  entity_delete_multiple('quiz_result', $result_ids);
-}
-
-/**
- * @defgroup quiz_public Public API functions.
- * @{
- */
-
-/**
- * Validate that a node is of type quiz, and that the user has access to it.
- *
- * @param $node
- * @param $op
- * @return unknown_type
- */
-function quiz_type_confirm($node, $op = NULL) {
-  if ($node->type != 'quiz') {
-    return FALSE;
-  }
-  if (isset($op)) {
-    $node_ops = array('view', 'update', 'delete', 'create');
-    if (in_array($op, $node_ops)) {
-      return node_access($op, $node);
-    }
-    return user_access($op);
-  }
-
-  return user_access('access quiz');
-}
-
-/**
- * Returns the titles for all quizzes the user has access to.
- *
- * @return quizzes
- *   Array with nids as keys and titles as values.
- */
-function quiz_get_all_titles() {
-  return db_select('node', 'n')
-      ->fields('n', array('nid', 'title'))
-      ->condition('n.type', 'quiz')
-      ->addTag('node_access')
-      ->execute()
-      ->fetchAllKeyed();
-}
-
-/**
- * Returns the titles for all quizzes the user has access to.
- *
- * @return quizzes
- *   Array with nids as keys and (array with vid as key and title as value) as values.
- *   Like this: array($nid => array($vid => $title))
- */
-function quiz_get_all_version_titles() {
-  $query = db_select('node', 'n');
-  $query->join('node_revision', 'nr', 'nr.nid = n.nid');
-  $query->fields('nr', array('nid', 'vid', 'title'))
-    ->condition('n.type', 'quiz')
-    ->execute();
-
-  $to_return = array();
-  while ($res_o = $query->fetch()) {
-    $to_return[$res_o->nid][$res_o->vid] = $res_o->title;
-  }
-  return $to_return;
-}
-
-/**
- * Return highest score data for given quizzes.
+ * Get controller for quiz result entity.
  *
- * @param $nids
- *   nids for the quizzes we want to collect scores from.
- * @param $uid
- *   uid for the user we want to collect score for.
- * @param $include_num_questions
- *   Do we want to collect information about the number of questions in a quiz?
- *   This adds a performance hit.
- * @return
- *   Array of score data.
- *   For several takes on the same quiz, only returns highest score.
+ * @return \Drupal\quiz\Entity\ResultController
  */
-function quiz_get_score_data($nids, $uid, $include_num_questions = FALSE) {
-  // Validate that the nids are integers.
-  foreach ($nids as $key => $nid) {
-    if (!_quiz_is_int($nid)) {
-      unset($nids[$key]);
-    }
-  }
-  if (empty($nids)) {
-    return array();
-  }
-
-  // Fetch score data for the validated nids.
-  $to_return = array();
-  $vids = array();
-  $sql = 'SELECT n.title, n.nid, n.vid, p.number_of_random_questions as num_random_questions, r.score AS percent_score, p.max_score, p.pass_rate AS percent_pass
-          FROM {node} n
-          JOIN {quiz_node_properties} p
-          ON n.vid = p.vid
-          LEFT OUTER JOIN {quiz_node_results} r
-          ON r.nid = n.nid AND r.uid = :uid
-          LEFT OUTER JOIN (
-            SELECT nid, max(score) as highest_score
-            FROM {quiz_node_results}
-            GROUP BY nid
-          ) rm
-          ON n.nid = rm.nid AND r.score = rm.highest_score
-          WHERE n.nid in (' . implode(', ', $nids) . ')
-          ';
-  $res = db_query($sql, array(':uid' => $uid));
-  foreach ($res as $res_o) {
-    if (!$include_num_questions) {
-      unset($res_o->num_random_questions);
-    }
-    if (!isset($to_return[$res_o->vid]) || $res_o->percent_score > $to_return[$res_o->vid]->percent_score) {
-      $to_return[$res_o->vid] = $res_o; // Fetch highest score
-    }
-    // $vids will be used to fetch number of questions.
-    $vids[] = $res_o->vid;
-  }
-  if (empty($vids)) {
-    return array();
-  }
-
-  // Fetch total number of questions.
-  if ($include_num_questions) {
-    $res = db_query('SELECT COUNT(*) AS num_always_questions, parent_vid
-            FROM {quiz_node_relationship}
-            WHERE parent_vid IN (' . implode(', ', $vids) . ')
-            AND question_status = ' . QUESTION_ALWAYS . '
-            GROUP BY parent_vid');
-    foreach ($res as $res_o) {
-      $to_return[$res_o->parent_vid]->num_questions = $to_return[$res_o->parent_vid]->num_random_questions + $res_o->num_always_questions;
-    }
-  }
-
-  return $to_return;
+function quiz_result_controller() {
+  return entity_get_controller('quiz_result');
 }
 
 /**
- * Finds out the number of questions for the quiz.
- *
- * Good example of usage could be to calculate the % of score.
+ * Gets an array of all quiz types, keyed by the type name.
  *
- * @param $nid
- *   Quiz ID.
- * @return
- *   Returns the number of quiz questions.
+ * @return \Drupal\quiz\Entity\QuizEntityType[]
  */
-function quiz_get_number_of_questions($vid) {
-  $always_count = _quiz_get_num_always_questions($vid);
-  $rand_count = db_query('SELECT number_of_random_questions FROM {quiz_node_properties} WHERE vid = :vid', array(':vid' => $vid))->fetchField();
-  return $always_count + (int) $rand_count;
+function quiz_get_types() {
+  return entity_load_multiple_by_name('quiz_type');
 }
 
 /**
- * Copies quiz-question relation entries in the quiz_node_relationship table
- * from an old version of a quiz to a new.
+ * Get quiz-type by machine name.
  *
- * @param $old_quiz_vid
- *   The quiz vid prior to a new revision.
- * @param $new_quiz_vid
- *   The quiz vid of the latest revision.
- * @param $quiz_nid
- *   The quiz node id.
+ * @param string $name
+ * @return \Drupal\quiz\Entity\QuizEntityType
  */
-function quiz_update_quiz_question_relationship($old_quiz_vid, $new_quiz_vid, $quiz_nid) {
-  // query for questions in previous version
-  $result = db_select('quiz_node_relationship', 'qnr')
-    ->fields('qnr', array('parent_nid', 'child_nid', 'child_vid', 'question_status', 'weight', 'max_score', 'auto_update_max_score', 'qnr_id', 'qnr_pid'))
-    ->condition('parent_nid', $quiz_nid)
-    ->condition('parent_vid', $old_quiz_vid)
-    ->condition('question_status', QUESTION_NEVER, '!=')
-    ->execute();
-
-  // only proceed if query returned data
-  if ($result->rowCount()) {
-    $questions = $result->fetchAll(PDO::FETCH_ASSOC);
-    foreach ($questions as &$quiz_question) {
-      $quiz_question['old_qnr_id'] = $quiz_question['qnr_id'];
-      $quiz_question['parent_nid'] = $quiz_nid;
-      $quiz_question['parent_vid'] = $new_quiz_vid;
-      unset($quiz_question['qnr_id']);
-      drupal_write_record('quiz_node_relationship', $quiz_question);
-    }
-
-    // Update the parentage when a new revision is created.
-    // @todo this is copy pasta from quiz_set_questions
-    foreach ($questions as $question) {
-      db_update('quiz_node_relationship')
-        ->condition('qnr_pid', $question['old_qnr_id'])
-        ->condition('parent_nid', $quiz_nid)
-        ->condition('parent_vid', $new_quiz_vid)
-        ->fields(array('qnr_pid' => $question['qnr_id']))
-        ->execute();
-    }
-  }
-
-  /* Update terms if any */
-  $result = db_select('quiz_terms', 'qt')
-    ->fields('qt', array('nid', 'tid', 'weight', 'max_score', 'number'))
-    ->condition('vid', $old_quiz_vid)
-    ->execute();
-  // only proceed if query returned data
-  if ($result->rowCount()) {
-    $insert_query = db_insert('quiz_terms')
-      ->fields(array('nid', 'vid', 'tid', 'weight', 'max_score', 'number'));
-    while ($quiz_term = $result->fetchAssoc()) {
-      $insert_query->values(array(
-        'nid' => $quiz_nid,
-        'vid' => $new_quiz_vid,
-        'tid' => $quiz_term['tid'],
-        'weight' => $quiz_term['weight'],
-        'max_score' => $quiz_term['max_score'],
-        'number' => $quiz_term['number'],
-      ));
-    }
-    $insert_query->execute();
-  }
-
-  /* $sql = "INSERT INTO {quiz_terms} (nid, vid, tid, weight, max_score, number)
-    SELECT qt.nid, %d, qt.tid, qt.weight, qt.max_score, qt.number
-    FROM {quiz_terms} qt
-    WHERE qt.vid = %d"; */
+function quiz_type_load($name) {
+  $type = entity_load_multiple_by_name('quiz_type', array($name));
+  return isset($type[$name]) ? $type[$name] : null;
 }
 
 /**
- * Handles quiz taking.
- *
- * This gets executed when the main quiz node is first loaded.
+ * Load quiz entity
  *
- * @param $quiz
- *   The quiz node.
- *
- * @return
- *   Content array.
+ * @return \Drupal\quiz\Entity\QuizEntity
  */
-function quiz_take_quiz($quiz) {
-  global $user;
-
-  // Make sure we use the same revision of the quiz throughout the quiz taking
-  // session.
-  $result_id = !empty($_SESSION['quiz'][$quiz->nid]['result_id']) ? $_SESSION['quiz'][$quiz->nid]['result_id'] : NULL;
-  if ($result_id && $quiz_result = quiz_result_load($result_id)) {
-    // Enforce that we have the same quiz version.
-    $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-  }
-  else {
-    // User doesn't have attempt in session. If we allow resuming we can load it
-    // from the database.
-    if ($quiz->allow_resume && !user_is_anonymous()) {
-      if ($result_id = _quiz_active_result_id($user->uid, $quiz->nid, $quiz->vid)) {
-        $_SESSION['quiz'][$quiz->nid]['result_id'] = $result_id;
-        $_SESSION['quiz'][$quiz->nid]['current'] = 1;
-        $quiz_result = quiz_result_load($result_id);
-        $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-        // Resume a quiz from the database.
-        drupal_set_message(t('Resuming a previous @quiz in-progress.', array('@quiz' => QUIZ_NAME)), 'status');
-      }
-    }
-  }
-
-  if (!$result_id) {
-    // Can user start quiz?
-    if (quiz_start_check($quiz)) {
-      // Set up a new attempt.
-      $quiz_result = quiz_create_attempt($quiz);
-      $_SESSION['quiz'][$quiz->nid]['result_id'] = $quiz_result->result_id;
-      $_SESSION['quiz'][$quiz->nid]['current'] = 1;
-
-      // Call hook_quiz_begin().
-      module_invoke_all('quiz_begin', $quiz, $quiz_result->result_id);
-    }
-    else {
-      return array('body' => array('#markup' => t('This @quiz is closed.', array('@quiz' => QUIZ_NAME))));
-    }
+function quiz_entity_single_load($id = NULL, $vid = NULL, $reset = FALSE) {
+  if (NULL === $vid) {
+    return entity_load_single('quiz_entity', $id);
   }
 
-  if (!quiz_availability($quiz)) {
-    return array('body' => array('#markup' => t('This @quiz is not available.', array('@quiz' => QUIZ_NAME))));
+  $results = entity_load('quiz_entity', array(), array('vid' => $vid), $reset);
+  if (!empty($results)) {
+    return reset($results);
   }
-
-  drupal_goto("node/{$quiz->nid}/take/" . ($_SESSION['quiz'][$quiz->nid]['current']));
-}
-
-/**
- * Take a quiz questions.
- *
- * @param type $quiz
- *   A quiz node.
- * @param type $question_number
- *   A question number, starting at 1. Pages do not have question numbers. Quiz
- *   directions are considered part of the numbering.
- */
-function quiz_take_question($quiz, $question_number) {
-  if (!empty($_SESSION['quiz'][$quiz->nid]['result_id'])) {
-    $quiz_result = quiz_result_load($_SESSION['quiz'][$quiz->nid]['result_id']);
-    $question = $quiz_result->layout[$question_number];
-
-    if (!empty($question['qnr_pid'])) {
-      // Find the parent.
-      foreach ($quiz_result->layout as $pquestion) {
-        if ($pquestion['qnr_id'] == $question['qnr_pid']) {
-          // Load the page that the requested question belongs to.
-          $question_node = node_load($pquestion['nid']);
-        }
-      }
-    }
-    else {
-      // Load the question.
-      $question_node = node_load($question['nid']);
-    }
-  }
-
-  if (!$question_node) {
-    // Question disappeared or invalid session. Start over.
-    unset($_SESSION['quiz'][$quiz->nid]);
-    drupal_goto("node/{$quiz->nid}");
-  }
-
-  // Mark this as the current question.
-  quiz_question_goto($quiz, $question_number);
-
-  // Added the progress info to the view.
-  $quiz_result = quiz_result_load($_SESSION['quiz'][$quiz->nid]['result_id']);
-  $questions = array();
-  $i = 0;
-  foreach ($quiz_result->layout as $idx => $question) {
-    if (empty($question['qnr_pid'])) {
-      // Question has no parent. Show it in the jumper.
-      $questions[$idx] = ++$i;
-    }
-  }
-
-  $content['progress']['#markup'] = theme('quiz_progress', array(
-    'questions' => $questions,
-    'current' => arg(3),
-    'allow_jumping' => $quiz->allow_jumping,
-    'pager' => count($questions) >= variable_get('quiz_pager_start', 100),
-    'time_limit' => $quiz->time_limit,
-  ));
-  $content['progress']['#weight'] = -50;
-
-  /**
-   * @todo: wat do?
-    if (count($_SESSION['quiz'][$quiz->nid]['quiz_questions']) + count($_SESSION['quiz'][$quiz->nid]['previous_quiz_questions']) > $number_of_questions) {
-    drupal_set_message(t('At least one question have been deleted from the quiz after you started taking it. You will have to start over.'), 'warning', FALSE);
-    unset($_SESSION['quiz'][$quiz->nid]);
-    drupal_goto('node/' . $quiz->nid . '/take');
-    }
-   */
-  if (function_exists('jquery_countdown_add') && variable_get('quiz_has_timer', 0) && $quiz->time_limit) {
-    jquery_countdown_add('.countdown', array('until' => ($quiz_result->time_start + $quiz->time_limit - REQUEST_TIME), 'onExpiry' => 'finished', 'compact' => TRUE, 'layout' => t('Time left') . ': {hnn}{sep}{mnn}{sep}{snn}'));
-    // These are the two button op values that are accepted for answering
-    // questions.
-    $button_op1 = drupal_json_encode(t('Finish'));
-    $button_op2 = drupal_json_encode(t('Next'));
-    $js = "
-            function finished() {
-              // Find all buttons with a name of 'op'.
-              var buttons = jQuery('input[type=submit][name=op], button[type=submit][name=op]');
-              // Filter out the ones that don't have the right op value.
-              buttons = buttons.filter(function() {
-                return this.value == $button_op1 || this.value == $button_op2;
-              });
-              if (buttons.length == 1) {
-                // Since only one button was found, this must be it.
-                buttons.click();
-              }
-              else {
-                // Zero, or more than one buttons were found; fall back on a page refresh.
-                window.location = window.location.href;
-              }
-            }
-          ";
-    drupal_add_js($js, array('type' => 'inline', 'scope' => JS_DEFAULT));
-  }
-
-  $question_form = drupal_get_form('quiz_question_answering_form', $question_node, $_SESSION['quiz'][arg(1)]['result_id']);
-  $content['body']['question']['#markup'] = drupal_render($question_form);
-  drupal_set_title($quiz->title);
-
-
-  return $content;
-}
-
-/**
- * Initialize a new attempt for a Quiz for the current user.
- *
- * This builds a question list from the list of questions or random options,
- * then instantiates the attempt layout in the database.
- *
- * This function does not set the quiz attempt as current, the calling function
- * should then set the current quiz attempt in the session for the user to be
- * able to take it.
- *
- * @todo wrap around entity_create()? We are creating an entity here anyway.
- *
- * @return stdClass
- *   The quiz attempt/result.
- */
-function quiz_create_attempt($quiz) {
-  // Create question list.
-  $questions = quiz_build_question_list($quiz);
-  if ($questions === FALSE) {
-    drupal_set_message(t('Not enough random questions were found. Please add more questions before trying to take this @quiz.', array('@quiz' => QUIZ_NAME)), 'error');
-    return FALSE;
-  }
-
-  if (count($questions) == 0) {
-    drupal_set_message(t('No questions were found. Please !assign_questions before trying to take this @quiz.', array('@quiz' => QUIZ_NAME, '!assign_questions' => l(t('assign questions'), 'node/' . $quiz->nid . '/quiz/questions'))), 'error');
-    return FALSE;
-  }
-
-  $quiz_result = quiz_result_load(quiz_create_rid($quiz));
-
-  // Write the layout for this result.
-  entity_save('quiz_result', $quiz_result);
-  $i = 0;
-  foreach ($questions as $question) {
-    $quiz_result_answer = entity_create('quiz_result_answer', array(
-      'result_id' => $quiz_result->result_id,
-      'question_nid' => $question['nid'],
-      'question_vid' => $question['vid'],
-      'number' => ++$i,
-    ));
-    entity_save('quiz_result_answer', $quiz_result_answer);
-  }
-
-  return quiz_result_load($quiz_result->result_id);
-}
-
-/**
- * Store a quiz question result.
- *
- * @param $quiz
- *  The quiz node
- * @param $result
- *  Object with data about the result for a question.
- * @param $options
- *  Array with options that affect the behavior of this function.
- *  ['set_msg'] - Sets a message if the last question was skipped.
- */
-function quiz_store_question_result($quiz, $result, $options) {
-  if (!isset($result->score)) {
-    $result->score = $result->is_correct ? 1 : 0;
-  }
-
-  // Points are stored pre-scaled in the quiz_node_results_answers table. We get the scale.
-  if ($quiz->randomization < 2) {
-    $scale = db_query("SELECT (max_score / (
-                  SELECT max_score
-                  FROM {quiz_question_properties}
-                  WHERE nid = :nid AND vid = :vid
-                )) as scale
-                FROM {quiz_node_relationship}
-                WHERE parent_nid = :parent_nid
-                AND parent_vid = :parent_vid
-                AND child_nid = :child_nid
-                AND child_vid = :child_vid
-               ", array(':nid' => $result->nid, ':vid' => $result->vid, ':parent_nid' => $quiz->nid, ':parent_vid' => $quiz->vid, ':child_nid' => $result->nid, ':child_vid' => $result->vid))->fetchField();
-  }
-  elseif ($quiz->randomization == 2) {
-    $scale = db_query("SELECT (max_score_for_random / (
-                  SELECT max_score
-                  FROM {quiz_question_properties}
-                  WHERE nid = :question_nid AND vid = :question_vid
-                )) as scale
-                FROM {quiz_node_properties}
-                WHERE vid = :quiz_vid
-               ", array(':question_nid' => $result->nid, ':question_vid' => $result->vid, ':quiz_vid' => $quiz->vid))->fetchField();
-  }
-  elseif ($quiz->randomization == 3) {
-    if (isset($options['question_data']['tid'])) {
-      $result->tid = $options['question_data']['tid'];
-    }
-    $scale = db_query("SELECT (max_score / (
-                  SELECT max_score
-                  FROM {quiz_question_properties}
-                  WHERE nid = :nid AND vid = :vid
-                )) as scale
-                FROM {quiz_terms}
-                WHERE vid = :vid
-                AND tid = :tid
-               ", array(':nid' => $result->nid, ':vid' => $result->vid, ':vid' => $quiz->vid, ':tid' => $result->tid))->fetchField();
-  }
-  $points = round($result->score * $scale);
-  // Insert result data, or update existing data.
-  $result_answer_id = db_query("SELECT result_answer_id
-              FROM {quiz_node_results_answers}
-              WHERE question_nid = :question_nid
-              AND question_vid = :question_vid
-              AND result_id = :result_id", array(':question_nid' => $result->nid, ':question_vid' => $result->vid, ':result_id' => $result->result_id))->fetchField();
-
-  $entity = (object) array(
-      'result_answer_id' => $result_answer_id,
-      'question_nid' => $result->nid,
-      'question_vid' => $result->vid,
-      'result_id' => $result->result_id,
-      'is_correct' => (int) $result->is_correct,
-      'points_awarded' => $points,
-      'answer_timestamp' => REQUEST_TIME,
-      'is_skipped' => (int) $result->is_skipped,
-      'is_doubtful' => (int) $result->is_doubtful,
-      'number' => $options['question_data']['number'],
-      'tid' => ($quiz->randomization == 3 && $result->tid) ? $result->tid : NULL,
-  );
-  entity_save('quiz_result_answer', $entity);
-}
-
-/**
- * Score a completed quiz.
- */
-function quiz_end_scoring($result_id) {
-  global $user;
-  $quiz_result = quiz_result_load($result_id);
-  $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-  $questions = $quiz_result->layout;
-
-  // Mark all missing answers as blank. This is essential here for when we may
-  // have pages of unanswered questions. Also kills a lot of the skip code that
-  // was necessary before.
-  foreach ($quiz_result->layout as $qinfo) {
-    // Load the Quiz answer submission from the database.
-    $qra = quiz_result_answer_load($result_id, $qinfo['nid'], $qinfo['vid']);
-    $current_question = node_load($qinfo['nid'], $qinfo['vid']);
-
-    foreach ($questions as $question) {
-      if ($question['nid'] == $current_question->nid) {
-        $question_array = $question;
-      }
-    }
-
-    if (!$qra) {
-      // This is the same skip code as in quiz_question_answering_form_submit() @todo find a way to save code.
-      $qi_instance = _quiz_question_response_get_instance($result_id, $current_question, NULL);
-      $qi_instance->delete();
-      $bare_object = $qi_instance->toBareObject();
-      quiz_store_question_result($quiz, $bare_object, array('set_msg' => TRUE, 'question_data' => $question_array));
-    }
-  }
-
-  $score = quiz_calculate_score($quiz, $result_id);
-  if (!isset($score['percentage_score'])) {
-    $score['percentage_score'] = 0;
-  }
-  $quiz_result->is_evaluated = $score['is_evaluated'];
-  $quiz_result->score = $score['percentage_score'];
-  $quiz_result->time_end = REQUEST_TIME;
-  entity_save('quiz_result', $quiz_result);
-  if ($user->uid) {
-    $score['passing'] = quiz_is_passed($user->uid, $quiz->nid, $quiz->vid);
-  }
-  else {
-    $score['passing'] = $score['percentage_score'] >= $quiz->pass_rate;
-  }
-  return $score;
-}
-
-/**
- * Actions to take at the end of a quiz
- *
- * @param $quiz
- *  The quiz node
- * @param $result_id
- *  Result id
- * @param $score
- *  Score as a number
- */
-function quiz_end_actions($quiz, $score, $session_data) {
-  // Call hook_quiz_finished().
-  module_invoke_all('quiz_finished', $quiz, $score, $session_data);
-}
-
-/**
- * Implements hook_quiz_finished().
- *
- * Performs actions like sending quiz results over email at the end of quiz.
- */
-function quiz_quiz_finished($quiz, $score, $session_data) {
-  // @TODO convert to entity/rules
-  $result_id = $session_data['result_id'];
-  // Load data about the quiz taker
-  $sql = 'SELECT u.uid, u.mail FROM {users} u JOIN {quiz_node_results} qnr ON u.uid = qnr.uid WHERE result_id = %d';
-  $taker = db_query('SELECT u.uid, u.mail FROM {users} u JOIN {quiz_node_results} qnr ON u.uid = qnr.uid WHERE result_id = :result_id', array(':result_id' => $result_id))->fetch();
-
-  if (variable_get('quiz_results_to_quiz_author', 0)) {
-    $author_mail = db_query('SELECT mail FROM {users} WHERE uid = :uid', array(':uid' => $quiz->uid))->fetchField();
-    drupal_mail('quiz', 'notice', $author_mail, NULL, array($quiz, $score, $result_id, 'author'));
-  }
-
-  if (variable_get('quiz_email_results', 0) && variable_get('quiz_use_passfail', 1) && $taker->uid != 0 && $score['is_evaluated']) {
-    drupal_mail('quiz', 'notice', $taker->mail, NULL, array($quiz, $score, $result_id, 'taker'));
-    drupal_set_message(t('Your results have been sent to your email address.'));
-  }
-
-  // Calls userpoints functions to credit user point based on number of correct
-  // answers.
-  if ($quiz->has_userpoints && $taker->uid != 0 && $score['is_evaluated']) {
-
-    //Looking up the tid of the selected Userpoint vocabulary
-    $selected_tid = db_query("SELECT tid FROM {taxonomy_index}
-                WHERE nid = :nid AND tid IN (
-                  SELECT tid
-                  FROM {taxonomy_term_data} t_t_d JOIN {taxonomy_vocabulary} t_v ON t_v.vid = t_t_d.vid
-                  WHERE t_t_d.vid = :vid
-                )", array(':nid' => $quiz->nid, ':vid' => $quiz->vid, ':vid' => userpoints_get_vid()))->fetchField();
-    $variables = array(
-      '@title' => $quiz->title,
-      '@quiz' => QUIZ_NAME,
-      '@time' => date('l jS \of F Y h:i:s A'),
-    );
-    $params = array(
-      'points' => $score['numeric_score'],
-      'description' => t('Attended @title @quiz on @time', $variables),
-      'tid' => $selected_tid,
-      'uid' => $taker->uid,
-    );
-    if ($quiz->userpoints_tid != 0) {
-      $params['tid'] = $quiz->userpoints_tid;
-    }
-    userpoints_userpointsapi($params);
-  }
-}
-
-/**
- * Implements hook_quiz_scored().
- *
- * Performs actions like sending quiz results over email at the end of quiz.
- */
-function quiz_quiz_scored($quiz, $score, $result_id) {
-  // @TODO Rules
-  $taker = db_query('SELECT u.uid, u.mail FROM {users} u JOIN {quiz_node_results} qnr ON u.uid = qnr.uid WHERE result_id = :rid', array(':rid' => $result_id))->fetch();
-  if (variable_get('quiz_email_results', 0) && $taker->uid != 0 && $score['is_evaluated']) {
-    drupal_mail('quiz', 'notice', $taker->mail, NULL, array($quiz, $score, $result_id, 'taker'));
-    drupal_set_message(t("The results has been sent to the user's email address."));
-  }
-
-  // Calls userpoints functions to credit user point based on number of correct
-  // answers.
-  if ($quiz->has_userpoints && $taker->uid != 0 && $score['is_evaluated']) {
-
-    //Looking up the tid of the selected Userpoint vocabulary
-    $selected_tid = db_query("SELECT ti.tid
-                FROM {taxonomy_index} ti
-                JOIN {taxonomy_term_data} td
-                  ON td.tid = ti.tid AND td.vid = :vid
-                WHERE ti.nid = :nid", array(':nid' => $quiz->nid, ':vid' => userpoints_get_vid()))->fetchField();
-    $variables = array(
-      '@title' => $quiz->title,
-      '@quiz' => QUIZ_NAME,
-      '@time' => date('l jS \of F Y h:i:s A'),
-    );
-    $params = array(
-      'points' => $score['numeric_score'],
-      'description' => t('Attended @title @quiz on @time', $variables),
-      'tid' => $selected_tid,
-    );
-    if ($quiz->userpoints_tid != 0) {
-      $params['tid'] = $quiz->userpoints_tid;
-    }
-    userpoints_userpointsapi($params);
-  }
-}
-
-/**
- * Implements hook_mail().
- */
-function quiz_mail($key, &$message, $params) {
-  global $user;
-
-  switch ($key) {
-    case 'notice':
-      list($quiz, $score, $result_id, $target) = $params;
-      $quiz_body = field_get_items('node', $quiz, 'body');
-      $substitutions = array(
-        '!title' => $quiz->title,
-        '!sitename' => variable_get('site_name', 'Quiz'),
-        '!taker' => $user->name,
-        '!author' => $quiz->name,
-        '!title' => check_plain($quiz->title),
-        '!date' => format_date(REQUEST_TIME),
-        '!desc' => $quiz_body ? $quiz_body[0]['value'] : '',
-        '!correct' => isset($score['numeric_score']) ? $score['numeric_score'] : 0,
-        '!total' => $score['possible_score'],
-        '!percentage' => $score['percentage_score'],
-        '!url' => url("user/{$user->uid}/quiz-results/{$result_id}/view", array('absolute' => TRUE)),
-        '!minutes' => db_query("SELECT CEIL((time_end - time_start)/60) FROM {quiz_node_results} WHERE result_id = :result_id AND time_end", array(':result_id' => $result_id))->fetchField()
-      );
-      $type = $target != 'author' ? '_taker' : '';
-      $test = variable_get('quiz_email_results_body' . $type, quiz_email_results_format('body', $target));
-      $message['subject'] = t(variable_get('quiz_email_results_subject' . $type, quiz_email_results_format('subject', $target)), $substitutions, array('langcode' => $user->language));
-      $message['body'][] = t($test, $substitutions, array('langcode' => $user->language));
-  }
-}
-
-/**
- * This functions returns the default email subject and body format which will
- * be used at the end of quiz.
- */
-function quiz_email_results_format($type, $target) {
-  global $user;
-  if ($type == 'subject') {
-    if ($target == 'author') {
-      return t('!title Results Notice from !sitename');
-    }
-    if ($target == 'taker') {
-      return t('!title Results Notice from !sitename');
-    }
-  }
-  if ($type == 'body') {
-    if ($target == 'author') {
-      return t('Dear !author') . "\n\n" .
-        t('!taker attended the @quiz !title on !date', array('@quiz' => QUIZ_NAME)) . "\n" .
-        t('Test Description : !desc') . "\n" .
-        t('!taker got !correct out of !total points in !minutes minutes. Score given in percentage is !percentage') . "\n" .
-        t('You can access the result here !url') . "\n";
-    }
-    if ($target == 'taker') {
-      return t('Dear !taker') . "\n\n" .
-        t('You attended the @quiz !title on !date', array('@quiz' => QUIZ_NAME)) . "\n" .
-        t('Test Description : !desc') . "\n" .
-        t('You got !correct out of !total points in !minutes minutes. Score given in percentage is !percentage') . "\n" .
-        t('You can access the result here !url') . "\n";
-    }
-  }
-}
-
-/**
- * Update a score for a quiz.
- *
- * This updates the quiz node results table.
- *
- * It is used in cases where a quiz score is changed after the quiz has been
- * taken. For example, if a long answer question is scored later by a human,
- * then the quiz should be updated when that answer is scored.
- *
- * Important: The value stored in the table is the *percentage* score.
- *
- * @param $quiz
- *   The quiz node for the quiz that is being scored.
- * @param $result_id
- *   The result ID to update.
- * @return
- *   The score as an integer representing percentage. E.g. 55 is 55%.
- */
-function quiz_update_total_score($quiz, $result_id) {
-  $score = quiz_calculate_score($quiz, $result_id);
-  db_update('quiz_node_results')
-    ->fields(array(
-      'score' => $score['percentage_score'],
-    ))
-    ->condition('result_id', $result_id)
-    ->execute();
-  if ($score['is_evaluated']) {
-    // Call hook_quiz_scored().
-    module_invoke_all('quiz_scored', $quiz, $score, $result_id);
-    _quiz_maintain_results($quiz, $result_id);
-    db_update('quiz_node_results')
-      ->fields(array('is_evaluated' => 1))
-      ->condition('result_id', $result_id)
-      ->execute();
-  }
-  return $score['percentage_score'];
-}
-
-/**
- * Updates the max_score property on the specified quizzes
- *
- * @param $quizzes_to_update
- *  Array with the vid's of the quizzes to update
- */
-function quiz_update_max_score_properties($quizzes_to_update) {
-  if (empty($quizzes_to_update)) {
-    return;
-  }
-
-  db_update('quiz_node_properties')
-    ->expression('max_score', 'max_score_for_random * number_of_random_questions + (
-      SELECT COALESCE(SUM(max_score), 0)
-      FROM {quiz_node_relationship} qnr
-      WHERE qnr.question_status = ' . QUESTION_ALWAYS . '
-      AND parent_vid = {quiz_node_properties}.vid)')
-    ->condition('vid', $quizzes_to_update, 'IN')
-    ->execute();
-
-  db_update('quiz_node_properties')
-    ->expression('max_score', '(SELECT COALESCE(SUM(qt.max_score * qt.number), 0)
-      FROM {quiz_terms} qt
-      WHERE qt.nid = {quiz_node_properties}.nid AND qt.vid = {quiz_node_properties}.vid)')
-    ->condition('randomization', 3)
-    ->condition('vid', $quizzes_to_update, 'IN')
-    ->execute();
-
-  db_update('node_revision')
-    ->fields(array('timestamp' => REQUEST_TIME))
-    ->condition('vid', $quizzes_to_update, 'IN')
-    ->execute();
-
-  db_update('node')
-    ->fields(array('changed' => REQUEST_TIME))
-    ->condition('vid', $quizzes_to_update, 'IN')
-    ->execute();
-
-  $results_to_update = db_query('SELECT vid FROM {quiz_node_properties} WHERE vid IN (:vid) AND max_score <> :max_score', array(':vid' => $quizzes_to_update, ':max_score' => 0))->fetchCol();
-  if (!empty($results_to_update)) {
-    db_update('quiz_node_results')
-      ->expression('score', 'ROUND(
-        100 * (
-          SELECT COALESCE (SUM(a.points_awarded), 0)
-          FROM {quiz_node_results_answers} a
-          WHERE a.result_id = {quiz_node_results}.result_id
-        ) / (
-          SELECT max_score
-          FROM {quiz_node_properties} qnp
-          WHERE qnp.vid = {quiz_node_results}.vid
-        )
-      )')
-      ->condition('vid', $results_to_update, 'IN')
-      ->execute();
-  }
-}
-
-/**
- * Check a user/quiz combo to see if the user passed the given quiz.
- *
- * This will return TRUE if the user has passed the quiz at least once, and
- * FALSE otherwise. Note that a FALSE may simply indicate that the user has not
- * taken the quiz.
- *
- * @param $uid
- *   The user ID.
- * @param $nid
- *   The node ID.
- * @param $vid
- *   The version ID.
- */
-function quiz_is_passed($uid, $nid, $vid) {
-  $passed = db_query('SELECT COUNT(result_id) AS passed_count FROM {quiz_node_results} qnrs
-    INNER JOIN {quiz_node_properties} USING (vid, nid)
-    WHERE qnrs.vid = :vid
-      AND qnrs.nid = :nid
-      AND qnrs.uid = :uid
-      AND score >= pass_rate', array(':vid' => $vid, ':nid' => $nid, ':uid' => $uid))->fetchField();
-  // Force into boolean context.
-  return ($passed !== FALSE && $passed > 0);
-}
-
-/**
- * Actions to take place at the start of a quiz.
- *
- * This is called when the quiz node is viewed for the first time. It ensures
- * that the quiz can be taken at this time.
- *
- * @param $quiz
- *   The quiz node.
- *
- * @return
- *   Return quiz_node_results result_id, or FALSE if there is an error.
- */
-function quiz_start_check($quiz) {
-  global $user;
-
-  $user_is_admin = node_access('update', $quiz);
-
-  // Make sure this is available.
-  if ($quiz->quiz_always != 1) {
-    // Compare current GMT time to the open and close dates (which should still
-    // be in GMT time).
-    $now = REQUEST_TIME;
-
-    if ($now >= $quiz->quiz_close || $now < $quiz->quiz_open) {
-      if ($user_is_admin) {
-        drupal_set_message(t('You are marked as an administrator or owner for this @quiz. While you can take this @quiz, the open/close times prohibit other users from taking this @quiz.', array('@quiz' => QUIZ_NAME)), 'status');
-      }
-      else {
-        drupal_set_message(t('This @quiz is not currently available.', array('@quiz' => QUIZ_NAME)), 'status');
-        // Can't take quiz.
-        return FALSE;
-      }
-    }
-  }
-
-  // Check to see if this user is allowed to take the quiz again:
-  if ($quiz->takes > 0) {
-    $taken = db_query("SELECT COUNT(*) AS takes FROM {quiz_node_results} WHERE uid = :uid AND nid = :nid", array(':uid' => $user->uid, ':nid' => $quiz->nid))->fetchField();
-    $allowed_times = format_plural($quiz->takes, '1 time', '@count times');
-    $taken_times = format_plural($taken, '1 time', '@count times');
-
-    // The user has already taken this quiz.
-    if ($taken) {
-      if ($user_is_admin) {
-
-        drupal_set_message(t('You have taken this @quiz already. You are marked as an owner or administrator for this quiz, so you can take this quiz as many times as you would like.', array('@quiz' => QUIZ_NAME)), 'status');
-      }
-      // If the user has already taken this quiz too many times, stop the user.
-      elseif ($taken >= $quiz->takes) {
-        drupal_set_message(t('You have already taken this @quiz @really. You may not take it again.', array('@quiz', QUIZ_NAME, '@really' => $taken_times)), 'error');
-        return FALSE;
-      }
-      // If the user has taken the quiz more than once, see if we should report
-      // this.
-      elseif ($quiz->show_attempt_stats) {
-        drupal_set_message(t("You can only take this @quiz @allowed. You have taken it @really.", array('@quiz' => QUIZ_NAME, '@allowed' => $allowed_times, '@really' => $taken_times)), 'status');
-      }
-    }
-  }
-
-  // Check to see if the user is registered, and user alredy passed this quiz.
-  if ($quiz->show_passed && $user->uid && quiz_is_passed($user->uid, $quiz->nid, $quiz->vid)) {
-    drupal_set_message(t('You have already passed this @quiz.', array('@quiz' => QUIZ_NAME)), 'status');
-  }
-
-  return TRUE;
-}
-
-/**
- * Find out if a quiz is available for taking or not
- *
- * @param $quiz
- *  The quiz node
- * @return
- *  TRUE if available
- *  Error message(String) if not available
- */
-function quiz_availability($quiz) {
-  global $user;
-
-  if ($user->uid == 0 && $quiz->takes > 0) {
-    return t('This @quiz only allows %num_attempts attempts. Anonymous users can only access quizzes that allows an unlimited number of attempts.', array('%num_attempts' => $quiz->takes, '@quiz' => QUIZ_NAME));
-  }
-
-  $user_is_admin = user_access('edit any quiz content') || (user_access('edit own quiz content') && $quiz->uid == $user->uid);
-  if ($user_is_admin || $quiz->quiz_always == 1) {
-    return TRUE;
-  }
-
-  // Compare current GMT time to the open and close dates (which should still be
-  // in GMT time).
-  $now = REQUEST_TIME;
-
-  if ($now >= $quiz->quiz_close || $now < $quiz->quiz_open) {
-    return t('This @quiz is closed.', array('@quiz' => QUIZ_NAME));
-  }
-  return TRUE;
-}
-
-/**
- * Creates a unique id to be used when storing results for a quiz taker.
- *
- * @param $quiz
- *   The quiz node.
- * @return $result_id
- *   The result id.
- */
-function quiz_create_rid($quiz) {
-  $result_id = db_insert('quiz_node_results')
-    ->fields(array(
-      'nid' => $quiz->nid,
-      'vid' => $quiz->vid,
-      'uid' => $GLOBALS['user']->uid,
-      'time_start' => REQUEST_TIME,
-    ))
-    ->execute();
-  if (!is_numeric($result_id)) {
-    form_set_error(t('There was a problem starting the @quiz. Please try again later.', array('@quiz' => QUIZ_NAME), array('langcode' => 'error')));
-    return FALSE;
-  }
-  return $result_id;
-}
-
-/**
- * Calculates the score user received on quiz.
- *
- * @param $quiz
- *   The quiz node.
- * @param $result_id
- *   Quiz result ID.
- *
- * @return array
- *   Contains three elements: question_count, num_correct and percentage_score.
- */
-function quiz_calculate_score($quiz, $result_id) {
-  // 1. Fetch all questions and their max scores
-  $questions = db_query('SELECT a.question_nid, a.question_vid, n.type, r.max_score
-    FROM {quiz_node_results_answers} a
-    LEFT JOIN {node} n ON (a.question_nid = n.nid)
-    LEFT OUTER JOIN {quiz_node_relationship} r ON (r.child_vid = a.question_vid) AND r.parent_vid = :vid
-    WHERE result_id = :rid', array(':vid' => $quiz->vid, ':rid' => $result_id));
-  // 2. Callback into the modules and let them do the scoring. @todo after 4.0: Why isn't the scores already saved? They should be
-  // Fetched from the db, not calculated....
-  $scores = array();
-  $count = 0;
-  foreach ($questions as $question) {
-    // Questions picked from term id's won't be found in the quiz_node_relationship table
-    if ($question->max_score === NULL && isset($quiz->tid) && $quiz->tid > 0) {
-      $question->max_score = $quiz->max_score_for_random;
-    }
-
-    // Invoke hook_quiz_question_score().
-    // We don't use module_invoke() because (1) we don't necessarily want to wed
-    // quiz type to module, and (2) this is more efficient (no NULL checks).
-    $mod = quiz_question_module_for_type($question->type);
-    if (!$mod) {
-      continue;
-    }
-    $function = $mod . '_quiz_question_score';
-
-    if (function_exists($function)) {
-      $score = $function($quiz, $question->question_nid, $question->question_vid, $result_id);
-      // Allow for max score to be considered.
-      $scores[] = $score;
-    }
-    else {
-      drupal_set_message(t('A @quiz question could not be scored: No scoring info is available', array('@quiz' => QUIZ_NAME)), 'error');
-      $dummy_score = new stdClass();
-      $dummy_score->possible = 0;
-      $dummy_score->attained = 0;
-      $scores[] = $dummy_score;
-    }
-    ++$count;
-  }
-  // 3. Sum the results.
-  $possible_score = 0;
-  $total_score = 0;
-  $is_evaluated = TRUE;
-  foreach ($scores as $score) {
-    $possible_score += $score->possible;
-    $total_score += $score->attained;
-    if (isset($score->is_evaluated)) {
-      // Flag the entire quiz if one question has not been evaluated.
-      $is_evaluated &= $score->is_evaluated;
-    }
-  }
-
-  // 4. Return the score.
-  return array(
-    'question_count' => $count,
-    'possible_score' => $possible_score,
-    'numeric_score' => $total_score,
-    'percentage_score' => ($possible_score == 0) ? 0 : round(($total_score * 100) / $possible_score),
-    'is_evaluated' => $is_evaluated,
-  );
-}
-
-/**
- * @param $type
- *
- * @return string
- *   Name of module matching the question type, as given by quiz_question_info()
- *   hook.
- */
-function quiz_question_module_for_type($type) {
-  $types = quiz_get_question_types();
-  if (!isset($types[$type])) {
-    drupal_set_message(t('The module for the questiontype %type is not enabled', array('%type' => $type)), 'warning');
-    return FALSE;
-  }
-  return $types[$type]['module'];
-}
-
-/**
- * Retrieves a list of questions (to be taken) for a given quiz.
- *
- * If the quiz has random questions this function only returns a random
- * selection of those questions. This function should be used to decide
- * what questions a quiz taker should answer.
- *
- * This question list is stored in the user's result, and may be different
- * when called multiple times. It should only be used to generate the layout
- * for a quiz attempt and NOT used to do operations on the questions inside of
- * a quiz.
- *
- * @param $quiz
- *   Quiz node.
- * @return
- *   Array of question node IDs.
- */
-function quiz_build_question_list($quiz) {
-  $questions = array();
-
-  if ($quiz->randomization == 3) {
-    $questions = _quiz_build_categorized_question_list($quiz);
-  }
-  else {
-    // Get required questions first.
-    $query = db_query('SELECT n.nid, n.vid, n.type, qnr.qnr_id, qnr.qnr_pid
-    FROM {quiz_node_relationship} qnr
-    JOIN {node} n ON qnr.child_nid = n.nid
-    LEFT JOIN {quiz_node_relationship} qnr2 ON (qnr.qnr_pid = qnr2.qnr_id OR (qnr.qnr_pid IS NULL AND qnr.qnr_id = qnr2.qnr_id))
-    WHERE qnr.parent_vid = :parent_vid
-    AND qnr.question_status = :question_status
-    AND n.status = 1
-    ORDER BY qnr2.weight, qnr.weight', array(':parent_vid' => $quiz->vid, ':question_status' => QUESTION_ALWAYS));
-    $i = 0;
-    while ($question_node = $query->fetchAssoc()) {
-      // Just to make it easier on us, let's use a 1-based index.
-      $i++;
-      $questions[$i] = $question_node;
-    }
-
-    // Get random questions for the remainder.
-    if ($quiz->number_of_random_questions > 0) {
-      $random_questions = _quiz_get_random_questions($quiz);
-      $questions = array_merge($questions, $random_questions);
-      if ($quiz->number_of_random_questions > count($random_questions)) {
-        // Unable to find enough requested random questions.
-        return FALSE;
-      }
-    }
-
-    // Shuffle questions if required.
-    if ($quiz->randomization > 0) {
-      shuffle($questions);
-    }
-  }
-
-  $count = 0;
-  $display_count = 0;
-  $questions_out = array();
-  foreach ($questions as &$question) {
-    $question_node = node_load($question['nid'], $question['vid']);
-    $count++;
-    $display_count++;
-    $question['number'] = $count;
-    if ($question['type'] != 'quiz_page') {
-      $question['display_number'] = $display_count;
-    }
-    $questions_out[$count] = $question;
-  }
-  return $questions_out;
-}
-
-/**
- * Builds the questionlist for quizzes with categorized random questions
- *
- * @see quiz_build_question_list()
- */
-function _quiz_build_categorized_question_list($quiz) {
-  $terms = _quiz_get_terms($quiz->vid);
-  $questions = array();
-  $nids = array();
-  $question_types = array_keys(quiz_get_question_types());
-  if (empty($question_types)) {
-    return array();
-  }
-  $total_count = 0;
-  foreach ($terms as $term) {
-
-    $query = db_select('node', 'n');
-    $query->join('taxonomy_index', 'tn', 'n.nid = tn.nid');
-    $query->fields('n', array('nid', 'vid'));
-    $query->fields('tn', array('tid'));
-    $query->condition('n.status', 1, '=');
-    $query->condition('n.type', $question_types, 'IN');
-    $query->condition('tn.tid', $term->tid, '=');
-    if (!empty($nids)) {
-      $query->condition('n.nid', $nids, 'NOT IN');
-    }
-    $query->range(0, $term->number);
-    $query->orderBy('RAND()');
-
-    $result = $query->execute();
-    $count = 0;
-    while ($question = $result->fetchAssoc()) {
-      $count++;
-      $question['tid'] = $term->tid;
-      $question['number'] = $count + $total_count;
-      $questions[] = $question;
-      $nids[] = $question['nid'];
-    }
-    $total_count += $count;
-    if ($count < $term->number) {
-      // Not enough questions
-      return array();
-    }
-  }
-  return $questions;
-}
-
-/**
- * Get data for all terms belonging to a Quiz with categorized random questions
- *
- * @param int $vid
- *  version id for the quiz
- * @return array
- *  Array with all terms that belongs to the quiz as objects
- */
-function _quiz_get_terms($vid) {
-  $sql = 'SELECT td.name, qt.*
-    FROM {quiz_terms} qt
-    JOIN {taxonomy_term_data} td ON qt.tid = td.tid
-    WHERE qt.vid = :vid
-    ORDER BY qt.weight';
-  return db_query($sql, array(':vid' => $vid))->fetchAll();
-}
-
-/**
- * Map node properties to a question object.
- *
- * @param $node
- *  The question node.
- *
- * @return
- *  Question object.
- */
-function quiz_node_map($node) {
-  $new_question = node_load($node->nid, $node->vid);
-  // Append extra fields.
-  $new_question->latest_vid = $node->latest_vid;
-  $new_question->question_status = isset($node->question_status) ? $node->question_status : QUESTION_NEVER;
-  if (isset($node->max_score)) {
-    $new_question->max_score = $node->max_score;
-  }
-  if (isset($node->auto_update_max_score)) {
-    $new_question->auto_update_max_score = $node->auto_update_max_score;
-  }
-  $new_question->weight = $node->weight;
-  $new_question->qnr_id = $node->qnr_id;
-  $new_question->qnr_pid = $node->qnr_pid;
-  return $new_question;
-}
-
-/**
- * Sets the questions that are assigned to a quiz.
- *
- * @param $quiz
- *   The quiz(node) to modify.
- * @param $questions
- *   An array of questions.
- * @param $set_new_revision
- *   If TRUE, a new revision will be generated. Note that saving
- *   quiz questions unmodified will still generate a new revision of the quiz if
- *   this is set to TRUE. Why? For a few reasons:
- *   - All of the questions are updated to their latest VID. That is supposed to
- *     be a feature.
- *   - All weights are updated.
- *   - All status flags are updated.
- *
- * @return
- *   Boolean TRUE if update was successful, FALSE otherwise.
- */
-function quiz_set_questions(&$quiz, $questions, $set_new_revision = FALSE) {
-  if ($set_new_revision) {
-    // Create a new Quiz VID, even if nothing changed.
-    $quiz->revision = 1;
-
-    node_save($quiz);
-  }
-
-  // When node_save() calls all of the node API hooks, old quiz info is
-  // automatically inserted into quiz_node_relationship. We could get clever and
-  // try to do strategic updates/inserts/deletes, but that method has already
-  // proven error prone as the module has gained complexity (See 5.x-2.0-RC2).
-  // So we go with the brute force method:
-  db_delete('quiz_node_relationship')
-    ->condition('parent_nid', $quiz->nid)
-    ->condition('parent_vid', $quiz->vid)
-    ->execute();
-
-  if (empty($questions)) {
-    return TRUE; // This is not an error condition.
-  }
-
-  foreach ($questions as $question) {
-    if ($question->state != QUESTION_NEVER) {
-      $question_inserts[$question->qnr_id] = array(
-        'parent_nid' => $quiz->nid,
-        'parent_vid' => $quiz->vid,
-        'child_nid' => $question->nid,
-        // Update to latest OR use the version given.
-        'child_vid' => $question->refresh ? db_query('SELECT vid FROM {node} WHERE nid = :nid', array(':nid' => $question->nid))->fetchField() : $question->vid,
-        'question_status' => $question->state,
-        'weight' => $question->weight,
-        'max_score' => (int) $question->max_score,
-        'auto_update_max_score' => (int) $question->auto_update_max_score,
-        'qnr_pid' => $question->qnr_pid,
-        'qnr_id' => !$set_new_revision ? $question->qnr_id : NULL,
-        'old_qnr_id' => $question->qnr_id,
-      );
-      drupal_write_record('quiz_node_relationship', $question_inserts[$question->qnr_id]);
-    }
-  }
-
-  // Update the parentage when a new revision is created.
-  // @todo this is copy pasta from quiz_update_quiz_question_relationship
-  foreach ($question_inserts as $question_insert) {
-    db_update('quiz_node_relationship')
-      ->condition('qnr_pid', $question_insert['old_qnr_id'])
-      ->condition('parent_vid', $quiz->vid)
-      ->condition('parent_nid', $quiz->nid)
-      ->fields(array('qnr_pid' => $question_insert['qnr_id']))
-      ->execute();
-  }
-
-  quiz_update_max_score_properties(array($quiz->vid));
-  return TRUE;
-}
-
-/**
- * @} End of "defgroup quiz_public".
- */
-
-/**
- * Delete quiz responses for quizzes that haven't been finished.
- *
- * @param $quiz
- *   A quiz node where old in progress results shall be deleted.
- * @param $uid
- *   The userid of the user the old in progress results belong to.
- */
-function _quiz_delete_old_in_progress($quiz, $uid) {
-  $res = db_query('SELECT qnr.result_id FROM {quiz_node_results} qnr
-          WHERE qnr.uid = :uid
-          AND qnr.nid = :nid
-          AND qnr.time_end = :time_end
-          AND qnr.vid < :vid', array(':uid' => $uid, ':nid' => $quiz->nid, ':time_end' => 1, ':vid' => $quiz->vid));
-  $result_ids = array();
-  while ($result_id = $res->fetchField()) {
-    $result_ids[] = $result_id;
-  }
-  entity_delete_multiple('quiz_result', $result_ids);
-}
-
-/**
- * Returns the result ID for any current result set for the given quiz.
- *
- * @param $uid
- *   User ID
- * @param $nid
- *   Quiz node ID
- * @param $vid
- *   Quiz node version ID
- * @param $now
- *   Timestamp used to check whether the quiz is still open. Default: current
- *   time.
- *
- * @return
- *   If a quiz is still open and the user has not finished the quiz,
- *   return the result set ID so that the user can continue. If no quiz is in
- *   progress, this will return 0.
- */
-function _quiz_active_result_id($uid, $nid, $vid, $now = NULL) {
-  if (!isset($now)) {
-    $now = REQUEST_TIME;
-  }
-
-  // Get any quiz that is open, for this user, and has not already
-  // been completed.
-  $result_id = db_query('SELECT result_id FROM {quiz_node_results} qnr
-          INNER JOIN {quiz_node_properties} qnp ON qnr.vid = qnp.vid
-          WHERE (qnp.quiz_always = :quiz_always OR (:between BETWEEN qnp.quiz_open AND qnp.quiz_close))
-          AND qnr.vid = :vid
-          AND qnr.uid = :uid
-          AND qnr.time_end IS NULL', array(':quiz_always' => 1, ':between' => $now, ':vid' => $vid, ':uid' => $uid))->fetchField();
-  return (int) $result_id;
-}
-
-/**
- * Insert call specific to result options.
- *
- * This is called by quiz_insert().
- *
- * @param $node
- *   The quiz node.
- */
-function _quiz_insert_resultoptions($node) {
-  if (!isset($node->resultoptions)) {
-    return;
-  }
-
-  $query = db_insert('quiz_node_result_options')
-    ->fields(array('nid', 'vid', 'option_name', 'option_summary', 'option_summary_format', 'option_start', 'option_end'));
-
-  foreach ($node->resultoptions as $id => $option) {
-    if (!empty($option['option_name'])) {
-      // When this function called direct from node form submit the $option['option_summary']['value'] and $option['option_summary']['format'] are we need
-      // But when updating a quiz node eg. on manage questions page, this values come from loaded node, not from a submitted form.
-      if (is_array($option['option_summary'])) {
-        $option['option_summary_format'] = $option['option_summary']['format'];
-        $option['option_summary'] = $option['option_summary']['value'];
-      }
-      $query->values(array(
-        'nid' => $node->nid,
-        'vid' => $node->vid,
-        'option_name' => $option['option_name'],
-        'option_summary' => $option['option_summary'],
-        'option_summary_format' => $option['option_summary_format'],
-        'option_start' => $option['option_start'],
-        'option_end' => $option['option_end']
-      ));
-    }
-  }
-
-  $query->execute();
-}
-
-/**
- * Modify result of option-specific updates.
- *
- * @param $node
- *   The quiz node.
- */
-function _quiz_update_resultoptions($node) {
-  // Brute force method. Easier to get correct, and probably faster as well.
-  db_delete('quiz_node_result_options')
-    ->condition('vid', $node->vid)
-    ->execute();
-  _quiz_insert_resultoptions($node);
-}
-
-/**
- * Get the summary message for a completed quiz.
- *
- * Summary is determined by whether we are using the pass / fail options, how
- * the user did, and where the method is called from.
- *
- * @todo Need better feedback for when a user is viewing their quiz results
- *   from the results list (and possibily when revisiting a quiz they can't take
- *   again).
- *
- * @param $quiz
- *   The quiz node object.
- * @param $score
- *   The score information as returned by quiz_calculate_score().
- * @return
- *   Filtered summary text or null if we are not displaying any summary.
- */
-function _quiz_get_summary_text($quiz, $score) {
-  $summary = array();
-  $admin = (arg(0) == 'admin');
-  $quiz_format = (isset($quiz->body[LANGUAGE_NONE][0]['format'])) ? $quiz->body[LANGUAGE_NONE][0]['format'] : NULL;
-  if (!$admin) {
-    if (!empty($score['result_option'])) {
-      // Unscored quiz, return the proper result option.
-      $summary['result'] = check_markup($score['result_option'], $quiz_format);
-    }
-    else {
-      $result_option = _quiz_pick_result_option($quiz->nid, $quiz->vid, $score['percentage_score']);
-      $summary['result'] = is_object($result_option) ? check_markup($result_option->option_summary, $result_option->option_summary_format) : '';
-    }
-  }
-
-  // If we are using pass/fail, and they passed.
-  if ($quiz->pass_rate > 0 && $score['percentage_score'] >= $quiz->pass_rate) {
-    // If we are coming from the admin view page.
-    if ($admin) {
-      $summary['passfail'] = t('The user passed this @quiz.', array('@quiz' => QUIZ_NAME));
-    }
-    elseif (variable_get('quiz_use_passfail', 1) == 0) {
-      // If there is only a single summary text, use this.
-      if (trim($quiz->summary_default) != '') {
-        $summary['passfail'] = check_markup($quiz->summary_default, $quiz_format);
-      }
-    }
-    elseif (trim($quiz->summary_pass) != '') {
-      // If there is a pass summary text, use this.
-      $summary['passfail'] = check_markup($quiz->summary_pass, $quiz->summary_pass_format);
-    }
-  }
-  // If the user did not pass or we are not using pass/fail.
-  else {
-    // If we are coming from the admin view page, only show a summary if we are
-    // using pass/fail.
-    if ($admin) {
-      if ($quiz->pass_rate > 0) {
-        $summary['passfail'] = t('The user failed this @quiz.', array('@quiz' => QUIZ_NAME));
-      }
-      else {
-        $summary['passfail'] = t('the user completed this @quiz.', array('@quiz' => QUIZ_NAME));
-      }
-    }
-    elseif (trim($quiz->summary_default) != '') {
-      $summary['passfail'] = check_markup($quiz->summary_default, $quiz->summary_default_format);
-    }
-  }
-  return $summary;
-}
-
-/**
- * Get summary text for a particular score from a set of result options.
- *
- * @param $qnid
- *   The quiz node id.
- * @param $qvid
- *   The quiz node revision id.
- * @param $score
- *   The user's final score.
- *
- * @return
- *   Summary text for the user's score.
- */
-function _quiz_pick_result_option($qnid, $qvid, $score) {
-  return db_query('SELECT option_summary, option_summary_format FROM {quiz_node_result_options}
-      WHERE nid = :nid AND vid = :vid AND :option BETWEEN option_start AND option_end', array(':nid' => $qnid, ':vid' => $qvid, ':option' => $score)
-    )->fetch();
-}
-
-/**
- * Get an array list of random questions for a quiz.
- *
- * @param $quiz
- *   The quiz node.
- *
- * @return
- *   Array of nid/vid combos for quiz questions.
- */
-function _quiz_get_random_questions($quiz) {
-  $num_random = $quiz->number_of_random_questions;
-  $tid = $quiz->tid;
-  $questions = array();
-  if ($num_random > 0) {
-    if ($tid > 0) {
-      $questions = _quiz_get_random_taxonomy_question_ids($tid, $num_random);
-    }
-    else {
-      // Select random question from assigned pool.
-      $result = db_query_range(
-        "SELECT child_nid as nid, child_vid as vid, n.type
-        FROM {quiz_node_relationship} qnr
-        JOIN {node} n on qnr.child_nid = n.nid
-        WHERE qnr.parent_vid = :parent_vid
-        AND qnr.parent_nid = :parent_nid
-        AND qnr.question_status = :question_status
-        AND n.status = 1
-        ORDER BY RAND()", 0, $quiz->number_of_random_questions, array(
-        ':parent_vid' => $quiz->vid,
-        ':parent_nid' => $quiz->nid,
-        ':question_status' => QUESTION_RANDOM
-        )
-      );
-      while ($question_node = $result->fetchAssoc()) {
-        $question_node['random'] = TRUE;
-        $question_node['relative_max_score'] = $quiz->max_score_for_random;
-        $questions[] = $question_node;
-      }
-    }
-  }
-  return $questions;
-}
-
-/**
- * Given a term ID, get all of the question nid/vids that have that ID.
- *
- * @param $tid
- *   Integer term ID.
- *
- * @return
- *   Array of nid/vid combos, like array(array('nid'=>1, 'vid'=>2)).
- */
-function _quiz_get_random_taxonomy_question_ids($tid, $num_random) {
-  if ($tid == 0) {
-    return array();
-  }
-
-  // Select random questions by taxonomy.
-  $term = taxonomy_term_load($tid);
-  $tree = taxonomy_get_tree($term->vid, $term->tid);
-
-  // Flatten the taxonomy tree, and just keep term id's.
-  $term_ids[] = $term->tid;
-  if (is_array($tree)) {
-    foreach ($tree as $term) {
-      $term_ids[] = $term->tid;
-    }
-  }
-  $term_ids = implode(',', $term_ids);
-
-  // Get all published questions with one of the allowed term ids.
-  // TODO Please convert this statement to the D7 database API syntax.
-  $result = db_query_range("SELECT n.nid, n.vid
-    FROM {node} n
-    INNER JOIN {taxonomy_index} tn USING (nid)
-    WHERE n.status = 1 AND tn.tid IN ($term_ids)
-    AND n.type IN ('" . implode("','", array_keys(quiz_get_question_types()))
-    . "') ORDER BY RAND()");
-
-  $questions = array();
-  while ($question_node = db_fetch_array($result)) {
-    $question_node['random'] = TRUE;
-    $questions[] = $question_node;
-  }
-
-  return $questions;
-}
-
-/**
- * Retrieve list of question types.
- *
- * @return
- *  Array of question types.
- */
-function quiz_get_question_types() {
-  $quiz_questions = &drupal_static(__FUNCTION__);
-
-  // We vastly improves performance by statically caching the question types.
-  if (!empty($quiz_questions)) {
-    return $quiz_questions;
-  }
-
-  // Get question types from the modules that defines them.
-  if ($quiz_questions = module_invoke_all('quiz_question_info')) {
-    return $quiz_questions;
-  }
-
-  drupal_set_message(t('You need to install and enable at least one question type(multichoice for instance) to use quiz.'), 'warning', FALSE);
-  return array();
-}
-
-/**
- * Retrieve list of vocabularies for all quiz question types.
- *
- * @return
- *   An array containing a vocabulary list.
- */
-function _quiz_get_vocabularies() {
-  $vocabularies = array();
-  $types = array_keys(quiz_get_question_types());
-  foreach ($types as $type) {
-    foreach (taxonomy_get_vocabularies($type) as $vid => $vocabulary) {
-      $vocabularies[$vid] = $vocabulary;
-    }
-  }
-  return $vocabularies;
-}
-
-/**
- * Prints a taxonomy selection form for each vocabulary.
- *
- * @param $value
- *   Default selected value(s).
- * @return
- *   HTML output to print to screen.
- */
-function _quiz_taxonomy_select($value = 0) {
-  $options = array();
-  foreach (_quiz_get_vocabularies() as $vid => $vocabulary) {
-    $temp = taxonomy_form($vid, $value);
-    $options = array_merge($options, $temp['#options']);
-  }
-  return $options;
-}
-
-/**
- * Retrieve list of published questions assigned to quiz.
- *
- * This function should be used for question browsers and similiar... It should not be used to decide what questions
- * a user should answer when taking a quiz. quiz_build_question_list is written for that purpose.
- *
- * @param $quiz_nid
- *   Quiz node id.
- * @param $quiz_vid
- *   Quiz node version id.
- *
- * @return
- *   An array of questions.
- */
-function quiz_get_questions($quiz_nid, $quiz_vid = NULL) {
-  $questions = array();
-  $query = db_select('node', 'n');
-  $query->fields('n', array('nid', 'type'));
-  $query->fields('nr', array('vid', 'title'));
-  $query->fields('qnr', array('question_status', 'weight', 'max_score', 'auto_update_max_score', 'qnr_id', 'qnr_pid'));
-  $query->addField('n', 'vid', 'latest_vid');
-  $query->join('node_revision', 'nr', 'n.nid = nr.nid');
-  $query->leftJoin('quiz_node_relationship', 'qnr', 'nr.vid = qnr.child_vid');
-  $query->condition('n.status', 1);
-  $query->condition('qnr.parent_nid', $quiz_nid);
-  if ($quiz_vid) {
-    $query->condition('qnr.parent_vid', $quiz_vid);
-  }
-  $query->condition('qnr_pid', NULL, 'IS');
-  $query->orderBy('qnr.weight');
-
-  $result = $query->execute();
-  foreach ($result as $question) {
-    $questions[] = $question;
-    quiz_get_sub_questions($question->qnr_id, $questions);
-  }
-
-  foreach ($questions as &$node) {
-    $node = quiz_node_map($node);
-  }
-
-  return $questions;
-}
-
-/**
- * Get sub-questions.
- */
-function quiz_get_sub_questions($qnr_pid, &$questions) {
-  $query = db_select('node', 'n');
-  $query->fields('n', array('nid', 'type'));
-  $query->fields('nr', array('vid', 'title'));
-  $query->fields('qnr', array('question_status', 'weight', 'max_score', 'auto_update_max_score', 'qnr_id', 'qnr_pid'));
-  $query->addField('n', 'vid', 'latest_vid');
-  $query->innerJoin('node_revision', 'nr', 'n.nid = nr.nid');
-  $query->innerJoin('quiz_node_relationship', 'qnr', 'nr.vid = qnr.child_vid');
-  $query->condition('qnr_pid', $qnr_pid);
-  $query->orderBy('weight');
-  $result = $query->execute();
-  foreach ($result as $question) {
-    $questions[] = $question;
-  }
-}
-
-/**
- * Get a list of all available quizzes.
- *
- * @param $uid
- *   An optional user ID. If supplied, only quizzes created by that user will be
- *   returned.
- *
- * @return
- *   A list of quizzes.
- */
-function _quiz_get_quizzes($uid = 0) {
-  $results = array();
-  $args = array();
-  $query = db_select('node', 'n')
-    ->fields('n', array('nid', 'vid', 'title', 'uid', 'created'))
-    ->fields('u', array('name'));
-  $query->leftJoin('users', 'u', 'u.uid = n.uid');
-  $query->condition('n.type', 'quiz');
-  if ($uid != 0) {
-    $query->condition('n.uid', $uid);
-  }
-  $query->orderBy('n.nid');
-  $quizzes = $query->execute();
-  foreach ($quizzes as $quiz) {
-    $results[$quiz->nid] = (array) $quiz;
-  }
-  return $results;
-}
-
-/**
- * Get answer data for a specific result.
- *
- * @param $result_id
- *   Result id.
- *
- * @return
- *   Array of answers.
- */
-function _quiz_get_answers($quiz, $result_id) {
-  $questions = array();
-  $ids = db_query("SELECT question_nid, question_vid, type, rs.max_score, qt.max_score as term_max_score
-                   FROM {quiz_node_results_answers} ra
-                   LEFT JOIN {node} n ON (ra.question_nid = n.nid)
-                   LEFT JOIN {quiz_node_results} r ON (ra.result_id = r.result_id)
-                   LEFT OUTER JOIN {quiz_node_relationship} rs ON (ra.question_vid = rs.child_vid) AND rs.parent_vid = r.vid
-                   LEFT OUTER JOIN {quiz_terms} qt ON (qt.vid = :vid AND qt.tid = ra.tid)
-                   WHERE ra.result_id = :rid
-                   ORDER BY ra.number, ra.answer_timestamp", array(':vid' => $quiz->vid, ':rid' => $result_id));
-  while ($line = $ids->fetch()) {
-    // Questions picked from term id's won't be found in the quiz_node_relationship table
-    if ($line->max_score === NULL) {
-      if ($quiz->randomization == 2 && isset($quiz->tid) && $quiz->tid > 0) {
-        $line->max_score = $quiz->max_score_for_random;
-      }
-      elseif ($quiz->randomization == 3) {
-        $line->max_score = $line->term_max_score;
-      }
-    }
-    $module = quiz_question_module_for_type($line->type);
-    if (!$module) {
-      continue;
-    }
-    // Invoke hook_get_report().
-    $report = module_invoke($module, 'get_report', $line->question_nid, $line->question_vid, $result_id);
-    if (!$report) {
-      continue;
-    }
-    $questions[$line->question_nid] = $report;
-    // Add max score info to the question.
-    if (!isset($questions[$line->question_nid]->score_weight)) {
-      if ($questions[$line->question_nid]->max_score == 0) {
-        $score_weight = 0;
-      }
-      else {
-        $score_weight = $line->max_score / $questions[$line->question_nid]->max_score;
-      }
-      $questions[$line->question_nid]->qnr_max_score = $line->max_score;
-      $questions[$line->question_nid]->score_weight = $score_weight;
-    }
-  }
-  return $questions;
-}
-
-/**
- * Get the quiz name variable and set it as a constant so we don't have to keep
- * calling it in every function.
- *
- * @return
- *   Quiz name variable.
- */
-function _quiz_get_quiz_name() {
-  return t(variable_get('quiz_name', 'Quiz'));
-}
-
-/**
- * Takes a time element and prepares to send it to form_date().
- *
- * @param $time
- *   The time to be turned into an array. This can be:
- *   - A timestamp when from the database.
- *   - An array (day, month, year) when previewing.
- *   - NULL for new nodes.
- *
- * @return
- *   An array for form_date (day, month, year).
- */
-function _quiz_form_prepare_date($time = '', $offset = 0) {
-  // If this is empty, get the current time.
-  if ($time == '') {
-    $time = REQUEST_TIME + $offset * 86400;
-  }
-  // If we are previewing, $time will be an array so just pass it through.
-  $time_array = array();
-  if (is_array($time)) {
-    $time_array = $time;
-  }
-  // Otherwise build the array from the timestamp.
-  elseif (is_numeric($time)) {
-    $time_array = array(
-      'day' => _quiz_date('j', $time),
-      'month' => _quiz_date('n', $time),
-      'year' => _quiz_date('Y', $time),
-    );
-  }
-  return $time_array;
 }
 
 /**
@@ -3425,370 +143,65 @@ function quiz_action_options($type = 'all') {
 }
 
 /**
- * Returns an array with quiz titles keyed with quiz node ids.
- *
- * @return array
- *   Options suitable for a form, in which the value is nid.
- */
-function quiz_get_quiz_options() {
-  $options = array();
-  $results = db_query('SELECT n.nid, n.title, n.type FROM {node} n WHERE n.type = :type', array(':type' => 'quiz'));
-  foreach ($results as $result) {
-    $options[$result->nid] = drupal_substr(check_plain($result->title), 0, 30);
-  }
-  return $options;
-}
-
-/**
- * Tells whether a question node is being viewed inside/outside of quiz.
- *
- * @return
- *   Boolean.
- */
-function _quiz_is_taking_context() {
-  return arg(2) == 'take';
-}
-
-/**
- * Retrieves the quiz node from the menu router.
- *
- * @return
- *   Quiz node, if found, or FALSE if quiz node can't be retrieved from the menu
- *   router.
- */
-function quiz_get_quiz_from_menu() {
-  if ($to_return = menu_get_object('quiz_type_access', 4)) {
-    return $to_return;
-  }
-  //TODO: FIX it. This seems to return NULL in feedback page.
-  $node = menu_get_object();
-  return (is_object($node) && $node->type == 'quiz') ? $node : FALSE;
-}
-
-/**
  * Finds out if a quiz has been answered or not.
  *
  * @return
  *   TRUE if there exists answers to the current question.
  */
-function quiz_has_been_answered($node) {
-  if (!isset($node->nid)) {
-    return FALSE;
-  }
-  $query = db_select('quiz_node_results', 'qnr');
-  $query->addField('qnr', 'result_id');
-  $query->condition('nid', $node->nid);
-  $query->condition('vid', $node->vid);
-  $query->range(0, 1);
-  return $query->execute()->rowCount() > 0;
-}
-
-/**
- * If a quiz is saved as not randomized we should make sure all random questions
- * are converted to always.
- *
- * @param $node
- *   Quiz node.
- */
-function _quiz_check_num_random(&$node) {
-  if ($node->randomization == 2) {
-    return;
-  }
-  db_delete('quiz_node_relationship')
-    ->condition('question_status', QUESTION_RANDOM)
-    ->condition('parent_vid', $node->vid)
-    ->execute();
-}
-
-/**
- * If a quiz is saved with random categories we should make sure all questions
- * are removed from the quiz
- *
- * @param $node
- *   Quiz node.
- */
-function _quiz_check_num_always(&$node) {
-  if ($node->randomization != 3) {
-    return;
-  }
-  db_delete('quiz_node_relationship')
-    ->condition('parent_vid', $node->vid)
-    ->execute();
-}
-
-/**
- * Checks if the user has access to save score for his quiz.
- */
-function quiz_allow_score_access($quiz, $account = NULL) {
-  global $user;
-
-  if ($account == NULL) {
-    $account = $user;
-  }
-
-  if (user_access('score any quiz', $account)) {
-    return true;
-  }
-
-  return user_access('score own quiz', $account) && ($quiz->uid == $account->uid);
-}
-
-/**
- * Determine who should have access to the My results tab.
- */
-function _quiz_user_results_access($user_id) {
-  global $user;
-  return ($user_id == $user->uid && user_access('view own quiz results')) || user_access('view any quiz results');
-}
-
-/**
- * Copies questions when a quiz is translated.
- *
- * @param $node
- *   The new translated quiz node.
- */
-function quiz_copy_questions($node) {
-  // Find original questions.
-  $query = db_query('SELECT child_nid, child_vid, question_status, weight, max_score, auto_update_max_score
-    FROM {quiz_node_relationship}
-    WHERE parent_vid = :parent_vid', array(':parent_vid' => $node->translation_source->vid));
-  foreach ($query as $res_o) {
-    $original_question = node_load($res_o->child_nid);
-
-    // Set variables we can't or won't carry with us to the translated node to
-    // NULL.
-    $original_question->nid = $original_question->vid = $original_question->created = $original_question->changed = NULL;
-    $original_question->revision_timestamp = $original_question->menu = $original_question->path = NULL;
-    $original_question->files = array();
-    if (isset($original_question->book['mlid'])) {
-      $original_question->book['mlid'] = NULL;
-    }
-
-    // Set the correct language.
-    $original_question->language = $node->language;
-
-    // Save the node.
-    node_save($original_question);
-
-    // Save the relationship between the new question and the quiz.
-    db_insert('quiz_node_relationship')
-      ->fields(array(
-        'parent_nid' => $node->nid,
-        'parent_vid' => $node->vid,
-        'child_nid' => $original_question->nid,
-        'child_vid' => $original_question->vid,
-        'question_status' => $res_o->question_status,
-        'weight' => $res_o->weight,
-        'max_score' => $res_o->max_score,
-        'auto_update_max_score' => $res_o->auto_update_max_score,
-      ))
-      ->execute();
-  }
-}
-
-/**
- * Deletes results for a quiz according to the keep results setting
- *
- * @param $quiz
- *  The quiz node to be maintained
- * @param $result_id
- *  The result id of the latest result for the current user
- * @return
- *  TRUE if results where deleted.
- */
-function _quiz_maintain_results($quiz, $result_id) {
-  global $user;
-
-  // Do not delete results for anonymous users
-  if ($user->uid == 0) {
-    return;
-  }
-
-  switch ($quiz->keep_results) {
-    case QUIZ_KEEP_ALL:
-      return FALSE;
-    case QUIZ_KEEP_BEST:
-      $best_result_id = db_query('SELECT result_id FROM {quiz_node_results}
-          WHERE nid = :nid AND uid = :uid AND is_evaluated = :is_evaluated
-          ORDER BY score DESC', array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1)
-        )
-        ->fetchField();
-      if (!$best_result_id) {
-        return;
-      }
-      $res = db_query('SELECT result_id FROM {quiz_node_results}
-          WHERE nid = :nid AND uid = :uid AND result_id != :best_rid AND is_evaluated = :is_evaluated', array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1, ':best_rid' => $best_result_id)
-      );
-      $result_ids = array();
-      while ($result_id2 = $res->fetchField()) {
-        $result_ids[] = $result_id2;
-      }
-      entity_delete_multiple('quiz_result', $result_ids);
-      return !empty($result_ids);
-    case QUIZ_KEEP_LATEST:
-      $res = db_query('SELECT result_id FROM {quiz_node_results}
-              WHERE nid = :nid AND uid = :uid AND is_evaluated = :is_evaluated AND result_id != :result_id', array(':nid' => $quiz->nid, ':uid' => $user->uid, ':is_evaluated' => 1, ':result_id' => $result_id));
-      $result_ids = array();
-      while ($result_id2 = $res->fetchField()) {
-        $result_ids[] = $result_id2;
-      }
-      entity_delete_multiple('quiz_result', $result_ids);
-      return !empty($result_ids);
-  }
-}
-
-/**
- * Delete quiz results by ID.
- *
- * @param $result_ids
- *   Result ids for the results to be deleted.
- */
-function quiz_delete_results($result_ids) {
-  entity_delete_multiple('quiz_result', $result_ids);
+function quiz_has_been_answered($quiz) {
+  return quiz()->getQuizHelper()->isAnswered($quiz);
 }
 
 /**
- * Implements hook_quiz_result_delete().
+ * Load a specific result answer.
  */
-function quiz_quiz_result_delete($quiz_result) {
-  // Fire the question types' implementations of question attempts deletion.
-  $sql = 'SELECT result_id, question_nid, question_vid FROM {quiz_node_results_answers}
-          WHERE result_id = (:result_id)';
-  $result = db_query($sql, array(':result_id' => $quiz_result->result_id));
-  foreach ($result as $record) {
-    quiz_question_delete_result($record->result_id, $record->question_nid, $record->question_vid);
-  }
-
-  // Delete Quiz's records of any attempts at a question.
-  db_delete('quiz_node_results_answers')
-    ->condition('result_id', $quiz_result->result_id)
-    ->execute();
+function quiz_result_answer_load($result_id, $question_nid, $question_vid) {
+  return quiz()->getQuizHelper()->getResultHelper()->loadAnswerResult($result_id, $question_nid, $question_vid);
 }
 
 /**
- * Returns the users default settings.
- *
- * @param $node
- *   Quiz node.
- * @param $uid
- *   (optional) The uid of the user to get the settings for. Defaults to the
- *   current user (NULL).
- *
- * @return
- *   An array of settings. The array is empty in case no settings are available.
+ * Action to add questions to the current quiz.
  */
-function quiz_get_defaults() {
-  global $user;
-
-  $entity = entity_load('quiz', FALSE, array(
-    'uid' => $user->uid,
-    'nid' => 0,
-    'vid' => 0,
-  ), TRUE);
-
-  if (count($entity)) {
-    // We found user defaults.
-    $defaults = reset($entity);
-    unset($defaults->nid, $defaults->uid, $defaults->vid);
-    return $defaults;
-  }
-
-  $entity = entity_load('quiz', FALSE, array(
-    'uid' => 0,
-    'nid' => 0,
-    'vid' => 0,
-  ), TRUE);
-
-  if (count($entity)) {
-    // Found global defaults.
-    $defaults = reset($entity);
-    unset($defaults->nid, $defaults->uid, $defaults->vid);
-    return $defaults;
-  }
-
-  // No defaults set yet.
-  return _quiz_get_node_defaults();
+function quiz_add_question_to_quiz($question) {
+  $quiz = quiz_entity_single_load(__quiz_get_context_id());
+  return quiz()->getQuizHelper()->addQuestion($quiz, $question);
 }
 
 /**
- * Get the number of compulsory questions for a quiz.
+ * Updates the max_score property on the specified quizzes
  *
- * @param $nid
- *   Quiz node id.
- * @param $vid
- *   Quiz node version id.
- * @return
- *   Number of compulsory questions.
+ * @param $vids
+ *  Array with the vid's of the quizzes to update
  */
-function _quiz_get_num_always_questions($vid) {
-  return db_query('SELECT COUNT(*) FROM {quiz_node_relationship} qnr
-          JOIN {node} n ON n.nid = qnr.child_nid
-          WHERE n.status=1 AND qnr.parent_vid = :parent_vid AND qnr.question_status = :question_status', array(':parent_vid' => $vid, ':question_status' => QUESTION_ALWAYS))->fetchField();
+function quiz_update_max_score_properties($vids) {
+  return quiz()->getQuizHelper()->updateMaxScoreProperties($vids);
 }
 
 /**
- * Modifies the format fieldset.
- *
- * Adds a class to all the format fieldsets and removes unwanted strings.
- * A javascript is added by the forms theme function to make sure all format
- * selectors follows the body field format selector.
- * Used when there are multiple format selectors on one page.
+ * @param string $question_type
  *
- * Could be a deprecated function in d7
- *
- * @param $format
- *   The format fieldset.
+ * @return string
+ *   Name of module matching the question type, as given by quiz_question_info()
+ *   hook.
  */
-function _quiz_format_mod(&$format) {
-  $format['#attributes']['class'] = array('quiz-filter');
-  if (isset($format['format'])) {
-    $format['format']['guidelines']['#value'] = ' ';
-    foreach ($format as $key => $value) {
-      if (is_numeric($key)) {
-        $format[$key]['#value'] = ' ';
-      }
-    }
-  }
+function quiz_question_module_for_type($question_type) {
+  return quiz()->getQuizHelper()->getQuestionModuleFromType($question_type);
 }
 
 /**
- * Limit the year options to the years 1970 - 2030 for form items of type date.
- *
- * Some systems don't support all the dates the forms api lets you choose from.
- * This function limits the options to dates most systems support.
- *
- * @param $form_element
- *   Form element of type date.
+ * Check if the question has already been answered in the requested attempt.
  *
- * @return
- *   Form element with a more limited set of years to choose from.
+ * @return bool
+ *   TRUE if the question has been answered in the attempt.
  */
-function _quiz_limit_year_options($form_element) {
-  $form_element['year']['#options'] = drupal_map_assoc(range(1970, 2030));
-  return $form_element;
+function quiz_result_is_question_answered($result, $question) {
+  $answer = quiz_result_answer_load($result->result_id, $question->nid, $question->vid);
+  return !empty($answer->answer_timestamp);
 }
 
-/**
- * Format a number of seconds to a hh:mm:ss format.
- *
- * @param $time_in_sec
- *   Integers time in seconds.
- *
- * @return
- *   String time in min : sec format.
- */
-function _quiz_format_duration($time_in_sec) {
-  $hours = intval($time_in_sec / 3600);
-  $min = intval(($time_in_sec - $hours * 3600) / 60);
-  $sec = $time_in_sec % 60;
-  if (strlen($min) == 1) {
-    $min = '0' . $min;
-  }
-  if (strlen($sec) == 1) {
-    $sec = '0' . $sec;
-  }
-  return "$hours:$min:$sec";
-}
+// ---------------------
+//  Internal functions
+// ---------------------
 
 /**
  * Helper function used when validating integers.
@@ -3805,421 +218,33 @@ function _quiz_format_duration($time_in_sec) {
  */
 function _quiz_is_int($value, $min = 1, $max = NULL) {
   $to_return = ((string) $value === (string) (int) $value);
-  // $value is not an integer.
-  if (!$to_return) {
-    return FALSE;
-  }
-  // $value is too small.
-  if ($value < $min) {
-    return FALSE;
-  }
-  // $value is too big.
-  if (isset($max)) {
-    if ($value > $max) {
-      return FALSE;
-    }
-  }
-  // $value is an integer in the allowed range.
-  return TRUE;
-}
-
-/**
- * Helper function used when validating plain text.
- *
- * @param $value
- *   The value to be validated.
- *
- * @return
- *   TRUE if plain text FALSE otherwise.
- */
-function _quiz_is_plain($value) {
-  return ($value === check_plain($value));
-}
-
-/**
- * Helper function used when figuring out if a textfield or textarea is empty.
- *
- * Solves a problem with some wysiwyg editors inserting spaces and tags without content.
- *
- * @param $html
- *   The html to evaluate
- *
- * @return
- *   TRUE if the field is empty(can still be tags there) false otherwise.
- */
-function _quiz_is_empty_html($html) {
-  return drupal_strlen(trim(str_replace('&nbsp;', '', strip_tags($html, '<img><object><embed>')))) == 0;
-}
-
-/**
- * Show the finish button?
- */
-function _quiz_show_finish_button($quiz) {
-  $quiz_result = quiz_result_load($_SESSION['quiz'][$quiz->nid]['result_id']);
-  $current = $_SESSION['quiz'][$quiz->nid]['current'];
-  foreach ($quiz_result->layout as $idx => $question) {
-    if ($question['type'] == 'quiz_page') {
-      if ($current == $idx) {
-        // Found a page that we are on
-        $in_page = TRUE;
-        $last_page = TRUE;
-      }
-      else {
-        // Found a quiz page that we are not on.
-        $last_page = FALSE;
-      }
-    }
-    else if (empty($question['qnr_pid'])) {
-      // A question without a parent showed up.
-      $in_page = FALSE;
-      $last_page = FALSE;
-    }
-  }
-
-  return $last_page || !isset($quiz_result->layout[$_SESSION['quiz'][$quiz->nid]['current'] + 1]);
-}
-
-/**
- * Implements hook_entity_info().
- */
-function quiz_entity_info() {
-  return array(
-    'quiz_result' => array(
-      'label' => t('Quiz result'),
-      'controller class' => 'EntityAPIController',
-      'base table' => 'quiz_node_results',
-      'entity keys' => array(
-        'id' => 'result_id',
-      ),
-      'views controller class' => 'EntityDefaultViewsController',
-    ),
-    'quiz_result_answer' => array(
-      'label' => t('Quiz result answer'),
-      'controller class' => 'EntityAPIController',
-      'base table' => 'quiz_node_results_answers',
-      'entity keys' => array(
-        'id' => 'result_answer_id',
-      ),
-      'views controller class' => 'EntityDefaultViewsController',
-    ),
-    'quiz_question_relationship' => array(
-      'label' => t('Quiz question relationship'),
-      'controller class' => 'EntityAPIController',
-      'base table' => 'quiz_node_relationship',
-      'entity keys' => array(
-        'id' => 'qnr_id',
-      ),
-      'views controller class' => 'EntityDefaultViewsController',
-    ),
-    'quiz' => array(
-      'label' => t('Quiz properties'),
-      'controller class' => 'EntityAPIController',
-      'base table' => 'quiz_node_properties',
-      'entity keys' => array(
-        'id' => 'qnp_id',
-      ),
-      'views controller class' => 'EntityDefaultViewsController',
-    ),
-  );
-}
-
-/**
- * Shortcut to entity_load_single().
- */
-function quiz_result_load($result_id) {
-  return entity_load_single('quiz_result', $result_id);
-}
 
-/**
- * Menu check to see if user has access to take this question.
- */
-function quiz_access_question($quiz, $question_number) {
-  if (!$question_number) {
+  if (!$to_return) { // $value is not an integer.
     return FALSE;
   }
 
-  if ($quiz->allow_jumping) {
-    // Access to go to any question. Yay.
-    return TRUE;
-  }
-
-  $result_id = $_SESSION['quiz'][$quiz->nid]['result_id'];
-  $quiz_result = quiz_result_load($result_id);
-  $question_index = $question_number;
-  $qinfo_last = $question_number == 1 ? NULL : $quiz_result->layout[$question_index - 1];
-  $qinfo = $quiz_result->layout[$question_index];
-  $question_node = node_load($qinfo['nid'], $qinfo['vid']);
-  $question_node_last = node_load($qinfo_last['nid'], $qinfo_last['vid']);
-
-  if (!$quiz->backwards_navigation) {
-    // No backwards navigation.
-    if (quiz_result_is_question_answered($quiz_result, $question_node)) {
-      // Already have an answer for the requested question.
-      return FALSE;
-    }
-  }
-
-  // Enforce normal navigation.
-  if ($question_number == 1 || quiz_result_is_question_answered($quiz_result, $question_node_last)) {
-    //  Previous answer was submitted or this is the first question.
-    return TRUE;
-  }
-}
-
-/**
- * Show feedback for a question response.
- */
-function quiz_take_question_feedback($quiz, $question_number) {
-  if (empty($_SESSION['quiz'][$quiz->nid]['result_id'])) {
-    $result_id = $_SESSION['quiz']['temp']['result_id'];
-  }
-  else {
-    $result_id = $_SESSION['quiz'][$quiz->nid]['result_id'];
-  }
-  $quiz_result = quiz_result_load($result_id);
-  $question_node = node_load($quiz_result->layout[$question_number]['nid']);
-  $feedback = quiz_question_feedback(node_load(arg(1)), $question_node);
-  return $feedback;
-}
-
-/**
- * Submit handler to go to the next question from the question feedback.
- */
-function quiz_take_question_feedback_submit($form, &$form_state) {
-  $quiz = node_load(arg(1));
-  $form_state['redirect'] = "node/{$quiz->nid}/take/" . $_SESSION['quiz'][$quiz->nid]['current'];
-}
-
-/**
- * Submit handler to go to the quiz results from the last question's feedback.
- */
-function quiz_take_question_feedback_end_submit($form, &$form_state) {
-  $quiz = node_load(arg(1));
-  $result_id = $_SESSION['quiz']['temp']['result_id'];
-  $form_state['redirect'] = "node/{$quiz->nid}/quiz-results/$result_id/view";
-}
-
-/**
- * Implements hook_entity_property_info_alter().
- *
- * Define our special schema fields and relationships.
- */
-function quiz_entity_property_info_alter(&$info) {
-  $info['quiz_question_relationship']['properties']['child_nid']['type'] = 'node';
-  $info['quiz_question_relationship']['properties']['child_vid']['type'] = 'integer';
-  $info['quiz_question_relationship']['properties']['parent_nid']['type'] = 'node';
-  $info['quiz_question_relationship']['properties']['parent_vid']['type'] = 'integer';
-
-  $info['quiz_result_answer']['properties']['result_id']['type'] = 'quiz_result';
-  $info['quiz_result_answer']['properties']['question_nid']['type'] = 'node';
-  $info['quiz_result_answer']['properties']['is_correct']['type'] = 'boolean';
-  $info['quiz_result_answer']['properties']['is_skipped']['type'] = 'boolean';
-  $info['quiz_result_answer']['properties']['is_doubtful']['type'] = 'boolean';
-  $info['quiz_result_answer']['properties']['answer_timestamp']['type'] = 'date';
-
-  $info['quiz_result']['properties']['nid']['type'] = 'node';
-  $info['quiz_result']['properties']['vid']['type'] = 'integer';
-  $info['quiz_result']['properties']['uid']['type'] = 'user';
-  $info['quiz_result']['properties']['time_start']['label'] = 'Date started';
-  $info['quiz_result']['properties']['time_start']['type'] = 'date';
-  $info['quiz_result']['properties']['time_end']['label'] = 'Date finished';
-  $info['quiz_result']['properties']['time_end']['type'] = 'date';
-  $info['quiz_result']['properties']['released']['type'] = 'date';
-  $info['quiz_result']['properties']['score']['label'] = 'Score';
-  $info['quiz_result']['properties']['score']['type'] = 'integer';
-  $info['quiz_result']['properties']['is_invalid']['label'] = 'Invalid';
-  $info['quiz_result']['properties']['is_invalid']['type'] = 'boolean';
-  $info['quiz_result']['properties']['is_evaluated']['label'] = 'Evaluated';
-  $info['quiz_result']['properties']['is_evaluated']['type'] = 'boolean';
-  $info['quiz_result']['properties']['time_left']['type'] = 'duration';
-
-  $info['quiz']['properties']['quiz_open']['label'] = 'Open date';
-  $info['quiz']['properties']['quiz_open']['type'] = 'date';
-  $info['quiz']['properties']['quiz_close']['label'] = 'Close date';
-  $info['quiz']['properties']['quiz_close']['type'] = 'date';
-}
-
-function quiz_jumper_form($form, $form_state, $questions, $current) {
-  $form['question_number'] = array(
-    '#type' => 'select',
-    '#options' => $questions,
-    '#default_value' => $current,
-  );
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Jump'),
-    '#attributes' => array('class' => array('js-hide')),
-  );
-  return $form;
-}
-
-function quiz_jumper_form_submit($form, &$form_state) {
-  $quiz = node_load(arg(1));
-  quiz_question_goto($quiz, $form_state['values']['question_number'] - 1);
-  $form_state['redirect'] = "node/{$quiz->nid}/take/" . $form_state['values']['question_number'];
-}
-
-/**
- * Menu access check for question feedback.
- */
-function quiz_question_feedback_access($quiz, $question_number) {
-  if ($question_number >= 1) {
-    if (array_filter($quiz->review_options['question'])) {
-      $question_index = $question_number;
-      if (empty($_SESSION['quiz'][$quiz->nid]['result_id'])) {
-        $result_id = $_SESSION['quiz']['temp']['result_id'];
-      }
-      else {
-        $result_id = $_SESSION['quiz'][$quiz->nid]['result_id'];
-      }
-      $quiz_result = quiz_result_load($result_id);
-      $qinfo = $quiz_result->layout[$question_index];
-
-      if ($qra = quiz_result_answer_load($result_id, $qinfo['nid'], $qinfo['vid'])) {
-        return TRUE;
-      }
-    }
-  }
-}
-
-/**
- * Load a specific result answer.
- */
-function quiz_result_answer_load($result_id, $nid, $vid) {
-  $sql = 'SELECT * from {quiz_node_results_answers} WHERE result_id = :result_id AND question_nid = :nid AND question_vid = :vid';
-  $result = db_query($sql, array(':result_id' => $result_id, ':nid' => $nid, ':vid' => $vid));
-  if ($row = $result->fetch()) {
-    return entity_load_single('quiz_result_answer', $row->result_answer_id);
-  }
-}
-
-/**
- * Action to add questions to the current quiz.
- */
-function quiz_add_question_to_quiz($question) {
-  $quiz = node_load(arg(1));
-  $quiz_questions = quiz_get_questions($quiz->nid, $quiz->vid);
-  // Do not add a question if it's already been added (selected in an earlier checkbox)
-  foreach ($quiz_questions as $q) {
-    if ($question->vid == $q->vid) {
-      return FALSE;
-    }
-  }
-  // Otherwise let's add a relationship!
-  $question->quiz_nid = $quiz->nid;
-  $question->quiz_vid = $quiz->vid;
-  _quiz_question_get_instance($question)->saveRelationships();
-  quiz_update_max_score_properties(array($quiz->vid));
-}
-
-/**
- * Implements hook_action_info().
- */
-function quiz_action_info() {
-  $actions = array();
-
-  $actions['quiz_add_question_to_quiz'] = array(
-    'type' => 'node',
-    'label' => t('Add questions to @quiz', array('@quiz' => QUIZ_NAME)),
-    'configurable' => FALSE,
-    'vbo_configurable' => FALSE,
-  );
-
-  return $actions;
-}
-
-/**
- * Workaround for menu_translate bug.
- */
-function question_number_to_arg() {
-  return 0;
-}
-
-/**
- * Can the quiz taker view the requested review?
- *
- * There's a workaround in here: @kludge
- *
- * When review for the question is enabled, and it is the last question,
- * technically it is the end of the quiz, and the "end of quiz" review settings
- * apply. So we check to make sure that we are in question taking and the
- * feedback is viewed within 5 seconds of completing the question/quiz.
- */
-function quiz_feedback_can_review($option, $quiz_result) {
-  $quiz = node_load($quiz_result->nid, $quiz_result->vid);
-
-  // Check what context the result is in.
-  if ($quiz_result->time_end && arg(2) != 'take') {
-    // Quiz is over. Pull from the "at quiz end" settings.
-    return !empty($quiz->review_options['end'][$option]);
+  if ($value < $min) { // $value is too small.
+    return FALSE;
   }
 
-  if (!$quiz_result->time_end || $quiz_result->time_end >= REQUEST_TIME - 5) {
-    // Quiz ongoing. Pull from the "after question" settings.
-    return !empty($quiz->review_options['question'][$option]);
+  if (isset($max) && $value > $max) { // $value is too big.
+    return FALSE;
   }
-}
-
-/**
- * Get the feedback options for Quizzes.
- */
-function quiz_get_feedback_options() {
-  $feedback_options = array(
-    'attempt' => "Attempt",
-    'correct' => "Whether correct",
-    'score' => "Score",
-    'answer_feedback' => 'Answer feedback',
-    'question_feedback' => 'Question feedback',
-    'solution' => "Correct answer",
-    'quiz_feedback' => t("@quiz feedback", array('@quiz' => QUIZ_NAME)),
-  );
 
-  drupal_alter('quiz_feedback_options', $feedback_options);
-
-  return $feedback_options;
+  return TRUE; // $value is an integer in the allowed range.
 }
 
 /**
- * Implements hook_quiz_result_load().
+ * Get the quiz name variable and set it as a constant so we don't have to keep
+ * calling it in every function.
  *
- * Attach the layout which previously used to be stored on the result.
+ * @return
+ *   Quiz name variable.
  */
-function quiz_quiz_result_load($quiz_results) {
-  foreach ($quiz_results as $quiz_result) {
-    $layout = entity_load('quiz_result_answer', FALSE, array('result_id' => $quiz_result->result_id));
-    foreach ($layout as $question) {
-      // @kludge
-      // This is bulky but now we have to manually find the type and parents of
-      // the question. This is the only information that is not stored in the
-      // quiz attempt. We reference back to the node relationships for this
-      // current version to get the hieararchy.
-      $sql = "SELECT n.type, qnr_id, qnr_pid FROM {quiz_node_results} qr
-        INNER JOIN {quiz_node_relationship} qnr on (qr.nid = qnr.parent_nid and qr.vid = qnr.parent_vid)
-        INNER JOIN {node} n ON (qnr.child_nid = n.nid and qnr.child_vid = n.vid)
-        WHERE qr.result_id = :result_id and n.vid = :vid";
-      $result = db_query($sql, array(':result_id' => $quiz_result->result_id, ':vid' => $question->question_vid));
-      $extra = $result->fetch();
-
-      $quiz_result->layout[$question->number] = array(
-        'display_number' => $question->number,
-        'nid' => $question->question_nid,
-        'vid' => $question->question_vid,
-        'number' => $question->number,
-        'type' => $extra->type,
-        'qnr_id' => $extra->qnr_id,
-        'qnr_pid' => $extra->qnr_pid,
-      );
-    }
-  }
+function _quiz_get_quiz_name() {
+  return t(variable_get('quiz_name', 'Quiz'));
 }
 
-/**
- * Check if the question has already been answered in the requested attempt.
- *
- * @return boolean
- *   TRUE if the question has been answered in the attempt.
- */
-function quiz_result_is_question_answered($quiz_result, $question) {
-  $qra = quiz_result_answer_load($quiz_result->result_id, $question->nid, $question->vid);
-  return !empty($qra->answer_timestamp);
+function __quiz_get_context_id() {
+  return (int) arg(1);
 }
diff --git a/quiz.pages.inc b/quiz.pages.inc
index 964ef19..db9c597 100644
--- a/quiz.pages.inc
+++ b/quiz.pages.inc
@@ -1,249 +1,190 @@
 <?php
 
-/**
- * User pages.
- * @file
- */
+use Drupal\quiz\Controller\Admin\QuizAdminSettingsController;
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Form\QuizAdminEntityForm;
+use Drupal\quiz\Form\QuizAnsweringForm;
+use Drupal\quiz\Form\QuizCategorizedForm;
+use Drupal\quiz\Form\QuizEntityForm;
+use Drupal\quiz\Form\QuizQuestionsForm;
+use Drupal\quiz\Form\QuizReportForm;
+use Drupal\quiz\Form\QuizTypeForm;
+use Drupal\quiz_question\QuizQuestion;
 
 /**
- * Show result page for a given result id
- *
- * @param $result_id
- *  Result id
+ * Form for /admin/quiz/settings/config.
  */
-function quiz_user_results($result_id) {
-  $result = quiz_result_load($result_id);
-
-  if ($result->nid) {
-    $quiz = node_load($result->nid, $result->vid);
-    $current_quiz = node_load($result->nid);
-    $questions = _quiz_get_answers($quiz, $result_id);
-    $score = quiz_calculate_score($quiz, $result_id);
-    $summary = _quiz_get_summary_text($quiz, $score);
-    $data = array(
-      'quiz' => $quiz,
-      'questions' => $questions,
-      'score' => $score,
-      'summary' => $summary,
-      'result_id' => $result_id,
-      'account' => user_load($result->uid),
-    );
-    if (user_access('view own quiz results') || (!empty($current_quiz->review_options['end']) && array_filter($current_quiz->review_options['end']))) {
-      // User can view own quiz results OR the current quiz has "display solution".
-      return theme('quiz_result', $data);
-    }
-    else {
-      // User cannot view own results or show solution. Show summary.
-      return theme('quiz_result', $data);
-    }
-  }
+function quiz_admin_settings_form($form, $form_state) {
+  $obj = new QuizAdminSettingsController();
+  return $obj->getForm($form, $form_state);
 }
 
-/**
- * Form for showing feedback, and for editing the feedback if necessary...
- *
- * @param $form_state
- *   FAPI form state(array)
- * @param $questions
- *   array of questions to inclide in the report
- * @return $form
- *   FAPI form array
- */
-function quiz_report_form($form, $form_state, $questions) {
-  $form = array();
-  // The submit button is only shown if one or more of the questions has input elements
-  $show_submit = FALSE;
-  foreach ($questions as $question) {
-    $module = quiz_question_module_for_type($question->type);
-    if (!$module) {
-      return array();
-    }
-    $function = $module . '_report_form';
-    $form_to_add = $function($question);
-    if (isset($form_to_add['submit'])) {
-      $show_submit = TRUE;
-    }
-    $form_to_add['#element_validate'][] = 'quiz_report_form_element_validate';
-    $form[] = $form_to_add;
-  }
-  $form['#tree'] = TRUE;
-  if ($show_submit) {
-    $form['submit'] = array(
-      '#type' => 'submit',
-      '#submit' => array('quiz_report_form_submit'),
-      '#value' => t('Save score'),
-    );
-  }
+function quiz_admin_settings_form_validate($form, $form_state) {
+  $obj = new QuizAdminSettingsController();
+  return $obj->validate($form, $form_state);
+}
 
-  if (arg(4) == 'feedback') {
-    // @todo figure something better than args.
-    $quiz = node_load(arg(1));
-    if (empty($_SESSION['quiz'][$quiz->nid])) {
-      // Quiz is done.
-      $form['finish'] = array(
-        '#type' => 'submit',
-        '#submit' => array('quiz_take_question_feedback_end_submit'),
-        '#value' => t('Finish'),
-      );
-    }
-    else {
-      $form['next'] = array(
-        '#type' => 'submit',
-        '#submit' => array('quiz_take_question_feedback_submit'),
-        '#value' => t('Next question'),
-      );
-    }
-  }
+function quiz_admin_settings_form_submit($form, $form_state) {
+  $obj = new QuizAdminSettingsController();
+  return $obj->submit($form, $form_state);
+}
 
-  return $form;
+function quiz_admin_entity_form($form, $form_state) {
+  $obj = new QuizAdminEntityForm();
+  return $obj->getForm($form, $form_state);
+}
+
+function quiz_admin_entity_form_validate($form, $form_state) {
+  $obj = new QuizAdminEntityForm();
+  return $obj->validateForm($form, $form_state);
+}
+
+function quiz_admin_entity_form_submit($form, $form_state) {
+  $obj = new QuizAdminEntityForm();
+  return $obj->submitForm($form, $form_state);
 }
 
 /**
- * Validate a single question sub-form.
+ * Define quiz-type editing form.
  */
-function quiz_report_form_element_validate(&$element, &$form_state) {
-  $question = node_load($element['nid']['#value'], $element['vid']['#value']);
-  $quizQuestionResponse = _quiz_question_response_get_instance($element['result_id']['#value'], $question);
-  $quizQuestionResponse->getReportFormValidate($element, $form_state);
+function quiz_type_form($form, &$form_state, $quiz_type, $op = 'edit') {
+  $obj = new QuizTypeForm();
+  return $obj->get($form, $form_state, $quiz_type, $op);
 }
 
 /**
- * Submit the report form
+ * Define quiz-entity editing form.
  */
-function quiz_report_form_submit($form, &$form_state) {
-  /* We go through the form state values and submit all
-   * questiontypes with validation functions declared.
-   */
-  global $user;
-  foreach ($form_state['values'] as $key => $q_values) {
-    // Questions has numeric keys in the report form
-    if (!is_numeric($key)) {
-      continue;
-    }
-    // Questions store the name of the validation function with the key 'submit'
-    if (!isset($q_values['submit'])) {
-      continue;
-    }
-    // The submit function must exist
-    if (!function_exists($q_values['submit'])) {
-      continue;
-    }
+function quiz_entity_form($form, &$form_state, $quiz, $op = 'edit') {
+  $obj = new QuizEntityForm($quiz);
+  return $obj->get($form, $form_state, $op);
+}
 
-    // Load the quiz
-    if (!isset($quiz)) {
-      $result = db_query('SELECT nid, uid, vid FROM {quiz_node_results} WHERE result_id = :result_id', array(':result_id' => $q_values['result_id']))->fetchObject();
-      $quiz = node_load($result->nid, $result->vid);
-      $result_id = $q_values['result_id'];
-    }
+function quiz_entity_form_validate($form, &$form_state) {
+  $quiz = $form_state['quiz_entity'];
+  $obj = new QuizEntityForm($quiz);
+  return $obj->validate($form, $form_state);
+}
 
-    $q_values['quiz'] = $quiz;
+function quiz_entity_form_submit($form, &$form_state) {
+  $quiz = $form_state['quiz_entity'];
+  $obj = new QuizEntityForm($quiz);
+  return $obj->submit($form, $form_state);
+}
 
-    // We call the submit function provided by the question
-    call_user_func($q_values['submit'], $q_values);
-  }
-  // Scores may have been changed. We take the necessary actions
-  quiz_update_total_score_fast($result_id, $quiz->vid);
-  $changed = db_update('quiz_node_results')
-    ->fields(array(
-      'is_evaluated' => 1,
-    ))
-    ->condition('result_id', $result_id)
-    ->execute();
-  $results_got_deleted = _quiz_maintain_results($quiz, $result_id);
-
-  // A message saying the quiz is unscored has already been set. We unset it here...
-  if ($changed > 0) {
-    _quiz_remove_unscored_message();
-  }
+function quiz_questions_form($form, $form_state, $quiz) {
+  $obj = new QuizQuestionsForm();
+  $array = $obj->formGet($form, $form_state, $quiz);
+  $array['#validate'][] = 'quiz_questions_form_validate';
+  return $array;
+}
 
-  // Notify the user if results got deleted as a result of him scoring an answer.
-  $add = $quiz->keep_results == QUIZ_KEEP_BEST && $results_got_deleted ? ' ' . t('Note that this @quiz is set to only keep each users best answer.', array('@quiz' => QUIZ_NAME)) : '';
+function quiz_questions_form_validate($form, $form_state) {
+  $obj = new QuizQuestionsForm();
+  return $obj->formValidate($form, $form_state);
+}
 
-  $score_data = quiz_get_score_array($result_id, $quiz->vid, TRUE);
+function quiz_questions_form_submit($form, $form_state) {
+  $obj = new QuizQuestionsForm();
+  return $obj->formSubmit($form, $form_state);
+}
 
-  module_invoke_all('quiz_scored', $quiz, $score_data, $result_id);
+function quiz_categorized_form($form, $form_state, QuizEntity $quiz) {
+  $obj = new QuizCategorizedForm();
+  return $obj->getForm($form, $form_state, $quiz);
+}
 
-  drupal_set_message(t('The scoring data you provided has been saved.') . $add);
-  if (user_access('score taken quiz answer') && !user_access('view any quiz results')) {
-    if ($result && $result->uid == $user->uid) {
-      $form_state['redirect'] = 'node/' . $quiz->nid . '/quiz/results/' . $result_id;
-    }
-  }
+function quiz_categorized_form_validate($form, &$form_state) {
+  $obj = new QuizCategorizedForm();
+  return $obj->formValidate($form, $form_state);
+}
+
+function quiz_categorized_form_submit($form, &$form_state) {
+  $obj = new QuizCategorizedForm();
+  return $obj->formSubmit($form, $form_state);
 }
 
 /**
- * Helper function to remove the message saying the quiz haven't been scored
+ * Jumper form.
  */
-function _quiz_remove_unscored_message() {
-  if (!empty($_SESSION['messages']['warning'])) {
-    // Search for the message, and remove it if we find it.
-    foreach ($_SESSION['messages']['warning'] as $key => $val) {
-      if ($val == t('This @quiz has not been scored yet.', array('@quiz' => QUIZ_NAME))) {
-        unset($_SESSION['messages']['warning'][$key]);
-      }
-    }
-    // Clean up if the message array was left empty
-    if (empty($_SESSION['messages']['warning'])) {
-      unset($_SESSION['messages']['warning']);
-      if (empty($_SESSION['messages'])) {
-        unset($_SESSION['messages']);
-      }
-    }
+function quiz_jumper_form($form, $form_state, $questions, $current) {
+  $form['#attached']['js'][] = drupal_get_path('module', 'quiz') . '/misc/js/quiz.jumper.js';
+  $form['question_number'] = array(
+      '#type'          => 'select',
+      '#options'       => $questions,
+      '#default_value' => $current,
+  );
+  $form['submit'] = array(
+      '#type'       => 'submit',
+      '#value'      => t('Jump'),
+      '#attributes' => array('class' => array('js-hide')),
+  );
+  return $form;
+}
+
+function quiz_jumper_form_submit($form, &$form_state) {
+  $quiz = quiz_entity_single_load(__quiz_get_context_id());
+  quiz()
+    ->getQuizHelper()
+    ->getQuestionHelper()
+    ->redirect($quiz, $form_state['values']['question_number'] - 1);
+  $form_state['redirect'] = "quiz/" . $quiz->qid . "/take/" . $form_state['values']['question_number'];
+}
+
+function quiz_answer_form($form, $form_state, $quiz, $question, $page_number, $result) {
+  $obj = new QuizAnsweringForm($quiz, $question, $page_number, $result);
+  if (is_array($question) || ($question->type !== 'quiz_page')) {
+    $f_array = $obj->getForm($form, $form_state, is_array($question) ? $question : array($question));
   }
+  else {
+    $f_array = $obj->getForm($form, $form_state, QuizAnsweringForm::findPageQuestions($result, $question));
+  }
+  return $f_array;
 }
 
-/**
- * Updates the total score using only one mySql query.
- *
- * @param $result_id
- *  Result id
- * @param $quiz_vid
- *  Quiz node version id
- */
-function quiz_update_total_score_fast($result_id, $quiz_vid) {
-  $subq1 = db_select('quiz_node_results_answers', 'a');
-  $subq1
-    ->condition('a.result_id', $result_id)
-    ->addExpression('SUM(a.points_awarded)');
-  $res1 = $subq1->execute()->fetchField();
+function quiz_answer_form_validate($form, &$form_state) {
+  $quiz = $form['#quiz'];
+  $question = $form['#question'];
+  $page_number = $form['#page_number'];
+  $result = $form['#result'];
+  $obj = new QuizAnsweringForm($quiz, $question, $page_number, $result);
+  return $obj->formValidate($form, $form_state);
+}
 
-  $subq2 = db_select('quiz_node_properties', 'qnp');
-  $subq2
-    ->condition('qnp.vid', $quiz_vid)
-    ->addField('qnp', 'max_score');
-  $res2 = $subq2->execute()->fetchField();
+function quiz_answer_form_submit_back($form, &$form_state) {
+  $quiz = $form['#quiz'];
+  $question = $form['#question'];
+  $page_number = $form['#page_number'];
+  $result = $form['#result'];
+  $obj = new QuizAnsweringForm($quiz, $question, $page_number, $result);
+  return $obj->getSubmit()->formBackSubmit($form, $form_state);
+}
 
-  db_update('quiz_node_results')
-    ->expression('score', 'ROUND(100*(:res1/:res2))', array(':res1' => $res1, ':res2' => $res2))
-    ->condition('result_id', $result_id)
-    ->execute();
+function quiz_answer_form_submit_blank($form, &$form_state) {
+  $quiz = $form['#quiz'];
+  $question = $form['#question'];
+  $page_number = $form['#page_number'];
+  $result = $form['#result'];
+  $obj = new QuizAnsweringForm($quiz, $question, $page_number, $result);
+  return $obj->getSubmit()->formBlankSubmit($form, $form_state);
 }
 
-/**
- * Returns an array of score information for a quiz
- *
- * @param unknown_type $result_id
- * @param unknown_type $quiz_vid
- * @param unknown_type $is_evaluated
- */
-function quiz_get_score_array($result_id, $quiz_vid, $is_evaluated) {
-  $properties = db_query('SELECT max_score, number_of_random_questions
-          FROM {quiz_node_properties}
-          WHERE vid = :vid', array(':vid' => $quiz_vid))->fetchObject();
-  $total_score = db_query('SELECT SUM(points_awarded)
-          FROM {quiz_node_results_answers}
-          WHERE result_id = :result_id', array(':result_id' => $result_id))->fetchField();
+function quiz_answer_form_submit($form, &$form_state) {
+  $quiz = $form['#quiz'];
+  $question = $form['#question'];
+  $page_number = $form['#page_number'];
+  $result = $form['#result'];
+  $obj = new QuizAnsweringForm($quiz, $question, $page_number, $result);
+  return $obj->getSubmit()->formSubmit($form, $form_state);
+}
 
-  return array(
-    'question_count' => $properties->number_of_random_questions + _quiz_get_num_always_questions($quiz_vid),
-    'possible_score' => $properties->max_score,
-    'numeric_score' => $total_score,
-    'percentage_score' => ($properties->max_score == 0) ? 0 : round(($total_score * 100) / $properties->max_score),
-    'is_evaluated' => $is_evaluated,
-  );
+function quiz_question_element_validate(&$element, &$form_state) {
+  return QuizQuestion::elementValidate($element, $form_state);
 }
 
-// THEME FUNCTIONS
+function quiz_report_form_element_validate(&$element, &$form_state) {
+  return QuizReportForm::validateElement($element, $form_state);
+}
 
 /**
  * Pass the correct mark to the theme so that theme authors can use an image.
@@ -261,25 +202,24 @@ function theme_quiz_answer_result($variables) {
  * @param $question_number
  *  The position of the current question in the sessions' array.
  * @param $num_of_question
- *  The number of questions for this quiz as returned by quiz_get_number_of_questions().
+ *  The number of questions for the quiz.
  * @return
  *  Themed html.
  *
  * @ingroup themeable
  */
 function theme_quiz_progress($variables) {
-  drupal_add_js(drupal_get_path('module', 'quiz') . '/js/quiz.jumper.js');
   $output = '';
   $output .= '<div id="quiz-progress">';
   $text = 'Page <span id="quiz-question-number">!x</span> of <span id="quiz-num-questions">@y</span>';
-
   if ($variables['allow_jumping']) {
     // Show jump form.
     if ($variables['pager']) {
       $output .= theme('quiz_pager', array(
-        'siblings' => variable_get('quiz_pager_siblings', 5),
-        'current' => $variables['current'],
-        'total' => count($variables['questions']),
+          'quiz'     => $variables['quiz'],
+          'siblings' => variable_get('quiz_pager_siblings', 5),
+          'current'  => $variables['current'],
+          'total'    => count($variables['questions']),
       ));
     }
     else {
@@ -314,72 +254,52 @@ function theme_quiz_no_feedback() {
 
 /**
  * Theme the stats on the views page
- *
- * @param $node
- *   The quiz node
  */
 function theme_quiz_view_stats($variables) {
-  $node = $variables['node'];
+  $quiz = $variables['quiz'];
+
   // Fetch data
-  $stats = array(
-    array(
-      'title' => t('Questions'),
-      'data' => $node->number_of_questions,
-    ),
-  );
-  if ($node->show_attempt_stats) {
-    $takes = $node->takes == 0 ? t('Unlimited') : $node->takes;
-    $stats[] = array(
-      'title' => t('Attempts allowed'),
-      'data' => $takes,
-    );
+  $stats[] = array('title' => t('Questions'), 'data' => isset($quiz->number_of_questions) ? $quiz->number_of_questions : 0);
+
+  if ($quiz->show_attempt_stats) {
+    $takes = $quiz->takes == 0 ? t('Unlimited') : $quiz->takes;
+    $stats[] = array('title' => t('Attempts allowed'), 'data' => $takes);
   }
-  if ($node->quiz_always) {
-    $stats[] = array(
-      'title' => t('Available'),
-      'data' => t('Always'),
-    );
+
+  if ($quiz->quiz_always) {
+    $stats[] = array('title' => t('Available'), 'data' => t('Always'));
   }
   else {
-    $stats[] = array(
-      'title' => t('Opens'),
-      'data' => format_date($node->quiz_open, 'short'),
-    );
-    $stats[] = array(
-      'title' => t('Closes'),
-      'data' => format_date($node->quiz_close, 'short'),
-    );
+    $stats[] = array('title' => t('Opens'), 'data' => format_date($quiz->quiz_open, 'short'));
+    $stats[] = array('title' => t('Closes'), 'data' => format_date($quiz->quiz_close, 'short'));
   }
-  if (!empty($node->pass_rate)) {
-    $stats[] = array(
-      'title' => t('Pass rate'),
-      'data' => $node->pass_rate . ' %',
-    );
+
+  if (!empty($quiz->pass_rate)) {
+    $stats[] = array('title' => t('Pass rate'), 'data' => $quiz->pass_rate . ' %');
   }
-  if (!empty($node->time_limit)) {
-    $stats[] = array(
-      'title' => t('Time limit'),
-      'data' => _quiz_format_duration($node->time_limit),
-    );
+
+  if (!empty($quiz->time_limit)) {
+    $stats[] = array('title' => t('Time limit'), 'data' => quiz()->formatDuration($quiz->time_limit));
   }
-  $stats[] = array(
-    'title' => t('Backwards navigation'),
-    'data' => $node->backwards_navigation ? t('Allowed') : t('Forbidden'),
-  );
+
+  $stats[] = array('title' => t('Backwards navigation'), 'data' => $quiz->backwards_navigation ? t('Allowed') : t('Forbidden'));
+
   // Format and output the data
-  $out = '<table id="quiz-view-table">' . "\n";
+  $table = '';
   foreach ($stats as $stat) {
-    $out .= '<tr><td class="quiz-view-table-title"><strong>' . $stat['title'] . ':</strong></td><td class="quiz-view-table-data"><em>' . $stat['data'] . '</em></td></tr>' . "\n";
+    $table .= '<tr>'
+      . '<td class="quiz-view-table-title"><strong>' . $stat['title'] . ':</strong></td>'
+      . '<td class="quiz-view-table-data"><em>' . $stat['data'] . '</em></td>'
+      . '</tr>';
   }
-  $out .= '</table>' . "\n";
-  return $out;
+  return '<table id="quiz-view-table">' . $table . '</table>';
 }
 
 /**
  * Theme the result page.
  *
  * @param $quiz
- *  The quiz node object.
+ *  The quiz entity object.
  * @param $questions
  *  The questions array as defined by _quiz_get_answers.
  * @param $score
@@ -393,11 +313,11 @@ function theme_quiz_view_stats($variables) {
  */
 function theme_quiz_result($variables) {
   global $user;
-  $quiz = $variables['quiz'];
+
   $questions = $variables['questions'];
   $score = $variables['score'];
   $summary = $variables['summary'];
-  $quiz_result = quiz_result_load($variables['result_id']);
+  $result = quiz_result_load($variables['result_id']);
   $account = $variables['account'];
 
   if (!$score['is_evaluated']) {
@@ -406,91 +326,217 @@ function theme_quiz_result($variables) {
   }
 
   // Display overall result.
-  $output = '';
   $params = array(
-    '%num_correct' => $score['numeric_score'],
-    '%question_count' => $score['possible_score'],
-    '!username' => ($user->uid == $account->uid) ? t('You') : theme('username', array('account' => $account)),
-    '@score' => $score['percentage_score'],
-    '!yourtotal' => ($user->uid == $account->uid) ? t('Your') : t('Total'),
+      '%num_correct'    => $score['numeric_score'],
+      '%question_count' => $score['possible_score'],
+      '!username'       => ($user->uid == $account->uid) ? t('You') : theme('username', array('account' => $account)),
+      '@score'          => $score['percentage_score'],
+      '!yourtotal'      => ($user->uid == $account->uid) ? t('Your') : t('Total'),
   );
 
-  // @todo should use quiz_feedback_can_review but we dont have the quiz result here.
-  if (quiz_feedback_can_review('score', $quiz_result)) {
+  $output = '';
+  if (quiz()->getQuizHelper()->getFeedbackHelper()->canReview('score', $result)) {
     // Show score.
     $output .= '<div id="quiz_score_possible">' . t('!username got %num_correct of %question_count possible points.', $params) . '</div>' . "\n";
     $output .= '<div id="quiz_score_percent">' . t('!yourtotal score: @score%', $params) . '</div>' . "\n";
   }
 
-  if (quiz_feedback_can_review('quiz_feedback', $quiz_result)) {
-    // Show quiz feedback.
+  if (quiz()->getQuizHelper()->getFeedbackHelper()->canReview('quiz_feedback', $result)) {
+    // Show quiz feedback
     if (isset($summary['passfail'])) {
       $output .= '<div id="quiz_summary">' . $summary['passfail'] . '</div>' . "\n";
     }
+
     if (isset($summary['result'])) {
       $output .= '<div id="quiz_summary">' . $summary['result'] . '</div>' . "\n";
     }
   }
 
   $report_form = drupal_get_form('quiz_report_form', $questions);
-  $output .= drupal_render($report_form);
-  return $output;
+  return $output . drupal_render($report_form);
+}
+
+function quiz_report_form($form, $form_state, $questions) {
+  $obj = new QuizReportForm();
+  $array = $obj->getForm($form, $form_state, $questions);
+
+  if (isset($array['next'])) {
+    $array['next']['#submit'] = array('quiz_report_form_submit_next');
+  }
+
+  if (isset($array['finish'])) {
+    $array['finish']['#submit'] = array('quiz_report_form_submit_finish');
+  }
+
+  return $array;
+}
+
+function quiz_report_form_submit_next($form, &$form_state) {
+  $obj = new QuizReportForm();
+  return $obj->formSubmitFeedback($form, $form_state);
+}
+
+function quiz_report_form_submit_finish($form, &$form_state) {
+  $obj = new QuizReportForm();
+  return $obj->formEndSubmit($form, $form_state);
+}
+
+function quiz_report_form_submit($form, &$form_state) {
+  $obj = new QuizReportForm();
+  return $obj->formSubmit($form, $form_state);
 }
 
 /**
- * Help us with special pagination.
- *
- * Why not the Drupal theme_pager()?
- *
- * It uses query strings. We have access on each menu argument (quiz question
- * number) so we unfortunately cannot use it.
+ * Theme the quiz pager.
  */
-function _quiz_pagination_helper($total, $perpage = null, $current = null, $siblings = null) {
-  $result = array();
+function theme_quiz_pager($variables) {
+  return quiz()
+      ->getQuizHelper()
+      ->getTakeJumperHelper($variables['quiz'], $variables['total'], $variables['siblings'], $variables['current'])
+      ->render();
+}
 
-  if (isset($total, $perpage) === true) {
-    $result = range(1, ceil($total / $perpage));
+/**
+ * Theme a question selection table, adding drag and drop support.
+ */
+function theme_question_selection_table($variables) {
+  $form = $variables['form'];
 
-    if (isset($current, $siblings) === true) {
-      if (($siblings = floor($siblings / 2) * 2 + 1) >= 1) {
-        $result = array_slice($result, max(0, min(count($result) - $siblings, intval($current) - ceil($siblings / 2))), $siblings);
-      }
+  // Building headers
+  $headers = array(t('Question'), t('Type'), t('Actions'), t('Update'), t('Max score'), t('Auto update max score'), t('Delete'));
+  if (isset($form['compulsories'])) {
+    $headers[] = t('Compulsory');
+  }
+  $headers[] = t('Weight');
+  $headers[] = t('Parent ID');
+  $headers[] = array('data' => t('ID'), 'class' => array('tabledrag-hide'));
+
+  // Building table body
+  $rows = array();
+  if (!empty($form['titles'])) {
+    foreach (element_children($form['titles']) as $id) {
+      $form['weights'][$id]['#attributes']['class'] = array('question-list-weight');
+      $form['qr_ids'][$id]['#attributes']['class'] = array('qnr-id');
+      $form['qr_pids'][$id]['#attributes']['class'] = array('qnr-pid');
+      $rows[] = _quiz_get_question_row($form, $id);
     }
+    // Make sure the same fields aren't rendered twice
+    unset($form['types'], $form['view_links'], $form['remove_links'], $form['stayers']);
+    unset($form['max_scores'], $form['auto_update_max_scores'], $form['revision'], $form['weights'], $form['titles'], $form['compulsories'], $form['qr_ids'], $form['qr_pids']);
   }
 
-  return $result;
+  // We hide the table if no questions have been added so that jQuery can show
+  // it the moment the first question is beeing added.
+  $html_attr = array();
+  if (isset($form['no_questions'])) {
+    $html_attr['style'] = "display:none;";
+  }
+
+  drupal_add_tabledrag('question-list', 'match', 'parent', 'qnr-pid', 'qnr-pid', 'qnr-id', TRUE, 1);
+  drupal_add_tabledrag('question-list', 'order', 'sibling', 'question-list-weight');
+
+  return drupal_render($form['random_settings'])
+    . theme('table', array(
+        'header'     => $headers,
+        'rows'       => $rows,
+        'attributes' => array('id' => 'question-list') + $html_attr))
+    . drupal_render_children($form);
 }
 
 /**
- * Theme the quiz pager.
+ * Helper function for theme_question_selection_table
+ *
+ * TODO: DELETE
+ *
+ * @see quiz_questions_form()
+ * @see theme_question_selection_table()
+ *
+ * @param $sub_form
+ *   Form definition array for a filtered questions list
+ * @param $id
+ *   Identifier used in $sub_form
+ * @return table row
+ *   Array defining a table row
  */
-function theme_quiz_pager($variables) {
-  $total = $variables['total'];
-  $current = $variables['current'];
-  $siblings = $variables['siblings'];
-  $items = array();
-  $nid = arg(1);
-  $items[] = array(
-    'class' => array('pager-first'),
-    'data' => l(t('first'), "node/$nid/take/1"),
+function _quiz_get_question_row($sub_form, $id) {
+  $type = $sub_form['types'][$id]['#markup'];
+
+  $action = theme('item_list', array(
+      'items'      => array(
+          drupal_render($sub_form['view_links'][$id]),
+          '<SPAN CLASS="q-remove" STYLE="display:none">' . drupal_render($sub_form['remove_links'][$id]) . '</SPAN>',
+      ),
+      'attributes' => array(
+          'class' => array('links', 'inline'),
+      ),
+  ));
+  $qr_pid = $sub_form['qr_pids'][$id]['#default_value'];
+  $data_array = array(
+      // The checkbox and the title
+      ($qr_pid ? theme('indentation', array('size' => 1)) : NULL) . drupal_render($sub_form['titles'][$id]),
+      $type,
+      $action,
+      isset($sub_form['revision'][$id]) ? drupal_render($sub_form['revision'][$id]) : t("Up to date"),
+      drupal_render($sub_form['max_scores'][$id]),
+      drupal_render($sub_form['auto_update_max_scores'][$id]),
+      drupal_render($sub_form['stayers'][$id]),
   );
-  foreach (_quiz_pagination_helper($total, 1, $current, $siblings) as $i) {
-    if ($i == $current) {
-      $items[] = array(
-        'class' => array('pager-current'),
-        'data' => $i,
-      );
-    }
-    else {
-      $items[] = array(
-        'class' => array('pager-item'),
-        'data' => l($i, "node/$nid/take/$i"),
-      );
-    }
+  if (isset($sub_form['compulsories'])) {
+    $data_array[] = drupal_render($sub_form['compulsories'][$id]);
   }
-  $items[] = array(
-    'class' => array('pager-last'),
-    'data' => l(t('last'), "node/$nid/take/$total"),
+  $data_array[] = drupal_render($sub_form['weights'][$id]);
+  $data_array[] = drupal_render($sub_form['qr_pids'][$id]);
+  $data_array[] = array(
+      'class' => array('tabledrag-hide'),
+      'data'  => drupal_render($sub_form['qr_ids'][$id]),
   );
-  return theme('item_list', array('items' => $items, 'attributes' => array('class' => array('pager'))));
+
+  $leaf_class = $sub_form['types'][$id]['#question_type'] != 'quiz_page' ? 'tabledrag-leaf' : '';
+
+  return array(
+      'class' => array('q-row', 'draggable', $leaf_class),
+      'id'    => 'q-' . $id,
+      'data'  => $data_array
+  );
+}
+
+/**
+ * Themes a categorized quiz form
+ */
+function theme_quiz_categorized_form($variables) {
+  $form = $variables['form'];
+
+  drupal_add_js(drupal_get_path('module', 'quiz') . '/misc/js/quiz.admin.categorized-form.js');
+
+  $rows = array();
+  foreach ($form as $key => &$existing) {
+    if (!is_numeric($key)) {
+      continue;
+    }
+    $cols = array();
+    $cols[] = drupal_render($existing['name']);
+    $cols[] = drupal_render($existing['number']);
+    $cols[] = drupal_render($existing['max_score']);
+    $cols[] = drupal_render($existing['remove']);
+    $cols[] = drupal_render($existing['weight']);
+    $rows[] = array('data' => $cols, 'class' => array('draggable'));
+  }
+
+  $output = '';
+  if (!empty($rows)) {
+    $header = array(t('Category'), t('Number of questions'), t('Max score per question'), t('Remove'), t('Weight'));
+    drupal_add_tabledrag('existing-terms', 'order', 'sibling', 'term-weight', NULL, NULL, TRUE);
+    $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'existing-terms')));
+  }
+  return $output . drupal_render_children($form);
+}
+
+/**
+ * Limit the year options to the years 1970 - 2030 for form items of type date.
+ * Some systems don't support all the dates the forms api lets you choose from.
+ * This function limits the options to dates most systems support.
+ */
+function _quiz_after_build_fix_year_options($element) {
+  $element['year']['#options'] = drupal_map_assoc(range(1970, 2030));
+  return $element;
 }
diff --git a/quiz_datetime.inc b/quiz_datetime.inc
deleted file mode 100644
index 96c7bb4..0000000
--- a/quiz_datetime.inc
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-
-/////////////////////////////////
-// Date and time routines for use with quiz module.
-// - Based on event module
-// - All references to event variables should be optional
-/////////////////////////////////
-
-/**
- * @file
- * Handles the start and end times in a node form submission.
- * - Takes the array from form_date() and turns it into a timestamp
- * - Adjusts times for time zone offsets.
- * - Adapted from event.module
- *
- * @param $node The submitted node with form data.
- * @param $date The name of the date ('quiz_open' or 'quiz_close') to translate.
- */
-function quiz_translate_form_date(&$node, $date) {
-  $prefix = $node->$date;
-  // If we have all the parameters, re-calculate $node->event_$date .
-  if (is_array($prefix) && isset($prefix['year']) && isset($prefix['month']) && isset($prefix['day'])) {
-    // Build a timestamp based on the date supplied and the configured timezone.
-    $node->$date = _quiz_mktime(0, 0, 0, $prefix['month'], $prefix['day'], $prefix['year'], 0);
-  }
-  else {
-    if (!_quiz_is_int($prefix, 1, 2147483647)) {
-      form_set_error('quiz_open', t('Please supply a valid date.'));
-    }
-  }
-}
-
-/**
- * Formats local time values to GMT timestamp using time zone offset supplied.
- * All time values in the database are GMT and translated here prior to insertion.
- *
- * Time zone settings are applied in the following order:
- * 1. If supplied, time zone offset is applied
- * 2. If user time zones are enabled, user time zone offset is applied
- * 3. If neither 1 nor 2 apply, the site time zone offset is applied
- *
- * @param $hour
- * @param $minute
- * @param $second
- * @param $month
- * @param $day
- * @param $year
- * @param $offset
- *   Time zone offset to apply to the timestamp.
- * @return timestamp
- */
-function _quiz_mktime($hour, $minute, $second, $month, $day, $year, $offset = NULL) {
-  global $user;
-  //print $user->timezone. " and ". variable_get('date_default_timezone', 0);
-  $timestamp = gmmktime($hour, $minute, $second, $month, $day, $year);
-  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
-    return $timestamp - $user->timezone;
-  }
-  else {
-    return $timestamp - variable_get('date_default_timezone', 0);
-  }
-}
-
-/**
- * Formats a GMT timestamp to local date values using time zone offset supplied.
- * All timestamp values in event nodes are GMT and translated for display here.
- *
- * Pulled from event
- *
- * Time zone settings are applied in the following order
- * 1. If supplied, time zone offset is applied
- * 2. If user time zones are enabled, user time zone offset is applied
- * 3. If neither 1 nor 2 apply, the site time zone offset is applied
- *
- * @param $format
- *   The date() format to apply to the timestamp.
- * @param $timestamp
- *   The GMT timestamp value.
- * @param $offset
- *   Time zone offset to apply to the timestamp.
- * @return gmdate() formatted date value
- */
-function _quiz_date($format, $timestamp, $offset = NULL) {
-  global $user;
-
-  if (isset($offset)) {
-    $timestamp += $offset;
-  }
-  elseif (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
-    $timestamp += $user->timezone;
-  }
-  else {
-    $timestamp += variable_get('date_default_timezone', 0);
-  }
-
-  // make sure we apply the site first day of the week setting for dow requests
-  $result = gmdate($format, $timestamp);
-  return $result;
-}
diff --git a/src/Controller/Admin/QuizAdminSettingsController.php b/src/Controller/Admin/QuizAdminSettingsController.php
new file mode 100644
index 0000000..671e432
--- /dev/null
+++ b/src/Controller/Admin/QuizAdminSettingsController.php
@@ -0,0 +1,337 @@
+<?php
+
+namespace Drupal\quiz\Controller\Admin;
+
+class QuizAdminSettingsController {
+
+  /**
+   * This builds the main settings form for the quiz module.
+   */
+  public function getForm($form, &$form_state) {
+    $form['quiz_global_settings'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Global configuration'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+        '#description' => t('Control aspects of the Quiz module\'s display'),
+    );
+
+    $form['quiz_global_settings']['quiz_auto_revisioning'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Auto revisioning'),
+        '#default_value' => variable_get('quiz_auto_revisioning', 1),
+        '#description'   => t('It is strongly recommended that auto revisioning is always on. It makes sure that when a question or quiz is changed a new revision is created if the current revision has been answered. If this feature is switched off result reports might be broken because a users saved answer might be connected to a wrong version of the quiz and/or question she was answering. All sorts of errors might appear.'),
+    );
+
+    $form['quiz_global_settings']['quiz_durod'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Delete results when a user is deleted'),
+        '#default_value' => variable_get('quiz_durod', 0),
+        '#description'   => t('When a user is deleted delete any and all results for that user.'),
+    );
+
+    $form['quiz_global_settings']['quiz_index_questions'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Index questions'),
+        '#default_value' => variable_get('quiz_index_questions', 1),
+        '#description'   => t('If you turn this off, questions will not show up in search results.'),
+    );
+
+    $form['quiz_global_settings']['quiz_default_close'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Default number of days before a @quiz is closed', array('@quiz' => QUIZ_NAME)),
+        '#default_value' => variable_get('quiz_default_close', 30),
+        '#size'          => 4,
+        '#maxlength'     => 4,
+        '#description'   => t('Supply a number of days to calculate the default close date for new quizzes.'),
+    );
+
+    $form['quiz_global_settings']['quiz_use_passfail'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Allow quiz creators to set a pass/fail option when creating a @quiz.', array('@quiz' => strtolower(QUIZ_NAME))),
+        '#default_value' => variable_get('quiz_use_passfail', 1),
+        '#description'   => t('Check this to display the pass/fail options in the @quiz form. If you want to prohibit other quiz creators from changing the default pass/fail percentage, uncheck this option.', array('@quiz' => QUIZ_NAME)),
+    );
+
+    $form['quiz_global_settings']['quiz_max_result_options'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Maximum result options'),
+        '#description'   => t('Set the maximum number of result options (categorizations for scoring a quiz). Set to 0 to disable result options.'),
+        '#default_value' => variable_get('quiz_max_result_options', 5),
+        '#size'          => 2,
+        '#maxlength'     => 2,
+        '#required'      => FALSE,
+    );
+
+    $form['quiz_global_settings']['quiz_remove_partial_quiz_record'] = array(
+        '#type'          => 'select',
+        '#title'         => t('Remove incomplete quiz records (older than)'),
+        '#options'       => $this->removePartialQuizRecordValue(),
+        '#description'   => t('Number of days to keep incomplete quiz attempts.'),
+        '#default_value' => variable_get('quiz_remove_partial_quiz_record', $this->removePartialQuizRecordValue()),
+    );
+
+    $form['quiz_global_settings']['quiz_autotitle_length'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Length of automatically set question titles'),
+        '#size'          => 3,
+        '#maxlength'     => 3,
+        '#description'   => t('Integer between 0 and 128. If the question creator doesn\'t set a question title the system will make a title automatically. Here you can decide how long the autotitle can be.'),
+        '#default_value' => variable_get('quiz_autotitle_length', 50),
+    );
+
+    $form['quiz_global_settings']['quiz_pager_start'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Pager start'),
+        '#size'          => 3,
+        '#maxlength'     => 3,
+        '#description'   => t('If a quiz has this many questions, a pager will be displayed instead of a select box.'),
+        '#default_value' => variable_get('quiz_pager_start', 100),
+    );
+
+    $form['quiz_global_settings']['quiz_pager_siblings'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Pager siblings'),
+        '#size'          => 3,
+        '#maxlength'     => 3,
+        '#description'   => t('Number of siblings to show.'),
+        '#default_value' => variable_get('quiz_pager_siblings', 5),
+    );
+
+    $target = array('attributes' => array('target' => '_blank'));
+
+    $links = array(
+        '!jquery_countdown' => l(t('JQuery Countdown'), 'http://drupal.org/project/jquery_countdown', $target),
+        '!userpoints'       => l(t('UserPoints'), 'http://drupal.org/project/userpoints', $target),
+    );
+
+    $form['quiz_addons'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Addons configuration'),
+        '#description' => t('Quiz has built in integration with some other modules. Disabled checkboxes indicates that modules are not enabled.', $links),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+    );
+
+    $form['quiz_addons']['quiz_has_userpoints'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('User Points'),
+        '#default_value' => variable_get('quiz_has_userpoints', 0),
+        '#description'   => t('!userpoints is an <strong>optional</strong> module for Quiz. It provides ways for users to gain or lose points for performing certain actions on your site like completing a Quiz.', $links),
+        '#disabled'      => !module_exists('userpoints'),
+    );
+
+    $form['quiz_addons']['quiz_has_timer'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Display timer'),
+        '#default_value' => variable_get('quiz_has_timer', 0),
+        '#description'   => t("!jquery_countdown is an <strong>optional</strong> module for Quiz. It is used to display a timer when taking a quiz. Without this timer, the user will not know how much time they have left to complete the Quiz", $links),
+        '#disabled'      => !function_exists('jquery_countdown_add'),
+    );
+
+    $form['quiz_look_feel'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Look and feel'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+        '#description' => t('Control aspects of the Quiz module\'s display'),
+    );
+
+    $form['quiz_look_feel']['quiz_name'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Display name'),
+        '#default_value' => QUIZ_NAME,
+        '#description'   => t('Change the name of the quiz type. Do you call it <em>test</em> or <em>assessment</em> instead? Change the display name of the module to something else. By default, it is called <em>Quiz</em>.'),
+        '#required'      => TRUE,
+    );
+
+    $form['quiz_email_settings'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Notifications'),
+        '#description' => t('Send results to quiz author/attendee via email. Configure email subject and body.'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+    );
+
+    $form['quiz_email_settings']['taker'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Email for Quiz takers'),
+        '#collapsible' => FALSE,
+    );
+
+    $form['quiz_email_settings']['taker']['quiz_email_results'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Email results to quiz takers'),
+        '#default_value' => variable_get('quiz_email_results', 0),
+        '#description'   => t('Check this to send users their results at the end of a quiz.')
+    );
+
+    $form['quiz_email_settings']['taker']['quiz_email_results_subject_taker'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Configure email subject'),
+        '#description'   => t('This format will be used when sending quiz results at the end of a quiz.'),
+        '#default_value' => variable_get('quiz_email_results_subject_taker', $this->formatEmailResults('subject', 'taker')),
+    );
+
+    $form['quiz_email_settings']['taker']['quiz_email_results_body_taker'] = array(
+        '#type'          => 'textarea',
+        '#title'         => t('Configure email Format'),
+        '#description'   => t('This format will be used when sending @quiz results at the end of a quiz. !title(quiz title), !sitename, !taker(quiz takers username), !date(time when quiz was finished), !minutes(How many minutes the quiz taker spent taking the quiz), !desc(description of the quiz), !correct(points attained), !total(max score for the quiz), !percentage(percentage score), !url(url to the result page) and !author are placeholders.'),
+        '#default_value' => variable_get('quiz_email_results_body_taker', $this->formatEmailResults('body', 'taker')),
+    );
+
+    $form['quiz_email_settings']['author'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Email for Quiz authors'),
+        '#collapsible' => FALSE,
+    );
+
+    $form['quiz_email_settings']['author']['quiz_results_to_quiz_author'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Email all results to quiz author.'),
+        '#default_value' => variable_get('quiz_results_to_quiz_author', 0),
+        '#description'   => t('Check this to send quiz results for all users to the quiz author.'),
+    );
+
+    $form['quiz_email_settings']['author']['quiz_email_results_subject'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Configure email subject'),
+        '#description'   => t('This format will be used when sending quiz results at the end of a quiz. Authors and quiz takers gets the same format.'),
+        '#default_value' => variable_get('quiz_email_results_subject', $this->formatEmailResults('subject', 'author')),
+    );
+
+    $form['quiz_email_settings']['author']['quiz_email_results_body'] = array(
+        '#type'          => 'textarea',
+        '#title'         => t('Configure E-mail Format'),
+        '#description'   => t('This format will be used when sending quiz results at the end of a quiz. !title(quiz title), !sitename, !taker(quiz takers username), !date(time when quiz was finished), !minutes(How many minutes the quiz taker spent taking the quiz), !desc(description of the quiz), !correct(points attained), !total(max score for the quiz), !percentage(percentage score), !url(url to the result page) and !author are placeholders.'),
+        '#default_value' => variable_get('quiz_email_results_body', $this->formatEmailResults('body', 'author')),
+    );
+
+    return system_settings_form($form);
+  }
+
+  /**
+   * Validation of the Form Settings form.
+   *
+   * Checks the values for the form administration form for quiz settings.
+   */
+  public function validate($form, &$form_state) {
+    if (!_quiz_is_int($form_state['values']['quiz_default_close'])) {
+      form_set_error('quiz_default_close', t('The default number of days before a quiz is closed must be a number greater than 0.'));
+    }
+
+    if (!_quiz_is_int($form_state['values']['quiz_autotitle_length'], 0, 128)) {
+      form_set_error('quiz_autotitle_length', t('The autotitle length value must be an integer between 0 and 128.'));
+    }
+
+    if (!_quiz_is_int($form_state['values']['quiz_max_result_options'], 0, 100)) {
+      form_set_error('quiz_max_result_options', t('The number of resultoptions must be an integer between 0 and 100.'));
+    }
+
+    if (!$this->isPlain($form_state['values']['quiz_name'])) {
+      form_set_error('quiz_name', t('The quiz name must be plain text.'));
+    }
+  }
+
+  /**
+   * Submit the admin settings form
+   */
+  public function submit($form, &$form_state) {
+    if (QUIZ_NAME !== $form_state['values']['quiz_name']) {
+      variable_set('quiz_name', $form_state['values']['quiz_name']);
+      define(QUIZ_NAME, $form_state['values']['quiz_name']);
+      menu_rebuild();
+    }
+  }
+
+  /**
+   * This functions returns the default email subject and body format which will
+   * be used at the end of quiz.
+   */
+  private function formatEmailResults($type, $target) {
+    global $user;
+
+    if ($type === 'subject') {
+      return quiz()->getMailHelper()->formatSubject($target, $user);
+    }
+
+    if ($type === 'body') {
+      return quiz()->getMailHelper()->formatBody($target, $user);
+    }
+  }
+
+  /**
+   * Helper function used when validating plain text.
+   *
+   * @param $value
+   *   The value to be validated.
+   *
+   * @return
+   *   TRUE if plain text FALSE otherwise.
+   */
+  private function isPlain($value) {
+    return ($value === check_plain($value));
+  }
+
+  /**
+   * Helper function returning number of days as values and corresponding
+   * number of milliseconds as array keys.
+   *
+   * @return
+   *   array of options for when we want to delete partial quiz record values.
+   */
+  private function removePartialQuizRecordValue() {
+    return array(
+        '0'        => t('Never'),
+        '86400'    => t('1 Day'),
+        '172800'   => t('2 Days'),
+        '259200'   => t('3 Days'),
+        '345600'   => t('4 Days'),
+        '432000'   => t('5 Days'),
+        '518400'   => t('6 Days'),
+        '604800'   => t('7 Days'),
+        '691200'   => t('8 Days'),
+        '777600'   => t('9 Days'),
+        '864000'   => t('10 Days'),
+        '950400'   => t('11 Days'),
+        '1036800'  => t('12 Days'),
+        '1123200'  => t('13 Days'),
+        '1209600'  => t('14 Days'),
+        '1296000'  => t('15 Days'),
+        '1382400'  => t('16 Days'),
+        '1468800'  => t('17 Days'),
+        '1555200'  => t('18 Days'),
+        '1641600'  => t('19 Days'),
+        '1728000'  => t('20 Days'),
+        '1814400'  => t('21 Days'),
+        '1900800'  => t('22 Days'),
+        '1987200'  => t('23 Days'),
+        '2073600'  => t('24 Days'),
+        '2160000'  => t('25 Days'),
+        '2246400'  => t('26 Days'),
+        '2332800'  => t('27 Days'),
+        '2419200'  => t('28 Days'),
+        '2505600'  => t('29 Days'),
+        '2592000'  => t('30 Days'),
+        '3024000'  => t('35 Days'),
+        '3456000'  => t('40 Days'),
+        '3888000'  => t('45 Days'),
+        '4320000'  => t('50 Days'),
+        '4752000'  => t('55 Days'),
+        '5184000'  => t('60 Days'),
+        '5616000'  => t('65 Days'),
+        '6048000'  => t('70 Days'),
+        '6480000'  => t('75 Days'),
+        '6912000'  => t('80 Days'),
+        '7344000'  => t('85 Days'),
+        '7776000'  => t('90 Days'),
+        '8208000'  => t('95 Days'),
+        '8640000'  => t('100 Days'),
+        '9072000'  => t('105 Days'),
+        '9504000'  => t('110 Days'),
+        '9936000'  => t('115 Days'),
+        '10368000' => t('120 Days'),
+    );
+  }
+
+}
diff --git a/src/Controller/Admin/QuizQuestionAdminController.php b/src/Controller/Admin/QuizQuestionAdminController.php
new file mode 100644
index 0000000..bda118f
--- /dev/null
+++ b/src/Controller/Admin/QuizQuestionAdminController.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\quiz\Controller\Admin;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuizQuestionAdminController {
+
+  private $quiz;
+
+  public function __construct(QuizEntity $quiz) {
+    $this->quiz = $quiz;
+  }
+
+  public static function staticCallback(QuizEntity $quiz) {
+    $obj = new self($quiz);
+
+    module_load_include('pages.inc', 'quiz');
+
+    if ($quiz->randomization >= 3) {
+      return drupal_get_form('quiz_categorized_form', $quiz);
+    }
+
+    return array(
+        '#type'            => 'vertical_tabs',
+        'question_admin'   => array(
+            '#type'  => 'fieldset',
+            '#title' => t('Manage questions'),
+            '#value' => '',
+            'links'  => array(
+                '#type'        => 'fieldset',
+                '#title'       => t('Create new question'),
+                '#collapsible' => TRUE,
+                '#collapsed'   => TRUE,
+                '#value'       => '',
+                'links'        => array(
+                    '#theme' => 'item_list',
+                    '#items' => $obj->getQuestionAddingLinks(),
+                ),
+            ),
+            'form'   => drupal_get_form('quiz_questions_form', $quiz),
+        ),
+        'global_questions' => array(
+            '#type'  => 'fieldset',
+            '#title' => t('Question bank'),
+            '#value' => views_get_view('quiz_question_bank')->preview(),
+        ),
+    );
+  }
+
+  public function getQuestionAddingLinks() {
+    $items = array();
+
+    foreach (quiz_get_question_types() as $type => $info) {
+      if (!node_access('create', $type)) {
+        continue;
+      }
+
+      $text = $info['name'];
+      $url = 'node/add/' . str_replace('_', '-', $type);
+      $items[] = l($text, $url, array('query' => drupal_get_destination()));
+    }
+
+    if (empty($items)) {
+      $items[] = t('You have not enabled any question type module or no has permission been given to create any question.');
+    }
+
+    return $items;
+  }
+
+}
diff --git a/src/Controller/Admin/QuizResultsAdminController.php b/src/Controller/Admin/QuizResultsAdminController.php
new file mode 100644
index 0000000..6d705be
--- /dev/null
+++ b/src/Controller/Admin/QuizResultsAdminController.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Drupal\quiz\Controller\Admin;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuizResultsAdminController {
+
+  public static function staticCallback(QuizEntity $quiz) {
+    return views_embed_view('quiz_results', 'page', $quiz->qid);
+  }
+
+}
diff --git a/src/Controller/Admin/QuizRevisionsAdminController.php b/src/Controller/Admin/QuizRevisionsAdminController.php
new file mode 100644
index 0000000..8fc752f
--- /dev/null
+++ b/src/Controller/Admin/QuizRevisionsAdminController.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\quiz\Controller\Admin;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuizRevisionsAdminController {
+
+  public static function staticCallback(QuizEntity $quiz) {
+    $revisions = db_query('SELECT qr.vid, qr.log, qr.changed, qr.revision_uid, u.uid, u.name'
+      . ' FROM {quiz_entity_revision} qr'
+      . '   LEFT JOIN {users} u ON qr.revision_uid = u.uid'
+      . ' WHERE qr.qid = :qid'
+      . ' ORDER BY qr.vid DESC', array(':qid' => $quiz->qid))->fetchAll();
+
+    $rows = array();
+    foreach ($revisions as $revision) {
+      $row = array(
+        t('!datetime by !name', array(
+          '!datetime' => format_date($revision->changed),
+          '!name'     => l($revision->name, "user/{$revision->uid}"),
+        )),
+        l(t('revert'), "quiz/{$quiz->qid}/revisions/{$revision->vid}/revert"),
+        l(t('delete'), "quiz/{$quiz->qid}/revisions/{$revision->vid}/delete"),
+      );
+
+      $row[0] .= '<div class="log">' . (!empty($revision->log) ? $revision->log : '<em>' . t('empty') . '</em>') . '</div>';
+
+      if ($quiz->vid == $revision->vid) {
+        unset($row[2]);
+        $row[1] = array('data' => t('current revision'), 'colspan' => 2);
+      }
+
+      $rows[] = $row;
+    }
+
+    return array(
+      '#theme'  => 'table',
+      '#header' => array(t('Revision'), array('data' => t('Operations'), 'colspan' => 2)),
+      '#rows'   => $rows);
+  }
+
+}
diff --git a/src/Controller/Legacy/QuizTakeLegacyController.php b/src/Controller/Legacy/QuizTakeLegacyController.php
new file mode 100644
index 0000000..d1b27b5
--- /dev/null
+++ b/src/Controller/Legacy/QuizTakeLegacyController.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\quiz\Controller\Legacy;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuizTakeLegacyController {
+
+  /** @var int */
+  protected $result_id;
+
+  /** @var QuizEntity */
+  protected $quiz;
+
+  protected function getQuizId() {
+    return $this->quiz->qid;
+  }
+
+  public function getResultId() {
+    return $this->result_id;
+  }
+
+  public function getQuestionTakePath() {
+    $id = $this->getQuizId();
+    $current = $_SESSION['quiz'][$id]['current'];
+    return "quiz/{$id}/take/{$current}";
+  }
+
+  /**
+   * Returns the result ID for any current result set for the given quiz.
+   *
+   * @param int $uid
+   * @param int $vid Quiz version ID
+   * @param int $now
+   *   Timestamp used to check whether the quiz is still open. Default: current
+   *   time.
+   *
+   * @return int
+   *   If a quiz is still open and the user has not finished the quiz,
+   *   return the result set ID so that the user can continue. If no quiz is in
+   *   progress, this will return 0.
+   */
+  protected function activeResultId($uid, $vid, $now = NULL) {
+    $sql = 'SELECT qnr.result_id '
+      . ' FROM {quiz_results} qnr '
+      . '   INNER JOIN {quiz_entity_revision} quiz ON qnr.quiz_vid = quiz.vid'
+      . ' WHERE '
+      . '   (quiz.quiz_always = :quiz_always OR (:between BETWEEN quiz.quiz_open AND quiz.quiz_close)) '
+      . '   AND qnr.quiz_vid = :vid '
+      . '   AND qnr.uid = :uid '
+      . '   AND qnr.time_end IS NULL';
+
+    // Get any quiz that is open, for this user, and has not already been completed.
+    return (int) db_query($sql, array(
+          ':quiz_always' => 1,
+          ':between'     => $now ? $now : REQUEST_TIME,
+          ':vid'         => $vid,
+          ':uid'         => $uid
+      ))->fetchField();
+  }
+
+}
diff --git a/src/Controller/QuizEntityAddController.php b/src/Controller/QuizEntityAddController.php
new file mode 100644
index 0000000..88a8227
--- /dev/null
+++ b/src/Controller/QuizEntityAddController.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+class QuizEntityAddController {
+
+  /**
+   * @TODO Only list quiz type if user has permission to create it.
+   */
+  public static function staticCallback() {
+    $output = '<ul class="admin-list quiz-type-list">';
+    foreach (quiz_get_types() as $name => $quiz_type) {
+      $output .= '<li>';
+      $output .= '<span class="label">' . l($quiz_type->label, "quiz/add/{$name}") . '</span>';
+      if (!empty($quiz_type->description)) {
+        $output .= '<div class="description">' . $quiz_type->description . '</div>';
+      }
+      $output .= '</li>';
+    }
+    $output .= '</ul>';
+    return $output;
+  }
+
+}
diff --git a/src/Controller/QuizEntityViewController.php b/src/Controller/QuizEntityViewController.php
new file mode 100644
index 0000000..12633d3
--- /dev/null
+++ b/src/Controller/QuizEntityViewController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+class QuizEntityViewController {
+
+  /**
+   * Callback for quiz/%/take
+   */
+  public static function staticCallback($quiz, $view_mode = 'default', $langcode = NULL) {
+    return entity_view('quiz_entity', array($quiz), $view_mode, $langcode);
+  }
+
+}
diff --git a/src/Controller/QuizMyResultsController.php b/src/Controller/QuizMyResultsController.php
new file mode 100644
index 0000000..f013725
--- /dev/null
+++ b/src/Controller/QuizMyResultsController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuizMyResultsController {
+
+  public static function staticCallback(QuizEntity $quiz) {
+    global $user;
+    return views_embed_view('quiz_user_results', 'page', $quiz->qid, $user->uid);
+  }
+
+}
diff --git a/src/Controller/QuizQuestionFeedbackController.php b/src/Controller/QuizQuestionFeedbackController.php
new file mode 100644
index 0000000..abb8f06
--- /dev/null
+++ b/src/Controller/QuizQuestionFeedbackController.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Entity\Result;
+
+class QuizQuestionFeedbackController {
+
+  /** @var QuizEntity */
+  private $quiz;
+
+  /** @var int */
+  private $quiz_id;
+
+  /** @var Result */
+  private $result;
+
+  /**
+   * Callback for quiz/%/take/%question_number/feedback. Show feedback for a
+   * question response.
+   */
+  public static function staticCallback($quiz, $page_number) {
+    $quiz_id = $quiz->qid;
+    $result_id = empty($_SESSION['quiz'][$quiz_id]['result_id']) ? $_SESSION['quiz']['temp']['result_id'] : $_SESSION['quiz'][$quiz_id]['result_id'];
+    $result = quiz_result_load($result_id);
+    $controller = new static($quiz, $result);
+    return $controller->render($page_number);
+  }
+
+  public function __construct(QuizEntity $quiz, Result $result) {
+    $this->quiz = $quiz;
+    $this->quiz_id = $this->quiz->qid;
+    $this->result = $result;
+  }
+
+  public function render($page_number) {
+    $question = node_load($this->result->layout[$page_number]['nid']);
+    return $this->buildRenderArray($question);
+  }
+
+  public function buildRenderArray($question) {
+    require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'quiz') . '/quiz.pages.inc';
+
+    $types = quiz_get_question_types();
+    $module = $types[$question->type]['module'];
+
+    // Invoke hook_get_report().
+    if ($report = module_invoke($module, 'get_report', $question->nid, $question->vid, $this->result->result_id)) {
+      return drupal_get_form('quiz_report_form', array($report));
+    }
+  }
+
+}
diff --git a/src/Controller/QuizResultBaseController.php b/src/Controller/QuizResultBaseController.php
new file mode 100644
index 0000000..606c91e
--- /dev/null
+++ b/src/Controller/QuizResultBaseController.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Entity\Result;
+
+abstract class QuizResultBaseController {
+
+  /** @var QuizEntity */
+  protected $quiz;
+
+  /** @var QuizEntity */
+  protected $quiz_revision;
+
+  /** @var Result */
+  protected $result;
+
+  /** @var int */
+  protected $quiz_id;
+
+  /**
+   * The score information as returned by quiz_calculate_score().
+   */
+  protected $score;
+
+  public function __construct(QuizEntity $quiz, QuizEntity $quiz_revision, $result) {
+    $this->quiz = $quiz;
+    $this->quiz_revision = $quiz_revision;
+    $this->result = $result;
+    $this->quiz_id = $this->result->quiz_qid;
+    $this->score = quiz_result_controller()->getScoreCalculator()->calculate($this->quiz_revision, $this->result->result_id);
+  }
+
+  /**
+   * Get answer data for a specific result.
+   *
+   * @param QuizEntity $this->quiz_revision
+   * @param int $this->result->result_id
+   * @return
+   *   Array of answers.
+   */
+  protected function getAnswers() {
+    $sql = "SELECT ra.question_nid, ra.question_vid, n.type, rs.max_score, qt.max_score as term_max_score "
+      . " FROM {quiz_results_answers} ra "
+      . "   LEFT JOIN {node} n ON ra.question_nid = n.nid"
+      . "   LEFT JOIN {quiz_results} r ON ra.result_id = r.result_id"
+      . "   LEFT OUTER JOIN {quiz_relationship} rs ON (ra.question_vid = rs.question_vid) AND rs.quiz_vid = r.quiz_vid"
+      . "   LEFT OUTER JOIN {quiz_terms} qt ON (qt.vid = :vid AND qt.tid = ra.tid) "
+      . " WHERE ra.result_id = :rid "
+      . " ORDER BY ra.number, ra.answer_timestamp";
+    $ids = db_query($sql, array(':vid' => $this->quiz_revision->vid, ':rid' => $this->result->result_id));
+    while ($db_row = $ids->fetch()) {
+      if ($report = $this->getAnswer($db_row)) {
+        $questions[] = $report;
+      }
+    }
+    return !empty($questions) ? $questions : array();
+  }
+
+  private function getAnswer($db_row) {
+    // Questions picked from term id's won't be found in the quiz_relationship table
+    if ($db_row->max_score === NULL) {
+      if ($this->quiz_revision->randomization == 2 && isset($this->quiz_revision->tid) && $this->quiz_revision->tid > 0) {
+        $db_row->max_score = $this->quiz_revision->max_score_for_random;
+      }
+      elseif ($this->quiz_revision->randomization == 3) {
+        $db_row->max_score = $db_row->term_max_score;
+      }
+    }
+
+    if (!$module = quiz_question_module_for_type($db_row->type)) {
+      return;
+    }
+
+    // Invoke hook_get_report().
+    if (!$report = module_invoke($module, 'get_report', $db_row->question_nid, $db_row->question_vid, $this->result->result_id)) {
+      return;
+    }
+
+    // Add max score info to the question.
+    if (!isset($report->score_weight)) {
+      $report->qnr_max_score = $db_row->max_score;
+      $report->score_weight = !$report->max_score ? 0 : ($db_row->max_score / $report->max_score);
+    }
+
+    return $report;
+  }
+
+  /**
+   * Get the summary message for a completed quiz.
+   *
+   * Summary is determined by whether we are using the pass / fail options, how
+   * the user did, and where the method is called from.
+   *
+   * @todo Need better feedback for when a user is viewing their quiz results
+   *   from the results list (and possibily when revisiting a quiz they can't take
+   *   again).
+   *
+   * @return
+   *   Filtered summary text or null if we are not displaying any summary.
+   */
+  public function getSummaryText() {
+    $summary = array();
+    $admin = arg(0) === 'admin';
+    $quiz_format = (isset($this->quiz_revision->body[LANGUAGE_NONE][0]['format'])) ? $this->quiz_revision->body[LANGUAGE_NONE][0]['format'] : NULL;
+
+    if (!$admin) {
+      if (!empty($this->score['result_option'])) {
+        // Unscored quiz, return the proper result option.
+        $summary['result'] = check_markup($this->score['result_option'], $quiz_format);
+      }
+      else {
+        $result_option = $this->pickResultOption($this->quiz_revision, $this->score['percentage_score']);
+        $summary['result'] = is_object($result_option) ? check_markup($result_option->option_summary, $result_option->option_summary_format) : '';
+      }
+    }
+
+    // If we are using pass/fail, and they passed.
+    if ($this->quiz_revision->pass_rate > 0 && $this->score['percentage_score'] >= $this->quiz_revision->pass_rate) {
+      // If we are coming from the admin view page.
+      if ($admin) {
+        $summary['passfail'] = t('The user passed this @quiz.', array('@quiz' => QUIZ_NAME));
+      }
+      elseif (variable_get('quiz_use_passfail', 1) == 0) {
+        // If there is only a single summary text, use this.
+        if (trim($this->quiz_revision->summary_default) != '') {
+          $summary['passfail'] = check_markup($this->quiz_revision->summary_default, $quiz_format);
+        }
+      }
+      elseif (trim($this->quiz_revision->summary_pass) != '') {
+        // If there is a pass summary text, use this.
+        $summary['passfail'] = check_markup($this->quiz_revision->summary_pass, $this->quiz_revision->summary_pass_format);
+      }
+    }
+    // If the user did not pass or we are not using pass/fail.
+    else {
+      // If we are coming from the admin view page, only show a summary if we are
+      // using pass/fail.
+      if ($admin) {
+        if ($this->quiz_revision->pass_rate > 0) {
+          $summary['passfail'] = t('The user failed this @quiz.', array('@quiz' => QUIZ_NAME));
+        }
+        else {
+          $summary['passfail'] = t('the user completed this @quiz.', array('@quiz' => QUIZ_NAME));
+        }
+      }
+      elseif (trim($this->quiz_revision->summary_default) != '') {
+        $summary['passfail'] = check_markup($this->quiz_revision->summary_default, $this->quiz_revision->summary_default_format);
+      }
+    }
+    return $summary;
+  }
+
+  /**
+   * Get summary text for a particular score from a set of result options.
+   *
+   * @param QuizEntity $quiz
+   * @param int $score
+   *   The user's final score.
+   *
+   * @return string
+   *   Summary text for the user's score.
+   */
+  private function pickResultOption(QuizEntity $quiz, $score) {
+    foreach ($quiz->resultoptions as $option) {
+      if ($score < $option['option_start'] || $score > $option['option_end']) {
+        continue;
+      }
+      return (object) array('option_summary' => $option['option_summary'], 'option_summary_format' => $option['option_summary_format']);
+    }
+  }
+
+}
diff --git a/src/Controller/QuizResultController.php b/src/Controller/QuizResultController.php
new file mode 100644
index 0000000..67aa67b
--- /dev/null
+++ b/src/Controller/QuizResultController.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuizResultController extends QuizResultBaseController {
+
+  /**
+   * Callback for quiz-result/%
+   *
+   * Quiz result report page for the quiz admin section
+   *
+   * @param QuizEntity $quiz
+   * @param $result_id
+   *   The result id
+   */
+  public static function staticCallback($quiz, $result_id) {
+    $result = quiz_result_load($result_id);
+    $quiz_revision = quiz_entity_single_load($quiz->qid, $result->vid);
+    $obj = new static($quiz, $quiz_revision, $result);
+    return $obj->render();
+  }
+
+  public function render() {
+    // Get all the data we need.
+    // Lets add the quiz title to the breadcrumb array.
+    # $breadcrumb = drupal_get_breadcrumb();
+    # $breadcrumb[] = l(t('Quiz Results'), 'admin/quiz/reports/results');
+    # $breadcrumb[] = l($quiz->title, 'admin/quiz/reports/results/' . __quiz_entity_id($quiz));
+    # drupal_set_breadcrumb($breadcrumb);
+
+    $data = array(
+        'quiz'      => $this->quiz_revision,
+        'questions' => $this->getAnswers(),
+        'score'     => $this->score,
+        'summary'   => $this->getSummaryText(),
+        'result_id' => $this->result->result_id,
+        'account'   => user_load($this->result->uid),
+    );
+
+    return theme('quiz_result', $data);
+  }
+
+}
diff --git a/src/Controller/QuizTakeController.php b/src/Controller/QuizTakeController.php
new file mode 100644
index 0000000..efa1638
--- /dev/null
+++ b/src/Controller/QuizTakeController.php
@@ -0,0 +1,216 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Controller\Legacy\QuizTakeLegacyController;
+use RuntimeException;
+use stdClass;
+
+class QuizTakeController extends QuizTakeLegacyController {
+
+  /** @var stdClass */
+  private $result;
+
+  /** @var stdClass */
+  private $account;
+
+  /**
+   * Callback for quiz/%/take
+   */
+  public static function staticCallback($quiz) {
+    global $user;
+
+    try {
+      if (isset($quiz->rendered_content)) {
+        return $quiz->rendered_content;
+      }
+
+      $controller = new static($quiz, $user);
+      $controller->initQuizResult();
+      if ($controller->getResultId()) {
+        drupal_goto($controller->getQuestionTakePath());
+      }
+    }
+    catch (RuntimeException $e) {
+      throw $e;
+      return array(
+          'body' => array('#markup' => $e->getMessage())
+      );
+    }
+  }
+
+  public function __construct($quiz, $account) {
+    $this->quiz = $quiz;
+    $this->account = $account;
+  }
+
+  public function initQuizResult() {
+    // Inject result from user's session
+    if (!empty($_SESSION['quiz'][$this->getQuizId()]['result_id'])) {
+      $this->result_id = $_SESSION['quiz'][$this->getQuizId()]['result_id'];
+      $this->result = quiz_result_load($this->result_id);
+    }
+
+    // Enforce that we have the same quiz version.
+    if (($this->result) && ($this->quiz->vid != $this->result->quiz_vid)) {
+      $this->quiz = quiz_entity_single_load($this->getQuizId(), $this->quiz->vid);
+    }
+
+    // Resume quiz progress
+    if (!$this->result && $this->quiz->allow_resume) {
+      $this->initQuizResume();
+    }
+
+    // Start new quiz progress
+    if (!$this->result) {
+      if (!$this->checkAvailability()) {
+        throw new RuntimeException(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
+      }
+
+      $this->result = $this->createQuizResultObject();
+      $this->result_id = $this->result->result_id;
+      $_SESSION['quiz'][$this->getQuizId()]['result_id'] = $this->result->result_id;
+      $_SESSION['quiz'][$this->getQuizId()]['current'] = 1;
+
+      // Call hook_quiz_begin().
+      module_invoke_all('quiz_begin', $this->quiz, $this->result->result_id);
+    }
+
+    if (!quiz()->getQuizHelper()->isAvailable($this->quiz)) {
+      throw new RuntimeException(t('This @quiz is not available.', array('@quiz' => QUIZ_NAME)));
+    }
+  }
+
+  /**
+   * If we allow resuming we can load it from the database.
+   */
+  public function initQuizResume() {
+    if (!$result_id = $this->activeResultId($this->account->uid, $this->quiz->vid)) {
+      return FALSE;
+    }
+
+    $_SESSION['quiz'][$this->getQuizId()]['result_id'] = $result_id;
+    $_SESSION['quiz'][$this->getQuizId()]['current'] = 1;
+    $this->result = quiz_result_load($result_id);
+    $this->quiz = quiz_entity_single_load($this->result->quiz_qid, $this->result->quiz_vid);
+    $this->result_id = $result_id;
+
+    // Resume a quiz from the database.
+    drupal_set_message(t('Resuming a previous @quiz in-progress.', array('@quiz' => QUIZ_NAME)), 'status');
+  }
+
+  /**
+   * Actions to take place at the start of a quiz.
+   *
+   * This is called when the quiz entity is viewed for the first time. It ensures
+   * that the quiz can be taken at this time.
+   *
+   * @return
+   *   Return quiz_results result_id, or FALSE if there is an error.
+   */
+  private function checkAvailability() {
+    $user_is_admin = entity_access('update', 'quiz_entity', $this->quiz);
+
+    // Make sure this is available.
+    if ($this->quiz->quiz_always != 1) {
+      // Compare current GMT time to the open and close dates (which should still
+      // be in GMT time).
+      $now = REQUEST_TIME;
+
+      if ($now >= $this->quiz->quiz_close || $now < $this->quiz->quiz_open) {
+        if ($user_is_admin) {
+          $msg = t('You are marked as an administrator or owner for this @quiz. While you can take this @quiz, the open/close times prohibit other users from taking this @quiz.', array('@quiz' => QUIZ_NAME));
+          drupal_set_message($msg, 'status');
+        }
+        else {
+          $msg = t('This @quiz is not currently available.', array('@quiz' => QUIZ_NAME));
+          drupal_set_message($msg, 'status');
+          return FALSE; // Can't take quiz.
+        }
+      }
+    }
+
+    // Check to see if this user is allowed to take the quiz again:
+    if ($this->quiz->takes > 0) {
+      $taken = db_query("SELECT COUNT(*) AS takes FROM {quiz_results} WHERE uid = :uid AND quiz_qid = :qid", array(
+          ':uid' => $this->account->uid,
+          ':qid' => $this->getQuizId()
+        ))->fetchField();
+      $allowed_times = format_plural($this->quiz->takes, '1 time', '@count times');
+      $taken_times = format_plural($taken, '1 time', '@count times');
+
+      // The user has already taken this quiz.
+      if ($taken) {
+        if ($user_is_admin) {
+          $msg = t('You have taken this @quiz already. You are marked as an owner or administrator for this quiz, so you can take this quiz as many times as you would like.', array('@quiz' => QUIZ_NAME));
+          drupal_set_message($msg, 'status');
+        }
+        // If the user has already taken this quiz too many times, stop the user.
+        elseif ($taken >= $this->quiz->takes) {
+          $msg = t('You have already taken this @quiz @really. You may not take it again.', array('@quiz', QUIZ_NAME, '@really' => $taken_times));
+          drupal_set_message($msg, 'error');
+          return FALSE;
+        }
+        // If the user has taken the quiz more than once, see if we should report
+        // this.
+        elseif ($this->quiz->show_attempt_stats) {
+          $msg = t("You can only take this @quiz @allowed. You have taken it @really.", array('@quiz' => QUIZ_NAME, '@allowed' => $allowed_times, '@really' => $taken_times));
+          drupal_set_message($msg, 'status');
+        }
+      }
+    }
+
+    // Check to see if the user is registered, and user alredy passed this quiz.
+    if ($this->quiz->show_passed && $this->account->uid && quiz()->getQuizHelper()->isPassed($this->account->uid, $this->getQuizId(), $this->quiz->vid)) {
+      $msg = t('You have already passed this @quiz.', array('@quiz' => QUIZ_NAME));
+      drupal_set_message($msg, 'status');
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Initialize a quiz attempt.
+   *
+   * @return QuizResult
+   *   The quiz attempt.
+   */
+  private function createQuizResultObject() {
+    // Create question list.
+    $questions = $this->quiz->getQuestionLoader()->getQuestionList();
+    if ($questions === FALSE) {
+      $msg = t('Not enough random questions were found. Please add more questions before trying to take this @quiz.', array('@quiz' => QUIZ_NAME));
+      throw new RuntimeException($msg);
+    }
+
+    if (!count($questions)) {
+      $msg = t('No questions were found. Please !assign_questions before trying to take this @quiz.', array('@quiz' => QUIZ_NAME, '!assign_questions' => l(t('assign questions'), 'quiz/' . $this->getQuizId() . '/questions')));
+      throw new RuntimeException($msg);
+    }
+
+    $quiz_result = entity_create('quiz_result', array(
+        'quiz_qid'   => $this->getQuizId(),
+        'quiz_vid'   => $this->quiz->vid,
+        'uid'        => $this->account->uid,
+        'time_start' => REQUEST_TIME,
+        'layout'     => $questions,
+    ));
+
+    // Write the layout for this result.
+    entity_save('quiz_result', $quiz_result);
+
+    $i = 0;
+    foreach ($questions as $question) {
+      $quiz_result_answer = entity_create('quiz_result_answer', array(
+          'result_id'    => $quiz_result->result_id,
+          'question_nid' => $question['nid'],
+          'question_vid' => $question['vid'],
+          'number'       => ++$i,
+      ));
+      entity_save('quiz_result_answer', $quiz_result_answer);
+    }
+
+    return quiz_result_load($quiz_result->result_id);
+  }
+
+}
diff --git a/src/Controller/QuizTakeQuestionController.php b/src/Controller/QuizTakeQuestionController.php
new file mode 100644
index 0000000..6beabb7
--- /dev/null
+++ b/src/Controller/QuizTakeQuestionController.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Helper\Quiz\TakingHelper;
+
+class QuizTakeQuestionController extends TakingHelper {
+
+  private $quiz;
+  private $question;
+  private $page_number;
+
+  /** @var \Drupal\quiz\Entity\Result */
+  private $result;
+  private $quiz_uri;
+  private $quiz_id;
+
+  /**
+   * Callback for quiz/%/take/%. Take a quiz questions.
+   *
+   * @param QuizEntity $quiz A quiz entity
+   * @param int $page_number
+   *   A question number, starting at 1. Pages do not have question numbers. Quiz
+   *   directions are considered part of the numbering.
+   */
+  public static function staticCallback($quiz, $page_number) {
+    $result = $layout_item = NULL;
+    $quiz_id = $quiz->qid;
+
+    if (isset($_SESSION['quiz'][$quiz_id]['result_id'])) {
+      $result = quiz_result_load($_SESSION['quiz'][$quiz_id]['result_id']);
+    }
+
+    // Load the page that the requested question belongs to.
+    if ($result && ($_layout_item = $result->getPageItem($page_number))) {
+      $layout_item = node_load($_layout_item['nid']);
+    }
+
+    $controller = new static($quiz, $result, $page_number, $layout_item);
+    return $controller->render();
+  }
+
+  public function __construct($quiz, $result, $question_number, $question) {
+    drupal_set_title($quiz->title);
+
+    $this->quiz = $quiz;
+    $this->result = $result;
+    $this->page_number = $question_number;
+    $this->question = $question;
+
+    // Legacy code
+    $this->quiz_uri = 'quiz/' . $quiz->qid;
+    $this->quiz_id = $quiz->qid;
+
+    // Question disappeared or invalid session. Start over.
+    if (!$question) {
+      drupal_set_message(t('Invalid session.'), 'error');
+      unset($_SESSION['quiz'][$this->quiz_id]);
+      drupal_goto($this->quiz_uri);
+    }
+  }
+
+  public function render() {
+    $content = array();
+    $questions = array();
+    $i = 0;
+
+    // Mark this as the current question.
+    $this->redirect($this->quiz, $this->page_number);
+
+    // Added the progress info to the view.
+    foreach ($this->result->layout as $idx => $question) {
+      if (empty($question['qr_pid'])) {
+        $questions[$idx] = ++$i; // Question has no parent. Show it in the jumper.
+      }
+    }
+
+    $content['progress']['#markup'] = theme('quiz_progress', array(
+        'quiz'          => $this->quiz,
+        'questions'     => $questions,
+        'current'       => $this->page_number,
+        'allow_jumping' => $this->quiz->allow_jumping,
+        'pager'         => count($questions) >= variable_get('quiz_pager_start', 100),
+        'time_limit'    => $this->quiz->time_limit,
+    ));
+    $content['progress']['#weight'] = -50;
+
+    if (function_exists('jquery_countdown_add') && variable_get('quiz_has_timer', 0) && $this->quiz->time_limit) {
+      $this->attachJs($this->result->time_start + $this->quiz->time_limit - REQUEST_TIME);
+    }
+
+    $question_form = drupal_get_form('quiz_answer_form', $this->quiz, $this->question, $this->page_number, $this->result);
+    $content['body']['question']['#markup'] = drupal_render($question_form);
+
+    return $content;
+  }
+
+  /**
+   * @param int $time
+   */
+  private function attachJs($time) {
+    jquery_countdown_add('.countdown', array(
+        'until'    => $time,
+        'onExpiry' => 'quiz_take_finished',
+        'compact'  => TRUE,
+        'layout'   => t('Time left') . ': {hnn}{sep}{mnn}{sep}{snn}'
+    ));
+
+    // These are the two button op values that are accepted for answering questions.
+    $vars = array('quiz_button_1' => t('Finish'), 'quiz_button_2' => t('Next'));
+    drupal_add_js($vars, array('type' => 'setting'));
+    drupal_add_js(drupal_get_path('module', 'quiz') . '/misc/js/quiz.take.count-down.js');
+  }
+
+}
diff --git a/src/Controller/QuizUserResultController.php b/src/Controller/QuizUserResultController.php
new file mode 100644
index 0000000..07a782d
--- /dev/null
+++ b/src/Controller/QuizUserResultController.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\quiz\Controller;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Entity\Result;
+
+/**
+ * Callback for:
+ *
+ *  - quiz-result/%
+ *  - user/%/quiz-results/%quiz_result/view
+ *
+ * Show result page for a given result
+ */
+class QuizUserResultController extends QuizResultBaseController {
+
+  /**
+   * @param Result $result
+   */
+  public static function staticCallback($result) {
+    $quiz = quiz_entity_single_load($result->quiz_qid);
+    $quiz_revision = quiz_entity_single_load($result->quiz_qid, $result->quiz_vid);
+    $obj = new static($quiz, $quiz_revision, $result);
+    return $obj->render();
+  }
+
+  /**
+   * Render user's result.
+   *
+   * Check issue #2362097
+   */
+  public function render() {
+    $this->setBreadcrumb();
+
+    $data = array(
+        'quiz'      => $this->quiz_revision,
+        'questions' => $this->getAnswers(),
+        'score'     => $this->score,
+        'summary'   => $this->getSummaryText(),
+        'result_id' => $this->result->result_id,
+        'account'   => user_load($this->result->uid),
+    );
+
+    // User can view own quiz results OR the current quiz has "display solution".
+    if (user_access('view own quiz results')) {
+      return theme('quiz_result', $data);
+    }
+
+    // the current quiz has "display solution".
+    if (!empty($this->quiz->review_options['end']) && array_filter($this->quiz->review_options['end'])) {
+      return theme('quiz_result', $data);
+    }
+
+    // User cannot view own results or show solution. Show summary.
+    return theme('quiz_result', $data);
+  }
+
+  private function setBreadcrumb() {
+    $bc = drupal_get_breadcrumb();
+    $bc[] = l($this->quiz->title, 'quiz/' . $this->quiz_id);
+    drupal_set_breadcrumb($bc);
+  }
+
+}
diff --git a/src/Entity/AnswerMetadataController.php b/src/Entity/AnswerMetadataController.php
new file mode 100644
index 0000000..fbad35e
--- /dev/null
+++ b/src/Entity/AnswerMetadataController.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultMetadataController;
+
+class AnswerMetadataController extends EntityDefaultMetadataController {
+
+  public function entityPropertyInfo() {
+    $info = parent::entityPropertyInfo();
+    $properties = &$info[$this->type]['properties'];
+
+    $properties['result_id']['type'] = 'quiz_result';
+    $properties['question_nid']['type'] = 'node';
+    $properties['is_correct']['type'] = 'boolean';
+    $properties['is_skipped']['type'] = 'boolean';
+    $properties['is_doubtful']['type'] = 'boolean';
+    $properties['answer_timestamp']['type'] = 'date';
+
+    return $info;
+  }
+
+}
diff --git a/src/Entity/QuizEntity.php b/src/Entity/QuizEntity.php
new file mode 100644
index 0000000..ba71da7
--- /dev/null
+++ b/src/Entity/QuizEntity.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use Drupal\quiz\Entity\QuizEntity\QuestionLoader;
+use Entity;
+
+class QuizEntity extends Entity {
+
+  /** @var int Quiz ID */
+  public $qid;
+
+  /** @var int Quiz Revision ID */
+  public $vid;
+
+  /** @var int */
+  public $status;
+
+  /** @var string The name of the quiz type. */
+  public $type = 'quiz';
+
+  /** @var string The quiz label. */
+  public $title;
+
+  /** @var integer The user id of the quiz owner. */
+  public $uid;
+
+  /** @var integer The Unix timestamp when the quiz was created. */
+  public $created;
+
+  /** @var integer The Unix timestamp when the quiz was most recently saved. */
+  public $changed;
+
+  /** @var bool */
+  public $allow_jumping;
+
+  /** @var array */
+  public $resultoptions = array();
+
+  /** @var bool Magic flag to create new revision on save */
+  public $is_new_revision;
+
+  /** @var string Revision log */
+  public $log;
+
+  /** @var \Drupal\quiz\Entity\QuizEntity\QuestionLoader */
+  private $question_loader;
+
+  public function __construct(array $values = array()) {
+    parent::__construct($values, 'quiz_entity');
+  }
+
+  public function save() {
+    global $user;
+
+    // Entity datetime
+    $this->changed = time();
+    if ($this->is_new = isset($this->is_new) ? $this->is_new : 0) {
+      $this->created = time();
+      if (null === $this->uid) {
+        $this->uid = $user->uid;
+      }
+    }
+
+    // Default properties
+    foreach (quiz_controller()->getSettingIO()->getQuizDefaultSettings() as $k => $v) {
+      if (!isset($this->{$k})) {
+        $this->{$k} = $v;
+      }
+    }
+
+    return parent::save();
+  }
+
+  /**
+   * Default quiz entity uri.
+   */
+  protected function defaultUri() {
+    return array('path' => 'quiz/' . $this->identifier());
+  }
+
+  /**
+   * @return QuestionLoader
+   */
+  public function getQuestionLoader() {
+    if (NULL === $this->question_loader) {
+      $this->question_loader = new QuestionLoader($this);
+    }
+    return $this->question_loader;
+  }
+
+  /**
+   * Get data for all terms belonging to a Quiz with categorized random questions
+   *
+   * @return array
+   *  Array with all terms that belongs to the quiz as objects
+   */
+  public function getTermsByVid() {
+    return db_query('SELECT td.name, qt.*
+        FROM {quiz_terms} qt
+        JOIN {taxonomy_term_data} td ON qt.tid = td.tid
+        WHERE qt.vid = :vid ORDER BY qt.weight', array(
+          ':vid' => $this->vid
+      ))->fetchAll();
+  }
+
+}
diff --git a/src/Entity/QuizEntity/DefaultPropertiesIO.php b/src/Entity/QuizEntity/DefaultPropertiesIO.php
new file mode 100644
index 0000000..8827a0c
--- /dev/null
+++ b/src/Entity/QuizEntity/DefaultPropertiesIO.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Drupal\quiz\Entity\QuizEntity;
+
+use Drupal\quiz\Helper\FormHelper;
+use Drupal\quiz\Entity\QuizEntity;
+
+/**
+ * Read and write default properties for quiz entity.
+ */
+class DefaultPropertiesIO extends FormHelper {
+
+  /**
+   * Returns the users default settings.
+   *
+   * @return
+   *   An array of settings. The array is empty in case no settings are available.
+   */
+  public function getUserDefaultSettings($remove_ids = TRUE) {
+    global $user;
+
+    // We found user defaults.
+    $conditions = array('status' => -1, 'uid' => $user->uid, 'qid' => 0, 'vid' => 0);
+    if ($quizzes = entity_load('quiz_entity', FALSE, $conditions)) {
+      $quiz = reset($quizzes);
+
+      if ($remove_ids) {
+        $quiz->qid = $quiz->uid = $quiz->vid = $quiz->quiz_open = $quiz->quiz_close = NULL;
+      }
+
+      return $quiz;
+    }
+
+    return $this->getSystemDefaultSettings($remove_ids);
+  }
+
+  public function getSystemDefaultSettings($remove_ids = TRUE) {
+    // Found global defaults.
+    $conditions = array('status' => -1, 'uid' => 0);
+    if ($quizzes = entity_load('quiz_entity', FALSE, $conditions)) {
+      $quiz = reset($quizzes);
+
+      if ($remove_ids) {
+        $quiz->qid = $quiz->uid = $quiz->vid = $quiz->quiz_open = $quiz->quiz_close = NULL;
+      }
+
+      return $quiz;
+    }
+
+    return entity_create('quiz_entity', $this->getQuizDefaultSettings());
+  }
+
+  /**
+   * Returns default values for all quiz settings.
+   *
+   * @todo also store this in the quiz_node_properties table
+   *
+   * @return
+   *   Array of default values.
+   */
+  public function getQuizDefaultSettings() {
+    return array(
+        'status'                     => -1,
+        'aid'                        => NULL,
+        'allow_jumping'              => 0,
+        'allow_resume'               => 1,
+        'allow_skipping'             => 1,
+        'always_available'           => TRUE,
+        'backwards_navigation'       => 1,
+        'has_userpoints'             => 0,
+        'keep_results'               => 2,
+        'mark_doubtful'              => 0,
+        'max_score'                  => 0,
+        'max_score_for_random'       => 1,
+        'number_of_random_questions' => 0,
+        'pass_rate'                  => 75,
+        'quiz_always'                => 1,
+        'quiz_close'                 => 0,
+        'quiz_close'                 => $this->prepareDate(NULL, variable_get('quiz_default_close', 30)),
+        'quiz_open'                  => 0,
+        'quiz_open'                  => $this->prepareDate(),
+        'randomization'              => 0,
+        'repeat_until_correct'       => 0,
+        'review_options'             => array('question' => array(), 'end' => array()),
+        'show_attempt_stats'         => 1,
+        'show_passed'                => 1,
+        'summary_default'            => '',
+        'summary_default_format'     => filter_fallback_format(),
+        'summary_pass'               => '',
+        'summary_pass_format'        => filter_fallback_format(),
+        'takes'                      => 0,
+        'tid'                        => 0,
+        'time_limit'                 => 0,
+        'userpoints_tid'             => 0,
+    );
+  }
+
+  public function updateUserDefaultSettings(QuizEntity $_quiz) {
+    global $user;
+
+    $quiz = clone $_quiz;
+
+    $quiz->aid = !empty($quiz->aid) ? $quiz->aid : 0;
+    $quiz->summary_pass = is_array($quiz->summary_pass) ? $quiz->summary_pass['value'] : $quiz->summary_pass;
+    $quiz->summary_pass_format = is_array($quiz->summary_pass) ? $quiz->summary_pass['format'] : isset($quiz->summary_pass_format) ? $quiz->summary_pass_format : filter_fallback_format();
+    $quiz->summary_default = is_array($quiz->summary_default) ? $quiz->summary_default['value'] : $quiz->summary_default;
+    $quiz->summary_default_format = is_array($quiz->summary_default) ? $quiz->summary_default['format'] : isset($quiz->summary_default_format) ? $quiz->summary_default_format : filter_fallback_format();
+    $quiz->tid = isset($quiz->tid) ? $quiz->tid : 0;
+
+    if (!empty($quiz->remember_settings)) {
+      // Save user defaults.
+      $user_quiz = clone $quiz;
+      $user_quiz->uid = $user->uid;
+
+      // Find ID of old entry
+      $conditions = array('status' => -1, 'uid' => $user->uid, 'qid' => 0, 'vid' => 0);
+      if ($quizzes = entity_load('quiz_entity', FALSE, $conditions)) {
+        $_user_quiz = reset($quizzes);
+        $user_quiz->qid = $_user_quiz->qid;
+        $user_quiz->vid = $_user_quiz->vid;
+      }
+      else {
+        $user_quiz->qid = $user_quiz->vid = NULL;
+      }
+
+      $this->saveQuizSettings($user_quiz);
+    }
+
+    if (!empty($quiz->remember_global)) {
+      $system_quiz = clone $quiz;
+      $system_quiz->uid = 0;
+
+      // Find ID of old entry
+      $conditions = array('status' => -1, 'uid' => 0);
+      if ($quizzes = entity_load('quiz_entity', FALSE, $conditions, TRUE)) {
+        $_system_quiz = reset($quizzes);
+        $system_quiz->qid = $_system_quiz->qid;
+        $system_quiz->vid = $_system_quiz->vid;
+      }
+      else {
+        $system_quiz->qid = $system_quiz->vid = NULL;
+      }
+
+      return $this->saveQuizSettings($system_quiz);
+    }
+  }
+
+  /**
+   * Insert or update the quiz entity properties accordingly.
+   */
+  private function saveQuizSettings(QuizEntity $quiz) {
+    $quiz->title = '';
+    $quiz->status = -1;
+    return entity_save('quiz_entity', $quiz);
+  }
+
+}
diff --git a/src/Entity/QuizEntity/QuestionLoader.php b/src/Entity/QuizEntity/QuestionLoader.php
new file mode 100644
index 0000000..e025d0a
--- /dev/null
+++ b/src/Entity/QuizEntity/QuestionLoader.php
@@ -0,0 +1,258 @@
+<?php
+
+namespace Drupal\quiz\Entity\QuizEntity;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class QuestionLoader {
+
+  private $quiz;
+
+  public function __construct(QuizEntity $quiz) {
+    $this->quiz = $quiz;
+  }
+
+  /**
+   * Builds the questionlist for quizzes with categorized random questions
+   */
+  public function buildCategoziedQuestionList() {
+    if (!$question_types = array_keys(quiz_get_question_types())) {
+      return array();
+    }
+
+    $questions = array();
+    $nids = array();
+    $total_count = 0;
+    foreach ($this->quiz->getTermsByVid() as $term) {
+      $query = db_select('node', 'n');
+      $query->join('taxonomy_index', 'tn', 'n.nid = tn.nid');
+      $query->fields('n', array('nid', 'vid'));
+      $query->fields('tn', array('tid'));
+      $query->condition('n.status', 1);
+      $query->condition('n.type', $question_types);
+      $query->condition('tn.tid', $term->tid);
+      if (!empty($nids)) {
+        $query->condition('n.nid', $nids, 'NOT IN');
+      }
+      $query->range(0, $term->number);
+      $query->orderBy('RAND()');
+
+      $result = $query->execute();
+      $count = 0;
+      while ($question = $result->fetchAssoc()) {
+        $count++;
+        $question['tid'] = $term->tid;
+        $question['number'] = $count + $total_count;
+        $questions[] = $question;
+        $nids[] = $question['nid'];
+      }
+      $total_count += $count;
+      if ($count < $term->number) {
+        return array(); // Not enough questions
+      }
+    }
+    return $questions;
+  }
+
+  /**
+   * Retrieves a list of questions (to be taken) for a given quiz.
+   *
+   * If the quiz has random questions this function only returns a random
+   * selection of those questions. This function should be used to decide
+   * what questions a quiz taker should answer.
+   *
+   * This question list is stored in the user's result, and may be different
+   * when called multiple times. It should only be used to generate the layout
+   * for a quiz attempt and NOT used to do operations on the questions inside of
+   * a quiz.
+   *
+   * @return array[] Array of question info.
+   */
+  public function getQuestionList() {
+    if ($this->quiz->randomization == 3) {
+      $questions = $this->buildCategoziedQuestionList();
+    }
+    else {
+      $questions = $this->getRequiredQuestions();
+    }
+
+    $count = 0;
+    $display_count = 0;
+    $questions_out = array();
+    foreach ($questions as &$question) {
+      $display_count++;
+      $question['number'] = ++$count;
+      if ($question['type'] !== 'quiz_page') {
+        $question['display_number'] = $display_count;
+      }
+      $questions_out[$count] = $question;
+    }
+    return $questions_out;
+  }
+
+  private function getRequiredQuestions() {
+    $questions = array();
+
+    // Get required questions first.
+    $query = db_query('SELECT n.nid, n.vid, n.type, qnr.qr_id, qnr.qr_pid
+        FROM {quiz_relationship} qnr
+          JOIN {node} n ON qnr.question_nid = n.nid
+          LEFT JOIN {quiz_relationship} qnr2 ON (qnr.qr_pid = qnr2.qr_id OR (qnr.qr_pid IS NULL AND qnr.qr_id = qnr2.qr_id))
+        WHERE qnr.quiz_vid = :quiz_vid
+          AND qnr.question_status = :question_status
+          AND n.status = 1
+        ORDER BY qnr2.weight, qnr.weight', array(
+        ':quiz_vid'        => $this->quiz->vid,
+        ':question_status' => QUESTION_ALWAYS
+    ));
+    $i = 0;
+    while ($question_node = $query->fetchAssoc()) {
+      // Just to make it easier on us, let's use a 1-based index.
+      $i++;
+      $questions[$i] = $question_node;
+    }
+
+    // Get random questions for the remainder.
+    if ($this->quiz->number_of_random_questions > 0) {
+      $random_questions = $this->getRandomQuestions();
+      $questions = array_merge($questions, $random_questions);
+      if ($this->quiz->number_of_random_questions > count($random_questions)) {
+        // Unable to find enough requested random questions.
+        return FALSE;
+      }
+    }
+
+    // Shuffle questions if required.
+    if ($this->quiz->randomization > 0) {
+      shuffle($questions);
+    }
+
+    return $questions;
+  }
+
+  /**
+   * Get an array list of random questions for a quiz.
+   *
+   * @return array[] Array of nid/vid combos for quiz questions.
+   */
+  private function getRandomQuestions() {
+    $num_random = $this->quiz->number_of_random_questions;
+    $tid = $this->quiz->tid;
+    $questions = array();
+    if ($num_random > 0) {
+      if ($tid > 0) {
+        $questions = quiz()->getQuizHelper()->getRandomTaxonomyQuestionIds($tid, $num_random);
+      }
+      else {
+        // Select random question from assigned pool.
+        $result = db_query_range(
+          "SELECT question_nid as nid, question_vid as vid, n.type
+          FROM {quiz_relationship} qnr
+          JOIN {node} n on qnr.question_nid = n.nid
+          WHERE qnr.quiz_vid = :quiz_vid
+          AND qnr.quiz_qid = :quiz_qid
+          AND qnr.question_status = :question_status
+          AND n.status = 1
+          ORDER BY RAND()", 0, $this->quiz->number_of_random_questions, array(
+            ':quiz_vid'        => $this->quiz->vid,
+            ':quiz_qid'        => $this->quiz->qid,
+            ':question_status' => QUESTION_RANDOM
+          )
+        );
+        while ($question_node = $result->fetchAssoc()) {
+          $question_node['random'] = TRUE;
+          $question_node['relative_max_score'] = $this->quiz->max_score_for_random;
+          $questions[] = $question_node;
+        }
+      }
+    }
+    return $questions;
+  }
+
+  /**
+   * Retrieve list of published questions assigned to quiz.
+   *
+   * This function should be used for question browsers and similiar... It should
+   * not be used to decide what questions a user should answer when taking a
+   * quiz. quiz_build_question_list is written for that purpose.
+   *
+   * @return
+   *   An array of questions.
+   */
+  public function getQuestions() {
+    $questions = array();
+    $query = db_select('node', 'n');
+    $query->fields('n', array('nid', 'type'));
+    $query->fields('nr', array('vid', 'title'));
+    $query->fields('qnr', array('question_status', 'weight', 'max_score', 'auto_update_max_score', 'qr_id', 'qr_pid'));
+    $query->addField('n', 'vid', 'latest_vid');
+    $query->join('node_revision', 'nr', 'n.nid = nr.nid');
+    $query->leftJoin('quiz_relationship', 'qnr', 'nr.vid = qnr.question_vid');
+    $query->condition('n.status', 1);
+    $query->condition('qnr.quiz_qid', $this->quiz->qid);
+    if ($this->quiz->vid) {
+      $query->condition('qnr.quiz_vid', $this->quiz->vid);
+    }
+    $query->condition('qr_pid', NULL, 'IS');
+    $query->orderBy('qnr.weight');
+
+    $result = $query->execute();
+    foreach ($result as $question) {
+      $questions[] = $question;
+      $this->getSubQuestions($question->qr_id, $questions);
+    }
+
+    foreach ($questions as &$node) {
+      $node = $this->reloadQuestion($node);
+    }
+
+    return $questions;
+  }
+
+  private function getSubQuestions($qr_pid, &$questions) {
+    $query = db_select('node', 'n');
+    $query->fields('n', array('nid', 'type'));
+    $query->fields('nr', array('vid', 'title'));
+    $query->fields('qnr', array('question_status', 'weight', 'max_score', 'auto_update_max_score', 'qr_id', 'qr_pid'));
+    $query->addField('n', 'vid', 'latest_vid');
+    $query->innerJoin('node_revision', 'nr', 'n.nid = nr.nid');
+    $query->innerJoin('quiz_relationship', 'qnr', 'nr.vid = qnr.question_vid');
+    $query->condition('qr_pid', $qr_pid);
+    $query->orderBy('weight');
+    $result = $query->execute();
+    foreach ($result as $question) {
+      $questions[] = $question;
+    }
+  }
+
+  /**
+   * Map node properties to a question object.
+   *
+   *  This was 'quiz_node_map($node)' before.
+   *
+   * @param $node
+   *  The question node.
+   *
+   * @return
+   *  Question object.
+   */
+  private function reloadQuestion($node) {
+    $question = node_load($node->nid, $node->vid);
+
+    // Append extra fields.
+    $question->latest_vid = $node->latest_vid;
+    $question->question_status = isset($node->question_status) ? $node->question_status : QUESTION_NEVER;
+    if (isset($node->max_score)) {
+      $question->max_score = $node->max_score;
+    }
+    if (isset($node->auto_update_max_score)) {
+      $question->auto_update_max_score = $node->auto_update_max_score;
+    }
+    $question->weight = $node->weight;
+    $question->qr_id = $node->qr_id;
+    $question->qr_pid = $node->qr_pid;
+
+    return $question;
+  }
+
+}
diff --git a/src/Entity/QuizEntityController.php b/src/Entity/QuizEntityController.php
new file mode 100644
index 0000000..7396e16
--- /dev/null
+++ b/src/Entity/QuizEntityController.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use DatabaseTransaction;
+use EntityAPIController;
+use stdClass;
+
+class QuizEntityController extends EntityAPIController {
+
+  /** @var \Drupal\quiz\Entity\QuizEntity\DefaultPropertiesIO */
+  private $default_properties_io;
+
+  /**
+   * @return \Drupal\quiz\Entity\QuizEntity\DefaultPropertiesIO
+   */
+  public function getSettingIO() {
+    if (NULL === $this->default_properties_io) {
+      $this->default_properties_io = new \Drupal\quiz\Entity\QuizEntity\DefaultPropertiesIO();
+    }
+    return $this->default_properties_io;
+  }
+
+  /**
+   * @param QuizEntity $quiz
+   */
+  public function buildContent($quiz, $view_mode = 'full', $langcode = NULL, $content = array()) {
+    drupal_alter('quiz_view', $quiz, $view_mode);
+
+    $extra_fields = field_extra_fields_get_display($this->entityType, $quiz->type, $view_mode);
+
+    // Render Stats
+    if ($extra_fields['stats']['visible']) {
+      // Number of questions is needed on the statistics page.
+      $quiz->number_of_questions = $quiz->number_of_random_questions + quiz()->getQuizHelper()->countAlwaysQuestions($quiz->vid);
+
+      $content['quiz_entity'][$quiz->qid]['stats'] = array(
+          '#markup' => theme('quiz_view_stats', array('quiz' => $quiz)),
+          '#weight' => $extra_fields['stats']['weight'],
+      );
+    }
+
+    // Render take button
+    if ($extra_fields['take']['visible']) {
+      $markup = l(t('Start @quiz', array('@quiz' => QUIZ_NAME)), 'quiz/' . $quiz->qid . '/take');
+      if (TRUE !== $checking = quiz()->getQuizHelper()->isAvailable($quiz)) {
+        $markup = $checking;
+      }
+
+      $content['quiz_entity'][$quiz->qid]['take'] = array(
+          '#prefix' => '<div class="quiz-not-available">',
+          '#suffix' => '</div>',
+          '#weight' => $extra_fields['take']['weight'],
+          '#markup' => $markup,
+      );
+    }
+
+    return parent::buildContent($quiz, $view_mode, $langcode, $content);
+  }
+
+  public function load($ids = array(), $conditions = array()) {
+    $entities = parent::load($ids, $conditions);
+
+    // quiz_entity_revision.review_options => serialize = TRUE already, not sure
+    // why it's string here
+    foreach ($entities as $entity) {
+      $vids[] = $entity->vid;
+      if (!empty($entity->review_options) && is_string($entity->review_options)) {
+        $entity->review_options = unserialize($entity->review_options);
+      }
+    }
+
+    if (!empty($vids)) {
+      $result_options = db_select('quiz_result_options', 'ro')
+        ->fields('ro')
+        ->condition('ro.quiz_vid', $vids)
+        ->execute();
+      foreach ($result_options->fetchAll() as $result_option) {
+        $entities[$result_option->quiz_qid]->resultoptions[] = (array) $result_option;
+      }
+    }
+
+    return $entities;
+  }
+
+  public function save($quiz, DatabaseTransaction $transaction = NULL) {
+    // QuizFeedbackTest::testFeedback() failed without this, mess!
+    if (empty($quiz->is_new_revision)) {
+      $quiz->is_new = $quiz->revision = 0;
+    }
+
+    if ($return = parent::save($quiz, $transaction)) {
+      $this->saveResultOptions($quiz);
+      return $return;
+    }
+  }
+
+  private function saveResultOptions(QuizEntity $quiz) {
+    db_delete('quiz_result_options')
+      ->condition('quiz_vid', $quiz->vid)
+      ->execute();
+
+    $query = db_insert('quiz_result_options')
+      ->fields(array('quiz_qid', 'quiz_vid', 'option_name', 'option_summary', 'option_summary_format', 'option_start', 'option_end'));
+
+    foreach ($quiz->resultoptions as $option) {
+      if (empty($option['option_name'])) {
+        continue;
+      }
+
+      // When this function called direct from node form submit the
+      // $option['option_summary']['value'] and $option['option_summary']['format'] are we need
+      // But when updating a quiz entity eg. on manage questions page, this values
+      // come from loaded node, not from a submitted form.
+      if (is_array($option['option_summary'])) {
+        $option['option_summary_format'] = $option['option_summary']['format'];
+        $option['option_summary'] = $option['option_summary']['value'];
+      }
+
+      $query->values(array(
+          'quiz_qid'              => $quiz->qid,
+          'quiz_vid'              => $quiz->vid,
+          'option_name'           => $option['option_name'],
+          'option_summary'        => $option['option_summary'],
+          'option_summary_format' => $option['option_summary_format'],
+          'option_start'          => $option['option_start'],
+          'option_end'            => $option['option_end']
+      ));
+    }
+
+    $query->execute();
+  }
+
+  /**
+   * Force save revision author ID.
+   *
+   * @global stdClass $user
+   * @param QuizEntity $entity
+   */
+  protected function saveRevision($entity) {
+    global $user;
+    $entity->revision_uid = $user->uid;
+    return parent::saveRevision($entity);
+  }
+
+  public function delete($ids, DatabaseTransaction $transaction = NULL) {
+    $return = parent::delete($ids, $transaction);
+
+    // Delete quiz results
+    $query = db_select('quiz_results');
+    $query->fields('quiz_results', array('result_id'));
+    $query->condition('quiz_qid', $ids);
+    if ($result_ids = $query->execute()->fetchCol()) {
+      entity_delete_multiple('quiz_result', $result_ids);
+    }
+
+    db_delete('quiz_relationship')->condition('quiz_qid', $ids)->execute();
+    db_delete('quiz_results')->condition('quiz_qid', $ids)->execute();
+    db_delete('quiz_result_options')->condition('quiz_qid', $ids)->execute();
+
+    return $return;
+  }
+
+}
diff --git a/src/Entity/QuizEntityMetadataController.php b/src/Entity/QuizEntityMetadataController.php
new file mode 100644
index 0000000..cd48c0f
--- /dev/null
+++ b/src/Entity/QuizEntityMetadataController.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultMetadataController;
+
+class QuizEntityMetadataController extends EntityDefaultMetadataController {
+
+  public function entityPropertyInfo() {
+    $info = parent::entityPropertyInfo();
+    $properties = &$info[$this->type]['properties'];
+
+    $properties['uid']['type'] = 'user';
+    $properties['created']['type'] = 'date';
+    $properties['created']['setter callback'] = 'entity_property_verbatim_set';
+    $properties['created']['setter permission'] = 'administer quizzes';
+    $properties['changed']['type'] = 'date';
+    $properties['changed']['setter callback'] = 'entity_property_verbatim_set';
+    $properties['changed']['setter permission'] = 'administer quizzes';
+    $properties['quiz_open']['label'] = 'Open date';
+    $properties['quiz_open']['type'] = 'date';
+    $properties['quiz_close']['label'] = 'Close date';
+    $properties['quiz_close']['type'] = 'date';
+
+    foreach (entity_metadata_convert_schema($this->info['revision table']) as $k => $v) {
+      if (isset($properties[$k])) {
+        continue;
+      }
+      $properties[$k] = $v;
+    }
+
+    return $info;
+  }
+
+}
diff --git a/src/Entity/QuizEntityType.php b/src/Entity/QuizEntityType.php
new file mode 100644
index 0000000..6f87fe3
--- /dev/null
+++ b/src/Entity/QuizEntityType.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use Entity;
+
+class QuizEntityType extends Entity {
+
+  public $type;
+  public $label;
+  public $description;
+  public $help;
+  public $weight = 0;
+
+  public function __construct(array $values = array()) {
+    parent::__construct($values, 'quiz_type');
+  }
+
+  /**
+   * Returns whether the quiz type is locked, thus may not be deleted or renamed.
+   *
+   * Quiz types provided in code are automatically treated as locked, as well
+   * as any fixed quiz type.
+   */
+  public function isLocked() {
+    return isset($this->status) && empty($this->is_new) && (($this->status & ENTITY_IN_CODE) || ($this->status & ENTITY_FIXED));
+  }
+
+}
diff --git a/src/Entity/QuizTypeEntityController.php b/src/Entity/QuizTypeEntityController.php
new file mode 100644
index 0000000..9185527
--- /dev/null
+++ b/src/Entity/QuizTypeEntityController.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use DatabaseTransaction;
+use EntityAPIControllerExportable;
+
+class QuizTypeEntityController extends EntityAPIControllerExportable {
+
+  public function save($entity, DatabaseTransaction $transaction = NULL) {
+    $return = parent::save($entity, $transaction);
+
+    $this->addBodyField($entity->type);
+
+    return $return;
+  }
+
+  /**
+   * Add default body field to a quiz type
+   */
+  private function addBodyField($bundle) {
+    if (!field_info_field('quiz_body')) {
+      field_create_field(array(
+          'field_name'   => 'quiz_body',
+          'type'         => 'text_with_summary',
+          'entity_types' => array('quiz_entity'),
+      ));
+    }
+
+    if (!$instance = field_info_instance('quiz_entity', 'quiz_body', $bundle)) {
+      $instance = field_create_instance(array(
+          'field_name'  => 'quiz_body',
+          'entity_type' => 'quiz_entity',
+          'bundle'      => $bundle,
+          'label'       => t('Body'),
+          'widget'      => array('type' => 'text_textarea_with_summary'),
+          'settings'    => array('display_summary' => TRUE),
+          'display'     => array(
+              'default' => array('label' => 'hidden', 'type' => 'text_default'),
+          ),
+      ));
+    }
+
+    return $instance;
+  }
+
+}
diff --git a/src/Entity/QuizTypeUIController.php b/src/Entity/QuizTypeUIController.php
new file mode 100644
index 0000000..979b96d
--- /dev/null
+++ b/src/Entity/QuizTypeUIController.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultUIController;
+
+class QuizTypeUIController extends EntityDefaultUIController {
+
+  /**
+   * Overrides hook_menu() defaults.
+   */
+  public function hook_menu() {
+    $items = parent::hook_menu();
+    $items[$this->path]['description'] = strtr('Manage !quiz types, including fields.', array('!quiz' => QUIZ_NAME));
+    return $items;
+  }
+
+}
diff --git a/src/Entity/QuizUIController.php b/src/Entity/QuizUIController.php
new file mode 100644
index 0000000..2ab0f84
--- /dev/null
+++ b/src/Entity/QuizUIController.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultUIController;
+
+class QuizUiController extends EntityDefaultUIController {
+
+  public function hook_menu() {
+    $items = parent::hook_menu();
+    $items['admin/content/quiz']['type'] = MENU_LOCAL_TASK;
+
+    $this->addQuizAddLinks($items);
+    $this->addQuizCRUDItems($items);
+    $this->addQuizTabItems($items);
+    $this->addQuizTakeItems($items);
+
+    return $items;
+  }
+
+  private function addQuizCRUDItems(&$items) {
+    $items['quiz/%quiz_entity_single'] = array(
+        'title callback'   => 'entity_class_label',
+        'title arguments'  => array(1),
+        'access callback'  => 'quiz_entity_access_callback',
+        'access arguments' => array('view'),
+        'page callback'    => 'Drupal\quiz\Controller\QuizEntityViewController::staticCallback',
+        'page arguments'   => array(1),
+    );
+
+    $items['quiz/%quiz_entity_single/view'] = array(
+        'title'  => 'View',
+        'type'   => MENU_DEFAULT_LOCAL_TASK,
+        'weight' => -10,
+    );
+
+    // Define menu item structure for /quiz/%/edit
+    $items['quiz/%entity_object/edit'] = $items['admin/content/quiz/manage/%entity_object'];
+    $items['quiz/%entity_object/edit']['title'] = 'Settings';
+    unset($items['quiz/%entity_object/edit']['title callback'], $items['quiz/%entity_object/edit']['title arguments']);
+    $items['quiz/%entity_object/edit']['type'] = MENU_LOCAL_TASK;
+    $items['quiz/%entity_object/edit']['page arguments'][1] = 1;
+    $items['quiz/%entity_object/edit']['access arguments'][2] = 1;
+
+    // Define menu item structure for /quiz/%/delete
+    $items['quiz/%entity_object/delete'] = array(
+        'load arguments'   => array('quiz_entity'),
+        'page callback'    => 'drupal_get_form',
+        'page arguments'   => array('quiz_entity_operation_form', 'quiz_entity', 1, 'delete'),
+        'access callback'  => 'entity_access',
+        'access arguments' => array('delete', 'quiz_entity', 1),
+        'file'             => 'includes/entity.ui.inc',
+    );
+  }
+
+  private function addQuizTabItems(&$items) {
+    // Define menu structure for /quiz/%/revisions
+    $items['quiz/%entity_object/revisions'] = array(
+        'title'            => 'Revisions',
+        'type'             => MENU_LOCAL_TASK,
+        'access callback'  => 'entity_access',
+        'access arguments' => array('update', 'quiz_entity', 1),
+        'page callback'    => 'Drupal\quiz\Controller\Admin\QuizRevisionsAdminController::staticCallback',
+        'page arguments'   => array(1),
+        'load arguments'   => array('quiz_entity'),
+    );
+
+    // Define menu structure for /quiz/%/questions
+    $items['quiz/%entity_object/questions'] = array(
+        'title'            => 'Questions',
+        'type'             => MENU_LOCAL_TASK,
+        'access callback'  => 'entity_access',
+        'access arguments' => array('update', 'quiz_entity', 1),
+        'page callback'    => 'Drupal\quiz\Controller\Admin\QuizQuestionAdminController::staticCallback',
+        'page arguments'   => array(1),
+        'load arguments'   => array('quiz_entity'),
+        'weight'           => 5,
+    );
+
+    // Define menu structure for /quiz/%/results
+    $items['quiz/%entity_object/results'] = array(
+        'title'            => 'Results',
+        'type'             => MENU_LOCAL_TASK,
+        'access callback'  => 'entity_access',
+        'access arguments' => array('update', 'quiz_entity', 1),
+        'page callback'    => 'Drupal\quiz\Controller\Admin\QuizResultsAdminController::staticCallback',
+        'page arguments'   => array(1),
+        'load arguments'   => array('quiz_entity'),
+        'weight'           => 6,
+    );
+
+    // Define menu structure for /quiz/%/results
+    $items['quiz/%entity_object/my-results'] = array(
+        'title'            => 'My results',
+        'type'             => MENU_LOCAL_TASK,
+        'access callback'  => 'entity_access',
+        'access arguments' => array('update', 'quiz_entity', 1),
+        'page callback'    => 'Drupal\quiz\Controller\QuizMyResultsController::staticCallback',
+        'page arguments'   => array(1),
+        'load arguments'   => array('quiz_entity'),
+        'weight'           => 6,
+    );
+
+    $items['quiz/%entity_object/questions/term_ahah'] = array(
+        'type'             => MENU_CALLBACK,
+        'access callback'  => 'entity_access',
+        'access arguments' => array('create', 'quiz_entity', 1),
+        'page callback'    => 'Drupal\quiz\Form\QuizCategorizedForm::categorizedTermAhah',
+        'load arguments'   => array('quiz_entity'),
+    );
+
+    if (module_exists('devel')) {
+      $items['quiz/%entity_object/devel'] = array(
+          'title'            => 'Devel',
+          'load arguments'   => array('quiz_entity'),
+          'access arguments' => array('access devel information'),
+          'page callback'    => 'devel_load_object',
+          'page arguments'   => array('quiz_entity', 1),
+          'type'             => MENU_LOCAL_TASK,
+          'file'             => 'devel.pages.inc',
+          'file path'        => drupal_get_path('module', 'devel')
+      );
+    }
+  }
+
+  private function addQuizAddLinks(&$items) {
+    // Change path from /admin/content/quiz/add -> /quizz/add
+    $items['quiz/add'] = $items['admin/content/quiz/add'];
+    unset($items['admin/content/quiz/add']);
+
+    // Menu items for /quiz/add/*
+    if (($types = quiz_get_types()) && (1 < count($types))) {
+      $items['quiz/add'] = array(
+          'title'           => 'Add ' . QUIZ_NAME,
+          'access callback' => 'quiz_can_create_quiz_entity',
+          'page callback'   => 'Drupal\quiz\Controller\QuizEntityAddController::staticCallback',
+      );
+
+      foreach (array_keys($types) as $name) {
+        $items["quiz/add/{$name}"] = array(
+            'title callback'   => 'entity_ui_get_action_title',
+            'title arguments'  => array('add', 'quiz_entity'),
+            'access callback'  => 'entity_access',
+            'access arguments' => array('create', 'quiz_entity'),
+            'page callback'    => 'Drupal\quiz\Form\QuizEntityForm::staticCallback',
+            'page arguments'   => array('add', $name),
+            'file path'        => drupal_get_path('module', 'quiz'),
+            'file'             => 'quiz.pages.inc',
+        );
+      }
+    }
+  }
+
+  private function addQuizTakeItems(&$items) {
+    // Define menu item structure for /quiz/%/take
+    $items['quiz/%entity_object/take'] = array(
+        'load arguments'   => array('quiz_entity'),
+        'page callback'    => 'Drupal\quiz\Controller\QuizTakeController::staticCallback',
+        'page arguments'   => array(1),
+        'access arguments' => array('access quiz'),
+    );
+
+    // Define menu item structure for /quiz/%/take/%
+    $items['quiz/%entity_object/take/%question_number'] = array(
+        'load arguments'   => array('quiz_entity'),
+        'access callback'  => 'quiz_access_question',
+        'access arguments' => array(1, 3),
+        'page callback'    => 'Drupal\quiz\Controller\QuizTakeQuestionController::staticCallback',
+        'page arguments'   => array(1, 3),
+        'file path'        => drupal_get_path('module', 'quiz'),
+        'file'             => 'quiz.pages.inc',
+    );
+
+    $items['quiz/%entity_object/take/%question_number/feedback'] = array(
+        'title'            => 'Feedback',
+        'load arguments'   => array('quiz_entity'),
+        'page callback'    => 'Drupal\quiz\Controller\QuizQuestionFeedbackController::staticCallback',
+        'page arguments'   => array(1, 3),
+        'access callback'  => 'quiz_question_feedback_access',
+        'access arguments' => array(1, 3),
+    );
+  }
+
+}
diff --git a/src/Entity/QuizViewsController.php b/src/Entity/QuizViewsController.php
new file mode 100644
index 0000000..20c3b8a
--- /dev/null
+++ b/src/Entity/QuizViewsController.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultViewsController;
+
+class QuizViewsController extends EntityDefaultViewsController {
+
+  public function views_data() {
+    $data = parent::views_data();
+
+    $data['quiz_entity']['view_node']['field'] = array(
+      'title'   => t('Link'),
+      'help'    => t('Provide a simple link to the !quiz.', array('!quiz' => QUIZ_NAME)),
+      'handler' => 'Drupal\quiz\Entity\Views\QuizEntityLink',
+    );
+
+    $data['quiz_entity']['edit_node']['field'] = array(
+      'title'   => t('Edit link'),
+      'help'    => t('Provide a simple link to edit the !quiz.', array('!quiz' => QUIZ_NAME)),
+      'handler' => 'Drupal\quiz\Entity\Views\QuizEntityEditLink',
+    );
+
+    $data['quiz_entity']['delete_node']['field'] = array(
+      'title'   => t('Delete link'),
+      'help'    => t('Provide a simple link to delete the !quiz.', array('!quiz' => QUIZ_NAME)),
+      'handler' => 'Drupal\quiz\Entity\Views\QuizEntityDeleteLink',
+    );
+
+    return $data;
+  }
+
+}
diff --git a/src/Entity/RelationshipMetadataController.php b/src/Entity/RelationshipMetadataController.php
new file mode 100644
index 0000000..19d059d
--- /dev/null
+++ b/src/Entity/RelationshipMetadataController.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultMetadataController;
+
+class RelationshipMetadataController extends EntityDefaultMetadataController {
+
+  public function entityPropertyInfo() {
+    $info = parent::entityPropertyInfo();
+    $properties = &$info[$this->type]['properties'];
+
+    $properties['question_nid']['type'] = 'node';
+    $properties['question_vid']['type'] = 'integer';
+    $properties['quiz_qid']['type'] = 'node';
+    $properties['quiz_vid']['type'] = 'integer';
+
+    return $info;
+  }
+
+}
diff --git a/src/Entity/Result.php b/src/Entity/Result.php
new file mode 100644
index 0000000..80592c3
--- /dev/null
+++ b/src/Entity/Result.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use Entity;
+
+class Result extends Entity {
+
+  public $result_id;
+  public $quiz_qid;
+  public $quiz_vid;
+  public $uid;
+  public $time_start;
+  public $time_end;
+  public $released;
+  public $score;
+  public $is_invalid;
+  public $is_evaluated;
+  public $time_left;
+  public $layout = array();
+
+  public function countPages() {
+    $count = 0;
+    foreach ($this->layout as $item) {
+      if (('quiz_page' === $item['type']) || !$item['qr_pid']) {
+        $count++;
+      }
+    }
+    return $count;
+  }
+
+  public function isLastPage($page_number) {
+    return $page_number == $this->countPages();
+  }
+
+  public function getNextPageNumber($page_number) {
+    if ($this->isLastPage($page_number)) {
+      return $page_number;
+    }
+    return $page_number + 1;
+  }
+
+  public function getPageItem($page_number) {
+    $number = 0;
+    foreach ($this->layout as $item) {
+      if (('quiz_page' === $item['type']) || !$item['qr_pid']) {
+        if (++$number == $page_number) {
+          return $item;
+        }
+      }
+    }
+  }
+
+  /**
+   * Checks if the user has access to save score for his quiz.
+   */
+  public function canAccessOwnScore($account) {
+    if (user_access('score any quiz', $account)) {
+      return TRUE;
+    }
+
+    if ($quiz = quiz_entity_single_load(NULL, $this->quiz_vid)) {
+      return user_access('score own quiz', $account) && ($quiz->uid == $account->uid);
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Dtermine if a user has access to view a specific quiz result.
+   *
+   * @param \Drupal\quiz\Entity\Result|int $result
+   * @return boolean
+   *  True if access, false otherwise
+   */
+  public function canAccessOwnResult($account) {
+    // Check if the quiz taking has been completed.
+    if ($this->time_end > 0 && $this->uid == $account->uid) {
+      return TRUE;
+    }
+
+    if ($this->canAccessOwnScore($account)) {
+      return TRUE;
+    }
+  }
+
+}
diff --git a/src/Entity/Result/ScoreCalculator.php b/src/Entity/Result/ScoreCalculator.php
new file mode 100644
index 0000000..060ab64
--- /dev/null
+++ b/src/Entity/Result/ScoreCalculator.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\quiz\Entity\Result;
+
+use Drupal\quiz\Entity\QuizEntity;
+use stdClass;
+
+class ScoreCalculator {
+
+  /**
+   * Calculates the score user received on quiz.
+   *
+   * @param $quiz
+   *   The quiz entity.
+   * @param $result_id
+   *   Quiz result ID.
+   *
+   * @return array
+   *   Contains three elements: question_count, num_correct and percentage_score.
+   */
+  public function calculate(QuizEntity $quiz, $result_id) {
+    // 1. Fetch all questions and their max scores
+    $questions = db_query('SELECT a.question_nid, a.question_vid, n.type, r.max_score
+      FROM {quiz_results_answers} a
+      LEFT JOIN {node} n ON (a.question_nid = n.nid)
+      LEFT OUTER JOIN {quiz_relationship} r ON (r.question_vid = a.question_vid) AND r.quiz_vid = :vid
+      WHERE result_id = :rid', array(':vid' => $quiz->vid, ':rid' => $result_id));
+
+    // 2. Callback into the modules and let them do the scoring. @todo after 4.0: Why isn't the scores already saved? They should be
+    // Fetched from the db, not calculated....
+    $scores = array();
+    $count = 0;
+    foreach ($questions as $question) {
+      // Questions picked from term id's won't be found in the quiz_relationship table
+      if ($question->max_score === NULL && isset($quiz->tid) && $quiz->tid > 0) {
+        $question->max_score = $quiz->max_score_for_random;
+      }
+
+      // Invoke hook_quiz_question_score().
+      // We don't use module_invoke() because (1) we don't necessarily want to wed
+      // quiz type to module, and (2) this is more efficient (no NULL checks).
+      $mod = quiz_question_module_for_type($question->type);
+      if (!$mod) {
+        continue;
+      }
+
+      $function = $mod . '_quiz_question_score';
+      if (function_exists($function)) {
+        // Allow for max score to be considered.
+        $scores[] = $function($quiz, $question->question_nid, $question->question_vid, $result_id);
+      }
+      else {
+        drupal_set_message(t('A @quiz question could not be scored: No scoring info is available', array('@quiz' => QUIZ_NAME)), 'error');
+        $dummy_score = new stdClass();
+        $dummy_score->possible = 0;
+        $dummy_score->attained = 0;
+        $scores[] = $dummy_score;
+      }
+      ++$count;
+    }
+
+    // 3. Sum the results.
+    $possible_score = 0;
+    $total_score = 0;
+    $is_evaluated = TRUE;
+    foreach ($scores as $score) {
+      $possible_score += $score->possible;
+      $total_score += $score->attained;
+      if (isset($score->is_evaluated)) {
+        // Flag the entire quiz if one question has not been evaluated.
+        $is_evaluated &= $score->is_evaluated;
+      }
+    }
+
+    // 4. Return the score.
+    return array(
+        'question_count'   => $count,
+        'possible_score'   => $possible_score,
+        'numeric_score'    => $total_score,
+        'percentage_score' => ($possible_score == 0) ? 0 : round(($total_score * 100) / $possible_score),
+        'is_evaluated'     => $is_evaluated,
+    );
+  }
+
+}
diff --git a/src/Entity/ResultController.php b/src/Entity/ResultController.php
new file mode 100644
index 0000000..ebdf511
--- /dev/null
+++ b/src/Entity/ResultController.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use DatabaseTransaction;
+use EntityAPIController;
+
+class ResultController extends EntityAPIController {
+
+  private $score_calculator;
+
+  /**
+   * @return \Drupal\quiz\Entity\Result\ScoreCalculator
+   */
+  public function getScoreCalculator() {
+    if (NULL === $this->score_calculator) {
+      $this->score_calculator = new \Drupal\quiz\Entity\Result\ScoreCalculator();
+    }
+    return $this->score_calculator;
+  }
+
+  public function setScoreCalculator($score_calculator) {
+    $this->score_calculator = $score_calculator;
+    return $this;
+  }
+
+  public function load($ids = array(), $conditions = array()) {
+    $entities = parent::load($ids, $conditions);
+
+    foreach ($entities as $result) {
+      $this->loadLayout($result);
+    }
+
+    return $entities;
+  }
+
+  /**
+   * Attach the layout which previously used to be stored on the result.
+   *
+   * @param Result $result
+   */
+  private function loadLayout(Result $result) {
+    $layout = entity_load('quiz_result_answer', FALSE, array('result_id' => $result->result_id));
+
+    foreach ($layout as $question) {
+      // @kludge
+      // This is bulky but now we have to manually find the type and parents of
+      // the question. This is the only information that is not stored in the
+      // quiz attempt. We reference back to the node relationships for this
+      // current version to get the hieararchy.
+      $sql = "SELECT n.type, qnr.qr_id, qnr.qr_pid
+        FROM {quiz_results} result
+          INNER JOIN {quiz_relationship} qnr ON result.quiz_vid = qnr.quiz_vid
+          INNER JOIN {node} n ON qnr.question_nid = n.nid
+        WHERE result.result_id = :result_id AND n.vid = :vid";
+      $query = db_query($sql, array(':result_id' => $result->result_id, ':vid' => $question->question_vid));
+      $extra = $query->fetch();
+
+      $result->layout[$question->number] = array(
+          'display_number' => $question->number,
+          'nid'            => $question->question_nid,
+          'vid'            => $question->question_vid,
+          'number'         => $question->number,
+          'type'           => $extra->type,
+          'qr_id'          => $extra->qr_id,
+          'qr_pid'         => $extra->qr_pid,
+      );
+    }
+  }
+
+  /**
+   * @global \stdlClass $user
+   * @param Result $result
+   * @param DatabaseTransaction $transaction
+   */
+  public function save($result, DatabaseTransaction $transaction = NULL) {
+    global $user;
+
+    $return = parent::save($result, $transaction);
+
+    if (isset($result->original) && $result->original->is_evaluated && !$result->is_evaluated) {
+      // Quiz is finished! Delete old results if necessary.
+      if ($quiz = quiz_entity_single_load($result->quiz_qid)) {
+        quiz()->getQuizHelper()->getResultHelper()->maintainResult($user, $quiz, $result->result_id);
+      }
+    }
+
+    return $return;
+  }
+
+  public function delete($ids, DatabaseTransaction $transaction = NULL) {
+    $return = parent::delete($ids, $transaction);
+
+    $select = db_select('quiz_results_answers', 'answer');
+    $select->fields('answer', array('result_id', 'question_nid', 'question_vid'));
+    $select->condition('answer.result_id', $ids);
+    $result = $select->execute();
+    while ($record = $result->fetchAll()) {
+      quiz_question_delete_result($record->result_id, $record->question_nid, $record->question_vid);
+    }
+
+    db_delete('quiz_results_answers')->condition('result_id', $ids)->execute();
+    db_delete('quiz_results')->condition('result_id', $ids)->execute();
+
+    return $return;
+  }
+
+}
diff --git a/src/Entity/ResultMetadataController.php b/src/Entity/ResultMetadataController.php
new file mode 100644
index 0000000..7552f13
--- /dev/null
+++ b/src/Entity/ResultMetadataController.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\quiz\Entity;
+
+use EntityDefaultMetadataController;
+
+class ResultMetadataController extends EntityDefaultMetadataController {
+
+  public function entityPropertyInfo() {
+    $info = parent::entityPropertyInfo();
+    $properties = &$info[$this->type]['properties'];
+
+    $properties['quiz_qid']['type'] = 'quiz_entity';
+    $properties['quiz_vid']['type'] = 'integer';
+    $properties['uid']['type'] = 'user';
+    $properties['time_start']['label'] = 'Date started';
+    $properties['time_start']['type'] = 'date';
+    $properties['time_end']['label'] = 'Date finished';
+    $properties['time_end']['type'] = 'date';
+    $properties['released']['type'] = 'date';
+    $properties['score']['label'] = 'Score';
+    $properties['score']['type'] = 'integer';
+    $properties['is_invalid']['label'] = 'Invalid';
+    $properties['is_invalid']['type'] = 'boolean';
+    $properties['is_evaluated']['label'] = 'Evaluated';
+    $properties['is_evaluated']['type'] = 'boolean';
+    $properties['time_left']['type'] = 'duration';
+
+    return $info;
+  }
+
+}
diff --git a/src/Entity/Views/QuizEntityDeleteLink.php b/src/Entity/Views/QuizEntityDeleteLink.php
new file mode 100644
index 0000000..02f1231
--- /dev/null
+++ b/src/Entity/Views/QuizEntityDeleteLink.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\quiz\Entity\Views;
+
+class QuizEntityDeleteLink extends QuizEntityLink {
+
+  function render_link($quiz, $values) {
+    if (entity_access('delete', 'quiz_entity', $quiz)) {
+      $uri = entity_uri('quiz_entity', $quiz);
+      $this->options['alter']['make_link'] = TRUE;
+      $this->options['alter']['path'] = $uri['path'] . '/delete';
+      $text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
+      return $text;
+    }
+  }
+
+}
diff --git a/src/Entity/Views/QuizEntityEditLink.php b/src/Entity/Views/QuizEntityEditLink.php
new file mode 100644
index 0000000..5bfedf4
--- /dev/null
+++ b/src/Entity/Views/QuizEntityEditLink.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\quiz\Entity\Views;
+
+class QuizEntityEditLink extends QuizEntityLink {
+
+  function render_link($quiz, $values) {
+    if (entity_access('update', 'quiz_entity', $quiz)) {
+      $uri = entity_uri('quiz_entity', $quiz);
+      $this->options['alter']['make_link'] = TRUE;
+      $this->options['alter']['path'] = $uri['path'] . '/edit';
+      $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
+      return $text;
+    }
+  }
+
+}
diff --git a/src/Entity/Views/QuizEntityLink.php b/src/Entity/Views/QuizEntityLink.php
new file mode 100644
index 0000000..8394d8a
--- /dev/null
+++ b/src/Entity/Views/QuizEntityLink.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\quiz\Entity\Views;
+
+use views_handler_field_node_link;
+
+class QuizEntityLink extends views_handler_field_node_link {
+
+  function render_link($quiz, $values) {
+    if (entity_access('view', 'quiz_entity', $quiz)) {
+      $uri = entity_uri('quiz_entity', $quiz);
+      $this->options['alter']['make_link'] = TRUE;
+      $this->options['alter']['path'] = $uri['path'];
+      $text = !empty($this->options['text']) ? $this->options['text'] : t('view');
+      return $text;
+    }
+  }
+
+}
diff --git a/src/Form/QuizAdminEntityForm.php b/src/Form/QuizAdminEntityForm.php
new file mode 100644
index 0000000..8ca0635
--- /dev/null
+++ b/src/Form/QuizAdminEntityForm.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+use stdClass;
+
+class QuizAdminEntityForm {
+
+  public function getForm($form, $form_state) {
+    // basic form
+    $dummy_quiz = quiz_controller()->getSettingIO()->getSystemDefaultSettings(FALSE);
+    $entity_form = new QuizEntityForm($dummy_quiz);
+    $form = $entity_form->get($form, $form_state, 'add');
+
+    $form['direction'] = array(
+        '#markup' => t('Here you can change the default @quiz settings for new users.', array('@quiz' => QUIZ_NAME)),
+        '#weight' => -10,
+    );
+
+    // unset values we can't or won't let the user edit default values for
+    unset($form['#quiz_check_revision_access']);
+    unset($form['title']);
+    unset($form['body_field']);
+    unset($form['taking']['aid']);
+    unset($form['taking']['addons']);
+    unset($form['quiz_availability']['quiz_open']);
+    unset($form['quiz_availability']['quiz_close']);
+    unset($form['resultoptions']);
+    unset($form['number_of_random_questions']);
+    unset($form['remember_global']);
+    unset($form['actions']['submit']);
+    unset($form['actions']['delete']);
+
+    $form['remember_settings']['#type'] = 'value';
+    $form['remember_settings']['#default_value'] = TRUE;
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+
+    return $form;
+  }
+
+  public function validateForm($form, &$form_state) {
+    /* @var $quiz \Drupal\quiz\Entity\QuizEntity */
+    $quiz = entity_create('quiz_entity', array(
+        'qid'               => $form['#quiz']->qid,
+        'vid'               => $form['#quiz']->vid,
+        'remember_settings' => 0,
+        'remember_global'   => 1,
+      ) + $form_state['values']);
+
+    quiz_validate($quiz);
+  }
+
+  public function submitForm($form, &$form_state) {
+    /* @var $quiz \Drupal\quiz\Entity\QuizEntity */
+    $quiz = entity_create('quiz_entity', array(
+        'qid'               => $form['#quiz']->qid,
+        'vid'               => $form['#quiz']->vid,
+        'remember_settings' => 0,
+        'remember_global'   => 1,
+      ) + $form_state['values']);
+
+    quiz_controller()->getSettingIO()->updateUserDefaultSettings($quiz);
+    $form_state['quiz'] = $quiz;
+  }
+
+}
diff --git a/src/Form/QuizAnsweringForm.php b/src/Form/QuizAnsweringForm.php
new file mode 100644
index 0000000..5b17da0
--- /dev/null
+++ b/src/Form/QuizAnsweringForm.php
@@ -0,0 +1,209 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Entity\Result;
+use Drupal\quiz\Form\QuizAnsweringForm\FormSubmission;
+use stdClass;
+
+class QuizAnsweringForm {
+
+  /** @var QuizEntity */
+  private $quiz;
+  private $question;
+  private $page_number;
+
+  /** @var Result */
+  private $result;
+
+  /** @var int */
+  private $quiz_id;
+
+  /** @var FormSubmission */
+  private $submit;
+
+  public function __construct($quiz, $question, $page_number, $result) {
+    $this->quiz = $quiz;
+    $this->question = $question;
+    $this->page_number = $page_number;
+    $this->result = $result;
+    $this->quiz_id = $quiz->qid;
+  }
+
+  /**
+   * Build question list in page.
+   * @param stdClass $result
+   * @param stdClass $page
+   */
+  public static function findPageQuestions($result, $page) {
+    $page_id = NULL;
+    $questions = array(node_load($page->nid));
+
+    foreach ($result->layout as $item) {
+      if ($item['vid'] == $page->vid) {
+        $page_id = $item['qr_id'];
+        break;
+      }
+    }
+
+    foreach ($result->layout as $item) {
+      if ($page_id == $item['qr_pid']) {
+        $questions[] = node_load($item['nid']);
+      }
+    }
+
+    return $questions;
+  }
+
+  /**
+   * Get the form to show to the quiz taker.
+   *
+   * @param $questions
+   *   A list of question nodes to get answers from.
+   * @param $result_id
+   *   The result ID for this attempt.
+   */
+  public function getForm($form, &$form_state, $questions) {
+    $form['#attributes']['class'] = array('answering-form');
+
+    $form['#quiz'] = $this->quiz;
+    $form['#question'] = $this->question;
+    $form['#page_number'] = $this->page_number;
+    $form['#result'] = $this->result;
+
+    foreach ($questions as $question) {
+      $question = _quiz_question_get_instance($question);
+      $this->buildQuestionItem($question, $form, $form_state);
+    }
+
+    // Build buttons
+    $allow_skipping = isset($question->type) ? $question->type !== 'quiz_directions' : $question->node->type;
+    $this->buildSubmitButtons($form, $allow_skipping);
+
+    return $form;
+  }
+
+  private function buildQuestionItem($question_instance, &$form, $form_state) {
+    $question = $question_instance->node;
+
+    // Element for a single question
+    $element = $question_instance->getAnsweringForm($form_state, $this->result->result_id);
+    node_build_content($question, 'question');
+    unset($question->content['answers']);
+    $form['questions'][$question->nid] = array(
+        '#attributes' => array('class' => array(drupal_html_class('quiz-question-' . $question->type))),
+        '#type'       => 'container',
+        'header'      => $question->content,
+        'question'    => array('#tree' => TRUE, $question->nid => $element),
+    );
+
+    // Should we disable this question?
+    if (empty($this->quiz->allow_change) && quiz_result_is_question_answered($this->result, $question)) {
+      // This question was already answered, and not skipped.
+      $form['questions'][$question->nid]['#disabled'] = TRUE;
+    }
+
+    if ($this->quiz->mark_doubtful) {
+      $form['is_doubtful'] = array(
+          '#type'          => 'checkbox',
+          '#title'         => t('doubtful'),
+          '#weight'        => 1,
+          '#prefix'        => '<div class="mark-doubtful checkbox enabled"><div class="toggle"><div></div></div>',
+          '#suffix'        => '</div>',
+          '#default_value' => 0,
+          '#attached'      => array('js' => array(drupal_get_path('module', 'quiz') . '/misc/js/quiz_take.js')),
+      );
+
+      // @TODO: Reduce queries
+      $sql = 'SELECT is_doubtful '
+        . ' FROM {quiz_results_answers} '
+        . ' WHERE result_id = :result_id '
+        . '   AND question_nid = :question_nid '
+        . '   AND question_vid = :question_vid';
+      $form['is_doubtful']['#default_value'] = db_query($sql, array(
+          ':result_id'    => $this->result->result_id,
+          ':question_nid' => $question->nid,
+          ':question_vid' => $question->vid))->fetchField();
+    }
+  }
+
+  private function buildSubmitButtons(&$form, $allow_skipping) {
+    $is_last = $this->result->isLastPage($this->page_number);
+
+    $form['navigation']['#type'] = 'actions';
+
+    if (!empty($this->quiz->backwards_navigation) && (arg(3) != 1)) {
+      // Backwards navigation enabled, and we are looking at not the first
+      // question. @todo detect when on the first page.
+      $form['navigation']['back'] = array(
+          '#weight'                  => 10,
+          '#type'                    => 'submit',
+          '#value'                   => t('Back'),
+          '#submit'                  => array('quiz_answer_form_submit_back'),
+          '#limit_validation_errors' => array(),
+      );
+
+      if ($is_last) {
+        $form['navigation']['#last'] = TRUE;
+        $form['navigation']['last_text'] = array(
+            '#weight' => 0,
+            '#markup' => '<p><em>' . t('This is the last question. Press Finish to deliver your answers') . '</em></p>',
+        );
+      }
+    }
+
+    $form['navigation']['submit'] = array(
+        '#weight' => 30,
+        '#type'   => 'submit',
+        '#value'  => $is_last ? t('Finish') : t('Next'),
+        '#submit' => array('quiz_answer_form_submit'),
+    );
+
+    // @TODO: Check this
+    $form['navigation']['skip'] = array(
+        '#weight'                  => 20,
+        '#type'                    => 'submit',
+        '#value'                   => $is_last ? t('Leave blank and finish') : t('Leave blank'),
+        '#access'                  => $allow_skipping,
+        '#submit'                  => array('quiz_answer_form_submit_blank'),
+        '#limit_validation_errors' => array(),
+        '#access'                  => $this->quiz->allow_skipping,
+    );
+
+    // Display a confirmation dialogue if this is the last question and a user
+    // is able to navigate backwards but not forced to answer correctly.
+    if ($is_last && $this->quiz->backwards_navigation && !$this->quiz->repeat_until_correct) {
+      $form['#attributes']['class'][] = 'quiz-answer-confirm';
+      $form['#attributes']['data-confirm-message'] = t("By proceeding you won't be able to go back and edit your answers.");
+      $form['#attached']['js'][] = drupal_get_path('module', 'quiz') . '/misc/js/quiz.answering.confirm.js';
+    }
+  }
+
+  /**
+   * Validation callback for quiz question submit.
+   */
+  public function formValidate(&$form, &$form_state) {
+    $time_reached = $this->quiz->time_limit && (REQUEST_TIME > ($this->result->time_start + $this->quiz->time_limit));
+
+    // Let's not validate anything, because the input won't get saved in submit either.
+    if ($time_reached) {
+      return;
+    }
+
+    foreach (array_keys($form_state['values']['question']) as $question_id) {
+      if ($current_question = node_load($question_id)) {
+        // There was an answer submitted.
+        _quiz_question_get_instance($current_question)->getAnsweringFormValidate($form, $form_state);
+      }
+    }
+  }
+
+  public function getSubmit() {
+    if (null === $this->submit) {
+      $this->submit = new FormSubmission($this->quiz, $this->result, $this->page_number);
+    }
+    return $this->submit;
+  }
+
+}
diff --git a/src/Form/QuizAnsweringForm/FormSubmission.php b/src/Form/QuizAnsweringForm/FormSubmission.php
new file mode 100644
index 0000000..da101fb
--- /dev/null
+++ b/src/Form/QuizAnsweringForm/FormSubmission.php
@@ -0,0 +1,296 @@
+<?php
+
+namespace Drupal\quiz\Form\QuizAnsweringForm;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Entity\Result;
+use Drupal\quiz\Helper\Quiz\TakingHelper;
+
+class FormSubmission extends TakingHelper {
+
+  private $quiz;
+  private $quiz_id;
+  private $quiz_uri;
+
+  /** @var Result */
+  private $result;
+  private $page_number;
+
+  /**
+   * @param QuizEntity $quiz
+   * @param Result $result
+   * @param int $page_number
+   */
+  public function __construct($quiz, $result, $page_number) {
+    $this->quiz = $quiz;
+    $this->quiz_id = $quiz->qid;
+    $this->quiz_uri = 'quiz/' . $quiz->qid;
+    $this->result = $result;
+    $this->result_id = $result->result_id;
+    $this->page_number = $page_number;
+
+    // Load data about the quiz taker
+    $this->taker = db_query('SELECT u.uid, u.mail'
+      . ' FROM {users} u'
+      . ' JOIN {quiz_results} qnr ON u.uid = qnr.uid'
+      . ' WHERE result_id = :result_id', array(':result_id' => $this->result->result_id))->fetch();
+  }
+
+  /**
+   * Submit handler for "back".
+   */
+  public function formBackSubmit(&$form, &$form_state) {
+    $this->redirect($this->quiz, $this->page_number - 1);
+    $item = $this->result->layout[$this->page_number];
+    if (!empty($item['qr_pid'])) {
+      foreach ($this->result->layout as $item) {
+        if ($item['qr_id'] == $item['qr_pid']) {
+          $this->redirect($this->quiz, $item['number']);
+        }
+      }
+    }
+    $form_state['redirect'] = $this->quiz_uri . '/take/' . $this->getCurrentPageNumber($this->quiz);
+  }
+
+  /**
+   * Submit action for "leave blank".
+   */
+  public function formBlankSubmit($form, &$form_state) {
+    foreach (array_keys($form_state['input']['question']) as $question_id) {
+      // Loop over all question inputs provided, and record them as skipped.
+      $question = node_load($question_id);
+
+      // Delete the user's answer.
+      _quiz_question_response_get_instance($this->result->result_id, $question)
+        ->delete();
+
+      // Mark our question attempt as skipped, reset the correct and points flag.
+      $answer = quiz_result_answer_load($this->result->result_id, $question->nid, $question->vid);
+      $answer->is_skipped = 1;
+      $answer->is_correct = 0;
+      $answer->points_awarded = 0;
+      $answer->answer_timestamp = REQUEST_TIME;
+      entity_save('quiz_result_answer', $answer);
+
+      $this->redirect($this->quiz, $this->result->getNextPageNumber($this->page_number));
+    }
+
+    // Advance to next question.
+    $form_state['redirect'] = $this->quiz_uri . '/take/' . $this->getCurrentPageNumber($this->quiz);
+
+    if (!isset($this->result->layout[$_SESSION['quiz'][$this->quiz->qid]['current']])) {
+      // If this is the last question, finalize the quiz.
+      $this->formSubmitFinalizeQuestionAnswering($form, $form_state);
+    }
+  }
+
+  /**
+   * Submit handler for the question answering form.
+   *
+   * There is no validation code here, but there may be feedback code for
+   * correct feedback.
+   */
+  public function formSubmit($form, &$form_state) {
+    if ($time_reached = $this->quiz->time_limit && (REQUEST_TIME > ($this->result->time_start + $this->quiz->time_limit))) {
+      // Too late.
+      // @todo move to quiz_question_answering_form_validate(), and then put all
+      // the "quiz end" logic in a sharable place. We just need to not fire the
+      // logic that saves all the users answers.
+      drupal_set_message(t('The last answer was not submitted, as the time ran out.'), 'error');
+    }
+    elseif (!empty($form_state['values']['question'])) {
+      foreach (array_keys($form_state['values']['question']) as $question_id) {
+        foreach ($this->result->layout as $item) {
+          if ($item['nid'] == $question_id) {
+            $question_array = $item;
+          }
+        }
+
+        $_question = node_load($question_id);
+        $_answer = $form_state['values']['question'][$question_id];
+        $qi_instance = _quiz_question_response_get_instance($this->result->result_id, $_question, $_answer);
+        $qi_instance->delete();
+        $qi_instance->saveResult();
+        $result = $qi_instance->toBareObject();
+        quiz()
+          ->getQuizHelper()
+          ->saveQuestionResult($this->quiz, $result, array('set_msg' => TRUE, 'question_data' => $question_array));
+
+        // Increment the counter.
+        $this->redirect($this->quiz, $this->result->getNextPageNumber($this->page_number));
+      }
+    }
+
+    // In case we have question feedback, redirect to feedback form.
+    $form_state['redirect'] = $this->quiz_uri . '/take/' . $this->getCurrentPageNumber($this->quiz);
+    if (!empty($this->quiz->review_options['question']) && array_filter($this->quiz->review_options['question'])) {
+      $form_state['redirect'] = $this->quiz_uri . '/take/' . ($this->getCurrentPageNumber($this->quiz) - 1) . '/feedback';
+    }
+
+    if ($time_reached || $this->result->isLastPage($this->page_number)) {
+      $this->formSubmitLastPage($form, $form_state);
+    }
+  }
+
+  private function formSubmitLastPage($form, &$form_state) {
+    // If this is the last question, finalize the quiz.
+    $this->formSubmitFinalizeQuestionAnswering($form, $form_state);
+  }
+
+  /**
+   * Helper function to finalize a quiz attempt.
+   * @see quiz_question_answering_form_submit()
+   * @see quiz_question_answering_form_submit_blank()
+   */
+  private function formSubmitFinalizeQuestionAnswering($form, &$form_state) {
+    // No more questions. Score quiz.
+    $score = $this->endScoring();
+
+    // Only redirect to question results if there is not question feedback.
+    if (empty($this->quiz->review_options['question']) || !array_filter($this->quiz->review_options['question'])) {
+      $form_state['redirect'] = "quiz-result/{$this->result->result_id}";
+    }
+
+    $this->endActions($score, $_SESSION['quiz'][$this->quiz_id]);
+
+    // Remove all information about this quiz from the session.
+    // @todo but for anon, we might have to keep some so they could access
+    // results
+    // When quiz is completed we need to make sure that even though the quiz has
+    // been removed from the session, that the user can still access the
+    // feedback for the last question, THEN go to the results page.
+    $_SESSION['quiz']['temp']['result_id'] = $this->result->result_id;
+    unset($_SESSION['quiz'][$this->quiz_id]);
+  }
+
+  /**
+   * Score a completed quiz.
+   */
+  private function endScoring() {
+    global $user;
+
+    // Mark all missing answers as blank. This is essential here for when we may
+    // have pages of unanswered questions. Also kills a lot of the skip code that
+    // was necessary before.
+    foreach ($this->result->layout as $qinfo) {
+      $current_question = node_load($qinfo['nid'], $qinfo['vid']);
+
+      foreach ($this->result->layout as $question) {
+        if ($question['nid'] == $current_question->nid) {
+          $question_array = $question;
+        }
+      }
+
+      // Load the Quiz answer submission from the database.
+      if (!$answer = quiz_result_answer_load($this->result->result_id, $qinfo['nid'], $qinfo['vid'])) {
+        $qi_instance = _quiz_question_response_get_instance($this->result->result_id, $current_question, NULL);
+        $qi_instance->delete();
+        $bare_object = $qi_instance->toBareObject();
+        quiz()
+          ->getQuizHelper()
+          ->saveQuestionResult($this->quiz, $bare_object, array('set_msg' => TRUE, 'question_data' => $question_array));
+      }
+    }
+
+    $score = quiz_result_controller()->getScoreCalculator()->calculate($this->quiz, $this->result->result_id);
+    if (!isset($score['percentage_score'])) {
+      $score['percentage_score'] = 0;
+    }
+    $this->result->is_evaluated = $score['is_evaluated'];
+    $this->result->score = $score['percentage_score'];
+    $this->result->time_end = REQUEST_TIME;
+    entity_save('quiz_result', $this->result);
+    if ($user->uid) {
+      $score['passing'] = quiz()->getQuizHelper()->isPassed($user->uid, $this->quiz->qid, $this->quiz->vid);
+    }
+    else {
+      $score['passing'] = $score['percentage_score'] >= $this->quiz->pass_rate;
+    }
+    return $score;
+  }
+
+  /**
+   * Actions to take at the end of a quiz
+   *
+   * @param int $score
+   *  Score as a number
+   */
+  private function endActions($score, $session_data) {
+    $this->endActionMailing();
+    $this->endActionUserPoint();
+
+    // Call hook_quiz_finished().
+    // @TODO consider hook_entity_update if we make quiz results rules capable
+    module_invoke_all('quiz_finished', $this->quiz, $score, $session_data);
+
+    // Lets piggy back here to perform the quiz defined action since were done
+    // with this quiz.
+    // We will verify that there is an associated action with this quiz and then
+    // perform that action.
+    if (!empty($this->quiz->aid)) {
+      // @TODO get rid of this. Replace with rules. Make quiz results entities or
+      // something
+      // Some actions are reliant on objects and I am unsure which ones, for now I
+      // have simply passed the actions_do() function an empty array. By passing
+      // this function a single id then it will retrieve the callback, get the
+      // parameters and perform that function (action) for you.
+      actions_do($this->quiz->aid, $this->quiz, $score, $session_data);
+    }
+
+    return $score;
+  }
+
+  private function endActionMailing() {
+    if (variable_get('quiz_results_to_quiz_author', 0)) {
+      $author_mail = db_query('SELECT mail FROM {users} WHERE uid = :uid', array(':uid' => $this->quiz->uid))->fetchField();
+      drupal_mail('quiz', 'notice', $author_mail, NULL, array($this->quiz, $this->score, $this->result_id, 'author'));
+    }
+
+    if (variable_get('quiz_email_results', 0) && variable_get('quiz_use_passfail', 1) && $this->taker->uid != 0 && $this->score['is_evaluated']) {
+      drupal_mail('quiz', 'notice', $this->taker->mail, NULL, array($this->quiz, $this->score, $this->result_id, 'taker'));
+      drupal_set_message(t('Your results have been sent to your email address.'));
+    }
+  }
+
+  /**
+   * Calls userpoints functions to credit user point based on number of correct
+   * answers.
+   *
+   * @TODO convert to entity/rules
+   */
+  private function endActionUserPoint() {
+    if (!$this->quiz->has_userpoints || !$this->taker->uid || !$this->score['is_evaluated']) {
+      return;
+    }
+
+    // Looking up the tid of the selected Userpoint vocabulary
+    $selected_tid = db_query("SELECT tid FROM {taxonomy_index}
+                WHERE nid = :nid AND tid IN (
+                  SELECT tid
+                  FROM {taxonomy_term_data} t_t_d JOIN {taxonomy_vocabulary} t_v ON t_v.vid = t_t_d.vid
+                  WHERE t_t_d.vid = :vid
+                )", array(':nid' => $this->quiz->qid, ':vid' => $this->quiz->vid,
+        ':vid' => userpoints_get_vid()))->fetchField();
+    $variables = array(
+        '@title' => $this->quiz->title,
+        '@quiz'  => QUIZ_NAME,
+        '@time'  => date('l jS \of F Y h:i:s A'),
+    );
+    $params = array(
+        'points'      => $this->score['numeric_score'],
+        'description' => t('Attended @title @quiz on @time', $variables),
+        'tid'         => $selected_tid,
+        'uid'         => $this->taker->uid,
+    );
+    if ($this->quiz->userpoints_tid != 0) {
+      $params['tid'] = $this->quiz->userpoints_tid;
+    }
+    userpoints_userpointsapi($params);
+  }
+
+  private function getCurrentPageNumber(QuizEntity $quiz) {
+    $id = $quiz->qid;
+    return isset($_SESSION['quiz'][$id]['current']) ? $_SESSION['quiz'][$id]['current'] : 1;
+  }
+
+}
diff --git a/src/Form/QuizCategorizedForm.php b/src/Form/QuizCategorizedForm.php
new file mode 100644
index 0000000..bd36c55
--- /dev/null
+++ b/src/Form/QuizCategorizedForm.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Helper\Quiz\BaseForm;
+
+class QuizCategorizedForm extends BaseForm {
+
+  /**
+   * Form for managing what questions should be added to a quiz with categorized random questions.
+   *
+   * @param array $form_state
+   * @param QuizEntity $quiz
+   *  The quiz entity
+   */
+  public function getForm($form, $form_state, QuizEntity $quiz) {
+    $form['#tree'] = TRUE;
+    $form['#theme'] = 'quiz_categorized_form';
+    $form['#quiz'] = $quiz;
+
+    $this->existingTermsForm($form, $form_state, $quiz);
+    $this->categorizedNewTermForm($form, $form_state, $quiz);
+
+    $form['qid'] = array('#type' => 'value', '#value' => $quiz->qid);
+    $form['vid'] = array('#type' => 'value', '#value' => $quiz->vid);
+    $form['tid'] = array('#type' => 'value', '#value' => NULL);
+
+    // Give the user the option to create a new revision of the quiz
+    $this->addRevisionCheckbox($form, $quiz);
+
+    // Timestamp is needed to avoid multiple users editing the same quiz at the same time.
+    $form['timestamp'] = array('#type' => 'hidden', '#default_value' => REQUEST_TIME);
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
+
+    return $form;
+  }
+
+  /**
+   * @param array $form
+   * @param array $form_state
+   * @param QuizEntity $quiz
+   */
+  private function existingTermsForm(&$form, $form_state, $quiz) {
+    if ($terms = $quiz->getTermsByVid()) {
+      if (empty($form_state['input']) && !$quiz->getQuestionLoader()->buildCategoziedQuestionList()) {
+        drupal_set_message(t('There are not enough questions in the requested categories.'), 'error');
+      }
+    }
+
+    foreach ($terms as $term) {
+      $form[$term->tid]['name'] = array('#markup' => check_plain($term->name));
+      $form[$term->tid]['number'] = array('#type' => 'textfield', '#size' => 3, '#default_value' => $term->number);
+      $form[$term->tid]['max_score'] = array('#type' => 'textfield', '#size' => 3, '#default_value' => $term->max_score);
+      $form[$term->tid]['remove'] = array('#type' => 'checkbox', '#default_value' => 0);
+      $form[$term->tid]['weight'] = array(
+          '#type'          => 'textfield',
+          '#size'          => 3,
+          '#default_value' => $term->weight,
+          '#attributes'    => array('class' => array('term-weight')),
+      );
+    }
+  }
+
+  /**
+   * Form for adding new terms to a quiz
+   *
+   * @see quiz_categorized_form
+   */
+  private function categorizedNewTermForm(&$form, $form_state, $quiz) {
+    $form['new'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Add category'),
+        '#collapsible' => FALSE,
+        '#collapsed'   => FALSE,
+        '#tree'        => FALSE,
+    );
+    $form['new']['term'] = array(
+        '#type'              => 'textfield',
+        '#title'             => t('Category'),
+        '#description'       => t('Type in the name of the term you would like to add questions from.'),
+        '#autocomplete_path' => "quiz/" . $quiz->qid . "/questions/term_ahah",
+        '#field_suffix'      => '<a id="browse-for-term" href="javascript:void(0)">' . t('browse') . '</a>',
+    );
+    $form['new']['number'] = array(
+        '#type'        => 'textfield',
+        '#title'       => t('Number of questions'),
+        '#description' => t('How many questions would you like to draw from this term?'),
+    );
+    $form['new']['max_score'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Max score for each question'),
+        '#description'   => t('The number of points a user will be awarded for each question he gets correct.'),
+        '#default_value' => 1,
+    );
+  }
+
+  /**
+   * Validate the categorized form
+   */
+  function formValidate($form, &$form_state) {
+    /* @var $quiz QuizEntity */
+    $quiz = $form['#quiz'];
+
+    if (!_quiz_is_int($quiz->qid)) {
+      $msg = t('A critical error has occured. Please report error code 28 on the quiz project page.');
+      form_set_error('changed', $msg);
+      return;
+    }
+
+    if ($quiz->changed > $form_state['values']['timestamp']) {
+      $msg = t('This content has been modified by another user, changes cannot be saved.');
+      form_set_error('changed', $msg);
+    }
+
+    if (!empty($form_state['values']['term'])) {
+      $tid = $this->getIdFromString($form_state['values']['term']);
+      if ($tid === FALSE) {
+        $terms = static::searchTerms($form_state['values']['term']);
+        $num_terms = count($terms);
+        if ($num_terms == 1) {
+          $tid = key($terms);
+        }
+        elseif ($num_terms > 1) {
+          form_set_error('term', t('You need to be more specific, or use the autocomplete feature. The term name you entered matches several terms: %terms', array('%terms' => implode(', ', $terms))));
+        }
+        elseif ($num_terms == 0) {
+          form_set_error('term', t("The term name you entered doesn't match any registered question terms."));
+        }
+      }
+
+      if (in_array($tid, array_keys($form))) {
+        form_set_error('term', t('The category you are trying to add has already been added to this quiz.'));
+      }
+      else {
+        form_set_value($form['tid'], $tid, $form_state);
+      }
+
+      if (!_quiz_is_int($form_state['values']['number'])) {
+        form_set_error('number', t('The number of questions needs to be a positive integer'));
+      }
+
+      if (!_quiz_is_int($form_state['values']['max_score'], 0)) {
+        form_set_error('max_score', t('The max score needs to be a positive integer or 0'));
+      }
+    }
+  }
+
+  /**
+   * Searches for an id in the end of a string.
+   *
+   * Id should be written like "(id:23)"
+   *
+   * @param string $string
+   *  The string where we will search for an id
+   * @return int
+   *  The matched integer
+   */
+  private function getIdFromString($string) {
+    $matches = array();
+    preg_match('/\(id:(\d+)\)$/', $string, $matches);
+    return isset($matches[1]) ? (int) $matches[1] : FALSE;
+  }
+
+  /**
+   * Submit the categorized form
+   */
+  public function formSubmit($form, $form_state) {
+    $quiz = quiz_entity_single_load($form_state['values']['qid'], $form_state['values']['vid']);
+    $quiz->number_of_random_questions = 0;
+
+    // Update the refresh latest quizzes table so that we know what the users latest quizzes are
+    if (variable_get('quiz_auto_revisioning', 1)) {
+      $is_new_revision = quiz_has_been_answered($quiz);
+    }
+    else {
+      $is_new_revision = (bool) $form_state['values']['new_revision'];
+    }
+    if (!empty($form_state['values']['tid'])) {
+      $quiz->number_of_random_questions += $this->categorizedAddTerm($form, $form_state);
+    }
+    $quiz->number_of_random_questions += $this->categorizedUpdateTerms($form, $form_state);
+    if ($is_new_revision) {
+      $quiz->is_new_revision = 1;
+    }
+
+    // We save the node to update its timestamp and let other modules react to the update.
+    // We also do this in case a new revision is required…
+    entity_save('quiz_entity', $quiz);
+  }
+
+  /**
+   * Adds a term to a categorized quiz
+   *
+   * This is a helper function for the submit function.
+   */
+  private function categorizedAddTerm($form, $form_state) {
+    drupal_set_message(t('The term was added'));
+
+    // Needs to be set to avoid error-message from db:
+    $form_state['values']['weight'] = 0;
+    drupal_write_record('quiz_terms', $form_state['values']);
+    return $form_state['values']['number'];
+  }
+
+  /**
+   * Update the categoriez belonging to a quiz with categorized random questions.
+   *
+   * Helper function for quiz_categorized_form_submit
+   */
+  private function categorizedUpdateTerms(&$form, &$form_state) {
+    $changed = array();
+    $removed = array();
+    $num_questions = 0;
+    foreach ($form_state['values'] as $key => $existing) {
+      if (is_numeric($key)) {
+        $this->categorizedUpdateTerm($form, $form_state, $key, $existing, $num_questions, $changed, $removed);
+      }
+    }
+
+    if (!empty($changed)) {
+      $msg = t('Updates were made for the following terms: %terms', array('%terms' => implode(', ', $changed)));
+      drupal_set_message($msg);
+    }
+
+    if (!empty($removed)) {
+      $msg = t('The following terms were removed: %terms', array('%terms' => implode(', ', $removed)));
+      drupal_set_message($msg);
+    }
+
+    return $num_questions;
+  }
+
+  private function categorizedUpdateTerm($form, $form_state, $key, $existing, &$num_questions, &$changed, &$removed) {
+    if (!$existing['remove']) {
+      $num_questions += $existing['number'];
+    }
+
+    foreach (array('weight', 'max_score', 'number') as $id) {
+      if ($existing[$id] != $form[$key][$id]['#default_value'] && !$existing['remove']) {
+        $existing['qid'] = $form_state['values']['qid'];
+        $existing['vid'] = $form_state['values']['vid'];
+        $existing['tid'] = $key;
+        if (empty($existing['weight'])) {
+          $existing['weight'] = 1;
+        }
+        $changed[] = $form[$key]['name']['#markup'];
+        drupal_write_record('quiz_terms', $existing, array('vid', 'tid'));
+        break;
+      }
+      elseif ($existing['remove']) {
+        db_delete('quiz_terms')
+          ->condition('tid', $key)
+          ->condition('vid', $form_state['values']['vid'])
+          ->execute();
+        $removed[] = $form[$key]['name']['#markup'];
+        break;
+      }
+    }
+  }
+
+  /**
+   * Helper function for finding terms...
+   *
+   * @param string $start
+   *  The start of the string we are looking for
+   */
+  public static function searchTerms($start, $all = FALSE) {
+    if (!$sql_args = array_keys(quiz()->getVocabularies())) {
+      return array();
+    }
+
+    $query = db_select('taxonomy_term_data', 't')
+      ->fields('t', array('name', 'tid'))
+      ->condition('t.vid', $sql_args);
+    if (!$all) {
+      $query->condition('t.name', '%' . $start . '%', 'LIKE');
+    }
+    $result = $query->execute();
+
+    // @TODO: Don't user db_fetch_object
+    $terms = array();
+    while ($row = $result->fetch()) {
+      $terms[$row->tid] = $row->name;
+    }
+
+    return $terms;
+  }
+
+  /**
+   * Callback for quiz/%/questions/term_ahah. Ahah function for finding terms…
+   *
+   * @param string $start
+   *  The start of the string we are looking for
+   */
+  public static function categorizedTermAhah($start) {
+    foreach (static::searchTerms($start, $start == '*') as $key => $value) {
+      $return["$value (id:$key)"] = $value;
+    }
+    drupal_json_output(!empty($return) ? $return : array());
+  }
+
+}
diff --git a/src/Form/QuizEntityForm.php b/src/Form/QuizEntityForm.php
new file mode 100644
index 0000000..fd7fd19
--- /dev/null
+++ b/src/Form/QuizEntityForm.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Form\QuizEntityForm\FormDefinition;
+use Drupal\quiz\Form\QuizEntityForm\FormValidation;
+
+class QuizEntityForm {
+
+  /** @var QuizEntity */
+  private $quiz;
+
+  public function __construct(QuizEntity $quiz) {
+    $this->quiz = $quiz;
+  }
+
+  public static function staticCallback($op, $quiz_type) {
+    $quiz = NULL;
+    if ($op === 'add') {
+      $values['type'] = $quiz_type;
+      $quiz = entity_create('quiz_entity', $values);
+    }
+    return entity_ui_get_form('quiz_entity', $quiz, $op);
+  }
+
+  public function get($form, &$form_state, $op) {
+    $def = new FormDefinition($this->quiz);
+    return $def->get($form, $form_state, $op);
+  }
+
+  public function validate($form, &$form_state) {
+    if (t('Delete') === $form_state['clicked_button']['#value']) {
+      $path = 'admin' === arg(0) ? 'admin/content/quiz/manage/' . $this->quiz->qid . '/delete' : 'quiz/' . $this->quiz->qid . '/delete';
+      drupal_goto($path);
+    }
+    else {
+      $validator = new FormValidation($form, $form_state);
+      return $validator->validate();
+    }
+  }
+
+  public function submit($form, &$form_state) {
+    /* @var $quiz QuizEntity */
+    $quiz = entity_ui_controller('quiz_entity')->entityFormSubmitBuildEntity($form, $form_state);
+
+    // convert formatted text fields
+    $quiz->summary_default_format = $quiz->summary_default['format'];
+    $quiz->summary_default = $quiz->summary_default['value'];
+    $quiz->summary_pass_format = $quiz->summary_pass['format'];
+    $quiz->summary_pass = $quiz->summary_pass['value'];
+
+    // convert value from date widget to timestamp
+    $quiz->quiz_open = mktime(0, 0, 0, $quiz->quiz_open['month'], $quiz->quiz_open['day'], $quiz->quiz_open['year']);
+    $quiz->quiz_close = mktime(0, 0, 0, $quiz->quiz_close['month'], $quiz->quiz_close['day'], $quiz->quiz_close['year']);
+
+    // Enable revision flag.
+    if (!empty($form_state['values']['revision'])) {
+      $quiz->is_new_revision = TRUE;
+    }
+
+    // Add in created and changed times.
+    $quiz->save();
+
+    // Use would like remembering settings
+    if (!empty($form_state['values']['remember_settings']) || !empty($form_state['values']['remember_global'])) {
+      quiz_controller()->getSettingIO()->updateUserDefaultSettings($quiz);
+    }
+
+    if ('admin' === arg(0)) {
+      $form_state['redirect'] = 'admin/content/quiz';
+    }
+
+    if (!$form['#quiz']->qid) {
+      drupal_set_message(t('You just created a new @quiz. Now you have to add questions to it. This page is for adding and managing questions. Here you can create new questions or add some of your already created questions. If you want to change the quiz settings, you can use the "edit" tab.'), array('@quiz' => QUIZ_NAME));
+      $form_state['redirect'] = "quiz/" . $quiz->qid . "/questions";
+    }
+
+    // If the quiz don't have any questions jump to the manage questions tab.
+    $sql = 'SELECT 1 FROM {quiz_relationship} WHERE quiz_vid = :vid LIMIT 1';
+    if (!db_query($sql, array(':vid' => $quiz->vid))->fetchField()) {
+      $form_state['redirect'] = 'quiz/' . $quiz->qid . '/questions';
+    }
+  }
+
+}
diff --git a/src/Form/QuizEntityForm/FormDefinition.php b/src/Form/QuizEntityForm/FormDefinition.php
new file mode 100644
index 0000000..41aeeae
--- /dev/null
+++ b/src/Form/QuizEntityForm/FormDefinition.php
@@ -0,0 +1,488 @@
+<?php
+
+namespace Drupal\quiz\Form\QuizEntityForm;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Helper\FormHelper;
+
+class FormDefinition extends FormHelper {
+
+  /** @var QuizEntity */
+  private $quiz;
+
+  public function __construct($quiz) {
+    $this->quiz = $quiz;
+
+    if (empty($this->quiz->qid) && 'admin' !== arg(0)) {
+      // If this is a new node we apply the user defaults for the quiz settings.
+      $msg = t('You are making your first @quiz. On this page you set the attributes, most of which you may tell the system to remember as defaults for the future. On the next screen you can add questions.', array('@quiz' => QUIZ_NAME));
+      drupal_set_message($msg);
+
+      foreach (quiz_controller()->getSettingIO()->getUserDefaultSettings() as $k => $v) {
+        if (!isset($this->quiz->{$k}) || is_null($this->quiz->{$k})) {
+          $this->quiz->{$k} = $v;
+        }
+      }
+    }
+
+    $this->quiz->status = 'admin' === arg(0) ? -1 : 1;
+  }
+
+  /**
+   * Main endpoint to get structure for quiz entity editing form.
+   *
+   * @param array $form
+   * @param array $form_state
+   * @param string $op
+   * @return array
+   */
+  public function get($form, &$form_state, $op) {
+    $quiz_type = quiz_type_load($this->quiz->type);
+
+    if (!empty($quiz_type->help)) {
+      $form['quiz_help'] = array(
+          '#prefix' => '<div class="quiz-help">',
+          '#markup' => $quiz_type->help,
+          '#suffix' => '</div>',
+      );
+    }
+
+    $form['#quiz'] = $this->quiz;
+    $form['#attached']['js'][] = drupal_get_path('module', 'quiz') . '/misc/js/quiz.entity-form.js';
+
+    $form['title'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Title'),
+        '#default_value' => isset($this->quiz->title) ? $this->quiz->title : '',
+        '#description'   => t('The name of this @quiz.', array('@quiz' => QUIZ_NAME)),
+        '#required'      => TRUE,
+        '#weight'        => -20,
+    );
+
+    $form['actions'] = array(
+        '#type'   => 'action',
+        '#weight' => 50,
+        'submit'  => array('#type' => 'submit', '#value' => t('Save')),
+    );
+
+    if (!empty($this->quiz->qid)) {
+      $form['actions']['delete'] = array(
+          '#type'   => 'submit',
+          '#value'  => t('Delete'),
+          '#suffix' => l(t('Cancel'), 'admin' === arg(0) ? 'admin/content/quiz' : 'quiz/' . $this->quiz->qid),
+      );
+    }
+
+    // Provides details in vertical tabs.
+    $form['vtabs'] = array('#type' => 'vertical_tabs');
+    $this->defineTakingOptions($form);
+    $this->defineUserPointOptionsFields($form);
+    $this->defineAvailabilityOptionsFields($form);
+    $this->definePassFailOptionsFields($form);
+    $this->defineResultFeedbackFields($form);
+    $this->defineRememberConfigOptionsFields($form);
+    $this->defineRevisionOptionsFields($form);
+
+    // Attach custom fields by admin
+    if (0 <= $this->quiz->status) {
+      field_attach_form('quiz_entity', $this->quiz, $form, $form_state);
+    }
+
+    return $form;
+  }
+
+  private function defineTakingOptions(&$form) {
+    $form['taking'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Taking options'),
+        '#collapsed'   => FALSE,
+        '#collapsible' => TRUE,
+        '#attributes'  => array('id' => 'taking-fieldset'),
+        '#group'       => 'vtabs',
+        '#weight'      => -2,
+    );
+    $form['taking']['allow_resume'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Allow resume'),
+        '#default_value' => $this->quiz->allow_resume,
+        '#description'   => t('Allow users to leave this @quiz incomplete and then resume it from where they left off.', array('@quiz' => QUIZ_NAME)),
+    );
+    $form['taking']['allow_skipping'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Allow skipping'),
+        '#default_value' => $this->quiz->allow_skipping,
+        '#description'   => t('Allow users to skip questions in this @quiz.', array('@quiz' => QUIZ_NAME)),
+    );
+    $form['taking']['allow_jumping'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Allow jumping'),
+        '#default_value' => $this->quiz->allow_jumping,
+        '#description'   => t('Allow users to jump to any question using a menu or pager in this @quiz.', array('@quiz' => QUIZ_NAME)),
+    );
+    $form['taking']['allow_change'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Allow changing answers'),
+        // https://www.drupal.org/node/2354355#comment-9241781
+        '#default_value' => isset($this->quiz->allow_change) ? $this->quiz->allow_change : 1,
+        '#description'   => t('If the user is able to visit a previous question, allow them to change the answer.'),
+    );
+    $form['taking']['backwards_navigation'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Backwards navigation'),
+        '#default_value' => $this->quiz->backwards_navigation,
+        '#description'   => t('Allow users to go back and revisit questions already answered.'),
+    );
+    $form['taking']['repeat_until_correct'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Repeat until correct'),
+        '#default_value' => $this->quiz->repeat_until_correct,
+        '#description'   => t('Require the user to retry the question until answered correctly.'),
+    );
+    $form['taking']['mark_doubtful'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Mark doubtful'),
+        '#default_value' => $this->quiz->mark_doubtful,
+        '#description'   => t('Allow users to mark their answers as doubtful.'),
+    );
+    $form['taking']['show_passed'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Show passed status'),
+        '#default_value' => $this->quiz->show_passed,
+        '#description'   => t('Show a message if the user has previously passed the @quiz.', array('@quiz' => QUIZ_NAME)),
+    );
+
+    $form['taking']['randomization'] = array(
+        '#type'          => 'radios',
+        '#title'         => t('Randomize questions'),
+        '#options'       => array(
+            t('No randomization'),
+            t('Random order'),
+            t('Random questions'),
+            t('Categorized random questions'),
+        ),
+        '#description'   => t('<strong>Random order</strong> - all questions display in random order')
+        . '<br/>' . t("<strong>Random questions</strong> - specific number of questions are drawn randomly from this @quiz's pool of questions", array('@quiz' => QUIZ_NAME))
+        . '<br/>' . t('<strong>Categorized random questions</strong> - specific number of questions are drawn from each specified taxonomy term'),
+        '#default_value' => $this->quiz->randomization,
+    );
+    $form['taking']['review_options'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Review options'),
+        '#collapsible' => FALSE,
+        '#collapsed'   => FALSE,
+        '#tree'        => TRUE,
+    );
+
+    $review_options = quiz()->getQuizHelper()->getFeedbackHelper()->getOptions();
+
+    foreach (array('question' => 'After the question', 'end' => t('After the @quiz', array('@quiz' => QUIZ_NAME))) as $key => $when) {
+      $form['taking']['review_options'][$key] = array(
+          '#title'         => $when,
+          '#type'          => 'checkboxes',
+          '#options'       => $review_options,
+          '#default_value' => isset($this->quiz->review_options[$key]) ? $this->quiz->review_options[$key] : array(),
+      );
+    }
+
+    $form['taking']['multiple_takes'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Multiple takes'),
+        '#collapsible' => FALSE,
+        '#collapsed'   => FALSE,
+        '#attributes'  => array('id' => 'multiple-takes-fieldset'),
+        '#description' => t('Allow users to take this quiz multiple times.'),
+    );
+    $form['taking']['multiple_takes']['takes'] = array(
+        '#type'          => 'select',
+        '#title'         => t('Allowed number of attempts'),
+        '#default_value' => $this->quiz->takes,
+        '#options'       => array(t('Unlimited')) + range(0, 10),
+        '#description'   => t('The number of times a user is allowed to take this @quiz. <strong>Anonymous users are only allowed to take @quiz that allow an unlimited number of attempts.</strong>', array('@quiz' => QUIZ_NAME)),
+    );
+    $form['taking']['multiple_takes']['show_attempt_stats'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Display allowed number of attempts'),
+        '#default_value' => $this->quiz->show_attempt_stats,
+        '#description'   => t('Display the allowed number of attempts on the starting page for this @quiz.', array('@quiz' => QUIZ_NAME)),
+    );
+
+    if (user_access('delete any quiz results') || user_access('delete results for own quiz')) {
+      $form['taking']['multiple_takes']['keep_results'] = array(
+          '#type'          => 'radios',
+          '#title'         => t('Store results'),
+          '#description'   => t('These results should be stored for each user.'),
+          '#options'       => array(t('The best'), t('The newest'), t('All')),
+          '#default_value' => $this->quiz->keep_results,
+      );
+    }
+    else {
+      $form['taking']['multiple_takes']['keep_results'] = array(
+          '#type'  => 'value',
+          '#value' => $this->quiz->keep_results,
+      );
+    }
+
+    $form['taking']['time_limit'] = array(
+        '#type'          => 'textfield',
+        '#title'         => t('Time limit'),
+        '#default_value' => isset($node->time_limit) ? $node->time_limit : 0,
+        '#description'   => t('Set the maximum allowed time in seconds for this @quiz. Use 0 for no limit.', array('@quiz' => QUIZ_NAME))
+        . '<br/>' . t('It is recommended to install the !countdown module, and enable the option in !link to show the time left to the user.', array(
+            '!link'      => l('Quiz configuration', 'admin/quiz/settings/config'),
+            '!countdown' => l('jquery_countdown', 'http://drupal.org/project/jquery_countdown'),
+    )));
+  }
+
+  private function defineUserPointOptionsFields($form) {
+    if (!function_exists('userpoints_userpointsapi') || !variable_get('quiz_has_userpoints', 1)) {
+      return;
+    }
+
+    $form['userpoints'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Userpoints'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => FALSE,
+        '#group'       => 'vtabs',
+    );
+
+    $form['userpoints']['has_userpoints'] = array(
+        '#type'          => 'checkbox',
+        '#default_value' => (isset($this->quiz->has_userpoints) ? $this->quiz->has_userpoints : 1),
+        '#title'         => t('Enable UserPoints Module Integration'),
+        '#description'   => t('If checked, marks scored in this @quiz will be credited to userpoints. For each correct answer 1 point will be added to user\'s point.', array('@quiz' => QUIZ_NAME)),
+    );
+
+    $form['userpoints']['userpoints_tid'] = array(
+        '#type'          => 'select',
+        '#options'       => $this->getUserpointsType(),
+        '#title'         => t('Userpoints Category'),
+        '#states'        => array(
+            'visible' => array(':input[name=has_userpoints]' => array('checked' => TRUE)),
+        ),
+        '#default_value' => isset($this->quiz->userpoints_tid) ? $this->quiz->userpoints_tid : 0,
+        '#description'   => t('Select the category to which user points to be added. To add new category see <a href="!url">admin/structure/taxonomy/userpoints</a>', array('!url' => url('admin/structure/taxonomy/userpoints'))),
+    );
+  }
+
+  /**
+   * Set up the availability options.
+   */
+  private function defineAvailabilityOptionsFields(&$form) {
+    $form['quiz_availability'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Availability options'),
+        '#collapsed'   => TRUE,
+        '#collapsible' => TRUE,
+        '#attributes'  => array('id' => 'availability-fieldset'),
+        '#group'       => 'vtabs',
+    );
+    $form['quiz_availability']['quiz_always'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Always available'),
+        '#default_value' => $this->quiz->quiz_always,
+        '#description'   => t('Ignore the open and close dates.'),
+    );
+    $form['quiz_availability']['quiz_open'] = array(
+        '#type'          => 'date',
+        '#title'         => t('Open date'),
+        '#default_value' => isset($this->quiz->quiz_open) ? $this->prepareDate($this->quiz->quiz_open) : NULL,
+        '#description'   => t('The date this @quiz will become available.', array('@quiz' => QUIZ_NAME)),
+        '#after_build'   => array('_quiz_after_build_fix_year_options'),
+    );
+    $form['quiz_availability']['quiz_close'] = array(
+        '#type'          => 'date',
+        '#title'         => t('Close date'),
+        '#default_value' => isset($this->quiz->quiz_close) ? $this->prepareDate($this->quiz->quiz_close, variable_get('quiz_default_close', 30)) : NULL,
+        '#description'   => t('The date this @quiz will become unavailable.', array('@quiz' => QUIZ_NAME)),
+        '#after_build'   => array('_quiz_after_build_fix_year_options'),
+    );
+  }
+
+  private function definePassFailOptionsFields(&$form) {
+    // Quiz summary options.
+    $form['summaryoptions'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Pass/fail options'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+        '#attributes'  => array('id' => 'summaryoptions-fieldset'),
+        '#group'       => 'vtabs',
+    );
+
+    // If pass/fail option is checked, present the form elements.
+    if (variable_get('quiz_use_passfail', 1)) {
+      $form['summaryoptions']['pass_rate'] = array(
+          '#type'          => 'textfield',
+          '#title'         => t('Passing rate for @quiz (%)', array('@quiz' => QUIZ_NAME)),
+          '#default_value' => $this->quiz->pass_rate,
+          '#description'   => t('Passing rate for this @quiz as a percentage score.', array('@quiz' => QUIZ_NAME)),
+          '#required'      => FALSE,
+      );
+      $form['summaryoptions']['summary_pass'] = array(
+          '#type'          => 'text_format',
+          '#base_type'     => 'textarea',
+          '#title'         => t('Summary text if passed'),
+          '#default_value' => $this->quiz->summary_pass,
+          '#cols'          => 60,
+          '#description'   => t("Summary text for when the user passes the @quiz. Leave blank to not give different summary text if passed, or if not using the \"percent to pass\" option above. If not using the \"percentage needed to pass\" field above, this text will not be used.", array('@quiz' => QUIZ_NAME)),
+          '#format'        => isset($this->quiz->summary_pass_format) && !empty($this->quiz->summary_pass_format) ? $this->quiz->summary_pass_format : NULL,
+      );
+    }
+    // If the pass/fail option is unchecked, use the default and hide it.
+    else {
+      $form['summaryoptions']['pass_rate'] = array(
+          '#type'     => 'hidden',
+          '#value'    => $this->quiz->pass_rate,
+          '#required' => FALSE,
+      );
+    }
+    // We use a helper to enable the wysiwyg module to add an editor to the
+    // textarea.
+    $form['summaryoptions']['helper']['summary_default'] = array(
+        '#type'          => 'text_format',
+        '#base_type'     => 'textarea',
+        '#title'         => t('Default summary text'),
+        '#default_value' => $this->quiz->summary_default,
+        '#cols'          => 60,
+        '#description'   => t("Default summary. Leave blank if you don't want to give a summary."),
+        '#format'        => isset($this->quiz->summary_default_format) && !empty($this->quiz->summary_default_format) ? $this->quiz->summary_default_format : NULL,
+    );
+
+    // Number of random questions, max score and tid for random questions are set on
+    // the manage questions tab. We repeat them here so that they're not removed
+    // if the quiz is being updated.
+    $num_rand = (isset($this->quiz->number_of_random_questions)) ? $this->quiz->number_of_random_questions : 0;
+    $form['number_of_random_questions'] = array('#type' => 'value', '#value' => $num_rand);
+    $max_score_for_random = (isset($this->quiz->max_score_for_random)) ? $this->quiz->max_score_for_random : 0;
+    $form['max_score_for_random'] = array('#type' => 'value', '#value' => $max_score_for_random);
+    $form['tid'] = array('#type' => 'value', '#value' => (isset($this->quiz->tid)) ? $this->quiz->tid : 0);
+  }
+
+  private function defineResultFeedbackFields(&$form) {
+    $options = !empty($this->quiz->resultoptions) ? $this->quiz->resultoptions : array();
+    $num_options = max(count($options), variable_get('quiz_max_result_options', 5));
+
+    if ($num_options > 0) {
+      $form['resultoptions'] = array(
+          '#type'        => 'fieldset',
+          '#title'       => t('Result feedback'),
+          '#collapsible' => TRUE,
+          '#collapsed'   => TRUE,
+          '#tree'        => TRUE,
+          '#attributes'  => array('id' => 'resultoptions-fieldset'),
+          '#group'       => 'vtabs',
+      );
+
+      for ($i = 0; $i < $num_options; $i++) {
+        $option = (count($options) > 0) ? array_shift($options) : NULL; // grab each option in the array
+        $form['resultoptions'][$i] = array(
+            '#type'        => 'fieldset',
+            '#title'       => t('Result Option ') . ($i + 1),
+            '#collapsible' => TRUE,
+            '#collapsed'   => FALSE,
+        );
+        $form['resultoptions'][$i]['option_name'] = array(
+            '#type'          => 'textfield',
+            '#title'         => t('Range title'),
+            '#default_value' => isset($option['option_name']) ? $option['option_name'] : '',
+            '#maxlength'     => 40,
+            '#size'          => 40,
+            '#description'   => 'e.g., "A" or "Passed"',
+        );
+        $form['resultoptions'][$i]['option_start'] = array(
+            '#type'          => 'textfield',
+            '#title'         => t('Percentage low'),
+            '#description'   => t('Show this result for scored @quiz in this range (0-100).', array('@quiz' => QUIZ_NAME)),
+            '#default_value' => isset($option['option_start']) ? $option['option_start'] : '',
+            '#size'          => 5,
+        );
+        $form['resultoptions'][$i]['option_end'] = array(
+            '#type'          => 'textfield',
+            '#title'         => t('Percentage high'),
+            '#description'   => t('Show this result for scored @quiz in this range (0-100).', array('@quiz' => QUIZ_NAME)),
+            '#default_value' => isset($option['option_end']) ? $option['option_end'] : '',
+            '#size'          => 5,
+        );
+        $form['resultoptions'][$i]['option_summary'] = array(
+            '#type'          => 'text_format',
+            '#base_type'     => 'textarea',
+            '#title'         => t('Feedback'),
+            '#default_value' => isset($option['option_summary']) ? $option['option_summary'] : '',
+            '#description'   => t("This is the text that will be displayed when the user's score falls in this range."),
+            '#format'        => isset($option['option_summary_format']) ? $option['option_summary_format'] : NULL,
+        );
+        if (isset($option['option_id'])) {
+          $form['resultoptions'][$i]['option_id'] = array(
+              '#type'  => 'hidden',
+              '#value' => isset($option['option_id']) ? $option['option_id'] : '',
+          );
+        }
+      }
+    }
+  }
+
+  private function defineRememberConfigOptionsFields(&$form) {
+    $form['remember_settings'] = array(
+        '#type'        => 'checkbox',
+        '#title'       => t('Remember my settings'),
+        '#description' => t('If this box is checked most of the @quiz specific settings you have made will be remembered and will be your default settings next time you create a @quiz.', array('@quiz' => QUIZ_NAME)),
+        '#weight'      => -15,
+    );
+
+    $form['remember_global'] = array(
+        '#type'        => 'checkbox',
+        '#title'       => t('Remember as global'),
+        '#description' => t('If this box is checked most of the @quiz specific settings you have made will be remembered and will be everyone\'s default settings next time they create a @quiz.', array('@quiz' => QUIZ_NAME)),
+        '#weight'      => -15,
+        '#access'      => user_access('administer quiz configuration'),
+    );
+
+    if (quiz_has_been_answered($this->quiz) && (!user_access('manual quiz revisioning') || variable_get('quiz_auto_revisioning', 1))) {
+      $this->quiz->revision = 1;
+      $this->quiz->log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
+    }
+  }
+
+  private function defineRevisionOptionsFields(&$form) {
+    $form['revision_information'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Revision information'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+        '#group'       => 'vtabs',
+        '#attributes'  => array('class' => array('node-form-revision-information')),
+        '#attached'    => array('js' => array(drupal_get_path('module', 'node') . '/node.js')),
+        '#weight'      => 20,
+        '#access'      => TRUE,
+    );
+
+    $form['revision_information']['revision'] = array(
+        '#type'          => 'checkbox',
+        '#title'         => t('Create new revision'),
+        '#default_value' => FALSE,
+        '#state'         => array('checked' => array('textarea[name="log"]' => array('empty' => FALSE))),
+    );
+
+    $form['revision_information']['log'] = array(
+        '#type'          => 'textarea',
+        '#title'         => t('Revision log message'),
+        '#row'           => 4,
+        '#default_value' => '',
+        '#description'   => t('Provide an explanation of the changes you are making. This will help other authors understand your motivations.'),
+    );
+
+    if (variable_get('quiz_auto_revisioning', 1) || !user_access('manual quiz revisioning')) {
+      $form['revision_information']['revision']['#type'] = 'value';
+      $form['revision_information']['revision']['#value'] = variable_get('quiz_auto_revisioning', 1);
+      $form['revision_information']['log']['#type'] = 'value';
+      $form['revision_information']['log']['#value'] = $form['revision_information']['log']['#default_value'];
+      $form['revision_information']['#access'] = FALSE;
+      if (quiz_has_been_answered($this->quiz)) {
+        $this->quiz->revision = 1;
+        $this->quiz->log = t('The current revision has been answered. We create a new revision so that the reports from the existing answers stays correct.');
+      }
+    }
+  }
+
+}
diff --git a/src/Form/QuizEntityForm/FormValidation.php b/src/Form/QuizEntityForm/FormValidation.php
new file mode 100644
index 0000000..010c90a
--- /dev/null
+++ b/src/Form/QuizEntityForm/FormValidation.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\quiz\Form\QuizEntityForm;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class FormValidation {
+
+  /** @var QuizEntity */
+  private $quiz;
+
+  /** @var array */
+  private $form;
+
+  /** @var array */
+  private $form_state;
+
+  public function __construct($form, &$form_state) {
+    $this->form = $form;
+    $this->form_state = &$form_state;
+    $this->quiz = entity_ui_controller('quiz_entity')->entityFormSubmitBuildEntity($form, $form_state);
+  }
+
+  public function validate() {
+    $this->validateResultOptions();
+    $this->validateTakingOptions();
+
+    // Notify field widgets to validate their data.
+    field_attach_form_validate('quiz_entity', $this->quiz, $this->form, $this->form_state);
+  }
+
+  private function validateResultOptions() {
+    if (!empty($this->quiz->pass_rate)) {
+      if (!_quiz_is_int($this->quiz->pass_rate, 0, 100)) {
+        form_set_error('pass_rate', t('"Passing rate" must be a number between 0 and 100.'));
+      }
+    }
+
+    if (isset($this->quiz->resultoptions) && count($this->quiz->resultoptions) > 0) {
+      $taken_values = array();
+      foreach ($this->quiz->resultoptions as $option) {
+        $this->validateResultOption($option, $taken_values);
+      }
+    }
+  }
+
+  private function validateResultOption($option, &$taken_values) {
+    if (empty($option['option_name']) && !$this->isEmptyHTML($option['option_summary']['value'])) {
+      return form_set_error('option_summary', t('Range has a summary, but no name.'));
+    }
+
+    if (empty($option['option_name'])) {
+      return;
+    }
+
+    if (empty($option['option_summary']['value'])) {
+      form_set_error('option_summary', t('Range has no summary text.'));
+    }
+
+    if ($this->quiz->pass_rate && (isset($option['option_start']) || isset($option['option_end']))) {
+      // Check for a number between 0-100.
+      foreach (array('option_start' => 'start', 'option_end' => 'end') as $bound => $bound_text) {
+        if (!_quiz_is_int($option[$bound], 0, 100)) {
+          form_set_error($bound, t('The range %start value must be a number between 0 and 100.', array(
+              '%start' => $bound_text
+          )));
+        }
+      }
+
+      // Check that range end >= start.
+      if ($option['option_start'] > $option['option_end']) {
+        form_set_error('option_start', t('The start must be less than the end of the range.'));
+      }
+
+      // Check that range doesn't collide with any other range.
+      $option_range = range($option['option_start'], $option['option_end']);
+      if ($intersect = array_intersect($taken_values, $option_range)) {
+        form_set_error('option_start', t('The ranges must not overlap each other. (%intersect)', array('%intersect' => implode(',', $intersect))));
+      }
+      else {
+        $taken_values = array_merge($taken_values, $option_range);
+      }
+    }
+  }
+
+  private function validateTakingOptions() {
+    // Don't check dates if the quiz is always available.
+    if (!$this->quiz->quiz_always) {
+      $_open = mktime(0, 0, 0, $this->quiz->quiz_open['month'], $this->quiz->quiz_open['day'], $this->quiz->quiz_open['year']);
+      $_close = mktime(0, 0, 0, $this->quiz->quiz_close['month'], $this->quiz->quiz_close['day'], $this->quiz->quiz_close['year']);
+      if ($_open > $_close) {
+        form_set_error('quiz_close', t('"Close date" must be later than the "open date".'));
+      }
+    }
+
+    if (isset($this->quiz->time_limit)) {
+      if (!_quiz_is_int($this->quiz->time_limit, 0)) {
+        form_set_error('time_limit', t('"Time limit" must be a positive number.'));
+      }
+    }
+
+    if ($this->quiz->allow_jumping && empty($this->quiz->allow_skipping)) {
+      // @todo when we have pages of questions, we have to check that jumping is
+      // not enabled, and randomization is not enabled unless there is only 1 page
+      form_set_error('allow_skipping', t('If jumping is allowed, skipping must also be allowed.'));
+    }
+  }
+
+  /**
+   * Helper function used when figuring out if a textfield or textarea is empty.
+   *
+   * Solves a problem with some wysiwyg editors inserting spaces and tags
+   * without content.
+   *
+   * @param string $html The html to evaluate
+   * @return bool
+   *  TRUE if the field is empty(can still be tags there) false otherwise.
+   */
+  private function isEmptyHTML($html) {
+    return drupal_strlen(trim(str_replace('&nbsp;', '', strip_tags($html, '<img><object><embed>')))) == 0;
+  }
+
+}
diff --git a/src/Form/QuizQuestionsForm.php b/src/Form/QuizQuestionsForm.php
new file mode 100644
index 0000000..e563599
--- /dev/null
+++ b/src/Form/QuizQuestionsForm.php
@@ -0,0 +1,555 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Helper\Quiz\BaseForm;
+use stdClass;
+
+class QuizQuestionsForm extends BaseForm {
+
+  /**
+   * Handles "manage questions" tab.
+   *
+   * Displays form which allows questions to be assigned to the given quiz.
+   *
+   * This function is not used if the question assignment type "categorized random questions" is chosen
+   *
+   * @param $form_state
+   *  The form state variable
+   * @param QuizEntity $quiz
+   * @return
+   *  HTML output to create page.
+   */
+  public function formGet($form, $form_state, $quiz) {
+    // Display questions in this quiz.
+    $form['question_list'] = array(
+        '#type'           => 'fieldset',
+        '#title'          => t('Questions in this @quiz', array('@quiz' => QUIZ_NAME)),
+        '#theme'          => 'question_selection_table',
+        '#collapsible'    => TRUE,
+        '#attributes'     => array('id' => 'mq-fieldset'),
+        'question_status' => array('#tree' => TRUE),
+    );
+
+    // Add randomization settings if this quiz allows randomized questions
+    $this->addFieldsForRandomQuiz($form, $quiz);
+
+    // @todo deal with $include_random
+    $questions = $quiz->getQuestionLoader()->getQuestions();
+
+    if (empty($questions)) {
+      $form['question_list']['no_questions'] = array(
+          '#markup' => '<div id = "no-questions">' . t('There are currently no questions in this @quiz. Assign existing questions by using the question browser below. You can also use the links above to create new questions.', array('@quiz' => QUIZ_NAME)) . '</div>',
+      );
+    }
+
+    // We add the questions to the form array
+    $types = quiz_get_question_types();
+    $this->addQuestionsToForm($form, $questions, $quiz, $types);
+
+    // Show the number of questions in the table header.
+    $always_count = isset($form['question_list']['titles']) ? count($form['question_list']['titles']) : 0;
+    $form['question_list']['#title'] .= ' (' . $always_count . ')';
+
+    // Give the user the option to create a new revision of the quiz
+    $this->addRevisionCheckbox($form, $quiz);
+
+    // Timestamp is needed to avoid multiple users editing the same quiz at the same time.
+    $form['timestamp'] = array('#type' => 'hidden', '#default_value' => REQUEST_TIME);
+
+    // Action buttons
+    $form['actions']['#type'] = 'actions';
+    $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
+
+    return $form;
+  }
+
+  /**
+   * Fields for creating new questions are added to the quiz_questions_form
+   *
+   * @param $form
+   *   FAPI form(array)
+   * @param $types
+   *   All the question types(array)
+   * @param $quiz
+   *   The quiz entity
+   */
+  private function addFieldsForCreatingQuestions(&$form, &$types, &$quiz) {
+    // Display links to create other questions.
+    $form['additional_questions'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Create new question'),
+        '#collapsible' => TRUE,
+        '#collapsed'   => TRUE,
+    );
+
+    $url_query = drupal_get_destination();
+    $url_query['quiz_qid'] = $quiz->qid;
+    $url_query['quiz_vid'] = $quiz->vid;
+    $create_question = FALSE;
+    foreach ($types as $type => $info) {
+      $url_type = str_replace('_', '-', $type);
+      $options = array(
+          'attributes' => array('title' => t('Create @name', array('@name' => $info['name']))),
+          'query'      => $url_query,
+      );
+      $access = node_access('create', $type);
+      if ($access) {
+        $create_question = TRUE;
+      }
+      $form['additional_questions'][$type] = array(
+          '#markup' => '<div class="add-questions">' . l($info['name'], "node/add/$url_type", $options) . '</div>',
+          '#access' => $access,
+      );
+    }
+
+    if (!$create_question) {
+      $form['additional_questions']['create'] = array(
+          '#type'   => 'markup',
+          '#markup' => t('You have not enabled any question type module or no has permission been given to create any question.'),
+        // @todo revisit UI text
+      );
+    }
+  }
+
+  /**
+   * Add fields for random quiz to the quiz_questions_form
+   *
+   * @param array $form
+   *   FAPI form array
+   * @param QuizEntity $quiz
+   */
+  private function addFieldsForRandomQuiz(&$form, $quiz) {
+    if ($quiz->randomization != 2) {
+      return;
+    }
+    $form['question_list']['random_settings'] = array(
+        '#type'        => 'fieldset',
+        '#title'       => t('Settings for random questions'),
+        '#collapsible' => TRUE,
+    );
+    $form['question_list']['random_settings']['num_random_questions'] = array(
+        '#type'          => 'textfield',
+        '#size'          => 3,
+        '#maxlength'     => 3,
+        '#weight'        => -5,
+        '#title'         => t('Number of random questions'),
+        '#description'   => t('The number of questions to be randomly selected each time someone takes this quiz'),
+        '#default_value' => isset($quiz->number_of_random_questions) ? $quiz->number_of_random_questions : 10,
+    );
+    $form['question_list']['random_settings']['max_score_for_random'] = array(
+        '#type'          => 'textfield',
+        '#size'          => 3,
+        '#maxlength'     => 3,
+        '#weight'        => -5,
+        '#title'         => t('Max score for each random question'),
+        '#default_value' => isset($quiz->max_score_for_random) ? $quiz->max_score_for_random : 1,
+    );
+    if ($quiz->randomization == 3) {
+      $terms = $this->taxonomySelect($quiz->tid);
+      if (!empty($terms) && function_exists('taxonomy_get_vocabularies')) {
+        $form['question_list']['random_settings']['random_term_id'] = array(
+            '#type'          => 'select',
+            '#title'         => t('Terms'),
+            '#size'          => 1,
+            '#options'       => $this->taxonomySelect($quiz->tid),
+            '#default_value' => $quiz->tid,
+            '#description'   => t('Randomly select from questions with this term, or choose from the question pool below'),
+            '#weight'        => -4,
+        );
+      }
+    }
+  }
+
+  /**
+   * Prints a taxonomy selection form for each vocabulary.
+   *
+   * @param $tid
+   *   Default selected value(s).
+   * @return
+   *   HTML output to print to screen.
+   */
+  private function taxonomySelect($tid = 0) {
+    $options = array();
+    foreach (quiz()->getVocabularies() as $vid => $vocabulary) {
+      $temp = taxonomy_form($vid, $tid);
+      $options = array_merge($options, $temp['#options']);
+    }
+    return $options;
+  }
+
+  /**
+   * Adds the questions in the $questions array to the form
+   *
+   * @param $form
+   *   FAPI form(array)
+   * @param $questions
+   *   The questions to be added to the question list(array)
+   * @param QuizEntity $quiz
+   *   The quiz entity
+   * @param $question_types
+   *   array of all available question types
+   */
+  private function addQuestionsToForm(&$form, &$questions, &$quiz, &$question_types) {
+    $form['question_list']['weights'] = array('#tree' => TRUE);
+    $form['question_list']['qr_ids'] = array('#tree' => TRUE);
+    $form['question_list']['qr_pids'] = array('#tree' => TRUE);
+    $form['question_list']['max_scores'] = array('#tree' => TRUE);
+    $form['question_list']['auto_update_max_scores'] = array('#tree' => TRUE);
+    $form['question_list']['stayers'] = array('#tree' => TRUE);
+    $form['question_list']['revision'] = array('#tree' => TRUE);
+    if ($quiz->randomization == 2) {
+      $form['question_list']['compulsories'] = array('#tree' => TRUE);
+    }
+
+    foreach ($questions as $question) {
+      // @todo replace entire form with usage of question instance
+      $question_node = node_load($question->nid, $question->vid);
+      $instance = _quiz_question_get_instance($question_node);
+      $fieldset = 'question_list';
+      $id = $question->nid . '-' . $question->vid;
+
+      $form[$fieldset]['weights'][$id] = array(
+          '#type'          => 'textfield',
+          '#size'          => 3,
+          '#maxlength'     => 4,
+          '#default_value' => isset($question->weight) ? $question->weight : 0,
+      );
+
+      $form[$fieldset]['qr_pids'][$id] = array(
+          '#type'          => 'textfield',
+          '#size'          => 3,
+          '#maxlength'     => 4,
+          '#default_value' => $question->qr_pid,
+      );
+
+      $form[$fieldset]['qr_ids'][$id] = array(
+          '#type'          => 'textfield',
+          '#size'          => 3,
+          '#maxlength'     => 4,
+          '#default_value' => $question->qr_id,
+      );
+
+      // Quiz directions don't have scoring...
+      $form[$fieldset]['max_scores'][$id] = array(
+          '#type'          => $instance->isGraded() ? 'textfield' : 'hidden',
+          '#size'          => 2,
+          '#maxlength'     => 2,
+          '#disabled'      => isset($question->auto_update_max_score) ? $question->auto_update_max_score : FALSE,
+          '#default_value' => isset($question->max_score) ? $question->max_score : 0,
+          '#states'        => array(
+              'disabled' => array("#edit-auto-update-max-scores-$id" => array('checked' => TRUE))
+          ),
+      );
+
+      $form[$fieldset]['auto_update_max_scores'][$id] = array(
+          '#type'          => $instance->isGraded() ? 'checkbox' : 'hidden',
+          '#default_value' => isset($question->auto_update_max_score) ? $question->auto_update_max_score : 0,
+      );
+
+      // Add checkboxes to remove questions in js disabled browsers...
+      $form[$fieldset]['stayers'][$id] = array(
+          '#type'          => 'checkbox',
+          '#default_value' => 0,
+          '#attributes'    => array('class' => array('q-staying')),
+      );
+
+      //Add checkboxes to mark compulsory questions for randomized quizzes.
+      if ($quiz->randomization == 2) {
+        $form[$fieldset]['compulsories'][$id] = array(
+            '#type'          => 'checkbox',
+            '#default_value' => isset($question->question_status) ? ($question->question_status == QUESTION_ALWAYS) ? 1 : 0 : 0,
+            '#attributes'    => array('class' => array('q-compulsory')),
+        );
+      }
+
+      if (user_access('view quiz question outside of a quiz')) {
+        $link_options = array(
+            'attributes' => array('class' => array('handle-changes')),
+        );
+        $question_titles = l($question->title, 'node/' . $question->nid, $link_options);
+      }
+      else {
+        $question_titles = check_plain($question->title);
+      }
+
+      $form[$fieldset]['titles'][$id] = array('#markup' => $question_titles);
+
+
+      $form[$fieldset]['types'][$id] = array(
+          '#markup'        => $question_types[$question->type]['name'],
+          '#question_type' => $question->type,
+      );
+
+      $form[$fieldset]['view_links'][$id] = array(
+          '#markup' => l(
+            t('Edit'), 'node/' . $question->nid . '/edit', array(
+              'query'      => drupal_get_destination(),
+              'attributes' => array('class' => array('handle-changes')),
+            )
+          ),
+          '#access' => node_access('update', node_load($question->nid, $question->vid)),
+      );
+      // For js enabled browsers questions are removed by pressing a remove link
+      $form[$fieldset]['remove_links'][$id] = array(
+          '#markup' => '<a href="#" class="rem-link">' . t('Remove') . '</a>',
+      );
+      // Add a checkbox to update to the latest revision of the question
+      if ($question->vid == $question->latest_vid) {
+        $update_cell = array('#markup' => t('<em>Up to date</em>'));
+      }
+      else {
+        $update_cell = array(
+            '#type'  => 'checkbox',
+            '#title' => (l(t('Latest'), 'node/' . $question->nid . '/revisions/' . $question->latest_vid . '/view')
+            . ' of ' .
+            l(t('revisions'), 'node/' . $question->nid . '/revisions')
+            ),
+        );
+      }
+      $form[$fieldset]['revision'][$id] = $update_cell;
+    }
+  }
+
+  /**
+   * Validate that the supplied questions are real.
+   */
+  public function formValidate($form, $form_state) {
+    if (!$quiz = quiz_entity_single_load(__quiz_get_context_id())) {
+      $msg = t('A critical error has occured. Please report error code 28 on the quiz project page.');
+      form_set_error('changed', $msg);
+      return;
+    }
+
+    if ($quiz->changed > $form_state['values']['timestamp']) {
+      $msg = t('This content has been modified by another user, changes cannot be saved.');
+      form_set_error('changed', $msg);
+    }
+
+    $already_checked = array();
+    $weight_map = $form_state['values']['weights'];
+
+    // Make sure the number of random questions is a positive number
+    if (isset($form_state['values']['num_random_questions']) && !_quiz_is_int($form_state['values']['num_random_questions'], 0)) {
+      form_set_error('num_random_questions', 'The number of random questions needs to be a positive number');
+    }
+
+    // Make sure the max score for random questions is a positive number
+    if (isset($form_state['values']['max_score_for_random']) && !_quiz_is_int($form_state['values']['max_score_for_random'], 0)) {
+      form_set_error('max_score_for_random', 'The max score for random questions needs to be a positive number');
+    }
+
+    if (empty($weight_map)) {
+      form_set_error('none', 'No questions were included.');
+      return;
+    }
+
+    $question_types = array_keys(quiz_get_question_types());
+
+    foreach ($weight_map as $id => $weight) {
+      list($nid, $vid) = explode('-', $id, 2);
+
+      // If a node isn't one of the question types we remove it from the question list
+      $has_questions = (Boolean) db_select('node', 'n')
+          ->fields('n', array('nid'))
+          ->condition('type', $question_types, 'IN')
+          ->addTag('node_access')
+          ->condition('n.nid', $nid)
+          ->execute()
+          ->fetchField();
+      if (!$has_questions) {
+        form_set_error('none', 'One of the supplied questions was invalid. It has been removed from the quiz.');
+        unset($form_state['values']['weights'][$id]);
+      }
+
+      // We also make sure that we don't have duplicate questions in the quiz.
+      elseif (in_array($nid, $already_checked)) {
+        form_set_error('none', 'A duplicate question has been removed. You can only ask a question once per quiz.');
+        unset($form_state['values']['weights'][$id]);
+      }
+      else {
+        $already_checked[] = $nid;
+      }
+    }
+
+    // We make sure max score is a positive number
+    $max_scores = $form_state['values']['max_scores'];
+    foreach ($max_scores as $id => $max_score) {
+      if (!_quiz_is_int($max_score, 0)) {
+        form_set_error("max_scores][$id", t('Max score needs to be a positive number'));
+      }
+    }
+  }
+
+  /**
+   * Submit function for quiz_questions.
+   *
+   * Updates from the "manage questions" tab.
+   */
+  public function formSubmit($form, &$form_state) {
+    /* @var $quiz QuizEntity */
+    $quiz = 'node' === arg(0) ? node_load(arg(1)) : quiz_entity_single_load(arg(1));
+
+    // Update the refresh latest quizzes table so that we know what the users latest quizzes are
+    if (variable_get('quiz_auto_revisioning', 1)) {
+      $is_new_revision = quiz_has_been_answered($quiz);
+    }
+    else {
+      $is_new_revision = (bool) $form_state['values']['new_revision'];
+    }
+
+    $this->questionBrowserSubmit($form, $form_state);
+
+    $weight_map = $form_state['values']['weights'];
+    $qr_pids_map = $form_state['values']['qr_pids'];
+    $qr_ids_map = $form_state['values']['qr_ids'];
+    $max_scores = $form_state['values']['max_scores'];
+    $auto_update_max_scores = $form_state['values']['auto_update_max_scores'];
+    $refreshes = isset($form_state['values']['revision']) ? $form_state['values']['revision'] : NULL;
+    $stayers = $form_state['values']['stayers'];
+    $compulsories = isset($form_state['values']['compulsories']) ? $form_state['values']['compulsories'] : NULL;
+    $num_random = isset($form_state['values']['num_random_questions']) ? $form_state['values']['num_random_questions'] : 0;
+    $quiz->max_score_for_random = isset($form_state['values']['max_score_for_random']) ? $form_state['values']['max_score_for_random'] : 1;
+    $term_id = isset($form_state['values']['random_term_id']) ? (int) $form_state['values']['random_term_id'] : 0;
+
+    // Store what questions belong to the quiz
+    $questions = $this->updateItems($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qr_ids_map, $qr_pids_map, $compulsories, $stayers);
+
+    // If using random questions and no term ID is specified, make sure we have enough.
+    if (empty($term_id)) {
+      $assigned_random = 0;
+
+      foreach ($questions as $question) {
+        if ($question->state == QUESTION_RANDOM) {
+          ++$assigned_random;
+        }
+      }
+
+      // Adjust number of random questions downward to match number of selected questions..
+      if ($num_random > $assigned_random) {
+        $num_random = $assigned_random;
+        drupal_set_message(t('The number of random questions for this @quiz have been lowered to %anum to match the number of questions you assigned.', array('@quiz' => QUIZ_NAME, '%anum' => $assigned_random), array('langcode' => 'warning')));
+      }
+    }
+    else {
+      // Warn user if not enough questions available with this term_id.
+      $available_random = count(quiz()->getQuizHelper()->getRandomTaxonomyQuestionIds($term_id, $num_random));
+      if ($num_random > $available_random) {
+        $num_random = $available_random;
+        drupal_set_message(t('There are currently not enough questions assigned to this term (@random). Please lower the number of random quetions or assign more questions to this taxonomy term before taking this @quiz.', array('@random' => $available_random, '@quiz' => QUIZ_NAME)), 'error');
+      }
+    }
+
+    // Get sum of max_score
+    $query = db_select('quiz_relationship', 'qnr');
+    $query->addExpression('SUM(max_score)', 'sum');
+    $query->condition('quiz_vid', $quiz->vid);
+    $query->condition('question_status', QUESTION_ALWAYS);
+    $score = $query->execute()->fetchAssoc();
+
+    // Update the quiz's properties.
+    $quiz->number_of_random_questions = $num_random ? $num_random : 0;
+    $quiz->max_score_for_random = $quiz->max_score_for_random;
+    $quiz->tid = $term_id;
+    $quiz->max_score = $quiz->max_score_for_random * $quiz->number_of_random_questions + $score['sum'];
+    $quiz->is_new_revision = $is_new_revision;
+
+    if (entity_save('quiz_entity', $quiz)) {
+      drupal_set_message(t('Questions updated successfully.'));
+    }
+    else {
+      drupal_set_message(t('There was an error updating the @quiz.', array('@quiz' => QUIZ_NAME)), 'error');
+    }
+  }
+
+  /**
+   * Takes care of the browser part of the submitted form values.
+   *
+   * This function changes the form_state to reflect questions added via the browser.
+   * (Especially if js is disabled)
+   *
+   *
+   * @param $form
+   *   FAPI form(array)
+   * @param $form_state
+   *   FAPI form_state(array)
+   */
+  private function questionBrowserSubmit($form, &$form_state) {
+    // Find the biggest weight:
+    $next_weight = max($form_state['values']['weights']);
+
+    // If a question is chosen in the browser, add it to the question list if it isn't already there
+    if (isset($form_state['values']['browser']['table']['titles'])) {
+      foreach ($form_state['values']['browser']['table']['titles'] as $id) {
+        if ($id !== 0) {
+          $matches = array();
+          preg_match('/([0-9]+)-([0-9]+)/', $id, $matches);
+          $nid = $matches[1];
+          $vid = $matches[2];
+          $form_state['values']['weights'][$id] = ++$next_weight;
+          $form_state['values']['max_scores'][$id] = quiz_question_get_max_score($nid, $vid);
+          $form_state['values']['stayers'][$id] = 1;
+        }
+      }
+    }
+  }
+
+  /**
+   * Update a quiz set of items with new weights and membership
+   * @param $quiz
+   *   The quiz entity
+   * @param $weight_map
+   *   Weights for each question(determines the order in which the question will be taken by the quiz taker)
+   * @param $max_scores
+   *   Array of max scores for each question
+   * @param $is_new_revision
+   *   Array of boolean values determining if the question is to be updated to the newest revision
+   * @param $refreshes
+   *   True if we are creating a new revision of the quiz
+   * @param $stayers
+   *   Questions added to the quiz
+   * @param $compulsories
+   *   Array of boolean values determining if the question is compulsory or not.
+   * @return array set of questions after updating
+   */
+  private function updateItems($quiz, $weight_map, $max_scores, $auto_update_max_scores, $is_new_revision, $refreshes, $stayers, $qr_ids, $qr_pids, $compulsories = NULL) {
+    $questions = array();
+    foreach ($weight_map as $id => $weight) {
+      if ($stayers[$id]) {
+        continue;
+      }
+      list($nid, $vid) = explode('-', $id, 2);
+      $question = new stdClass();
+      $question->nid = (int) $nid;
+      $question->vid = (int) $vid;
+      if (isset($compulsories)) {
+        if ($compulsories[$id] == 1) {
+          $question->state = QUESTION_ALWAYS;
+        }
+        else {
+          $question->state = QUESTION_RANDOM;
+          $max_scores[$id] = $quiz->max_score_for_random;
+        }
+      }
+      else {
+        $question->state = QUESTION_ALWAYS;
+      }
+      $question->weight = $weight;
+      $question->max_score = $max_scores[$id];
+      $question->auto_update_max_score = $auto_update_max_scores[$id];
+      $question->qr_pid = $qr_pids[$id] > 0 ? $qr_pids[$id] : NULL;
+      $question->qr_id = $qr_ids[$id] > 0 ? $qr_ids[$id] : NULL;
+      $question->refresh = (isset($refreshes[$id]) && $refreshes[$id] == 1);
+
+      // Add item as an object in the questions array.
+      $questions[] = $question;
+    }
+
+    // Save questions.
+    quiz()->getQuizHelper()->setQuestions($quiz, $questions, $is_new_revision);
+
+    return $questions;
+  }
+
+}
diff --git a/src/Form/QuizReportForm.php b/src/Form/QuizReportForm.php
new file mode 100644
index 0000000..b714873
--- /dev/null
+++ b/src/Form/QuizReportForm.php
@@ -0,0 +1,218 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+class QuizReportForm {
+
+  /**
+   * Form for showing feedback, and for editing the feedback if necessary...
+   *
+   * @param $form_state
+   *   FAPI form state(array)
+   * @param $questions
+   *   array of questions to inclide in the report
+   * @return $form
+   *   FAPI form array
+   */
+  public function getForm($form, $form_state, $questions) {
+    $form['#theme'] = 'quiz_report_form'; # @TODO: Remove me
+    $form['#tree'] = TRUE;
+
+    foreach ($questions as $question) {
+      if (!$module = quiz_question_module_for_type($question->type)) {
+        return array();
+      }
+      $function = $module . '_report_form';
+      $form_to_add = $function($question);
+      if (isset($form_to_add['submit'])) {
+        $show_submit = TRUE;
+      }
+      $form_to_add['#element_validate'][] = 'quiz_report_form_element_validate';
+      $form[] = $form_to_add;
+    }
+
+    // The submit button is only shown if one or more of the questions has input elements
+    if (!empty($show_submit)) {
+      $form['submit'] = array('#type' => 'submit', '#value' => t('Save score'));
+    }
+
+    if (arg(4) === 'feedback') {
+      // @todo figure something better than args.
+      $quiz = quiz_entity_single_load(__quiz_get_context_id());
+      $quiz_id = $quiz->qid;
+      if (empty($_SESSION['quiz'][$quiz_id])) { // Quiz is done.
+        $form['finish'] = array('#type' => 'submit', '#value' => t('Finish'));
+      }
+      else {
+        $form['next'] = array('#type' => 'submit', '#value' => t('Next question'));
+      }
+    }
+
+    return $form;
+  }
+
+  /**
+   * Submit handler to go to the next question from the question feedback.
+   */
+  public function formSubmitFeedback($form, &$form_state) {
+    $quiz_id = __quiz_get_context_id();
+    $form_state['redirect'] = "quiz/{$quiz_id}/take/" . $_SESSION['quiz'][$quiz_id]['current'];
+  }
+
+  /**
+   * Validate a single question sub-form.
+   */
+  public static function validateElement(&$element, &$form_state) {
+    $question = node_load($element['nid']['#value'], $element['vid']['#value']);
+    if ($quizQuestionResponse = _quiz_question_response_get_instance($element['result_id']['#value'], $question)) {
+      $quizQuestionResponse->getReportFormValidate($element, $form_state);
+    }
+  }
+
+  /**
+   * Submit the report form
+   *
+   * We go through the form state values and submit all questiontypes with
+   * validation functions declared.
+   */
+  public function formSubmit($form, &$form_state) {
+    global $user;
+
+    foreach ($form_state['values'] as $key => $q_values) {
+      // Questions has numeric keys in the report form
+      if (!is_numeric($key)) {
+        continue;
+      }
+      // Questions store the name of the validation function with the key 'submit'
+      if (!isset($q_values['submit'])) {
+        continue;
+      }
+      // The submit function must exist
+      if (!function_exists($q_values['submit'])) {
+        continue;
+      }
+
+      // Load the quiz
+      if (!isset($quiz)) {
+        $result = db_query(
+          'SELECT quiz_qid, quiz_vid, uid FROM {quiz_results} WHERE result_id = :result_id', array(
+            ':result_id' => $q_values['result_id']
+          ))->fetchObject();
+        $quiz = quiz_entity_single_load($result->quiz_qid, $result->quiz_vid);
+        $result_id = $q_values['result_id'];
+      }
+
+      $q_values['quiz'] = $quiz;
+
+      // We call the submit function provided by the question
+      call_user_func($q_values['submit'], $q_values);
+    }
+
+    // Scores may have been changed. We take the necessary actions
+    $this->updateLastTotalScore($result_id, $quiz->vid);
+    $changed = db_update('quiz_results')
+      ->fields(array('is_evaluated' => 1))
+      ->condition('result_id', $result_id)
+      ->execute();
+    $results_got_deleted = quiz()->getQuizHelper()->getResultHelper()->maintainResult($user, $quiz, $result_id);
+
+    // A message saying the quiz is unscored has already been set. We unset it here...
+    if ($changed > 0) {
+      $this->removeUnscoredMessage();
+    }
+
+    // Notify the user if results got deleted as a result of him scoring an answer.
+    $add = $quiz->keep_results == QUIZ_KEEP_BEST && $results_got_deleted ? ' ' . t('Note that this @quiz is set to only keep each users best answer.', array('@quiz' => QUIZ_NAME)) : '';
+
+    $score_data = $this->getScoreArray($result_id, $quiz->vid, TRUE);
+
+    module_invoke_all('quiz_scored', $quiz, $score_data, $result_id);
+
+    drupal_set_message(t('The scoring data you provided has been saved.') . $add);
+    if (user_access('score taken quiz answer') && !user_access('view any quiz results')) {
+      if ($result && $result->uid == $user->uid) {
+        $form_state['redirect'] = 'quiz-result/' . $result_id;
+      }
+    }
+  }
+
+  /**
+   * Submit handler to go to the quiz results from the last question's feedback.
+   */
+  public function formEndSubmit($form, &$form_state) {
+    $result_id = $_SESSION['quiz']['temp']['result_id'];
+    $form_state['redirect'] = "quiz-result/{$result_id}";
+  }
+
+  /**
+   * Helper function to remove the message saying the quiz haven't been scored
+   */
+  private function removeUnscoredMessage() {
+    if (!empty($_SESSION['messages']['warning'])) {
+      // Search for the message, and remove it if we find it.
+      foreach ($_SESSION['messages']['warning'] as $key => $val) {
+        if ($val == t('This @quiz has not been scored yet.', array('@quiz' => QUIZ_NAME))) {
+          unset($_SESSION['messages']['warning'][$key]);
+        }
+      }
+      // Clean up if the message array was left empty
+      if (empty($_SESSION['messages']['warning'])) {
+        unset($_SESSION['messages']['warning']);
+        if (empty($_SESSION['messages'])) {
+          unset($_SESSION['messages']);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns an array of score information for a quiz
+   *
+   * @param int $result_id
+   * @param int $quiz_vid
+   * @param int $is_evaluated
+   */
+  private function getScoreArray($result_id, $quiz_vid, $is_evaluated) {
+    $properties = db_query(
+      'SELECT max_score, number_of_random_questions
+          FROM {quiz_entity_revision}
+          WHERE vid = :vid', array(':vid' => $quiz_vid))->fetchObject();
+
+    $total_score = db_query(
+      'SELECT SUM(points_awarded)
+          FROM {quiz_results_answers}
+          WHERE result_id = :result_id', array(':result_id' => $result_id))->fetchField();
+
+    return array(
+        'question_count'   => $properties->number_of_random_questions + quiz()->getQuizHelper()->countAlwaysQuestions($quiz_vid),
+        'possible_score'   => $properties->max_score,
+        'numeric_score'    => $total_score,
+        'percentage_score' => ($properties->max_score == 0) ? 0 : round(($total_score * 100) / $properties->max_score),
+        'is_evaluated'     => $is_evaluated,
+    );
+  }
+
+  /**
+   * Updates the total score using only one mySql query.
+   *
+   * @param $result_id
+   * @param int $quiz_vid
+   *  Quiz version ID
+   */
+  private function updateLastTotalScore($result_id, $quiz_vid) {
+    $subq1 = db_select('quiz_results_answers', 'a');
+    $subq1
+      ->condition('a.result_id', $result_id)
+      ->addExpression('SUM(a.points_awarded)');
+
+    $score = $subq1->execute()->fetchField();
+    $max_score = quiz_entity_single_load(NULL, $quiz_vid)->max_score;
+    $final_score = round(100 * ($score / $max_score));
+
+    db_update('quiz_results')
+      ->expression('score', $final_score)
+      ->condition('result_id', $result_id)
+      ->execute();
+  }
+
+}
diff --git a/src/Form/QuizTypeForm.php b/src/Form/QuizTypeForm.php
new file mode 100644
index 0000000..4948813
--- /dev/null
+++ b/src/Form/QuizTypeForm.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\quiz\Form;
+
+class QuizTypeForm {
+
+  public function get($form, &$form_state, $quiz_type, $op) {
+    if ($op === 'clone') {
+      $quiz_type->label .= ' (cloned)';
+      $quiz_type->type = '';
+    }
+
+    $form['label'] = array(
+      '#type'          => 'textfield',
+      '#title'         => t('Label'),
+      '#default_value' => $quiz_type->label,
+      '#description'   => t('The human-readable name of this !quiz type.', array('!quiz' => QUIZ_NAME)),
+      '#required'      => TRUE,
+      '#size'          => 30,
+    );
+
+    $form['description'] = array(
+      '#type'          => 'textarea',
+      '#title'         => t('Description'),
+      '#description'   => t('Describe this !quiz type. The text will be displayed on the Add new !quiz page.', array('!quiz' => QUIZ_NAME)),
+      '#default_value' => $quiz_type->description,
+    );
+
+    $form['help'] = array(
+      '#type'          => 'textarea',
+      '#title'         => t('Explanation or submission guidelines'),
+      '#description'   => t('This text will be displayed at the top of the page when creating or editing !quiz of this type.', array('!quiz' => QUIZ_NAME)),
+      '#default_value' => $quiz_type->help,
+    );
+
+    // Machine-readable type name.
+    $form['type'] = array(
+      '#type'          => 'machine_name',
+      '#default_value' => isset($quiz_type->type) ? $quiz_type->type : '',
+      '#maxlength'     => 32,
+      '#disabled'      => $quiz_type->isLocked() && $op !== 'clone',
+      '#machine_name'  => array(
+        'exists' => 'quiz_type_load',
+        'source' => array('label'),
+      ),
+      '#description'   => t('A unique machine-readable name for this !quiz type. It must only contain lowercase letters, numbers, and underscores.', array('!quiz' => QUIZ_NAME)),
+    );
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save quiz type'), '#weight' => 40);
+
+    if (!$quiz_type->isLocked() && $op != 'add' && $op != 'clone') {
+      $form['actions']['delete'] = array(
+        '#type'                    => 'submit',
+        '#value'                   => t('Delete !quiz type', array('!quiz' => QUIZ_NAME)),
+        '#weight'                  => 45,
+        '#limit_validation_errors' => array(),
+        '#submit'                  => array(array($this, 'submitDelete'))
+      );
+    }
+
+    $form['#submit'][] = array($this, 'submit');
+
+    return $form;
+  }
+
+  /**
+   * Form API submit callback for the type form.
+   */
+  function submit(&$form, &$form_state) {
+    $quiz_type = entity_ui_form_submit_build_entity($form, $form_state);
+    $quiz_type->description = filter_xss_admin($quiz_type->description);
+    $quiz_type->help = filter_xss_admin($quiz_type->help);
+    $quiz_type->save();
+    $form_state['redirect'] = 'admin/structure/quiz';
+  }
+
+  public function submitDelete($form, &$form_state) {
+    $form_state['redirect'] = 'admin/structure/quiz/manage/' . $form_state['quiz_type']->type . '/delete';
+  }
+
+}
diff --git a/src/Helper/FormHelper.php b/src/Helper/FormHelper.php
new file mode 100644
index 0000000..ef3c416
--- /dev/null
+++ b/src/Helper/FormHelper.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\quiz\Helper;
+
+/**
+ * Date and time routines for use with quiz module.
+ * - Based on event module
+ * - All references to event variables should be optional
+ */
+class FormHelper {
+
+  /**
+   * Formats a GMT timestamp to local date values using time zone offset supplied.
+   * All timestamp values in event nodes are GMT and translated for display here.
+   *
+   * Pulled from event
+   *
+   * Time zone settings are applied in the following order
+   * 1. If supplied, time zone offset is applied
+   * 2. If user time zones are enabled, user time zone offset is applied
+   * 3. If neither 1 nor 2 apply, the site time zone offset is applied
+   *
+   * @param $format
+   *   The date() format to apply to the timestamp.
+   * @param $timestamp
+   *   The GMT timestamp value.
+   * @param $offset
+   *   Time zone offset to apply to the timestamp.
+   * @return gmdate() formatted date value
+   */
+  private function date($format, $timestamp, $offset = NULL) {
+    global $user;
+
+    if (isset($offset)) {
+      $timestamp += $offset;
+    }
+    elseif (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+      $timestamp += $user->timezone;
+    }
+    else {
+      $timestamp += variable_get('date_default_timezone', 0);
+    }
+
+    // make sure we apply the site first day of the week setting for dow requests
+    $result = gmdate($format, $timestamp);
+    return $result;
+  }
+
+  /**
+   * Takes a time element and prepares to send it to form_date().
+   *
+   * @param $time
+   *   The time to be turned into an array. This can be:
+   *   - A timestamp when from the database.
+   *   - An array (day, month, year) when previewing.
+   *   - NULL for new nodes.
+   *
+   * @return
+   *   An array for form_date (day, month, year).
+   */
+  protected function prepareDate($time = '', $offset = 0) {
+    if (!$time) { // If this is empty, get the current time.
+      $time = REQUEST_TIME + $offset * 86400;
+    }
+
+    // If we are previewing, $time will be an array so just pass it through.
+    $time_array = array();
+    if (is_array($time)) {
+      $time_array = $time;
+    }
+    // Otherwise build the array from the timestamp.
+    elseif (is_numeric($time)) {
+      $time_array = array(
+        'day'   => $this->date('j', $time),
+        'month' => $this->date('n', $time),
+        'year'  => $this->date('Y', $time),
+      );
+    }
+
+    return $time_array;
+  }
+
+  protected function getUserpointsType() {
+    $userpoints_terms = taxonomy_get_tree(userpoints_get_vid());
+    $userpoints_tids = array(0 => t('Select'));
+    foreach ($userpoints_terms as $userpoints_term) {
+      $userpoints_tids[$userpoints_term->tid] = str_repeat('-', $userpoints_term->depth) . $userpoints_term->name;
+    }
+    return $userpoints_tids;
+  }
+
+}
diff --git a/src/Helper/HookImplementation.php b/src/Helper/HookImplementation.php
new file mode 100644
index 0000000..51cdda1
--- /dev/null
+++ b/src/Helper/HookImplementation.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\quiz\Helper;
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Helper\HookImplementation\HookEntityInfo;
+use Drupal\quiz\Helper\HookImplementation\HookFieldExtraFields;
+use Drupal\quiz\Helper\HookImplementation\HookMenu;
+use Drupal\quiz\Helper\HookImplementation\HookQuizFinished;
+use Drupal\quiz\Helper\HookImplementation\HookQuizScored;
+use Drupal\quiz\Helper\HookImplementation\HookUserCancel;
+
+class HookImplementation {
+
+  private $hookMenu;
+  private $hookEntityInfo;
+  private $hookFieldExtraFields;
+  private $hookQuizScored;
+  private $hookUserCancel;
+
+  /**
+   * @return HookMenu
+   */
+  public function getHookMenu() {
+    if (null === $this->hookMenu) {
+      $this->hookMenu = new HookMenu();
+    }
+    return $this->hookMenu;
+  }
+
+  public function setHookMenu($hookMenu) {
+    $this->hookMenu = $hookMenu;
+    return $this;
+  }
+
+  /**
+   * @return HookEntityInfo
+   */
+  public function getHookEntityInfo() {
+    if (null === $this->hookEntityInfo) {
+      $this->hookEntityInfo = new HookEntityInfo();
+    }
+    return $this->hookEntityInfo;
+  }
+
+  public function setHookEntityInfo($hookEntityInfo) {
+    $this->hookEntityInfo = $hookEntityInfo;
+    return $this;
+  }
+
+  /**
+   * @return HookFieldExtraFields
+   */
+  public function getHookFieldExtraFields() {
+    if (null === $this->hookFieldExtraFields) {
+      $this->hookFieldExtraFields = new HookFieldExtraFields();
+    }
+    return $this->hookFieldExtraFields;
+  }
+
+  public function setHookFieldExtraFields($hookFieldExtraFields) {
+    $this->hookFieldExtraFields = $hookFieldExtraFields;
+    return $this;
+  }
+
+  /**
+   * @return HookQuizScored
+   */
+  public function getHookQuizScored($quiz, $score, $result_id) {
+    if (NULL === $this->hookQuizScored) {
+      $this->hookQuizScored = new HookQuizScored($quiz, $score, $result_id);
+    }
+    return $this->hookQuizScored;
+  }
+
+  public function setHookQuizScored($hookQuizScored) {
+    $this->hookQuizScored = $hookQuizScored;
+    return $this;
+  }
+
+  /**
+   * @return HookUserCancel
+   */
+  public function getHookUserCancel($account, $method) {
+    if (NULL === $this->hookUserCancel) {
+      $this->hookUserCancel = new HookUserCancel($account, $method);
+    }
+    return $this->hookUserCancel;
+  }
+
+  public function setHookUserCancel($hookUserCancel) {
+    $this->hookUserCancel = $hookUserCancel;
+    return $this;
+  }
+
+}
diff --git a/src/Helper/HookImplementation/HookEntityInfo.php b/src/Helper/HookImplementation/HookEntityInfo.php
new file mode 100644
index 0000000..1144473
--- /dev/null
+++ b/src/Helper/HookImplementation/HookEntityInfo.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\quiz\Helper\HookImplementation;
+
+class HookEntityInfo {
+
+  public function execute() {
+    return array(
+        'quiz_type'                  => $this->getQuizEntityTypeInfo(),
+        'quiz_entity'                => $this->getQuizEntityInfo(),
+        'quiz_question_relationship' => $this->getQuizQuestionRelationshipInfo(),
+        'quiz_result'                => $this->getQuizResultInfo(),
+        'quiz_result_answer'         => $this->getQuizResultAnswerInfo(),
+    );
+  }
+
+  private function getQuizEntityTypeInfo() {
+    return array(
+        'label'            => t('!quiz type', array('!quiz' => QUIZ_NAME)),
+        'plural label'     => t('!quiz types', array('!quiz' => QUIZ_NAME)),
+        'description'      => t('Types of !quiz.', array('!quiz' => QUIZ_NAME)),
+        'entity class'     => 'Drupal\quiz\Entity\QuizEntityType',
+        'controller class' => 'Drupal\quiz\Entity\QuizTypeEntityController',
+        'base table'       => 'quiz_type',
+        'fieldable'        => FALSE,
+        'bundle of'        => 'quiz_entity',
+        'exportable'       => TRUE,
+        'entity keys'      => array('id' => 'id', 'name' => 'type', 'label' => 'label'),
+        'access callback'  => 'quiz_type_access',
+        'module'           => 'quiz',
+        'admin ui'         => array(// Enable the entity API's admin UI.
+            'path'             => 'admin/structure/quiz',
+            'file'             => 'quiz.pages.inc',
+            'controller class' => 'Drupal\quiz\Entity\QuizTypeUIController',
+        ),
+    );
+  }
+
+  private function getQuizEntityInfo() {
+    $entity_info = array(
+        'label'                     => t('Quiz properties'),
+        'description'               => t('!quiz entity', array('!quiz' => QUIZ_NAME)),
+        'entity class'              => 'Drupal\quiz\Entity\QuizEntity',
+        'controller class'          => 'Drupal\quiz\Entity\QuizEntityController',
+        'metadata controller class' => 'Drupal\quiz\Entity\QuizEntityMetadataController',
+        'views controller class'    => 'Drupal\quiz\Entity\QuizViewsController',
+        'base table'                => 'quiz_entity',
+        'revision table'            => 'quiz_entity_revision',
+        'fieldable'                 => TRUE,
+        'entity keys'               => array('id' => 'qid', 'bundle' => 'type', 'revision' => 'vid', 'label' => 'title'),
+        'bundle keys'               => array('bundle' => 'type'),
+        'access callback'           => 'quiz_entity_access_callback',
+        'label callback'            => 'entity_class_label',
+        'uri callback'              => 'entity_class_uri',
+        'module'                    => 'quiz',
+        'bundles'                   => array(),
+        'view modes'                => array(
+            'question' => array('label' => t('Question'), 'custom settings' => TRUE),
+        ),
+        'admin ui'                  => array(// Enable the entity API's admin UI.
+            'path'             => 'admin/content/quiz',
+            'file'             => 'quiz.pages.inc',
+            'controller class' => 'Drupal\quiz\Entity\QuizUIController',
+        ),
+    );
+
+    // Add bundle info but bypass entity_load() as we cannot use it here.
+    foreach (db_select('quiz_type', 'qt')->fields('qt')->execute()->fetchAllAssoc('type') as $type => $info) {
+      $entity_info['bundles'][$type] = array(
+          'label' => $info->label,
+          'admin' => array(
+              'path'             => 'admin/structure/quiz/manage/%quiz_type',
+              'real path'        => 'admin/structure/quiz/manage/' . $type,
+              'bundle argument'  => 4,
+              'access arguments' => array('administer quiz'),
+          ),
+      );
+    }
+
+    // Support entity cache module.
+    if (module_exists('entitycache')) {
+      $entity_info['field cache'] = FALSE;
+      $entity_info['entity cache'] = TRUE;
+    }
+
+    return $entity_info;
+  }
+
+  private function getQuizQuestionRelationshipInfo() {
+    return array(
+        'label'                     => t('Quiz question relationship'),
+        'controller class'          => 'EntityAPIController',
+        'base table'                => 'quiz_relationship',
+        'entity keys'               => array('id' => 'qr_id'),
+        'views controller class'    => 'EntityDefaultViewsController',
+        'metadata controller class' => 'Drupal\quiz\Entity\RelationshipMetadataController',
+    );
+  }
+
+  private function getQuizResultInfo() {
+    return array(
+        'label'                     => t('Quiz result'),
+        'entity class'              => 'Drupal\quiz\Entity\Result',
+        'controller class'          => 'Drupal\quiz\Entity\ResultController',
+        'base table'                => 'quiz_results',
+        'entity keys'               => array('id' => 'result_id'),
+        'views controller class'    => 'EntityDefaultViewsController',
+        'metadata controller class' => 'Drupal\quiz\Entity\ResultMetadataController',
+    );
+  }
+
+  private function getQuizResultAnswerInfo() {
+    return array(
+        'label'                     => t('Quiz result answer'),
+        'controller class'          => 'EntityAPIController',
+        'base table'                => 'quiz_results_answers',
+        'entity keys'               => array('id' => 'result_answer_id'),
+        'views controller class'    => 'EntityDefaultViewsController',
+        'metadata controller class' => 'Drupal\quiz\Entity\AnswerMetadataController',
+    );
+  }
+
+}
diff --git a/src/Helper/HookImplementation/HookFieldExtraFields.php b/src/Helper/HookImplementation/HookFieldExtraFields.php
new file mode 100644
index 0000000..6775cf2
--- /dev/null
+++ b/src/Helper/HookImplementation/HookFieldExtraFields.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\quiz\Helper\HookImplementation;
+
+class HookFieldExtraFields {
+
+  public function execute() {
+    $extra = array();
+
+    if ($types = quiz_get_types()) {
+      foreach (array_keys($types) as $name) {
+        $extra['quiz_entity'][$name] = array(
+            'display' => $this->getQuizDisplayFields(),
+            'form'    => $this->getQuizFormExtraFields(),
+        );
+      }
+    }
+
+    return $extra;
+  }
+
+  private function getQuizDisplayFields() {
+    return array(
+        'take'  => array(
+            'label'       => t('Take @quiz button', array('@quiz' => QUIZ_NAME)),
+            'description' => t('The take button.'),
+            'weight'      => 10,
+        ),
+        'stats' => array(
+            'label'       => t('@quiz summary', array('@quiz' => QUIZ_NAME)),
+            'description' => t('@quiz summary', array('@quiz' => QUIZ_NAME)),
+            'weight'      => 9,
+        ),
+    );
+  }
+
+  private function getQuizFormExtraFields() {
+    return array(
+        'taking'            => array(
+            'label'       => t('Taking options'),
+            'description' => t('Fieldset for customizing how a quiz is taken'),
+            'weight'      => 0,
+        ),
+        'quiz_availability' => array(
+            'label'       => t('Availability options'),
+            'description' => t('Fieldset for customizing when a @quiz is available', array('@quiz' => QUIZ_NAME)),
+            'weight'      => 0,
+        ),
+        'quiz_help'         => array(
+            'label'  => t('Explanation or submission guidelines'),
+            'weight' => -25,
+        ),
+        'summaryoptions'    => array(
+            'label'       => t('Summary options'),
+            'description' => t('Fieldset for customizing summaries in the @quiz reports', array('@quiz' => QUIZ_NAME)),
+            'weight'      => 0,
+        ),
+        'resultoptions'     => array(
+            'label'       => t('Result options'),
+            'description' => t('Fieldset for customizing result comments in @quiz reports', array('@quiz' => QUIZ_NAME)),
+            'weight'      => 0,
+        ),
+        'remember_settings' => array(
+            'label'       => t('Remember settings'),
+            'description' => t('Checkbox for remembering @quiz settings', array('@quiz' => QUIZ_NAME)),
+            'weight'      => 0,
+        ),
+        'remember_global'   => array(
+            'label'       => t('Remember as global'),
+            'description' => t('Checkbox for remembering quiz settings'),
+            'weight'      => 0,
+        ),
+    );
+  }
+
+}
diff --git a/src/Helper/HookImplementation/HookMenu.php b/src/Helper/HookImplementation/HookMenu.php
new file mode 100644
index 0000000..b4e2329
--- /dev/null
+++ b/src/Helper/HookImplementation/HookMenu.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\quiz\Helper\HookImplementation;
+
+class HookMenu {
+
+  public function execute() {
+    $items = array();
+
+    $items += $this->getQuizAdminMenuItems();
+    $items += $this->getQuizUserMenuItems();
+
+    $items['quiz-result/%quiz_result'] = array(
+        'title'            => 'User results',
+        'access callback'  => 'quiz_access_my_result',
+        'access arguments' => array(1),
+        'page callback'    => 'Drupal\quiz\Controller\QuizUserResultController::staticCallback',
+        'page arguments'   => array(1),
+        'file'             => 'quiz.pages.inc',
+    );
+
+    return $items;
+  }
+
+  private function getQuizAdminMenuItems() {
+    $items = array();
+
+    // Admin pages.
+    $items['admin/quiz'] = array(
+        'title'            => '@quiz',
+        'title arguments'  => array('@quiz' => QUIZ_NAME),
+        'description'      => 'View results, score answers, run reports and edit configurations.',
+        'page callback'    => 'system_admin_menu_block_page',
+        'access arguments' => array('administer quiz configuration', 'score any quiz', 'score own quiz', 'view any quiz results', 'view results for own quiz'),
+        'access callback'  => 'quiz_access_multi_or',
+        'type'             => MENU_NORMAL_ITEM,
+        'file'             => 'system.admin.inc',
+        'file path'        => drupal_get_path('module', 'system'),
+    );
+
+    $items['admin/quiz/settings'] = array(
+        'title'            => '@quiz settings',
+        'title arguments'  => array('@quiz' => QUIZ_NAME),
+        'description'      => 'Change settings for the all Quiz project modules.',
+        'page callback'    => 'system_admin_menu_block_page',
+        'access arguments' => array('administer quiz configuration'),
+        'type'             => MENU_NORMAL_ITEM,
+        'file'             => 'system.admin.inc',
+        'file path'        => drupal_get_path('module', 'system'),
+    );
+
+    $items['admin/quiz/settings/config'] = array(
+        'title'            => '@quiz configuration',
+        'title arguments'  => array('@quiz' => QUIZ_NAME),
+        'description'      => 'Configure the Quiz module.',
+        'page callback'    => 'drupal_get_form',
+        'page arguments'   => array('quiz_admin_settings_form'),
+        'access arguments' => array('administer quiz configuration'),
+        'type'             => MENU_NORMAL_ITEM, // optional
+        'file'             => 'quiz.pages.inc',
+    );
+
+    $items['admin/quiz/settings/quiz-form'] = array(
+        'title'            => '@quiz form configuration',
+        'title arguments'  => array('@quiz' => QUIZ_NAME),
+        'description'      => 'Configure default values for the quiz creation form.',
+        'page callback'    => 'drupal_get_form',
+        'page arguments'   => array('quiz_admin_entity_form'),
+        'access arguments' => array('administer quiz configuration'),
+        'type'             => MENU_NORMAL_ITEM, // optional
+        'file'             => 'quiz.pages.inc',
+    );
+
+    $items['admin/quiz/reports'] = array(
+        'title'            => '@quiz reports and scoring',
+        'title arguments'  => array('@quiz' => QUIZ_NAME),
+        'description'      => 'View reports and score answers.',
+        'page callback'    => 'system_admin_menu_block_page',
+        'access arguments' => array('view any quiz results', 'view results for own quiz'),
+        'access callback'  => 'quiz_access_multi_or',
+        'type'             => MENU_NORMAL_ITEM,
+        'file'             => 'system.admin.inc',
+        'file path'        => drupal_get_path('module', 'system'),
+    );
+
+    return $items;
+  }
+
+  private function getQuizUserMenuItems() {
+    $items = array();
+
+    // User pages.
+    $items['user/%/quiz-results/%quiz_result/view'] = array(
+        'title'            => 'User results',
+        'page callback'    => 'Drupal\quiz\Controller\QuizUserResultController::staticCallback',
+        'page arguments'   => array(3),
+        'access arguments' => array(3),
+        'access callback'  => 'quiz_access_my_result',
+        'type'             => MENU_CALLBACK,
+        'file'             => 'quiz.pages.inc',
+    );
+
+    return $items;
+  }
+
+}
diff --git a/src/Helper/HookImplementation/HookQuizScored.php b/src/Helper/HookImplementation/HookQuizScored.php
new file mode 100644
index 0000000..019154f
--- /dev/null
+++ b/src/Helper/HookImplementation/HookQuizScored.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\quiz\Helper\HookImplementation;
+
+class HookQuizScored {
+
+  private $quiz;
+  private $score;
+  private $result_id;
+  private $taker;
+
+  public function __construct($quiz, $score, $result_id) {
+    $this->quiz = $quiz;
+    $this->score = $score;
+    $this->result_id = $result_id;
+    $this->taker = db_query('SELECT u.uid, u.mail'
+      . ' FROM {users} u '
+      . '   JOIN {quiz_results} qnr ON u.uid = qnr.uid '
+      . ' WHERE result_id = :rid', array(':rid' => $this->result_id))->fetch();
+  }
+
+  /**
+   * @TODO Rules
+   */
+  public function execute() {
+    if (variable_get('quiz_email_results', 0) && $this->taker->uid != 0 && $this->score['is_evaluated']) {
+      drupal_mail('quiz', 'notice', $this->taker->mail, NULL, array($this->quiz, $this->score, $this->result_id, 'taker'));
+      drupal_set_message(t("The results has been sent to the user's email address."));
+    }
+    $this->executeUserPoints();
+  }
+
+  /**
+   * Calls userpoints functions to credit user point based on number of correct
+   * answers.
+   */
+  private function executeUserPoints() {
+    if (!$this->quiz->has_userpoints || !$this->taker->uid || !$this->score['is_evaluated']) {
+      return;
+    }
+
+    //Looking up the tid of the selected Userpoint vocabulary
+    $selected_tid = db_query("SELECT ti.tid
+      FROM {taxonomy_index} ti
+      JOIN {taxonomy_term_data} td
+        ON td.tid = ti.tid AND td.vid = :vid
+      WHERE ti.nid = :nid", array(':nid' => $this->quiz->qid, ':vid' => userpoints_get_vid()))->fetchField();
+
+    $variables = array(
+        '@title' => $this->quiz->title,
+        '@quiz'  => QUIZ_NAME,
+        '@time'  => date('l jS \of F Y h:i:s A'),
+    );
+
+    $params = array(
+        'points'      => $this->score['numeric_score'],
+        'description' => t('Attended @title @quiz on @time', $variables),
+        'tid'         => $selected_tid,
+    );
+
+    if ($this->quiz->userpoints_tid != 0) {
+      $params['tid'] = $this->quiz->userpoints_tid;
+    }
+
+    userpoints_userpointsapi($params);
+  }
+
+}
diff --git a/src/Helper/HookImplementation/HookUserCancel.php b/src/Helper/HookImplementation/HookUserCancel.php
new file mode 100644
index 0000000..c9bb25d
--- /dev/null
+++ b/src/Helper/HookImplementation/HookUserCancel.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\quiz\Helper\HookImplementation;
+
+class HookUserCancel {
+
+  private $account;
+  private $method;
+
+  public function __construct($account, $method) {
+    $this->account = $account;
+    $this->method = $method;
+  }
+
+  /**
+   * Deletes all results associated with a given user.
+   */
+  public function execute() {
+    if (variable_get('quiz_durod', 0)) {
+      $res = db_query("SELECT result_id FROM {quiz_results} WHERE uid = :uid", array(':uid' => $this->account->uid));
+      $result_ids = array();
+      while ($result_id = $res->fetchField()) {
+        $result_ids[] = $result_id;
+      }
+      entity_delete_multiple('quiz_result', $result_ids);
+    }
+  }
+
+}
diff --git a/src/Helper/MailHelper.php b/src/Helper/MailHelper.php
new file mode 100644
index 0000000..5d12505
--- /dev/null
+++ b/src/Helper/MailHelper.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\quiz\Helper;
+
+class MailHelper {
+
+  /**
+   * Build subject and body for notice email.
+   *
+   * @param \stdClass $account
+   * @param \stdClass $quiz
+   * @param type $score
+   * @param type $result_id
+   * @param type $target
+   * @return array($subject, $body)
+   */
+  public function buildNotice($account, $quiz, $score, $result_id, $target) {
+    $quiz_body = field_get_items('node', $quiz, 'body');
+    $substitutions = array(
+        '!title'      => $quiz->title,
+        '!sitename'   => variable_get('site_name', 'Quiz'),
+        '!taker'      => $account->name,
+        '!author'     => $quiz->name,
+        '!title'      => check_plain($quiz->title),
+        '!date'       => format_date(REQUEST_TIME),
+        '!desc'       => $quiz_body ? $quiz_body[0]['value'] : '',
+        '!correct'    => isset($score['numeric_score']) ? $score['numeric_score'] : 0,
+        '!total'      => $score['possible_score'],
+        '!percentage' => $score['percentage_score'],
+        '!url'        => url("user/{$account->uid}/quiz-results/{$result_id}/view", array('absolute' => TRUE)),
+        '!minutes'    => db_query("SELECT CEIL((time_end - time_start)/60) FROM {quiz_results} WHERE result_id = :result_id AND time_end", array(':result_id' => $result_id))->fetchField()
+    );
+    $type = $target !== 'author' ? '_taker' : '';
+    $test = variable_get('quiz_email_results_body' . $type, $this->formatBody($target, $account));
+
+    return array(
+        t(variable_get('quiz_email_results_subject' . $type, $this->formatSubject($target, $account)), $substitutions, array('langcode' => $account->language)),
+        t($test, $substitutions, array('langcode' => $account->language))
+    );
+  }
+
+  public function formatSubject($target, $account) {
+    if ($target === 'author') {
+      return t('!title Results Notice from !sitename');
+    }
+    if ($target === 'taker') {
+      return t('!title Results Notice from !sitename');
+    }
+  }
+
+  public function formatBody($target, $account) {
+    if ($target === 'author') {
+      return t('Dear !author') . "\n\n" .
+        t('!taker attended the @quiz !title on !date', array('@quiz' => QUIZ_NAME)) . "\n" .
+        t('Test Description : !desc') . "\n" .
+        t('!taker got !correct out of !total points in !minutes minutes. Score given in percentage is !percentage') . "\n" .
+        t('You can access the result here !url') . "\n";
+    }
+    if ($target === 'taker') {
+      return t('Dear !taker') . "\n\n" .
+        t('You attended the @quiz !title on !date', array('@quiz' => QUIZ_NAME)) . "\n" .
+        t('Test Description : !desc') . "\n" .
+        t('You got !correct out of !total points in !minutes minutes. Score given in percentage is !percentage') . "\n" .
+        t('You can access the result here !url') . "\n";
+    }
+  }
+
+}
diff --git a/src/Helper/Node/NodeHelper.php b/src/Helper/Node/NodeHelper.php
new file mode 100644
index 0000000..5b89649
--- /dev/null
+++ b/src/Helper/Node/NodeHelper.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\quiz\Helper\Node;
+
+abstract class NodeHelper {
+
+  /**
+   * Common actions that need to be done before a quiz is inserted or updated
+   *
+   * @param $quiz
+   *   Quiz entity
+   */
+  protected function presaveActions(&$quiz) {
+    $this->translateFormDate($quiz, 'quiz_open');
+    $this->translateFormDate($quiz, 'quiz_close');
+
+    if (empty($quiz->pass_rate)) {
+      $quiz->pass_rate = 0;
+    }
+
+    if ($quiz->randomization < 2) {
+      $quiz->number_of_random_questions = 0;
+    }
+  }
+
+  /**
+   * Handles the start and end times in a node form submission.
+   * - Takes the array from form_date() and turns it into a timestamp
+   * - Adjusts times for time zone offsets.
+   * - Adapted from event.module
+   *
+   * @param $node The submitted node with form data.
+   * @param $date_field_name The name of the date ('quiz_open' or 'quiz_close') to translate.
+   */
+  private function translateFormDate(&$node, $date_field_name) {
+    $prefix = $node->$date_field_name;
+    // If we have all the parameters, re-calculate $node->event_$date .
+    if (is_array($prefix) && isset($prefix['year']) && isset($prefix['month']) && isset($prefix['day'])) {
+      // Build a timestamp based on the date supplied and the configured timezone.
+      $node->$date_field_name = $this->mktime(0, 0, 0, $prefix['month'], $prefix['day'], $prefix['year'], 0);
+    }
+    else {
+      if (!_quiz_is_int($prefix, 1, 2147483647)) {
+        form_set_error('quiz_open', t('Please supply a valid date.'));
+      }
+    }
+  }
+
+  /**
+   * Formats local time values to GMT timestamp using time zone offset supplied.
+   * All time values in the database are GMT and translated here prior to insertion.
+   *
+   * Time zone settings are applied in the following order:
+   * 1. If supplied, time zone offset is applied
+   * 2. If user time zones are enabled, user time zone offset is applied
+   * 3. If neither 1 nor 2 apply, the site time zone offset is applied
+   *
+   * @param $hour
+   * @param $minute
+   * @param $second
+   * @param $month
+   * @param $day
+   * @param $year
+   * @param $offset
+   *   Time zone offset to apply to the timestamp.
+   * @return timestamp
+   */
+  private function mktime($hour, $minute, $second, $month, $day, $year, $offset = NULL) {
+    global $user;
+    //print $user->timezone. " and ". variable_get('date_default_timezone', 0);
+    $timestamp = gmmktime($hour, $minute, $second, $month, $day, $year);
+    if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+      return $timestamp - $user->timezone;
+    }
+    else {
+      return $timestamp - variable_get('date_default_timezone', 0);
+    }
+  }
+
+  /**
+   * If a quiz is saved as not randomized we should make sure all random questions
+   * are converted to always.
+   *
+   * @param \Drupal\quiz\Entity\QuizEntity $quiz
+   */
+  protected function checkNumRandom(&$quiz) {
+    if ($quiz->randomization == 2) {
+      return;
+    }
+
+    db_delete('quiz_relationship')
+      ->condition('question_status', QUESTION_RANDOM)
+      ->condition('quiz_vid', $quiz->vid)
+      ->execute();
+  }
+
+  /**
+   * If a quiz is saved with random categories we should make sure all questions
+   * are removed from the quiz
+   *
+   * @param \Drupal\quiz\Entity\QuizEntity $quiz
+   */
+  protected function checkNumAlways($quiz) {
+    if ($quiz->randomization != 3) {
+      return;
+    }
+    db_delete('quiz_relationship')
+      ->condition('quiz_vid', $quiz->vid)
+      ->execute();
+  }
+
+}
diff --git a/src/Helper/Node/NodeInsertHelper.php b/src/Helper/Node/NodeInsertHelper.php
new file mode 100644
index 0000000..08737f5
--- /dev/null
+++ b/src/Helper/Node/NodeInsertHelper.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\quiz\Helper\Node;
+
+class NodeInsertHelper extends NodeHelper {
+
+  public function execute($quiz) {
+    // Need to set max_score if this is a cloned node
+    $max_score = 0;
+
+    // Copy all the questions belonging to the quiz if this is a new translation.
+    if ($quiz->is_new && isset($quiz->translation_source)) {
+      quiz()->getQuizHelper()->copyQuestions($quiz);
+    }
+
+    // Add references to all the questions belonging to the quiz if this is a cloned quiz (node_clone compatibility)
+    if ($quiz->is_new && isset($quiz->clone_from_original_qid)) {
+      $old_quiz = quiz_entity_single_load($quiz->clone_from_original_qid, NULL, TRUE);
+      $max_score = $old_quiz->max_score;
+      $questions = $old_quiz->getQuestionLoader()->getQuestions();
+
+      // Format the current questions for referencing
+      foreach ($questions as $question) {
+        $nid = $questions['nid'];
+        $questions[$nid]->state = $question->question_status;
+        $questions[$nid]->refresh = 0;
+      }
+
+      quiz()->getQuizHelper()->setQuestions($quiz, $questions);
+    }
+
+    $this->presaveActions($quiz);
+
+    // If the quiz is saved as not randomized we have to make sure that questions belonging to the quiz are saved as not random
+    $this->checkNumRandom($quiz);
+    $this->checkNumAlways($quiz);
+
+    quiz_controller()->getSettingIO()->updateUserDefaultSettings($quiz);
+  }
+
+}
diff --git a/src/Helper/Node/NodeUpdateHelper.php b/src/Helper/Node/NodeUpdateHelper.php
new file mode 100644
index 0000000..1a599e7
--- /dev/null
+++ b/src/Helper/Node/NodeUpdateHelper.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\quiz\Helper\Node;
+
+use PDO;
+
+class NodeUpdateHelper extends NodeHelper {
+
+  public function execute($quiz) {
+    // Quiz entity vid (revision) was updated.
+    if (isset($quiz->revision) && $quiz->revision) {
+      // Create new quiz-question relation entries in the quiz_relationship table.
+      $this->updateQuestionRelationship($quiz->old_vid, $quiz->vid, $quiz->qid);
+    }
+
+    $this->presaveActions($quiz);
+    quiz_controller()->getSettingIO()->updateUserDefaultSettings($quiz);
+
+    $this->checkNumRandom($quiz);
+    $this->checkNumAlways($quiz);
+    quiz_update_max_score_properties(array($quiz->vid));
+    drupal_set_message(t('Some of the updated settings may not apply to quiz being taken already. To see all changes in action you need to start again.'), 'warning');
+  }
+
+  /**
+   * Copies quiz-question relation entries in the quiz_relationship table
+   * from an old version of a quiz to a new.
+   *
+   * @param int $old_quiz_vid
+   *   The quiz vid prior to a new revision.
+   * @param int $new_quiz_vid
+   *   The quiz vid of the latest revision.
+   * @param int $quiz_qid
+   *   The quiz id.
+   */
+  private function updateQuestionRelationship($old_quiz_vid, $new_quiz_vid, $quiz_qid) {
+    // query for questions in previous version
+    $result = db_select('quiz_relationship', 'qnr')
+      ->fields('qnr', array('quiz_qid', 'question_nid', 'question_vid', 'question_status', 'weight', 'max_score', 'auto_update_max_score', 'qr_id', 'qr_pid'))
+      ->condition('quiz_qid', $quiz_qid)
+      ->condition('quiz_vid', $old_quiz_vid)
+      ->condition('question_status', QUESTION_NEVER, '!=')
+      ->execute();
+
+    // only proceed if query returned data
+    if ($result->rowCount()) {
+      $questions = $result->fetchAll(PDO::FETCH_ASSOC);
+      foreach ($questions as &$quiz_question) {
+        $quiz_question['old_qr_id'] = $quiz_question['qr_id'];
+        $quiz_question['quiz_qid'] = $quiz_qid;
+        $quiz_question['quiz_vid'] = $new_quiz_vid;
+        unset($quiz_question['qr_id']);
+        drupal_write_record('quiz_relationship', $quiz_question);
+      }
+
+      // Update the parentage when a new revision is created.
+      // @todo this is copy pasta from quiz_set_questions
+      foreach ($questions as $question) {
+        db_update('quiz_relationship')
+          ->condition('qr_pid', $question['old_qr_id'])
+          ->condition('quiz_qid', $quiz_qid)
+          ->condition('quiz_vid', $new_quiz_vid)
+          ->fields(array('qr_pid' => $question['qr_id']))
+          ->execute();
+      }
+    }
+
+    /* Update terms if any */
+    $result = db_select('quiz_terms', 'qt')
+      ->fields('qt', array('nid', 'tid', 'weight', 'max_score', 'number'))
+      ->condition('vid', $old_quiz_vid)
+      ->execute();
+
+    // only proceed if query returned data
+    if ($result->rowCount()) {
+      $insert_query = db_insert('quiz_terms')
+        ->fields(array('nid', 'vid', 'tid', 'weight', 'max_score', 'number'));
+      while ($quiz_term = $result->fetchAssoc()) {
+        $insert_query->values(array(
+            'nid'       => $quiz_qid,
+            'vid'       => $new_quiz_vid,
+            'tid'       => $quiz_term['tid'],
+            'weight'    => $quiz_term['weight'],
+            'max_score' => $quiz_term['max_score'],
+            'number'    => $quiz_term['number'],
+        ));
+      }
+      $insert_query->execute();
+    }
+  }
+
+}
diff --git a/src/Helper/Node/NodeValidateHelper.php b/src/Helper/Node/NodeValidateHelper.php
new file mode 100644
index 0000000..702c3e6
--- /dev/null
+++ b/src/Helper/Node/NodeValidateHelper.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\quiz\Helper\Node;
+
+class NodeValidateHelper {
+
+  public function execute($quiz) {
+    // Don't check dates if the quiz is always available.
+    if (!$quiz->quiz_always) {
+      if (mktime(0, 0, 0, $quiz->quiz_open['month'], $quiz->quiz_open['day'], $quiz->quiz_open['year']) > mktime(0, 0, 0, $quiz->quiz_close['month'], $quiz->quiz_close['day'], $quiz->quiz_close['year'])) {
+        form_set_error('quiz_close', t('"Close date" must be later than the "open date".'));
+      }
+    }
+
+    if (!empty($quiz->pass_rate)) {
+      if (!_quiz_is_int($quiz->pass_rate, 0, 100)) {
+        form_set_error('pass_rate', t('"Passing rate" must be a number between 0 and 100.'));
+      }
+    }
+
+    if (isset($quiz->time_limit)) {
+      if (!_quiz_is_int($quiz->time_limit, 0)) {
+        form_set_error('time_limit', t('"Time limit" must be a positive number.'));
+      }
+    }
+
+    $this->validateResultOptions($quiz);
+
+    if ($quiz->allow_jumping && !$quiz->allow_skipping) {
+      // @todo when we have pages of questions, we have to check that jumping is
+      // not enabled, and randomization is not enabled unless there is only 1 page
+      form_set_error('allow_skipping', t('If jumping is allowed, skipping must also be allowed.'));
+    }
+  }
+
+  private function validateResultOptions($node) {
+    if (!isset($node->resultoptions) || !count($node->resultoptions)) {
+      return;
+    }
+
+    $taken_values = array();
+    $num_options = 0;
+    foreach ($node->resultoptions as $option) {
+      $this->validateResultOption($node, $option, $taken_values, $num_options);
+    }
+  }
+
+  private function validateResultOption($node, $option, &$taken_values, &$num_options) {
+    if (empty($option['option_name']) && !$this->isEmptyHTML($option['option_summary']['value'])) {
+      form_set_error('option_summary', t('Range has a summary, but no name.'));
+      return;
+    }
+
+    $num_options++;
+    if (empty($option['option_summary'])) {
+      form_set_error('option_summary', t('Range has no summary text.'));
+    }
+
+    if ($node->pass_rate && (isset($option['option_start']) || isset($option['option_end']))) {
+      // Check for a number between 0-100.
+      foreach (array('option_start' => 'start', 'option_end' => 'end') as $bound => $bound_text) {
+        if (!_quiz_is_int($option[$bound], 0, 100)) {
+          form_set_error($bound, t('The range %start value must be a number between 0 and 100.', array('%start' => $bound_text)));
+        }
+      }
+
+      // Check that range end >= start.
+      if ($option['option_start'] > $option['option_end']) {
+        form_set_error('option_start', t('The start must be less than the end of the range.'));
+      }
+
+      // Check that range doesn't collide with any other range.
+      $option_range = range($option['option_start'], $option['option_end']);
+      if ($intersect = array_intersect($taken_values, $option_range)) {
+        form_set_error('option_start', t('The ranges must not overlap each other. (%intersect)', array('%intersect' => implode(',', $intersect))));
+      }
+      else {
+        $taken_values = array_merge($taken_values, $option_range);
+      }
+    }
+  }
+
+  /**
+   * Helper function used when figuring out if a textfield or textarea is empty.
+   *
+   * Solves a problem with some wysiwyg editors inserting spaces and tags without content.
+   *
+   * @param $html
+   *   The html to evaluate
+   *
+   * @return
+   *   TRUE if the field is empty(can still be tags there) false otherwise.
+   */
+  private function isEmptyHTML($html) {
+    return drupal_strlen(trim(str_replace('&nbsp;', '', strip_tags($html, '<img><object><embed>')))) == 0;
+  }
+
+}
diff --git a/src/Helper/NodeHelper.php b/src/Helper/NodeHelper.php
new file mode 100644
index 0000000..07f17cc
--- /dev/null
+++ b/src/Helper/NodeHelper.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\quiz\Helper;
+
+use Drupal\quiz\Helper\Node\NodeInsertHelper;
+use Drupal\quiz\Helper\Node\NodeUpdateHelper;
+use Drupal\quiz\Helper\Node\NodeValidateHelper;
+
+class NodeHelper {
+
+  private $nodeValidateHelper;
+  private $nodeInsertHelper;
+  private $nodeUpdateHelper;
+
+  /**
+   * @return NodeValidateHelper
+   */
+  public function getNodeValidateHelper() {
+    if (null === $this->nodeValidateHelper) {
+      $this->nodeValidateHelper = new NodeValidateHelper();
+    }
+    return $this->nodeValidateHelper;
+  }
+
+  public function setNodeValidateHelper($nodeValidateHelper) {
+    $this->nodeValidateHelper = $nodeValidateHelper;
+    return $this;
+  }
+
+  /**
+   * @return NodeInsertHelper
+   */
+  public function getNodeInsertHelper() {
+    if (null === $this->nodeInsertHelper) {
+      $this->nodeInsertHelper = new NodeInsertHelper();
+    }
+    return $this->nodeInsertHelper;
+  }
+
+  public function setNodeInsertHelper($nodeInsertHelper) {
+    $this->nodeInsertHelper = $nodeInsertHelper;
+    return $this;
+  }
+
+  /**
+   * @return NodeUpdateHelper
+   */
+  public function getNodeUpdateHelper() {
+    if (null === $this->nodeUpdateHelper) {
+      $this->nodeUpdateHelper = new NodeUpdateHelper();
+    }
+    return $this->nodeUpdateHelper;
+  }
+
+  public function setNodeUpdateHelper($nodeUpdateHelper) {
+    $this->nodeUpdateHelper = $nodeUpdateHelper;
+    return $this;
+  }
+
+  public function validate($node) {
+    $this->getNodeValidateHelper()->execute($node);
+  }
+
+  public function insert($node) {
+    $this->getNodeInsertHelper()->execute($node);
+  }
+
+  public function update($node) {
+    $this->getNodeUpdateHelper()->execute($node);
+  }
+
+}
diff --git a/src/Helper/Quiz/AccessHelper.php b/src/Helper/Quiz/AccessHelper.php
new file mode 100644
index 0000000..9036476
--- /dev/null
+++ b/src/Helper/Quiz/AccessHelper.php
@@ -0,0 +1,175 @@
+<?php
+
+namespace Drupal\quiz\Helper\Quiz;
+
+use Drupal\quiz\Controller\QuizTakeController;
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Quiz;
+use stdClass;
+
+/**
+ * Helper class to provide methods to check user access right to quiz,
+ * questions, feedback, score, …
+ */
+class AccessHelper {
+
+  public function userHasResult($quiz, $uid) {
+    $sql = 'SELECT 1 FROM {quiz_results} WHERE quiz_qid = :qid AND uid = :uid AND is_evaluated = :is_evaluated';
+    return db_query($sql, array(':qid' => $quiz->qid, ':uid' => $uid, ':is_evaluated' => 1))
+        ->fetchField();
+  }
+
+  /**
+   * Helper function to determine if a user has access to view his quiz results
+   *
+   * @param object $quiz
+   *  The Quiz entity
+   */
+  public function canAccessMyResults($quiz, $account) {
+    if ($quiz->type !== 'quiz') {
+      return false;
+    }
+    return $this->userHasResult($quiz, $account->uid);
+  }
+
+  /**
+   * Helper function to determine if a user has access to the different results
+   * pages.
+   *
+   * @param $quiz
+   *   The quiz entity.
+   * @param $result_id
+   *   The result id of a result we are trying to access.
+   * @return boolean
+   *   TRUE if user has permission.
+   */
+  public function canAccessResults($account, $quiz, $result_id = NULL) {
+    if ($quiz->type !== 'quiz') {
+      return FALSE;
+    }
+    // If rid is set we must make sure the result belongs to the quiz we are
+    // viewing results for.
+    if (isset($result_id)) {
+      $result = db_query('SELECT qnr.quiz_qid, qnr.uid '
+        . ' FROM {quiz_results} qnr '
+        . ' WHERE result_id = :result_id', array(':result_id' => $result_id)
+        )->fetch();
+      if ($result && $result->quiz_qid != $quiz->qid) {
+        return FALSE;
+      }
+    }
+
+    if (user_access('view any quiz results')) {
+      return TRUE;
+    }
+
+    if (user_access('view results for own quiz') && $account->uid == $quiz->uid) {
+      return TRUE;
+    }
+
+    if (user_access('score taken quiz answer')) {
+      // check if the taken user is seeing his result
+      if (isset($result_id) && $result && $result->uid == $account->uid) {
+        return TRUE;
+      }
+    }
+  }
+
+  /**
+   * Helper function to determine if a user has access to score a quiz.
+   *
+   * @param $quiz_creator
+   *   uid of the quiz creator.
+   */
+  public function canAccessQuizScore($account, $quiz_creator = NULL) {
+    if ($quiz_creator == NULL && ($quiz = $this->getQuizFromMenu())) {
+      $quiz_creator = $quiz->uid;
+    }
+    if (user_access('score any quiz')) {
+      return TRUE;
+    }
+    if (user_access('score own quiz') && $account->uid == $quiz_creator) {
+      return TRUE;
+    }
+    if (user_access('score taken quiz answer')) {
+      return TRUE;
+    }
+  }
+
+  /**
+   * Retrieves the quiz entity from the menu router.
+   *
+   * @return
+   *   Quiz entity, if found, or FALSE if quiz entity can't be retrieved from the menu
+   *   router.
+   */
+  private function getQuizFromMenu() {
+    if ($to_return = menu_get_object('quiz_type_access', 4)) {
+      return $to_return;
+    }
+
+    // @TODO: FIX it. This seems to return NULL in feedback page.
+    $node = menu_get_object();
+    return (is_object($node) && $node->type == 'quiz') ? $node : FALSE;
+  }
+
+  /**
+   *
+   * @global stdClass $user
+   * @param QuizEntity $quiz
+   * @param int $page_number
+   * @return boolean
+   */
+  public function canAccessQuestion($quiz, $page_number) {
+    global $user;
+
+    if (!$page_number) {
+      return FALSE;
+    }
+
+    // User maybe revisiting the quiz, trying to resume
+    if (!isset($_SESSION['quiz'][$quiz->qid]) && !user_is_anonymous()) {
+      $controller = new QuizTakeController($quiz, $user, FALSE);
+      if (FALSE === $controller->initQuizResume()) {
+        return FALSE;
+      }
+    }
+
+    if ($quiz->allow_jumping) { // Access to go to any question
+      return TRUE;
+    }
+
+    $result_id = (int) $_SESSION['quiz'][$quiz->qid]['result_id'];
+    $result = quiz_result_load($result_id);
+    $question_index = $page_number;
+    $qinfo_last = $page_number == 1 ? NULL : $result->layout[$question_index - 1];
+    $qinfo = $result->layout[$question_index];
+    $question_node = node_load($qinfo['nid'], $qinfo['vid']);
+    $question_node_last = node_load($qinfo_last['nid'], $qinfo_last['vid']);
+
+    // No backwards navigation & Already have an answer for the requested question.
+    if (!$quiz->backwards_navigation && quiz_result_is_question_answered($result, $question_node)) {
+      return FALSE;
+    }
+
+    // this is the first question.
+    if (1 == $page_number) {
+      return TRUE;
+    }
+
+    // Enforce normal navigation. Previous answer was submitted
+    if (!quiz_result_is_question_answered($result, $question_node_last)) {
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  public function canTakeQuiz($quiz, $account) {
+    if (!quiz()->getQuizHelper()->isAvailable($quiz)) {
+      return FALSE;
+    }
+    return entity_access('view', 'quiz_entity', $quiz, $account) && user_access('access quiz', $account);
+  }
+
+}
diff --git a/src/Helper/Quiz/BaseForm.php b/src/Helper/Quiz/BaseForm.php
new file mode 100644
index 0000000..f35496e
--- /dev/null
+++ b/src/Helper/Quiz/BaseForm.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\quiz\Helper\Quiz;
+
+abstract class BaseForm {
+
+  public static function staticGet($form, $form_state, $quiz) {
+    module_load_include('pages.inc', 'quiz', 'quiz');
+    $obj = new static();
+    return $obj->formGet($form, $form_state, $quiz);
+  }
+
+  /**
+   * Adds checkbox for creating new revision. Checks it by default if answers exists.
+   *
+   * @param array $form FAPI form(array)
+   * @param stdClass $quiz Quiz entity(object)
+   */
+  protected function addRevisionCheckbox(&$form, &$quiz) {
+    // Recomend and preselect to create the quiz as a new revision if it already has been answered
+    if (quiz()->getQuizHelper()->isAnswered($quiz)) {
+      $rev_default = TRUE;
+      $rev_description = t('This quiz has been answered. To maintain correctnes of existing answer reports changes should be saved as a new revision.');
+    }
+    else {
+      $rev_default = in_array('revision', variable_get('node_options_quiz', array()));
+      $rev_description = t('Allow question status changes to create a new revision of the quiz?');
+    }
+
+    if (user_access('manual quiz revisioning') && !variable_get('quiz_auto_revisioning', 1)) {
+      $form['new_revision'] = array(
+          '#type'          => 'checkbox',
+          '#default_value' => $rev_default,
+          '#title'         => t('New revision'),
+          '#description'   => $rev_description,
+      );
+    }
+    else {
+      $form['new_revision'] = array('#type' => 'value', '#value' => $rev_default);
+    }
+  }
+
+}
diff --git a/src/Helper/Quiz/FeedbackHelper.php b/src/Helper/Quiz/FeedbackHelper.php
new file mode 100644
index 0000000..e061c77
--- /dev/null
+++ b/src/Helper/Quiz/FeedbackHelper.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\quiz\Helper\Quiz;
+
+class FeedbackHelper {
+
+  /**
+   * Get the feedback options for Quizzes.
+   */
+  public function getOptions() {
+    $feedback_options = array(
+        'attempt'           => 'Attempt',
+        'correct'           => 'Whether correct',
+        'score'             => 'Score',
+        'answer_feedback'   => 'Answer feedback',
+        'question_feedback' => 'Question feedback',
+        'solution'          => 'Correct answer',
+        'quiz_feedback'     => t('@quiz feedback', array('@quiz' => QUIZ_NAME)),
+    );
+
+    drupal_alter('quiz_feedback_options', $feedback_options);
+
+    return $feedback_options;
+  }
+
+  /**
+   * Menu access check for question feedback.
+   */
+  public function canAccess($quiz, $question_number) {
+    if ($question_number <= 0) {
+      return FALSE;
+    }
+
+    if (array_filter($quiz->review_options['question'])) {
+      $question_index = $question_number;
+      if (empty($_SESSION['quiz'][$quiz->qid]['result_id'])) {
+        $result_id = $_SESSION['quiz']['temp']['result_id'];
+      }
+      else {
+        $result_id = $_SESSION['quiz'][$quiz->qid]['result_id'];
+      }
+      $quiz_result = quiz_result_load($result_id);
+      $qinfo = $quiz_result->layout[$question_index];
+
+      if ($answer = quiz_result_answer_load($result_id, $qinfo['nid'], $qinfo['vid'])) {
+        return TRUE;
+      }
+    }
+  }
+
+  /**
+   * Can the quiz taker view the requested review?
+   *
+   * There's a workaround in here: @kludge
+   *
+   * When review for the question is enabled, and it is the last question,
+   * technically it is the end of the quiz, and the "end of quiz" review settings
+   * apply. So we check to make sure that we are in question taking and the
+   * feedback is viewed within 5 seconds of completing the question/quiz.
+   */
+  public function canReview($option, $result) {
+    $quiz = quiz_entity_single_load($result->quiz_qid, $result->quiz_vid);
+
+    // Check what context the result is in.
+    if ($result->time_end && arg(2) !== 'take') {
+      // Quiz is over. Pull from the "at quiz end" settings.
+      return !empty($quiz->review_options['end'][$option]);
+    }
+
+    // Quiz ongoing. Pull from the "after question" settings.
+    if (!$result->time_end || $result->time_end >= REQUEST_TIME - 5) {
+      return !empty($quiz->review_options['question'][$option]);
+    }
+  }
+
+}
diff --git a/src/Helper/Quiz/ResultHelper.php b/src/Helper/Quiz/ResultHelper.php
new file mode 100644
index 0000000..b72404b
--- /dev/null
+++ b/src/Helper/Quiz/ResultHelper.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace Drupal\quiz\Helper\Quiz;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class ResultHelper {
+
+  /**
+   * Update a score for a quiz.
+   *
+   * This updates the quiz entity results table.
+   *
+   * It is used in cases where a quiz score is changed after the quiz has been
+   * taken. For example, if a long answer question is scored later by a human,
+   * then the quiz should be updated when that answer is scored.
+   *
+   * Important: The value stored in the table is the *percentage* score.
+   *
+   * @param $quiz
+   *   The quiz entity for the quiz that is being scored.
+   * @param $result_id
+   *   The result ID to update.
+   * @return
+   *   The score as an integer representing percentage. E.g. 55 is 55%.
+   */
+  public function updateTotalScore(QuizEntity $quiz, $result_id) {
+    global $user;
+
+    $score = quiz_result_controller()->getScoreCalculator()->calculate($quiz, $result_id);
+    db_update('quiz_results')
+      ->fields(array('score' => $score['percentage_score']))
+      ->condition('result_id', $result_id)
+      ->execute();
+
+    if ($score['is_evaluated']) {
+      // Call hook_quiz_scored().
+      module_invoke_all('quiz_scored', $quiz, $score, $result_id);
+
+      $this->maintainResult($user, $quiz, $result_id);
+      db_update('quiz_results')
+        ->fields(array('is_evaluated' => 1))
+        ->condition('result_id', $result_id)
+        ->execute();
+    }
+
+    return $score['percentage_score'];
+  }
+
+  /**
+   * Load a specific result answer.
+   */
+  public function loadAnswerResult($result_id, $question_nid, $question_vid) {
+    $sql = 'SELECT * '
+      . ' FROM {quiz_results_answers} '
+      . ' WHERE result_id = :result_id '
+      . '   AND question_nid = :nid '
+      . '   AND question_vid = :vid';
+    $params = array(':result_id' => $result_id, ':nid' => $question_nid, ':vid' => $question_vid);
+    if ($row = db_query($sql, $params)->fetch()) {
+      return entity_load_single('quiz_result_answer', $row->result_answer_id);
+    }
+  }
+
+  /**
+   * Deletes results for a quiz according to the keep results setting
+   *
+   * @param QuizEntity $quiz
+   *  The quiz entity to be maintained
+   * @param int $result_id
+   *  The result id of the latest result for the current user
+   * @return
+   *  TRUE if results where deleted.
+   */
+  public function maintainResult($account, $quiz, $result_id) {
+    // Do not delete results for anonymous users
+    if ($account->uid == 0) {
+      return;
+    }
+
+    switch ($quiz->keep_results) {
+      case QUIZ_KEEP_ALL:
+        return FALSE;
+      case QUIZ_KEEP_BEST:
+        $best_result_id = db_query(
+          'SELECT result_id FROM {quiz_results}
+           WHERE quiz_qid = :qid
+             AND uid = :uid
+             AND is_evaluated = :is_evaluated
+           ORDER BY score DESC', array(
+            ':qid'          => $quiz->qid,
+            ':uid'          => $account->uid,
+            ':is_evaluated' => 1))->fetchField();
+        if (!$best_result_id) {
+          return;
+        }
+
+        $res = db_query('SELECT result_id
+          FROM {quiz_results}
+          WHERE quiz_qid = :qid
+            AND uid = :uid
+            AND result_id != :best_rid
+            AND is_evaluated = :is_evaluated', array(
+            ':qid'          => $quiz->qid,
+            ':uid'          => $account->uid,
+            ':is_evaluated' => 1,
+            ':best_rid'     => $best_result_id
+        ));
+        $result_ids = array();
+        while ($result_id2 = $res->fetchField()) {
+          $result_ids[] = $result_id2;
+        }
+        entity_delete_multiple('quiz_result', $result_ids);
+        return !empty($result_ids);
+      case QUIZ_KEEP_LATEST:
+        $res = db_query('SELECT result_id
+            FROM {quiz_results}
+            WHERE quiz_qid = :qid
+              AND uid = :uid
+              AND is_evaluated = :is_evaluated
+              AND result_id != :result_id', array(
+            ':qid'          => $quiz->qid,
+            ':uid'          => $account->uid,
+            ':is_evaluated' => 1,
+            ':result_id'    => $result_id
+        ));
+        $result_ids = array();
+        while ($result_id2 = $res->fetchField()) {
+          $result_ids[] = $result_id2;
+        }
+        entity_delete_multiple('quiz_result', $result_ids);
+        return !empty($result_ids);
+    }
+  }
+
+  /**
+   * Delete quiz responses for quizzes that haven't been finished.
+   *
+   * This was _quiz_delete_old_in_progress()
+   *
+   * @param $quiz
+   *   A quiz entity where old in progress results shall be deleted.
+   * @param $uid
+   *   The userid of the user the old in progress results belong to.
+   */
+  public function deleteIncompletedResultsByUserId($quiz, $uid) {
+    $res = db_query('SELECT qnr.result_id
+          FROM {quiz_results} qnr
+          WHERE qnr.uid = :uid
+            AND qnr.quiz_qid = :qid
+            AND qnr.time_end = :time_end
+            AND qnr.quiz_vid < :vid', array(
+        ':uid'      => $uid,
+        ':qid'      => $quiz->qid,
+        ':time_end' => 1,
+        ':vid'      => $quiz->vid));
+    $result_ids = array();
+    while ($result_id = $res->fetchField()) {
+      $result_ids[] = $result_id;
+    }
+    entity_delete_multiple('quiz_result', $result_ids);
+  }
+
+}
diff --git a/src/Helper/Quiz/TakeJumperHelper.php b/src/Helper/Quiz/TakeJumperHelper.php
new file mode 100644
index 0000000..a03b8e6
--- /dev/null
+++ b/src/Helper/Quiz/TakeJumperHelper.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\quiz\Helper\Quiz;
+
+class TakeJumperHelper {
+
+  private $quiz;
+  private $total;
+  private $siblings;
+  private $current;
+
+  public function __construct($quiz, $total, $siblings, $current) {
+    $this->quiz = $quiz;
+    $this->total = $total;
+    $this->siblings = $siblings;
+    $this->current = $current;
+  }
+
+  public function render() {
+    return theme('item_list', array(
+      'items'      => $this->buildItems(),
+      'attributes' => array('class' => array('pager'))
+    ));
+  }
+
+  private function buildItems() {
+    $items = array();
+    $items[] = array(
+      'class' => array('pager-first'),
+      'data'  => l(t('first'), "quiz/" . $this->quiz->qid . "/take/1"),
+    );
+
+    foreach ($this->buildElements() as $i) {
+      if ($i == $this->current) {
+        $items[] = array(
+          'class' => array('pager-current'),
+          'data'  => $i,
+        );
+      }
+      else {
+        $items[] = array(
+          'class' => array('pager-item'),
+          'data'  => l($i, "quiz/" . $this->quiz->qid . "/take/{$i}"),
+        );
+      }
+    }
+
+    $items[] = array(
+      'class' => array('pager-last'),
+      'data'  => l(t('last'), "quiz/" . $this->quiz->qid . "/take/{$this->total}"),
+    );
+
+    return $items;
+  }
+
+  /**
+   * Help us with special pagination.
+   *
+   * Why not the Drupal theme_pager()?
+   *
+   * It uses query strings. We have access on each menu argument (quiz question
+   * number) so we unfortunately cannot use it.
+   */
+  private function buildElements($perpage = 1) {
+    $result = array();
+
+    if (isset($this->total, $perpage) === true) {
+      $result = range(1, ceil($this->total / $perpage));
+
+      if (isset($this->current, $this->siblings) === true) {
+        if (($this->siblings = floor($this->siblings / 2) * 2 + 1) >= 1) {
+          $result = array_slice($result, max(0, min(count($result) - $this->siblings, intval($this->current) - ceil($this->siblings / 2))), $this->siblings);
+        }
+      }
+    }
+
+    return $result;
+  }
+
+}
diff --git a/src/Helper/Quiz/TakingHelper.php b/src/Helper/Quiz/TakingHelper.php
new file mode 100644
index 0000000..d40ee94
--- /dev/null
+++ b/src/Helper/Quiz/TakingHelper.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\quiz\Helper\Quiz;
+
+use Drupal\quiz\Entity\QuizEntity;
+
+class TakingHelper {
+
+  /**
+   * Update the session for this quiz to the active question.
+   *
+   * @param QuizEntity $quiz
+   * @param int $page_number
+   *   Question number starting at 1.
+   */
+  public function redirect(QuizEntity $quiz, $page_number) {
+    $_SESSION['quiz'][$quiz->qid]['current'] = $page_number;
+  }
+
+}
diff --git a/src/Helper/QuizHelper.php b/src/Helper/QuizHelper.php
new file mode 100644
index 0000000..a155745
--- /dev/null
+++ b/src/Helper/QuizHelper.php
@@ -0,0 +1,724 @@
+<?php
+
+namespace Drupal\quiz\Helper;
+
+use Drupal\quiz\Helper\Quiz\AccessHelper;
+use Drupal\quiz\Helper\Quiz\FeedbackHelper;
+use Drupal\quiz\Helper\Quiz\TakingHelper;
+use Drupal\quiz\Helper\Quiz\ResultHelper;
+use Drupal\quiz\Helper\Quiz\SettingHelper;
+use Drupal\quiz\Helper\Quiz\TakeJumperHelper;
+
+class QuizHelper {
+
+  private $resultHelper;
+  private $accessHelper;
+  private $feedbackHelper;
+  private $takeJumperHelper;
+  private $questionHelper;
+
+  /**
+   * @return ResultHelper
+   */
+  public function getResultHelper() {
+    if (null === $this->resultHelper) {
+      $this->resultHelper = new ResultHelper();
+    }
+    return $this->resultHelper;
+  }
+
+  public function setResultHelper($resultHelper) {
+    $this->resultHelper = $resultHelper;
+    return $this;
+  }
+
+  /**
+   * @return AccessHelper
+   */
+  public function getAccessHelper() {
+    if (null === $this->accessHelper) {
+      $this->accessHelper = new AccessHelper();
+    }
+    return $this->accessHelper;
+  }
+
+  public function setAccessHelper($accessHelper) {
+    $this->accessHelper = $accessHelper;
+    return $this;
+  }
+
+  /**
+   * @return FeedbackHelper
+   */
+  public function getFeedbackHelper() {
+    if (null === $this->feedbackHelper) {
+      $this->feedbackHelper = new FeedbackHelper();
+    }
+    return $this->feedbackHelper;
+  }
+
+  public function setFeedbackHelper($feedbackHelper) {
+    $this->feedbackHelper = $feedbackHelper;
+    return $this;
+  }
+
+  /**
+   * @return TakeJumperHelper
+   */
+  public function getTakeJumperHelper($quiz, $total, $siblings, $current) {
+    if (null == $this->takeJumperHelper) {
+      $this->takeJumperHelper = new TakeJumperHelper($quiz, $total, $siblings, $current);
+    }
+    return $this->takeJumperHelper;
+  }
+
+  public function setTakeJumperHelper($takeJumperHelper) {
+    $this->takeJumperHelper = $takeJumperHelper;
+    return $this;
+  }
+
+  /**
+   * @return TakingHelper
+   */
+  public function getQuestionHelper() {
+    if (null === $this->questionHelper) {
+      $this->questionHelper = new TakingHelper();
+    }
+    return $this->questionHelper;
+  }
+
+  public function setQuestionHelper($questionHelper) {
+    $this->questionHelper = $questionHelper;
+    return $this;
+  }
+
+  /**
+   * Returns the titles for all quizzes the user has access to.
+   *
+   * @return quizzes
+   *   Array with nids as keys and titles as values.
+   */
+  public function getAllTitles() {
+    return db_select('node', 'n')
+        ->fields('n', array('nid', 'title'))
+        ->condition('n.type', 'quiz')
+        ->addTag('node_access')
+        ->execute()
+        ->fetchAllKeyed();
+  }
+
+  /**
+   * Returns the titles for all quizzes the user has access to.
+   *
+   * @return quizzes
+   *   Array with nids as keys and (array with vid as key and title as value) as values.
+   *   Like this: array($nid => array($vid => $title))
+   */
+  public function getAllRevisionTitles() {
+    $query = db_select('node', 'n');
+    $query->join('node_revision', 'nr', 'nr.nid = n.nid');
+    $query->fields('nr', array('nid', 'vid', 'title'))
+      ->condition('n.type', 'quiz')
+      ->execute();
+
+    $to_return = array();
+    while ($res_o = $query->fetch()) {
+      $to_return[$res_o->nid][$res_o->vid] = $res_o->title;
+    }
+    return $to_return;
+  }
+
+  /**
+   * @param \Drupal\quiz\Entity\QuizEntity $quiz
+   * @param \stdClass $question
+   */
+  public function addQuestion($quiz, $question) {
+    $quiz_questions = $quiz->getQuestionLoader()->getQuestions();
+
+    // Do not add a question if it's already been added (selected in an earlier checkbox)
+    foreach ($quiz_questions as $q) {
+      if ($question->vid == $q->vid) {
+        return FALSE;
+      }
+    }
+
+    // Otherwise let's add a relationship!
+    $question->quiz_qid = $quiz->qid;
+    $question->quiz_vid = $quiz->vid;
+    _quiz_question_get_instance($question)->saveRelationships();
+    $this->updateMaxScoreProperties(array($quiz->vid));
+  }
+
+  /**
+   * Given a term ID, get all of the question nid/vids that have that ID.
+   *
+   * @param $tid
+   *   Integer term ID.
+   *
+   * @return
+   *   Array of nid/vid combos, like array(array('nid'=>1, 'vid'=>2)).
+   */
+  public function getRandomTaxonomyQuestionIds($tid, $num_random) {
+    if ($tid == 0) {
+      return array();
+    }
+
+    // Select random questions by taxonomy.
+    $term = taxonomy_term_load($tid);
+    $tree = taxonomy_get_tree($term->vid, $term->tid);
+
+    // Flatten the taxonomy tree, and just keep term id's.
+    $term_ids[] = $term->tid;
+    if (is_array($tree)) {
+      foreach ($tree as $term) {
+        $term_ids[] = $term->tid;
+      }
+    }
+    $term_ids = implode(',', $term_ids);
+
+    // Get all published questions with one of the allowed term ids.
+    // TODO Please convert this statement to the D7 database API syntax.
+    $result = db_query_range("SELECT n.nid, n.vid
+    FROM {node} n
+    INNER JOIN {taxonomy_index} tn USING (nid)
+    WHERE n.status = 1 AND tn.tid IN ($term_ids)
+    AND n.type IN ('" . implode("','", array_keys(quiz_get_question_types()))
+      . "') ORDER BY RAND()");
+
+    $questions = array();
+    while ($question_node = db_fetch_array($result)) {
+      $question_node['random'] = TRUE;
+      $questions[] = $question_node;
+    }
+
+    return $questions;
+  }
+
+  /**
+   * Sets the questions that are assigned to a quiz.
+   *
+   * @param $quiz
+   *   The quiz(node) to modify.
+   * @param $questions
+   *   An array of questions.
+   * @param $set_new_revision
+   *   If TRUE, a new revision will be generated. Note that saving
+   *   quiz questions unmodified will still generate a new revision of the quiz if
+   *   this is set to TRUE. Why? For a few reasons:
+   *   - All of the questions are updated to their latest VID. That is supposed to
+   *     be a feature.
+   *   - All weights are updated.
+   *   - All status flags are updated.
+   *
+   * @return
+   *   Boolean TRUE if update was successful, FALSE otherwise.
+   */
+  public function setQuestions(&$quiz, $questions, $set_new_revision = FALSE) {
+    if ($set_new_revision) {
+      // Create a new Quiz VID, even if nothing changed.
+      $quiz->revision = 1;
+
+      node_save($quiz);
+    }
+
+    // When node_save() calls all of the node API hooks, old quiz info is
+    // automatically inserted into quiz_relationship. We could get clever and
+    // try to do strategic updates/inserts/deletes, but that method has already
+    // proven error prone as the module has gained complexity (See 5.x-2.0-RC2).
+    // So we go with the brute force method:
+    db_delete('quiz_relationship')
+      ->condition('quiz_qid', $quiz->qid)
+      ->condition('quiz_vid', $quiz->vid)
+      ->execute();
+
+    if (empty($questions)) {
+      return TRUE; // This is not an error condition.
+    }
+
+    foreach ($questions as $question) {
+      if ($question->state != QUESTION_NEVER) {
+        $question_inserts[$question->qr_id] = array(
+            'quiz_qid'              => $quiz->qid,
+            'quiz_vid'              => $quiz->vid,
+            'question_nid'          => $question->nid,
+            // Update to latest OR use the version given.
+            'question_vid'          => $question->refresh ? db_query('SELECT vid FROM {node} WHERE nid = :nid', array(
+                  ':nid' => $question->nid))->fetchField() : $question->vid,
+            'question_status'       => $question->state,
+            'weight'                => $question->weight,
+            'max_score'             => (int) $question->max_score,
+            'auto_update_max_score' => (int) $question->auto_update_max_score,
+            'qr_pid'                => $question->qr_pid,
+            'qr_id'                 => !$set_new_revision ? $question->qr_id : NULL,
+            'old_qr_id'             => $question->qr_id,
+        );
+        drupal_write_record('quiz_relationship', $question_inserts[$question->qr_id]);
+      }
+    }
+
+    // Update the parentage when a new revision is created.
+    // @todo this is copy pasta from quiz_update_quiz_question_relationship
+    foreach ($question_inserts as $question_insert) {
+      db_update('quiz_relationship')
+        ->condition('qr_pid', $question_insert['old_qr_id'])
+        ->condition('quiz_vid', $quiz->vid)
+        ->condition('quiz_qid', $quiz->qid)
+        ->fields(array('qr_pid' => $question_insert['qr_id']))
+        ->execute();
+    }
+
+    $this->updateMaxScoreProperties(array($quiz->vid));
+    return TRUE;
+  }
+
+  /**
+   * Get a list of all available quizzes.
+   *
+   * @param $uid
+   *   An optional user ID. If supplied, only quizzes created by that user will be
+   *   returned.
+   *
+   * @return
+   *   A list of quizzes.
+   */
+  public function getQuizzesByUserId($uid) {
+    $results = array();
+    $args = array();
+    $query = db_select('node', 'n')
+      ->fields('n', array('nid', 'vid', 'title', 'uid', 'created'))
+      ->fields('u', array('name'));
+    $query->leftJoin('users', 'u', 'u.uid = n.uid');
+    $query->condition('n.type', 'quiz');
+    if ($uid != 0) {
+      $query->condition('n.uid', $uid);
+    }
+    $query->orderBy('n.nid');
+    $quizzes = $query->execute();
+    foreach ($quizzes as $quiz) {
+      $results[$quiz->qid] = (array) $quiz;
+    }
+    return $results;
+  }
+
+  /**
+   * Copies questions when a quiz is translated.
+   *
+   * @param $quiz
+   *   The new translated quiz entity.
+   */
+  public function copyQuestions($quiz) {
+    // Find original questions.
+    $query = db_query('SELECT question_nid, question_vid, question_status, weight, max_score, auto_update_max_score
+        FROM {quiz_relationship}
+        WHERE quiz_vid = :quiz_vid', array(':quiz_vid' => $quiz->translation_source->vid));
+    foreach ($query as $res_o) {
+      $original_question = node_load($res_o->question_nid);
+
+      // Set variables we can't or won't carry with us to the translated node to
+      // NULL.
+      $original_question->nid = $original_question->vid = $original_question->created = $original_question->changed = NULL;
+      $original_question->revision_timestamp = $original_question->menu = $original_question->path = NULL;
+      $original_question->files = array();
+      if (isset($original_question->book['mlid'])) {
+        $original_question->book['mlid'] = NULL;
+      }
+
+      // Set the correct language.
+      $original_question->language = $quiz->language;
+
+      // Save the node.
+      node_save($original_question);
+
+      // Save the relationship between the new question and the quiz.
+      db_insert('quiz_relationship')
+        ->fields(array(
+            'quiz_qid'              => $quiz->qid,
+            'quiz_vid'              => $quiz->vid,
+            'question_nid'          => $original_question->nid,
+            'question_vid'          => $original_question->vid,
+            'question_status'       => $res_o->question_status,
+            'weight'                => $res_o->weight,
+            'max_score'             => $res_o->max_score,
+            'auto_update_max_score' => $res_o->auto_update_max_score,
+        ))
+        ->execute();
+    }
+  }
+
+  /**
+   * Finds out the number of questions for the quiz.
+   *
+   * Good example of usage could be to calculate the % of score.
+   *
+   * @param int $quiz_vid
+   *   Quiz version ID.
+   * @return
+   *   Returns the number of quiz questions.
+   */
+  public function countQuestion($quiz_vid) {
+    return $this->countAlwaysQuestions($quiz_vid) + (int) db_query(
+        'SELECT number_of_random_questions'
+        . ' FROM {quiz_entity_revision}'
+        . ' WHERE vid = :vid', array(':vid' => $quiz_vid)
+      )->fetchField();
+  }
+
+  /**
+   * Get the number of compulsory questions for a quiz.
+   *
+   * @param int $quiz_vid
+   * @return int
+   *   Number of compulsory questions.
+   */
+  public function countAlwaysQuestions($quiz_vid) {
+    return db_query('SELECT COUNT(*)
+      FROM {quiz_relationship} qnr
+        JOIN {node} n ON n.nid = qnr.question_nid
+      WHERE n.status=1
+        AND qnr.quiz_vid = :quiz_vid
+        AND qnr.question_status = :question_status', array(
+          ':quiz_vid'        => $quiz_vid,
+          ':question_status' => QUESTION_ALWAYS
+      ))->fetchField();
+  }
+
+  /**
+   * Return highest score data for given quizzes.
+   *
+   * @param int[] $qids
+   *   IDs for the quizzes we want to collect scores from.
+   * @param int $uid
+   *   uid for the user we want to collect score for.
+   * @param $include_num_questions
+   *   Do we want to collect information about the number of questions in a quiz?
+   *   This adds a performance hit.
+   * @return
+   *   Array of score data.
+   *   For several takes on the same quiz, only returns highest score.
+   */
+  public function getScoreData($qids, $uid, $include_num_questions = FALSE) {
+    // Validate that the nids are integers.
+    foreach ($qids as $key => $nid) {
+      if (!_quiz_is_int($nid)) {
+        unset($qids[$key]);
+      }
+    }
+
+    if (empty($qids)) {
+      return array();
+    }
+
+    // Fetch score data for the validated nids.
+    $to_return = array();
+    $vids = array();
+    $sql = '
+        SELECT quiz.title, quiz.qid, quiz.vid,
+          p.number_of_random_questions AS num_random_questions,
+          r.score AS percent_score,
+          p.max_score,
+          p.pass_rate AS percent_pass
+        FROM {quiz_entity} quiz
+          JOIN {quiz_entity_revision} p ON quiz.vid = p.vid
+          LEFT OUTER JOIN {quiz_results} r ON r.nid = quiz.qid AND r.uid = :uid
+          LEFT OUTER JOIN
+            (
+              SELECT nid, max(score) as highest_score
+              FROM {quiz_results}
+              GROUP BY nid
+            )
+            rm ON quiz.qid = rm.nid AND r.score = rm.highest_score
+        WHERE quiz.qid in (' . implode(', ', $qids) . ')';
+    $res = db_query($sql, array(':uid' => $uid));
+    foreach ($res as $res_o) {
+      if (!$include_num_questions) {
+        unset($res_o->num_random_questions);
+      }
+      if (!isset($to_return[$res_o->vid]) || $res_o->percent_score > $to_return[$res_o->vid]->percent_score) {
+        $to_return[$res_o->vid] = $res_o; // Fetch highest score
+      }
+      // $vids will be used to fetch number of questions.
+      $vids[] = $res_o->vid;
+    }
+
+    if (empty($vids)) {
+      return array();
+    }
+
+    // Fetch total number of questions.
+    if ($include_num_questions) {
+      $res = db_query('SELECT COUNT(*) AS num_always_questions, quiz_vid
+            FROM {quiz_relationship}
+            WHERE quiz_vid IN (' . implode(', ', $vids) . ')
+            AND question_status = ' . QUESTION_ALWAYS . '
+            GROUP BY quiz_vid');
+      foreach ($res as $res_o) {
+        $to_return[$res_o->quiz_vid]->num_questions = $to_return[$res_o->quiz_vid]->num_random_questions + $res_o->num_always_questions;
+      }
+    }
+
+    return $to_return;
+  }
+
+  /**
+   * Store a quiz question result.
+   *
+   * @param $quiz
+   *  The quiz entity
+   * @param $result
+   *  Object with data about the result for a question.
+   * @param $options
+   *  Array with options that affect the behavior of this function.
+   *  ['set_msg'] - Sets a message if the last question was skipped.
+   */
+  public function saveQuestionResult($quiz, $result, $options) {
+    if (isset($result->is_skipped) && $result->is_skipped == TRUE) {
+      if ($options['set_msg']) {
+        drupal_set_message(t('Last question skipped.'), 'status');
+      }
+      $result->is_correct = FALSE;
+      $result->score = 0;
+    }
+    else {
+      // Make sure this is set.
+      $result->is_skipped = FALSE;
+    }
+    if (!isset($result->score)) {
+      $result->score = $result->is_correct ? 1 : 0;
+    }
+
+    // Points are stored pre-scaled in the quiz_results_answers table. We get the scale.
+    if ($quiz->randomization < 2) {
+      $scale = db_query("
+        SELECT (max_score / (
+              SELECT max_score FROM {quiz_question_properties} WHERE nid = :nid AND vid = :vid
+            )) as scale
+            FROM {quiz_relationship}
+            WHERE quiz_qid = :quiz_qid
+            AND quiz_vid = :quiz_vid
+            AND question_nid = :question_nid
+            AND question_vid = :question_vid", array(
+          ':nid'          => $result->quiz_qid,
+          ':vid'          => $result->quiz_vid,
+          ':quiz_qid'     => $quiz->qid,
+          ':quiz_vid'     => $quiz->vid,
+          ':question_nid' => $result->quiz_qid,
+          ':question_vid' => $result->quiz_vid
+        ))->fetchField();
+    }
+    elseif ($quiz->randomization == 2) {
+      $scale = db_query("
+          SELECT
+            (max_score_for_random /
+              (SELECT max_score FROM {quiz_question_properties} WHERE nid = :question_nid AND vid = :question_vid)
+            ) as scale
+          FROM {quiz_entity_revision}
+          WHERE vid = :quiz_vid", array(
+          ':question_nid' => $result->quiz_qid,
+          ':question_vid' => $result->quiz_vid,
+          ':quiz_vid'     => $quiz->vid
+        ))->fetchField();
+    }
+    elseif ($quiz->randomization == 3) {
+      if (isset($options['question_data']['tid'])) {
+        $result->tid = $options['question_data']['tid'];
+      }
+      $scale = db_query("
+          SELECT
+            (max_score /
+              (SELECT max_score FROM {quiz_question_properties} WHERE nid = :nid AND vid = :vid)
+            ) as scale
+          FROM {quiz_terms} WHERE vid = :vid AND tid = :tid", array(
+          ':nid' => $result->quiz_qid,
+          ':vid' => $result->quiz_vid,
+          ':vid' => $quiz->vid,
+          ':tid' => $result->tid
+        ))->fetchField();
+    }
+    $points = round($result->score * $scale);
+
+    // Insert result data, or update existing data.
+    $result_answer_id = db_query("SELECT result_answer_id
+        FROM {quiz_results_answers}
+        WHERE question_nid = :question_nid
+          AND question_vid = :question_vid
+          AND result_id = :result_id", array(
+        ':question_nid' => $result->quiz_qid,
+        ':question_vid' => $result->quiz_vid,
+        ':result_id'    => $result->result_id
+      ))->fetchField();
+
+    $answer = (object) array(
+          'result_answer_id' => $result_answer_id,
+          'question_nid'     => $result->quiz_qid,
+          'question_vid'     => $result->quiz_vid,
+          'result_id'        => $result->result_id,
+          'is_correct'       => (int) $result->is_correct,
+          'points_awarded'   => $points,
+          'answer_timestamp' => REQUEST_TIME,
+          'is_skipped'       => (int) $result->is_skipped,
+          'is_doubtful'      => (int) $result->is_doubtful,
+          'number'           => $options['question_data']['number'],
+          'tid'              => ($quiz->randomization == 3 && $result->tid) ? $result->tid : 0,
+    );
+
+    entity_save('quiz_result_answer', $answer);
+  }
+
+  /**
+   * Updates the max_score property on the specified quizzes
+   *
+   * @param $quiz_vids
+   *  Array with the vid's of the quizzes to update
+   */
+  public function updateMaxScoreProperties($quiz_vids) {
+    if (empty($quiz_vids)) {
+      return;
+    }
+
+    db_update('quiz_entity_revision')
+      ->expression('max_score', 'max_score_for_random * number_of_random_questions + (
+            SELECT COALESCE(SUM(max_score), 0)
+            FROM {quiz_relationship} qnr
+            WHERE qnr.question_status = :status AND quiz_vid = {quiz_entity_revision}.vid)', array(
+          ':status' => QUESTION_ALWAYS))
+      ->condition('vid', $quiz_vids)
+      ->execute();
+
+    db_update('quiz_entity_revision')
+      ->expression('max_score', '(SELECT COALESCE(SUM(qt.max_score * qt.number), 0)
+            FROM {quiz_terms} qt
+            WHERE qt.nid = {quiz_entity_revision}.qid AND qt.vid = {quiz_entity_revision}.vid)')
+      ->condition('randomization', 3)
+      ->condition('vid', $quiz_vids)
+      ->execute();
+
+    db_update('quiz_entity_revision')
+      ->fields(array('changed' => REQUEST_TIME))
+      ->condition('vid', $quiz_vids)
+      ->execute();
+
+    db_update('quiz_entity')
+      ->fields(array('changed' => REQUEST_TIME))
+      ->condition('vid', $quiz_vids)
+      ->execute();
+
+    $results_to_update = db_query('SELECT vid '
+      . ' FROM {quiz_entity_revision} '
+      . ' WHERE vid IN (:vid) AND max_score <> :max_score', array(
+        ':vid'       => $quiz_vids,
+        ':max_score' => 0
+      ))->fetchCol();
+    if (!empty($results_to_update)) {
+      db_update('quiz_results')
+        ->expression('score', 'ROUND(
+          100 * (
+            SELECT COALESCE (SUM(a.points_awarded), 0)
+            FROM {quiz_results_answers} a
+            WHERE a.result_id = {quiz_results}.result_id
+          ) / (
+            SELECT max_score
+            FROM {quiz_entity_revision} qnp
+            WHERE qnp.vid = {quiz_results}.quiz_vid
+          ))')
+        ->condition('quiz_vid', $results_to_update)
+        ->execute();
+    }
+  }
+
+  /**
+   * Find out if a quiz is available for taking or not
+   *
+   * @param \Drupal\quiz\Entity\QuizEntity $quiz
+   * @return
+   *  TRUE if available
+   *  Error message(String) if not available
+   */
+  public function isAvailable(\Drupal\quiz\Entity\QuizEntity $quiz) {
+    global $user;
+
+    if (!$user->uid && $quiz->takes > 0) {
+      return t('This @quiz only allows %num_attempts attempts. Anonymous users can only access quizzes that allows an unlimited number of attempts.', array(
+          '%num_attempts' => $quiz->takes,
+          '@quiz'         => QUIZ_NAME
+      ));
+    }
+
+    $user_is_admin = entity_access('update', 'quiz_entity', $quiz);
+    if ($user_is_admin || $quiz->quiz_always) {
+      return TRUE;
+    }
+
+    // Compare current GMT time to the open and close dates (which should still be
+    // in GMT time).
+    if ((REQUEST_TIME >= $quiz->quiz_close) || (REQUEST_TIME < $quiz->quiz_open)) {
+      return t('This @quiz is closed.', array('@quiz' => QUIZ_NAME));
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Check a user/quiz combo to see if the user passed the given quiz.
+   *
+   * This will return TRUE if the user has passed the quiz at least once, and
+   * FALSE otherwise. Note that a FALSE may simply indicate that the user has not
+   * taken the quiz.
+   *
+   * @param int $uid
+   * @param int $quiz_qid
+   * @param int $quiz_vid
+   */
+  public function isPassed($uid, $quiz_qid, $quiz_vid) {
+    $passed = db_query(
+      'SELECT COUNT(result_id) AS passed_count
+          FROM {quiz_results} result
+            INNER JOIN {quiz_entity_revision} revision
+              ON result.quiz_vid = revision.vid AND result.quiz_qid = revision.vid
+          WHERE result.quiz_vid = :vid
+            AND result.quiz_qid = :qid
+            AND result.uid = :uid
+            AND score >= pass_rate', array(
+        ':vid' => $quiz_vid,
+        ':qid' => $quiz_qid,
+        ':uid' => $uid
+      ))->fetchField();
+
+    // Force into boolean context.
+    return ($passed !== FALSE && $passed > 0);
+  }
+
+  /**
+   * Finds out if a quiz has been answered or not.
+   *
+   * @return
+   *   TRUE if there exists answers to the current question.
+   */
+  public function isAnswered($node) {
+    if (!isset($node->nid)) {
+      return FALSE;
+    }
+    $query = db_select('quiz_results', 'qnr');
+    $query->addField('qnr', 'result_id');
+    $query->condition('nid', $node->nid);
+    $query->condition('vid', $node->vid);
+    $query->range(0, 1);
+    return $query->execute()->rowCount() > 0;
+  }
+
+  /**
+   * @param string $question_type
+   *
+   * @return string
+   *   Name of module matching the question type, as given by quiz_question_info()
+   *   hook.
+   */
+  public function getQuestionModuleFromType($question_type) {
+    $types = quiz_get_question_types();
+    if (!isset($types[$question_type])) {
+      drupal_set_message(t('The module for the questiontype %type is not enabled', array('%type' => $question_type)), 'warning');
+      return FALSE;
+    }
+    return $types[$question_type]['module'];
+  }
+
+}
diff --git a/src/Quiz.php b/src/Quiz.php
new file mode 100644
index 0000000..4978cca
--- /dev/null
+++ b/src/Quiz.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Drupal\quiz;
+
+use Drupal\quiz\Helper\HookImplementation;
+use Drupal\quiz\Helper\MailHelper;
+use Drupal\quiz\Helper\NodeHelper;
+use Drupal\quiz\Helper\QuizHelper;
+
+/**
+ * Wrapper for helper classes. We just use classes to organise functions, make
+ * them easier to access, able to override, there is no OOP in helper classes
+ * yet.
+ *
+ * Quiz.nodeHelper — Helper for node-hook implementations.
+ * Quiz.quizHelper — Helper for quiz entity/object.
+ * Quiz.mailHelper — Build/format email messages.
+ * Quiz.quizHelper.settingHelper - Get/Set/… quiz settings.
+ * Quiz.quizHelper.resultHelper — Helper methods for quiz's results.
+ * Quiz.quizHelper.accessHelper — Access helpers
+ * Quiz.quizHelper.feedbackHelper — Helper methods for quiz's feedback.
+ *
+ * Extends this class and sub classes if you would like override things.
+ *
+ * You should not create object directly from this class, use quiz() factory
+ * function instead — which support overriding from module's side.
+ */
+class Quiz {
+
+  private $hookImplementation;
+  private $nodeHelper;
+  private $quizHelper;
+  private $mailHelper;
+
+  /**
+   * @return HookImplementation
+   */
+  public function getHookImplementation() {
+    if (null === $this->hookImplementation) {
+      $this->hookImplementation = new HookImplementation();
+    }
+    return $this->hookImplementation;
+  }
+
+  public function setHookImplementation($hookImplementation) {
+    $this->hookImplementation = $hookImplementation;
+    return $this;
+  }
+
+  /**
+   * @return NodeHelper
+   */
+  public function getNodeHelper() {
+    if (null === $this->nodeHelper) {
+      $this->nodeHelper = new NodeHelper();
+    }
+    return $this->nodeHelper;
+  }
+
+  /**
+   * Inject node helper.
+   *
+   * @param NodeHelper $nodeHelper
+   * @return Quiz
+   */
+  public function setNodeHelper($nodeHelper) {
+    $this->nodeHelper = $nodeHelper;
+    return $this;
+  }
+
+  /**
+   * @return QuizHelper
+   */
+  public function getQuizHelper() {
+    if (null === $this->quizHelper) {
+      $this->quizHelper = new QuizHelper();
+    }
+    return $this->quizHelper;
+  }
+
+  /**
+   * Inject quizHelper.
+   *
+   * @param QuizHelper $quizHelper
+   * @return Quiz
+   */
+  public function setQuizHelper($quizHelper) {
+    $this->quizHelper = $quizHelper;
+    return $this;
+  }
+
+  /**
+   * @return MailHelper
+   */
+  public function getMailHelper() {
+    if (null === $this->mailHelper) {
+      $this->mailHelper = new MailHelper();
+    }
+    return $this->mailHelper;
+  }
+
+  /**
+   * Inject mail helper.
+   *
+   * @param MailHelper $mailHelper
+   * @return Quiz
+   */
+  public function setMailHelper($mailHelper) {
+    $this->mailHelper = $mailHelper;
+    return $this;
+  }
+
+  /**
+   * Format a number of seconds to a hh:mm:ss format.
+   *
+   * @param $time_in_sec
+   *   Integers time in seconds.
+   *
+   * @return
+   *   String time in min : sec format.
+   */
+  function formatDuration($time_in_sec) {
+    $hours = intval($time_in_sec / 3600);
+    $min = intval(($time_in_sec - $hours * 3600) / 60);
+    $sec = $time_in_sec % 60;
+    if (strlen($min) == 1) {
+      $min = '0' . $min;
+    }
+    if (strlen($sec) == 1) {
+      $sec = '0' . $sec;
+    }
+    return "$hours:$min:$sec";
+  }
+
+  /**
+   * Retrieve list of vocabularies for all quiz question types.
+   *
+   * @return
+   *   An array containing a vocabulary list.
+   */
+  function getVocabularies() {
+    $vocabularies = array();
+    $types = array_keys(quiz_get_question_types());
+    foreach ($types as $type) {
+      foreach (taxonomy_get_vocabularies($type) as $vid => $vocabulary) {
+        $vocabularies[$vid] = $vocabulary;
+      }
+    }
+    return $vocabularies;
+  }
+
+  /**
+   * Modifies the format fieldset.
+   *
+   * Adds a class to all the format fieldsets and removes unwanted strings.
+   * A javascript is added by the forms theme function to make sure all format
+   * selectors follows the body field format selector.
+   * Used when there are multiple format selectors on one page.
+   *
+   * This was _quiz_format_mod(), this is only used in @multichoice/theme/multichoice-alternative-creation.tpl.php
+   * Could be a deprecated function.
+   *
+   * @param $format
+   *   The format fieldset.
+   */
+  public function formatMod(&$format) {
+    $format['#attributes']['class'] = array('quiz-filter');
+    if (isset($format['format'])) {
+      $format['format']['guidelines']['#value'] = ' ';
+      foreach (array_keys($format) as $key) {
+        if (is_numeric($key)) {
+          $format[$key]['#value'] = ' ';
+        }
+      }
+    }
+  }
+
+}
diff --git a/tests/QuizCreation.test b/tests/QuizCreation.test
deleted file mode 100644
index f42b59f..0000000
--- a/tests/QuizCreation.test
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the quiz question Module.
- */
-
-/**
- * Test aspects of quiz creation including global and user defaults.
- */
-class QuizCreationTestCase extends QuizTestCase {
-
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
-    $modules[] = 'truefalse';
-    parent::setUp($modules);
-  }
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz creation'),
-      'description' => t('Unit test for Quiz creation.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  /**
-   * Test basic quiz creation.
-   */
-  public function testQuizCreation() {
-    $this->drupalLogin($this->admin);
-    $this->drupalGet("node/add/quiz");
-    $this->drupalPost(NULL, array(
-      'title' => 'Test quiz creation',
-      ), t('Save'));
-    $this->assertText('has been created');
-  }
-
-  /**
-   * Test system, global, and user defaults.
-   */
-  public function testQuizDefaults() {
-    // We need another admin user here.
-    $admin_permissions = array();
-    $admin_permissions[] = 'administer quiz configuration';
-    $admin_permissions[] = 'create quiz content';
-    $admin_permissions[] = 'edit any quiz content';
-    $admin_permissions[] = 'edit question titles';
-    $admin_permissions[] = 'access quiz';
-    $this->admin2 = $this->drupalCreateUser(array_unique($admin_permissions));
-
-    $this->drupalLogin($this->admin);
-    $this->drupalGet("node/add/quiz");
-
-    // These are the basic system defaults.
-    $this->assertFieldChecked('edit-allow-resume');
-    $this->assertFieldChecked('edit-allow-skipping');
-    $this->assertNoFieldChecked('edit-allow-jumping');
-    $this->assertFieldChecked('edit-allow-change');
-    $this->assertFieldChecked('edit-backwards-navigation');
-    $this->assertNoFieldChecked('edit-repeat-until-correct');
-    $this->assertNoFieldChecked('edit-mark-doubtful');
-    $this->assertFieldChecked('edit-show-passed');
-
-    $this->drupalGet('admin/quiz/settings/quiz-form');
-    // Verify the same ones show up.
-    $this->assertFieldChecked('edit-allow-resume');
-    $this->assertFieldChecked('edit-allow-skipping');
-    $this->assertNoFieldChecked('edit-allow-jumping');
-    $this->assertFieldChecked('edit-allow-change');
-    $this->assertFieldChecked('edit-backwards-navigation');
-    $this->assertNoFieldChecked('edit-repeat-until-correct');
-    $this->assertNoFieldChecked('edit-mark-doubtful');
-    $this->assertFieldChecked('edit-show-passed');
-
-    // Change a default.
-    $this->drupalPost(NULL, array('allow_resume' => FALSE), t('Save'));
-    $this->drupalGet('admin/quiz/settings/quiz-form');
-    $this->assertNoFieldChecked('edit-allow-resume');
-
-    // Check that the default carried over. Check that other options remained
-    // set.
-    $this->drupalGet("node/add/quiz");
-    $this->assertNoFieldChecked('edit-allow-resume');
-    $this->assertFieldChecked('edit-allow-skipping');
-
-    // Check defaults for another user.
-    $this->drupalLogin($this->admin2);
-    $this->drupalGet("node/add/quiz");
-    $this->assertNoFieldChecked('edit-allow-resume');
-    $this->assertFieldChecked('edit-allow-skipping');
-
-    // Set allow skipping to off as a user default.
-    $this->drupalGet("node/add/quiz");
-    $this->drupalPost(NULL, array(
-      'title' => 'Test quiz',
-      'allow_skipping' => FALSE,
-      'remember_settings' => TRUE,
-      ), t('Save'));
-    $this->drupalGet("node/add/quiz");
-    $this->assertNoFieldChecked('edit-allow-resume');
-    $this->assertNoFieldChecked('edit-allow-skipping');
-
-    // Ensure it didn't affect another user.
-    $this->drupalLogin($this->admin);
-    $this->drupalGet("node/add/quiz");
-    $this->assertNoFieldChecked('edit-allow-resume');
-    $this->assertFieldChecked('edit-allow-skipping');
-  }
-
-}
diff --git a/tests/QuizEntityFeedbackTestCase.test b/tests/QuizEntityFeedbackTestCase.test
new file mode 100644
index 0000000..c0ebf78
--- /dev/null
+++ b/tests/QuizEntityFeedbackTestCase.test
@@ -0,0 +1,136 @@
+<?php
+
+use Drupal\quiz\Entity\QuizEntity;
+
+/**
+ * Base test class for Quiz questions.
+ */
+class QuizEntityFeedbackTestCase extends QuizTestCase {
+
+  protected $extraModules = array('truefalse');
+  protected $extraUserPermissions = array('view any quiz question correct response');
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Quiz feedback'),
+        'description' => t('Unit test for Quiz feedback.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  /**
+   * Test question feedback. Note that we are only testing if any feedback
+   * displays, each question type has its own tests for testing feedback
+   * returned from that question type.
+   */
+  public function testAnswerFeedback() {
+    $this->drupalLogin($this->admin);
+    $quiz = $this->drupalCreateQuiz();
+
+    // 3 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+
+    // This is a dynamic test that only tests the feedback columns showing up.
+    variable_set('quiz_auto_revisioning', 0);
+
+    $review_options = array(
+        'attempt'         => t('Your answer'),
+        'correct'         => t('Correct?'),
+        'score'           => t('Score'),
+        'answer_feedback' => t('Feedback'),
+        'solution'        => t('Correct answer'),
+    );
+
+    $this->drupalLogin($this->user);
+
+    $this->checkAfterQuestion($quiz, $question_node1, $review_options);
+    $this->checkAfterQuiz($quiz, $question_node2, $review_options);
+  }
+
+  private function checkAfterQuestion(QuizEntity $quiz, $question_node1, $review_options) {
+    // Answer the first question.
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+
+    // Check feedback after the Question
+    foreach ($review_options as $option => $text) {
+      $quiz->review_options = array('question' => array($option => $option));
+      entity_save('quiz_entity', $quiz);
+
+      $this->drupalGet("quiz/{$quiz->qid}/take/1/feedback");
+      $this->assertRaw('<th>' . $text . '</th>');
+
+      foreach ($review_options as $option2 => $text2) {
+        if ($option != $option2) {
+          $this->assertNoRaw('<th>' . $text2 . '</th>');
+        }
+      }
+    }
+  }
+
+  private function checkAfterQuiz(QuizEntity $quiz, $question_node2, $review_options) {
+    // Feedback only after the quiz.
+    $this->drupalGet("quiz/{$quiz->qid}/take/1/feedback");
+    $this->drupalPost(NULL, array(), t('Next question'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 1), t('Finish'));
+
+    // Check feedback after the Quiz
+    foreach ($review_options as $option => $text) {
+      $quiz->review_options = array('end' => array($option => $option));
+      entity_save('quiz_entity', $quiz);
+
+      $this->drupalGet("quiz-result/1");
+      $this->assertRaw('<th>' . $text . '</th>');
+      foreach ($review_options as $option2 => $text2) {
+        if ($option != $option2) {
+          $this->assertNoRaw('<th>' . $text2 . '</th>');
+        }
+      }
+    }
+  }
+
+  /**
+   * Test general Quiz question feedback.
+   */
+  public function testQuestionFeedback() {
+    $this->drupalLogin($this->admin);
+
+    // Turn on question feedback at the end.
+    $quiz = $this->drupalCreateQuiz(
+      array(
+          'review_options' => array('end' => array('question_feedback' => 'question_feedback')),
+      )
+    );
+
+    // Add 2 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+
+    // Set feedback.
+    $quiz_question = entity_load('quiz_question', FALSE, array('nid' => $question_node1->nid, 'vid' => $question_node1->vid));
+    $quiz_question = reset($quiz_question);
+    $quiz_question->feedback = 'Feedback for TF test.';
+    $quiz_question->feedback_format = filter_default_format();
+    entity_save('quiz_question', $quiz_question);
+
+    $quiz_question = entity_load('quiz_question', FALSE, array('nid' => $question_node2->nid, 'vid' => $question_node2->vid));
+    $quiz_question = reset($quiz_question);
+    $quiz_question->feedback = 'Feedback for TF test.';
+    $quiz_question->feedback_format = filter_default_format();
+    entity_save('quiz_question', $quiz_question);
+
+    // Test
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->assertNoText('Feedback for TF test.');
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 1), t('Finish'));
+    $this->assertText('Feedback for TF test.');
+  }
+
+}
diff --git a/tests/QuizEntityNavigationTestCase.test b/tests/QuizEntityNavigationTestCase.test
new file mode 100644
index 0000000..66ed108
--- /dev/null
+++ b/tests/QuizEntityNavigationTestCase.test
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * Base test class for Quiz questions.
+ */
+class QuizEntityNavigationTestCase extends QuizTestCase {
+
+  protected $extraModules = array('truefalse');
+
+  public static function getInfo() {
+    return array(
+      'name'        => t('Quiz navigation'),
+      'description' => t('Unit test for Quiz navigation.'),
+      'group'       => t('Quiz'),
+    );
+  }
+
+  /**
+   * Test the question navigation and access.
+   */
+  public function testQuestionNavigationBasic() {
+    $this->drupalLogin($this->admin);
+    $quiz = $this->drupalCreateQuiz();
+
+    // 5 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    // Testing basic navigation. Ensure next questions are not yet available.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(403);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertResponse(403);
+
+    // Answer a question, ensure next question is available.
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertResponse(403);
+  }
+
+  /**
+   * Test that all questions are available when quiz jumping is on.
+   */
+  function testQuestionNavigationJumping() {
+    $this->drupalLogin($this->admin);
+    $quiz = $this->drupalCreateQuiz(array('allow_jumping' => 1));
+
+    // 5 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+    $question_node4 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node4, $quiz);
+    $question_node5 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node5, $quiz);
+
+    // Testing jumpable navigation.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertResponse(200);
+
+    // We should have a selectbox right now.
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->assertFieldById('edit-question-number', NULL);
+    // Check that the "first" pager link does not appear.
+    $this->assertNoLinkByHref("quiz/{$quiz->qid}/take/1");
+
+    // Test the switch between select/pager.
+    variable_set('quiz_pager_start', 5);
+    // One on each side.
+    variable_set('quiz_pager_siblings', 2);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertNoFieldById('edit-question-number');
+    $this->assertNoLink('1');
+    $this->assertLinkByHref("quiz/{$quiz->qid}/take/2");
+    $this->assertNoLinkByHref("quiz/{$quiz->qid}/take/3");
+    $this->assertLinkByHref("quiz/{$quiz->qid}/take/4");
+    $this->assertNoLink('5');
+  }
+
+  /**
+   * Test that a user can skip a question.
+   */
+  function testQuestionNavigationSkipping() {
+    $this->drupalLogin($this->admin);
+    // Default behavior, anyway.
+    $quiz = $this->drupalCreateQuiz(array('allow_skipping' => 1));
+
+    // 3 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    // Ensure next questions are blocked until skipped.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(403);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertResponse(403);
+
+    // Leave a question blank.
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array(), t('Leave blank'));
+    // Now question 2 is accessible.
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertResponse(403);
+  }
+
+  /**
+   * Test preventing backwards navigation of questions.
+   */
+  function testQuestionNavigationBackwards() {
+    $this->drupalLogin($this->admin);
+    $quiz = $this->drupalCreateQuiz(array(
+      'backwards_navigation' => 0,
+      'allow_skipping'       => 0,
+      'allow_jumping'        => 0,
+    ));
+
+    // 3 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    // Testing basic navigation.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(403);
+    $this->drupalGet("quiz/{$quiz->qid}/take/3");
+    $this->assertResponse(403);
+
+    // Answer a question, ensure next question is available. Ensure previous
+    // question is not.
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(200);
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->assertResponse(403);
+  }
+
+}
diff --git a/tests/QuizEntityTakingTestCase.test b/tests/QuizEntityTakingTestCase.test
new file mode 100644
index 0000000..170f1f9
--- /dev/null
+++ b/tests/QuizEntityTakingTestCase.test
@@ -0,0 +1,167 @@
+<?php
+
+class QuizEntityTakingTestCase extends QuizTestCase {
+
+  protected $extraModules = array('truefalse');
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Quiz taking'),
+        'description' => t('Unit test for Quiz take behaviors.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  /**
+   * Test the repeat until correct behavior. We do not have to test what type
+   * of feedback shows here, that is tested elsewhere.
+   */
+  public function testQuestionRepeatUntilCorrect() {
+    $this->drupalLogin($this->admin);
+    $quiz = $this->drupalCreateQuiz(array(
+        'repeat_until_correct' => 1,
+        'review_options'       => array('question' => array('answer_feedback' => 'answer_feedback')),
+    ));
+
+    $question_node = $this->drupalCreateNode(array(
+        'type'           => 'truefalse',
+        'correct_answer' => 1,
+    ));
+    $this->linkQuestionToQuiz($question_node, $quiz);
+
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 0), t('Finish'));
+    $this->assertText('The answer was incorrect. Please try again.');
+
+    // Check that we are still on the question.
+    $this->assertUrl("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 1), t('Finish'));
+    $this->assertNoText('The answer was incorrect. Please try again.');
+  }
+
+  /**
+   * Test the quiz resuming from database.
+   */
+  public function testQuizResuming() {
+    // Resuming is default behavior.
+    $quiz = $this->drupalCreateQuiz(array('allow_resume' => 1));
+
+    // 2 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+
+    // Answer a question. Ensure the question navigation restrictions are
+    // maintained.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(403);
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+
+    // Login again.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->assertText('Resuming');
+
+    // Assert 2nd question is accessible (indicating the answer to #1 was saved.)
+    $this->drupalGet("quiz/{$quiz->qid}/take/2");
+    $this->assertResponse(200);
+  }
+
+  /**
+   * Test the quiz availability tests.
+   */
+  public function testQuizAvailability() {
+    $this->drupalLogin($this->user);
+
+    $future = REQUEST_TIME + 86400;
+    $past = REQUEST_TIME - 86400;
+
+    // Within range.
+    $quiz = $this->drupalCreateQuiz(array('quiz_always' => 0, 'quiz_open' => $past, 'quiz_close' => $future));
+    $this->drupalGet("quiz/{$quiz->qid}");
+    $this->assertNoText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
+
+    // Starts in the future.
+    $quiz = $this->drupalCreateQuiz(array('quiz_always' => 0, 'quiz_open' => $future, 'quiz_close' => $future + 1));
+    $this->drupalGet("quiz/{$quiz->qid}");
+    $this->assertText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
+
+    // Ends in the past.
+    $quiz = $this->drupalCreateQuiz(array('quiz_always' => 0, 'quiz_open' => $past, 'quiz_close' => $past + 1));
+    $this->drupalGet("quiz/{$quiz->qid}");
+    $this->assertText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
+
+    // Always available.
+    $quiz = $this->drupalCreateQuiz(array('quiz_always' => 1, 'quiz_open' => $future, 'quiz_close' => $past));
+    $this->drupalGet("quiz/{$quiz->qid}");
+    $this->assertNoText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
+  }
+
+  /**
+   * Test the 'view quiz question outside of a quiz' permission.
+   */
+  function testViewQuestionsOutsideQuiz() {
+    $this->drupalLogin($this->admin);
+    // Resuming is default behavior.
+    $quiz = $this->drupalCreateQuiz();
+
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+
+    node_access_rebuild(FALSE);
+
+    $this->drupalLogin($this->user);
+    $this->drupalGet("node/{$question_node1->nid}");
+    $this->assertResponse(403);
+
+    $user_with_privs = $this->drupalCreateUser(array('view quiz question outside of a quiz', 'access quiz'));
+    $this->drupalLogin($user_with_privs);
+    $this->drupalGet("node/{$question_node1->nid}");
+    $this->assertResponse(200);
+  }
+
+  /**
+   * Test allow/restrict changing of answers.
+   */
+  function testChangeAnswer() {
+    $quiz = $this->drupalCreateQuiz(array(
+        'review_options' => array('question' => array('score' => 'score')),
+    ));
+
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 0), t('Next'));
+    $this->assertText('Score: 0 of 1');
+
+    // Go back and correct the answer.
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->assertText('Score: 1 of 1');
+
+    // Restrict change after answer.
+    $quiz->allow_change = 0;
+    entity_save('quiz_entity', $quiz);
+
+    // Check that the answer cannot be changed.
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $xpath = '//input[@id=:id and @disabled="disabled"]';
+    $disabled_field = $this->xpath($xpath, array(':id' => 'edit-question-1-1'));
+    $this->assertTrue($disabled_field, t('The answer cannot be changed.'));
+    $this->drupalPost(NULL, array(), t('Next'));
+    $this->assertText('Score: 1 of 1');
+  }
+
+}
diff --git a/tests/QuizEntityTestCase.test b/tests/QuizEntityTestCase.test
new file mode 100644
index 0000000..c9a74ba
--- /dev/null
+++ b/tests/QuizEntityTestCase.test
@@ -0,0 +1,174 @@
+<?php
+
+use Drupal\quiz\Entity\QuizEntity;
+use Drupal\quiz\Entity\QuizEntityType;
+
+/**
+ * Test aspects of quiz creation including global and user defaults.
+ */
+class QuizEntityTestCase extends QuizTestCase {
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Quiz entity'),
+        'description' => t('Unit test for Quiz entity.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
+    $modules[] = 'quiz';
+    parent::setUp($modules);
+  }
+
+  public function testInitQuizEntityType() {
+    // Default quiz type.
+    $types = entity_load('quiz_type', array('quiz'));
+    $default_quiz_type = reset($types);
+    $this->assertTrue($default_quiz_type instanceof QuizEntityType, 'Default quiz type is created.');
+
+    $quiz_type = entity_create('quiz_type', array('type' => 'new_quiz'));
+    $this->assertTrue($quiz_type instanceof QuizEntityType, 'Quiz type entity is created correctly using entity_create().');
+    entity_save('quiz_type', $quiz_type);
+  }
+
+  public function testQuizEntity() {
+    // ---------------------
+    // Create entity
+    // ---------------------
+    $quiz = entity_create('quiz_entity', array('type' => 'quiz', 'pass_rate' => 95));
+    $this->assertTrue($quiz instanceof QuizEntity, 'Quiz entity is created correctly using entity_create().');
+
+    // ---------------------
+    // Entity form, no warning
+    // ---------------------
+    $form = entity_form('quiz_entity', $quiz);
+    foreach (array('title', 'vtabs', 'taking', 'summaryoptions', 'resultoptions', 'quiz_availability') as $k) {
+      $this->assertTrue(isset($form[$k]), "Found {$k} in form.");
+    }
+
+    // ---------------------
+    // Insert entity
+    // ---------------------
+    entity_save('quiz_entity', $quiz);
+    $this->assertNotNull($quiz, 'Quiz entity is created successully.');
+
+    // ---------------------
+    // Update entity
+    // ---------------------
+    $quiz->title = 'Updated demo quiz';
+    entity_save('quiz_entity', $quiz);
+
+    // Reload entity
+    $quiz = entity_load_single('quiz_entity', $quiz->identifier());
+    $this->assertEqual('Updated demo quiz', $quiz->title, 'Quiz entity is updated.');
+
+    // ---------------------
+    // Test revision supporting
+    // ---------------------
+    $old_revision_id = $quiz->vid;
+
+    // Save new revision
+    $quiz->title .= ' [new revision]';
+    $quiz->is_new_revision = true;
+    $quiz->log = 'Trying revision supporting';
+    entity_save('quiz_entity', $quiz);
+
+    // Load old revision
+    $quiz_1 = quiz_entity_single_load($quiz->qid, $old_revision_id);
+    $quiz_2 = quiz_entity_single_load($quiz->qid);
+    $this->assertNotEqual($quiz_1->log, $quiz_2->log, 'New revision is created correctly.');
+    $this->assertEqual(95, $quiz_1->pass_rate, 'Quiz property is saved and loaded correctly.');
+
+    // ---------------------
+    // Delete quiz entity
+    // ---------------------
+    entity_delete('quiz_entity', $quiz->identifier());
+    $null_quiz = entity_load_single('quiz_entity', $quiz->identifier());
+    $this->assertFalse($null_quiz, 'Quiz entity is deleted');
+  }
+
+  /**
+   * Test basic quiz creation.
+   */
+  public function testQuizCreation() {
+    $this->drupalLogin($this->admin);
+    $this->drupalGet("quiz/add");
+    $this->drupalPost(NULL, array('title' => 'Test quiz creation'), t('Save'));
+    $this->drupalGet("quiz/1");
+    $this->assertText('Test quiz creation');
+    $this->assertText(t('Start !quiz', array('!quiz' => QUIZ_NAME)));
+  }
+
+  /**
+   * Test system, global, and user defaults.
+   */
+  public function testQuizDefaults() {
+    // We need another admin user here.
+    $admin_permissions = array();
+    $admin_permissions[] = 'administer quiz configuration';
+    $admin_permissions[] = 'create quiz content';
+    $admin_permissions[] = 'edit any quiz content';
+    $admin_permissions[] = 'edit question titles';
+    $admin_permissions[] = 'access quiz';
+    $this->admin2 = $this->drupalCreateUser(array_unique($admin_permissions));
+
+    $this->drupalLogin($this->admin);
+    $this->drupalGet('quiz/add');
+
+    // These are the basic system defaults.
+    $this->assertFieldChecked('edit-allow-resume');
+    $this->assertFieldChecked('edit-allow-skipping');
+    $this->assertNoFieldChecked('edit-allow-jumping');
+    $this->assertFieldChecked('edit-allow-change');
+    $this->assertFieldChecked('edit-backwards-navigation');
+    $this->assertNoFieldChecked('edit-repeat-until-correct');
+    $this->assertNoFieldChecked('edit-mark-doubtful');
+    $this->assertFieldChecked('edit-show-passed');
+
+    $this->drupalGet('admin/quiz/settings/quiz-form');
+    // Verify the same ones show up.
+    $this->assertFieldChecked('edit-allow-resume');
+    $this->assertFieldChecked('edit-allow-skipping');
+    $this->assertNoFieldChecked('edit-allow-jumping');
+    $this->assertFieldChecked('edit-allow-change');
+    $this->assertFieldChecked('edit-backwards-navigation');
+    $this->assertNoFieldChecked('edit-repeat-until-correct');
+    $this->assertNoFieldChecked('edit-mark-doubtful');
+    $this->assertFieldChecked('edit-show-passed');
+
+    // Change a default.
+    $this->drupalPost(NULL, array('allow_resume' => FALSE), t('Save'));
+    $this->drupalGet('admin/quiz/settings/quiz-form');
+    $this->assertNoFieldChecked('edit-allow-resume');
+
+    // Check that the default carried over. Check that other options remained
+    // set.
+    $this->drupalGet('quiz/add');
+    $this->assertNoFieldChecked('edit-allow-resume');
+    $this->assertFieldChecked('edit-allow-skipping');
+
+    // Check defaults for another user.
+    $this->drupalLogin($this->admin2);
+    $this->drupalGet('quiz/add');
+    $this->assertNoFieldChecked('edit-allow-resume');
+    $this->assertFieldChecked('edit-allow-skipping');
+
+    // Set allow skipping to off as a user default.
+    $this->drupalGet('quiz/add');
+    $this->drupalPost(NULL, array(
+        'title'             => 'Test quiz',
+        'allow_jumping'     => FALSE,
+        'remember_settings' => TRUE), t('Save'));
+    $this->drupalGet('quiz/add');
+    $this->assertNoFieldChecked('edit-allow-resume');
+    $this->assertNoFieldChecked('edit-allow-jumping');
+
+    // Ensure it didn't affect another user.
+    $this->drupalLogin($this->admin);
+    $this->drupalGet('quiz/add');
+    $this->assertNoFieldChecked('edit-allow-resume');
+    $this->assertFieldChecked('edit-allow-skipping');
+  }
+
+}
diff --git a/tests/QuizFeedback.test b/tests/QuizFeedback.test
deleted file mode 100644
index f8480bf..0000000
--- a/tests/QuizFeedback.test
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the quiz question Module.
- */
-
-/**
- * Base test class for Quiz questions.
- */
-class QuizFeedbackTestCase extends QuizTestCase {
-
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
-    $modules[] = 'truefalse';
-    parent::setUp($modules);
-  }
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz feedback'),
-      'description' => t('Unit test for Quiz feedback.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  /**
-   * Test question feedback. Note that we are only testing if any feedback
-   * displays, each question type has its own tests for testing feedback
-   * returned from that question type.
-   */
-  public function testAnswerFeedback() {
-    $this->drupalLogin($this->admin);
-    $quiz_node = $this->drupalCreateQuiz();
-
-    // 3 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1,));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1,));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-
-    // This is a dynamic test that only tests the feedback columns showing up.
-    variable_set('quiz_auto_revisioning', 0);
-
-    $review_options = array(
-      'attempt' => t('Your answer'),
-      'correct' => t('Correct?'),
-      'score' => t('Score'),
-      'answer_feedback' => t('Feedback'),
-      'solution' => t('Correct answer'),
-    );
-
-    $this->drupalLogin($this->user);
-
-    // Answer the first question.
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-
-    // Check feedback after the Question
-    foreach ($review_options as $option => $text) {
-      $quiz_node->review_options = array('question' => array($option => $option));
-      node_save($quiz_node);
-
-      $this->drupalGet("node/{$quiz_node->nid}/take/1/feedback");
-      $this->assertRaw('<th>' . $text . '</th>');
-      foreach ($review_options as $option2 => $text2) {
-        if ($option != $option2) {
-          $this->assertNoRaw('<th>' . $text2 . '</th>');
-        }
-      }
-    }
-
-    // Feedback only after the quiz.
-    $this->drupalGet("node/{$quiz_node->nid}/take/1/feedback");
-    $this->drupalPost(NULL, array(), t('Next question'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 1,
-      ), t('Finish'));
-
-    // Check feedback after the Quiz
-    foreach ($review_options as $option => $text) {
-      $quiz_node->review_options = array('end' => array($option => $option));
-      node_save($quiz_node);
-
-      $this->drupalGet("node/{$quiz_node->nid}/quiz-results/1/view");
-      $this->assertRaw('<th>' . $text . '</th>');
-      foreach ($review_options as $option2 => $text2) {
-        if ($option != $option2) {
-          $this->assertNoRaw('<th>' . $text2 . '</th>');
-        }
-      }
-    }
-  }
-
-  /**
-   * Test general Quiz question feedback.
-   */
-  public function testQuestionFeedback() {
-    $this->drupalLogin($this->admin);
-
-    // Turn on question feedback at the end.
-    $quiz_node = $this->drupalCreateQuiz(
-      array(
-        'review_options' => array('end' => array('question_feedback' => 'question_feedback')),
-      )
-    );
-
-    // Add 2 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-
-    // Set feedback.
-    $quiz_question = reset(entity_load('quiz_question', FALSE, array('nid' => $question_node1->nid, 'vid' => $question_node1->vid)));
-    $quiz_question->feedback = 'Feedback for TF test.';
-    $quiz_question->feedback_format = filter_default_format();
-    entity_save('quiz_question', $quiz_question);
-
-    $quiz_question = reset(entity_load('quiz_question', FALSE, array('nid' => $question_node2->nid, 'vid' => $question_node2->vid)));
-    $quiz_question->feedback = 'Feedback for TF test.';
-    $quiz_question->feedback_format = filter_default_format();
-    entity_save('quiz_question', $quiz_question);
-
-    // Test
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->assertNoText('Feedback for TF test.');
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 1,
-      ), t('Finish'));
-    $this->assertText('Feedback for TF test.');
-  }
-
-}
diff --git a/tests/QuizNavigation.test b/tests/QuizNavigation.test
deleted file mode 100644
index 2e35c72..0000000
--- a/tests/QuizNavigation.test
+++ /dev/null
@@ -1,188 +0,0 @@
-<?php
-
-/**
- * @file
- * Unit tests for the quiz question Module.
- */
-
-/**
- * Base test class for Quiz questions.
- */
-class QuizNavigationTestCase extends QuizTestCase {
-
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
-    $modules[] = 'truefalse';
-    parent::setUp($modules);
-  }
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz navigation'),
-      'description' => t('Unit test for Quiz navigation.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  /**
-   * Test the question navigation and access.
-   */
-  public function testQuestionNavigationBasic() {
-    $this->drupalLogin($this->admin);
-    $quiz_node = $this->drupalCreateQuiz();
-
-    // 3 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-
-    // Testing basic navigation. Ensure next questions are not yet available.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(403);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertResponse(403);
-
-    // Answer a question, ensure next question is available.
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertResponse(403);
-  }
-
-  /**
-   * Test that all questions are available when quiz jumping is on.
-   */
-  function testQuestionNavigationJumping() {
-    $this->drupalLogin($this->admin);
-    $quiz_node = $this->drupalCreateQuiz(array('allow_jumping' => 1));
-
-    // 5 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-    $question_node4 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node4, $quiz_node);
-    $question_node5 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node5, $quiz_node);
-
-    // Testing jumpable navigation.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertResponse(200);
-
-    // We should have a selectbox right now.
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->assertFieldById('edit-question-number', NULL);
-    // Check that the "first" pager link does not appear.
-    $this->assertNoLinkByHref("node/{$quiz_node->nid}/take/1");
-
-    // Test the switch between select/pager.
-    variable_set('quiz_pager_start', 5);
-    // One on each side.
-    variable_set('quiz_pager_siblings', 2);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertNoFieldById('edit-question-number');
-    $this->assertNoLink('1');
-    $this->assertLinkByHref("node/{$quiz_node->nid}/take/2");
-    $this->assertNoLinkByHref("node/{$quiz_node->nid}/take/3");
-    $this->assertLinkByHref("node/{$quiz_node->nid}/take/4");
-    $this->assertNoLink('5');
-  }
-
-  /**
-   * Test that a user can skip a question.
-   */
-  function testQuestionNavigationSkipping() {
-    $this->drupalLogin($this->admin);
-    // Default behavior, anyway.
-    $quiz_node = $this->drupalCreateQuiz(array('allow_skipping' => 1));
-
-    // 3 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-
-    // Ensure next questions are blocked until skipped.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(403);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertResponse(403);
-
-    // Leave a question blank.
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      ), t('Leave blank'));
-    // Now question 2 is accessible.
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertResponse(403);
-  }
-
-  /**
-   * Test preventing backwards navigation of questions.
-   */
-  public function testQuestionNavigationBackwards() {
-    $this->drupalLogin($this->admin);
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'backwards_navigation' => 0,
-      'allow_skipping' => 0,
-      'allow_jumping' => 0,
-    ));
-
-    // 3 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-
-    // Testing basic navigation.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(403);
-    $this->drupalGet("node/{$quiz_node->nid}/take/3");
-    $this->assertResponse(403);
-
-    // Answer a question, ensure next question is available. Ensure previous
-    // question is not.
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(200);
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->assertResponse(403);
-  }
-
-}
diff --git a/tests/QuizResult.test b/tests/QuizResult.test
deleted file mode 100644
index a0197a7..0000000
--- a/tests/QuizResult.test
+++ /dev/null
@@ -1,212 +0,0 @@
-<?php
-
-class QuizResultTestCase extends QuizTestCase {
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz results'),
-      'description' => t('Unit test for Quiz results.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
-    $modules[] = 'truefalse';
-    parent::setUp($modules);
-  }
-
-  /**
-   * Test the various result summaries and pass rate.
-   */
-  public function testPassRateSummary() {
-    // By default, the feedback is after the quiz.
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'pass_rate' => 75,
-      'summary_pass' => 'This is the summary if passed',
-      'summary_pass_format' => 'plain_text',
-      'summary_default' => 'This is the default summary text',
-      'summary_default_format' => 'plain_text',
-      'resultoptions' => array(
-        array(
-          'option_name' => '90 and higher',
-          'option_summary' => 'You got 90 or more on the quiz',
-          'option_summary_format' => 'filtered_html',
-          'option_start' => 90,
-          'option_end' => 100,
-        ),
-        array(
-          'option_name' => '50 and higher',
-          'option_summary' => 'You got between 50 and 89',
-          'option_summary_format' => 'filtered_html',
-          'option_start' => 50,
-          'option_end' => 89,
-        ),
-        array(
-          'option_name' => 'Below 50',
-          'option_summary' => 'You failed bro',
-          'option_summary_format' => 'filtered_html',
-          'option_start' => 0,
-          'option_end' => 49,
-        ),
-      ),
-    ));
-
-    // 3 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'feedback' => 'Q1Feedback'));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'feedback' => 'Q2Feedback'));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'feedback' => 'Q3Feedback'));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-
-    // Test 100%
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 1,
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node3->nid]" => 1,
-      ), t('Finish'));
-    $this->assertText('You got 90 or more on the quiz');
-    $this->assertText('This is the summary if passed');
-    $this->assertNoText('This is the default summary text');
-
-    // Test 66%
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 1,
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node3->nid]" => 0,
-      ), t('Finish'));
-    $this->assertText('You got between 50 and 89');
-    $this->assertNoText('This is the summary if passed');
-    $this->assertText('This is the default summary text');
-
-    // Test 33%
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 0,
-      ), t('Next'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node3->nid]" => 0,
-      ), t('Finish'));
-    $this->assertText('You failed bro');
-    $this->assertNoText('This is the summary if passed');
-    $this->assertText('This is the default summary text');
-  }
-
-  /**
-   * Test result CRUD operations.
-   *
-   * We have (at least) 3 different tables to clean up from on a Quiz result
-   * deletion - the quiz_result, the result answers, and the question type's
-   * answer storage. Let's ensure at least that happens.
-   */
-  function testQuizResultCrud() {
-    $this->drupalLogin($this->admin);
-
-    $question_node1 = $this->drupalCreateNode(array(
-      'type' => 'truefalse',
-      'correct_answer' => 1,
-    ));
-    $quiz_node = $this->linkQuestionToQuiz($question_node1);
-
-    // Submit an answer.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Finish'));
-
-    $quiz_result = entity_load('quiz_result', FALSE, array(
-      'nid' => $quiz_node->nid,
-      'vid' => $quiz_node->vid,
-      'uid' => $this->user->uid,
-    ));
-    $this->assertFalse(empty($quiz_result), 'Found quiz result');
-    $quiz_result = reset($quiz_result);
-
-    $quiz_result_answer = entity_load('quiz_result_answer', FALSE, array(
-      'result_id' => $quiz_result->result_id,
-      'question_nid' => $question_node1->nid,
-      'question_vid' => $question_node1->vid,
-    ));
-    $this->assertFalse(empty($quiz_result_answer), 'Found quiz result answer');
-    $quiz_result_answer = reset($quiz_result_answer);
-
-    // This isn't an entity yet.
-    $result = db_select('quiz_truefalse_user_answers', 'qtua')
-      ->fields('qtua')
-      ->condition('result_id', $quiz_result->result_id)
-      ->condition('question_nid', $question_node1->nid)
-      ->condition('question_vid', $question_node1->vid)
-      ->execute();
-    $quiz_truefalse_result_answer = $result->fetch();
-    $this->assertFalse(empty($quiz_truefalse_result_answer), 'Found quiz result question answer');
-
-    // Now check the deletion.
-    entity_delete('quiz_result', $quiz_result->result_id);
-    $quiz_result2 = entity_load('quiz_result', FALSE, array(
-      'nid' => $quiz_node->nid,
-      'vid' => $quiz_node->vid,
-      'uid' => $this->user->uid,
-      ), TRUE);
-    $this->assertTrue(empty($quiz_result2), 'Did not find quiz result');
-
-    $quiz_result_answer = entity_load('quiz_result_answer', FALSE, array(
-      'result_id' => $quiz_result->result_id,
-      'question_nid' => $question_node1->nid,
-      'question_vid' => $question_node1->vid,
-      ), TRUE);
-    $this->assertTrue(empty($quiz_result_answer), 'Did not find quiz result answer');
-
-    // This isn't an entity yet.
-    $result = db_select('quiz_truefalse_user_answers', 'qtua')
-      ->fields('qtua')
-      ->condition('result_id', $quiz_result->result_id)
-      ->condition('question_nid', $question_node1->nid)
-      ->condition('question_vid', $question_node1->vid)
-      ->execute();
-    $quiz_truefalse_result_answer = $result->fetch();
-    $this->assertTrue(empty($quiz_truefalse_result_answer), 'Did not find quiz result question answer');
-  }
-
-  /**
-   * Test access to results.
-   */
-  function testQuizResultAccess() {
-    $this->drupalLogin($this->admin);
-
-    $question_node1 = $this->drupalCreateNode(array(
-      'type' => 'truefalse',
-      'correct_answer' => 1,
-    ));
-    $quiz_node = $this->linkQuestionToQuiz($question_node1);
-
-    // Submit an answer.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/$quiz_node->nid/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Finish'));
-
-    $resultsUrl = $this->getUrl();
-
-    $this->drupalGet($resultsUrl);
-    $this->assertResponse(200, t('User can view own result'));
-    $this->drupalLogout();
-    $this->drupalGet($resultsUrl);
-    $this->assertNoResponse(200, t('Anonymous user cannot view result'));
-  }
-}
diff --git a/tests/QuizResultTestCase.test b/tests/QuizResultTestCase.test
new file mode 100644
index 0000000..dd24534
--- /dev/null
+++ b/tests/QuizResultTestCase.test
@@ -0,0 +1,111 @@
+<?php
+
+class QuizResultTestCase extends QuizTestCase {
+
+  protected $extraModules = array('truefalse');
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Quiz results'),
+        'description' => t('Unit test for Quiz results.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  /**
+   * Test the various result summaries and pass rate.
+   */
+  public function testPassRateSummary() {
+    // By default, the feedback is after the quiz.
+    $quiz = $this->drupalCreateQuiz(array(
+        'pass_rate'              => 75,
+        'summary_pass'           => 'This is the summary if passed',
+        'summary_pass_format'    => 'plain_text',
+        'summary_default'        => 'This is the default summary text',
+        'summary_default_format' => 'plain_text',
+        'resultoptions'          => array(
+            array(
+                'option_name'           => '90 and higher',
+                'option_summary'        => 'You got 90 or more on the quiz',
+                'option_summary_format' => 'filtered_html',
+                'option_start'          => 90,
+                'option_end'            => 100,
+            ),
+            array(
+                'option_name'           => '50 and higher',
+                'option_summary'        => 'You got between 50 and 89',
+                'option_summary_format' => 'filtered_html',
+                'option_start'          => 50,
+                'option_end'            => 89,
+            ),
+            array(
+                'option_name'           => 'Below 50',
+                'option_summary'        => 'You failed bro',
+                'option_summary_format' => 'filtered_html',
+                'option_start'          => 0,
+                'option_end'            => 49,
+            ),
+        ),
+    ));
+
+    // 3 questions.
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'feedback' => 'Q1Feedback'));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'feedback' => 'Q2Feedback'));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1, 'feedback' => 'Q3Feedback'));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    // Test 100%
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 1), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node3->nid]" => 1), t('Finish'));
+    $this->assertText('You got 90 or more on the quiz');
+    $this->assertText('This is the summary if passed');
+    $this->assertNoText('This is the default summary text');
+
+    // Test 66%
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 1), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node3->nid]" => 0), t('Finish'));
+    $this->assertText('You got between 50 and 89');
+    $this->assertNoText('This is the summary if passed');
+    $this->assertText('This is the default summary text');
+
+    // Test 33%
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 0), t('Next'));
+    $this->drupalPost(NULL, array("question[$question_node3->nid]" => 0), t('Finish'));
+    $this->assertText('You failed bro');
+    $this->assertNoText('This is the summary if passed');
+    $this->assertText('This is the default summary text');
+  }
+
+  /**
+   * Test access to results.
+   */
+  function testQuizResultAccess() {
+    $this->drupalLogin($this->admin);
+
+    $question_node = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $quiz = $this->linkQuestionToQuiz($question_node);
+
+    // Submit an answer.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/$quiz->qid/take");
+    $this->drupalPost(NULL, array("question[$question_node->nid]" => 1), t('Finish'));
+
+    $resultsUrl = $this->getUrl();
+
+    $this->drupalGet($resultsUrl);
+    $this->assertResponse(200, t('User can view own result'));
+    $this->drupalLogout();
+    $this->drupalGet($resultsUrl);
+    $this->assertNoResponse(200, t('Anonymous user cannot view result'));
+  }
+
+}
diff --git a/tests/QuizTaking.test b/tests/QuizTaking.test
deleted file mode 100644
index 9eb027c..0000000
--- a/tests/QuizTaking.test
+++ /dev/null
@@ -1,197 +0,0 @@
-<?php
-
-class QuizTakingTestCase extends QuizTestCase {
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz taking'),
-      'description' => t('Unit test for Quiz take behaviors.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'truefalse';
-    parent::setUp($modules);
-  }
-
-  /**
-   * Test the repeat until correct behavior. We do not have to test what type
-   * of feedback shows here, that is tested elsewhere.
-   */
-  public function testQuestionRepeatUntilCorrect() {
-    $this->drupalLogin($this->admin);
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'repeat_until_correct' => 1,
-      'review_options' => array('question' => array('answer_feedback' => 'answer_feedback')),
-    ));
-
-    $question_node = $this->drupalCreateNode(array(
-      'type' => 'truefalse',
-      'correct_answer' => 1,
-    ));
-    $this->linkQuestionToQuiz($question_node, $quiz_node);
-
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 0,
-      ), t('Finish'));
-    $this->assertText('The answer was incorrect. Please try again.');
-    // Check that we are still on the question.
-    $this->assertUrl("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node->nid]" => 1,
-      ), t('Finish'));
-    $this->assertNoText('The answer was incorrect. Please try again.');
-  }
-
-  /**
-   * Test the quiz resuming from database.
-   */
-  public function testQuizResuming() {
-    $this->drupalLogin($this->admin);
-    // Resuming is default behavior.
-    $quiz_node = $this->drupalCreateQuiz(array('allow_resume' => 1));
-
-    // 2 questions.
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-
-    // Answer a question. Ensure the question navigation restrictions are
-    // maintained.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(403);
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-
-    // Login again.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->assertText('Resuming');
-
-    // Assert 2nd question is accessible (indicating the answer to #1 was
-    // saved.)
-    $this->drupalGet("node/{$quiz_node->nid}/take/2");
-    $this->assertResponse(200);
-  }
-
-  /**
-   * Test the quiz availability tests.
-   */
-  public function testQuizAvailability() {
-    $future = REQUEST_TIME + 86400;
-    $past = REQUEST_TIME - 86400;
-
-    // Within range.
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'quiz_always' => 0,
-      'quiz_open' => $past,
-      'quiz_close' => $future,
-    ));
-    $this->drupalGet("node/{$quiz_node->nid}");
-    $this->assertNoText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
-
-    // Starts in the future.
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'quiz_always' => 0,
-      'quiz_open' => $future,
-      'quiz_close' => $future + 1,
-    ));
-    $this->drupalGet("node/{$quiz_node->nid}");
-    $this->assertText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
-
-    // Ends in the past.
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'quiz_always' => 0,
-      'quiz_open' => $past,
-      'quiz_close' => $past + 1,
-    ));
-    $this->drupalGet("node/{$quiz_node->nid}");
-    $this->assertText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
-
-    // Always available.
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'quiz_always' => 1,
-      'quiz_open' => $future,
-      'quiz_close' => $past,
-    ));
-    $this->drupalGet("node/{$quiz_node->nid}");
-    $this->assertNoText(t('This @quiz is closed.', array('@quiz' => QUIZ_NAME)));
-  }
-
-  /**
-   * Test the 'view quiz question outside of a quiz' permission.
-   */
-  function testViewQuestionsOutsideQuiz() {
-    $this->drupalLogin($this->admin);
-    // Resuming is default behavior.
-    $quiz_node = $this->drupalCreateQuiz();
-
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-
-    node_access_rebuild(FALSE);
-
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$question_node1->nid}");
-    $this->assertResponse(403);
-
-    $user_with_privs = $this->drupalCreateUser(array('view quiz question outside of a quiz', 'access quiz'));
-    $this->drupalLogin($user_with_privs);
-    $this->drupalGet("node/{$question_node1->nid}");
-    $this->assertResponse(200);
-  }
-
-  /**
-   * Test allow/restrict changing of answers.
-   */
-  function testChangeAnswer() {
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'review_options' => array('question' => array('score' => 'score')),
-    ));
-
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 0,
-      ), t('Next'));
-    $this->assertText('Score: 0 of 1');
-
-    // Go back and correct the answer.
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->assertText('Score: 1 of 1');
-
-    // Restrict change after answer. Turn auto revisioning off since we need to
-    // operate on the same result.
-    variable_set('quiz_auto_revisioning', 0);
-    $quiz_node->allow_change = 0;
-    node_save($quiz_node);
-
-    // Check that the answer cannot be changed.
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $disabled_field = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-question-2-1'));
-    $this->assertTrue($disabled_field, t('The answer cannot be changed.'));
-    $this->drupalPost(NULL, array(
-      ), t('Next'));
-    $this->assertText('Score: 1 of 1');
-  }
-
-}
diff --git a/tests/QuizTestCase.test b/tests/QuizTestCase.test
index 766a5fd..84d67a1 100644
--- a/tests/QuizTestCase.test
+++ b/tests/QuizTestCase.test
@@ -1,9 +1,6 @@
 <?php
 
-/**
- * @file
- * Unit tests for the quiz question Module.
- */
+use Drupal\quiz\Entity\QuizEntity;
 
 /**
  * Base test class for Quiz questions.
@@ -11,6 +8,8 @@
 abstract class QuizTestCase extends DrupalWebTestCase {
 
   protected $profile = 'testing';
+  protected $extraModules = array();
+  protected $extraUserPermissions = array();
 
   /**
    * Set up a question test case.
@@ -22,114 +21,102 @@ abstract class QuizTestCase extends DrupalWebTestCase {
    * @param array $user_permissions
    *   Array of user permissions to add.
    */
-  function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
+  public function setUp($modules = array(), $admin_permissions = array(), $user_permissions = array()) {
     $modules[] = 'quiz';
     $modules[] = 'quiz_question';
+    foreach ($this->extraModules as $module) {
+      if (!in_array($module, $modules)) {
+        $modules[] = $module;
+      }
+    }
     parent::setUp($modules);
 
-    // Add text formats.
-    $filtered_html_format = array(
-      'format' => 'filtered_html',
-      'name' => 'Filtered HTML',
-      'weight' => 0,
-      'filters' => array(
-        // URL filter.
-        'filter_url' => array(
-          'weight' => 0,
-          'status' => 1,
-        ),
-        // HTML filter.
-        'filter_html' => array(
-          'weight' => 1,
-          'status' => 1,
-        ),
-        // Line break filter.
-        'filter_autop' => array(
-          'weight' => 2,
-          'status' => 1,
-        ),
-        // HTML corrector filter.
-        'filter_htmlcorrector' => array(
-          'weight' => 10,
-          'status' => 1,
-        ),
-      ),
-    );
-    $filtered_html_format = (object) $filtered_html_format;
-    filter_format_save($filtered_html_format);
-
-    $full_html_format = array(
-      'format' => 'full_html',
-      'name' => 'Full HTML',
-      'weight' => 1,
-      'filters' => array(
-        // URL filter.
-        'filter_url' => array(
-          'weight' => 0,
-          'status' => 1,
-        ),
-        // Line break filter.
-        'filter_autop' => array(
-          'weight' => 1,
-          'status' => 1,
-        ),
-        // HTML corrector filter.
-        'filter_htmlcorrector' => array(
-          'weight' => 10,
-          'status' => 1,
-        ),
-      ),
-    );
-    $full_html_format = (object) $full_html_format;
-    filter_format_save($full_html_format);
-
-    // Enable default permissions for system roles.
-    $filtered_html_permission = filter_permission_name($filtered_html_format);
-    user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array($filtered_html_permission));
-    user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array($filtered_html_permission));
-
+    $this->setUpFilters();
 
     $admin_permissions[] = 'administer quiz configuration';
     $admin_permissions[] = 'create quiz content';
     $admin_permissions[] = 'edit any quiz content';
     $admin_permissions[] = 'edit question titles';
     $admin_permissions[] = 'access quiz';
+
     $user_permissions[] = 'access quiz';
+    foreach ($this->extraUserPermissions as $perm) {
+      if (!in_array($perm, $user_permissions)) {
+        $user_permissions[] = $perm;
+      }
+    }
 
     $this->admin = $this->drupalCreateUser(array_unique($admin_permissions));
     $this->user = $this->drupalCreateUser(array_unique($user_permissions));
   }
 
   /**
-   * Link a question to a new or provided quiz.
+   * Add text formats.
    */
-  public function linkQuestionToQuiz($question_node, $quiz_node = NULL) {
-    if (!$quiz_node) {
-      // Create a new quiz with defaults.
-      $quiz_node = $this->drupalCreateQuiz();
-    }
-
-    $question_node->quiz_nid = $quiz_node->nid;
-    $question_node->quiz_vid = $quiz_node->vid;
-    $qq = _quiz_question_get_instance($question_node);
-    $qq->saveRelationships();
-    return $quiz_node;
-  }
+  private function setUpFilters() {
+    $filtered_html_format = (object) array(
+          'format'  => 'filtered_html',
+          'name'    => 'Filtered HTML',
+          'weight'  => 0,
+          'filters' => array(
+              'filter_url'           => array('weight' => 0, 'status' => 1), // URL filter
+              'filter_html'          => array('weight' => 1, 'status' => 1), // HTML filter
+              'filter_autop'         => array('weight' => 2, 'status' => 1), // Line break filter
+              'filter_htmlcorrector' => array('weight' => 10, 'status' => 1), // HTML corrector filter
+          ),
+    );
+    filter_format_save($filtered_html_format);
 
-  public function drupalCreateQuiz($settings = array()) {
-    $settings += array(
-      'type' => 'quiz',
-      'review_options' => array('end' => drupal_map_assoc(array_keys(quiz_get_feedback_options()))),
+    $full_html_format = (object) array(
+          'format'  => 'full_html',
+          'name'    => 'Full HTML',
+          'weight'  => 1,
+          'filters' => array(
+              'filter_url'           => array('weight' => 0, 'status' => 1), // URL filter
+              'filter_autop'         => array('weight' => 1, 'status' => 1), // Line break filter
+              'filter_htmlcorrector' => array('weight' => 10, 'status' => 1), // HTML corrector filter
+          ),
     );
-    return $this->drupalCreateNode($settings);
+    filter_format_save($full_html_format);
+
+    // Enable default permissions for system roles.
+    $filtered_html_permission = filter_permission_name($filtered_html_format);
+    user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array($filtered_html_permission));
+    user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array($filtered_html_permission));
   }
 
-  public function testQuizNodeSettings() {
+  /**
+   * Link a question to a new or provided quiz.
+   *
+   * @param stdClass $question_node
+   * @param QuizEntity|null $quiz
+   * @return QuizEntity
+   */
+  public function linkQuestionToQuiz($question_node, $quiz = NULL) {
+    if (is_null($quiz)) {
+      $quiz = $this->drupalCreateQuiz();
+    }
+    $question_node->quiz_qid = $quiz->qid;
+    $question_node->quiz_vid = $quiz->vid;
+    _quiz_question_get_instance($question_node)->saveRelationships();
+    return $quiz;
+  }
 
+  /**
+   * @return QuizEntity
+   */
+  public function drupalCreateQuiz($settings = array()) {
+    $quiz = entity_create('quiz_entity', $settings + array(
+        'review_options' => array(
+            'end' => drupal_map_assoc(array_keys(quiz()->getQuizHelper()->getFeedbackHelper()->getOptions()))
+        ),
+    ));
+    entity_save('quiz_entity', $quiz);
+    return $quiz;
   }
 
   public function quizDeleteAllResults() {
-    db_query('DELETE FROM {quiz_node_results}');
+    db_query('DELETE FROM {quiz_results}');
   }
 
 }
diff --git a/tests/QuizTimer.test b/tests/QuizTimer.test
deleted file mode 100644
index 7cb2ac3..0000000
--- a/tests/QuizTimer.test
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-class QuizTimerTestCase extends QuizTestCase {
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Quiz timer'),
-      'description' => t('Unit test for Quiz and question timer behaviors.'),
-      'group' => t('Quiz'),
-    );
-  }
-
-  public function setUp($modules = array()) {
-    $modules[] = 'truefalse';
-    parent::setUp($modules);
-  }
-
-  /**
-   * Test quiz timer expiration.
-   */
-  function testQuizTimer() {
-    // Set up a quiz to show us feedback, 30 second expiration.
-    $quiz_node = $this->drupalCreateQuiz(array(
-      'review_options' => array('end' => array('score' => 'score')),
-      'time_limit' => 30,
-    ));
-
-    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node1, $quiz_node);
-    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node2, $quiz_node);
-    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
-    $this->linkQuestionToQuiz($question_node3, $quiz_node);
-
-    // Record 2 answers before expiration.
-    $this->drupalLogin($this->user);
-    $this->drupalGet("node/{$quiz_node->nid}/take");
-    $this->drupalGet("node/{$quiz_node->nid}/take/1");
-    $this->drupalPost(NULL, array(
-      "question[$question_node1->nid]" => 1,
-      ), t('Next'));
-    $this->assertNoText(t('The last answer was not submitted, as the time ran out.'));
-    $this->drupalPost(NULL, array(
-      "question[$question_node2->nid]" => 1,
-      ), t('Next'));
-    $this->assertNoText(t('The last answer was not submitted, as the time ran out.'));
-
-    // Set the quiz result to have started 31 seconds ago.
-    db_query('UPDATE {quiz_node_results} SET time_start = :time', array(':time' => REQUEST_TIME - 31));
-
-    // Submit the last question past the expiration.
-    $this->drupalPost(NULL, array(
-      "question[$question_node3->nid]" => 1,
-      ), t('Finish'));
-    $this->assertText(t('The last answer was not submitted, as the time ran out.'));
-    $this->assertText('You got 2 of 3 possible points.');
-  }
-
-}
diff --git a/tests/QuizTimerTestCase.test b/tests/QuizTimerTestCase.test
new file mode 100644
index 0000000..b43e7e5
--- /dev/null
+++ b/tests/QuizTimerTestCase.test
@@ -0,0 +1,50 @@
+<?php
+
+class QuizTimerTestCase extends QuizTestCase {
+
+  protected $extraModules = array('truefalse');
+
+  public static function getInfo() {
+    return array(
+        'name'        => t('Quiz timer'),
+        'description' => t('Unit test for Quiz and question timer behaviors.'),
+        'group'       => t('Quiz'),
+    );
+  }
+
+  /**
+   * Test quiz timer expiration.
+   */
+  function testQuizTimer() {
+    // Set up a quiz to show us feedback, 30 second expiration.
+    $quiz = $this->drupalCreateQuiz(array(
+        'review_options' => array('end' => array('score' => 'score')),
+        'time_limit'     => 30,
+    ));
+
+    $question_node1 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node1, $quiz);
+    $question_node2 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node2, $quiz);
+    $question_node3 = $this->drupalCreateNode(array('type' => 'truefalse', 'correct_answer' => 1));
+    $this->linkQuestionToQuiz($question_node3, $quiz);
+
+    // Record 2 answers before expiration.
+    $this->drupalLogin($this->user);
+    $this->drupalGet("quiz/{$quiz->qid}/take");
+    $this->drupalGet("quiz/{$quiz->qid}/take/1");
+    $this->drupalPost(NULL, array("question[$question_node1->nid]" => 1), t('Next'));
+    $this->assertNoText(t('The last answer was not submitted, as the time ran out.'));
+    $this->drupalPost(NULL, array("question[$question_node2->nid]" => 1), t('Next'));
+    $this->assertNoText(t('The last answer was not submitted, as the time ran out.'));
+
+    // Set the quiz result to have started 31 seconds ago.
+    db_query('UPDATE {quiz_results} SET time_start = :time', array(':time' => REQUEST_TIME - 31));
+
+    // Submit the last question past the expiration.
+    $this->drupalPost(NULL, array("question[$question_node3->nid]" => 1), t('Finish'));
+    $this->assertText(t('The last answer was not submitted, as the time ran out.'));
+    $this->assertText('You got 2 of 3 possible points.');
+  }
+
+}
diff --git a/theme/question_bg.png b/theme/question_bg.png
deleted file mode 100644
index 8276db312326cbe3133ef5461f055a1a91147825..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3789
zcmb`JS2)}a)5iZol+}9|Ey_mkeX)8CR;&`8m58WO6TS0b^|pdUFA-v8^<IM2g0M=o
zhzOo2S&8;}&%XEkJ-Fw0&A}YpGjq)~Ne@kQDM4%?001cU^|Z`ynR}~X(mS{DwP>;K
zEy#TIto;Fioc=!|01As)003nA1PXol(8D{x+uy_6hg%;C<@WLOc7O8B?Y4RuWr2WM
z>@lh0*G@GJ6C<+?z0Da(xy?0GA{meZU>-6WgG8RPB}TJ8T5WA2_R-Qr!nCx=R7Nuq
zie%6il5L*yw3s)E$)7GZ!rpi;bmG5FUbil)?be^5TBb;PNhxymq|Ia_DQYyiXurmc
z3=VH>i>XI}_<aBx(iT@9|0_NM;5=MK<pFOmNjE?cJV#Cn^colSN^m6HQ2)>@up@|!
zCFl)6Dj1SRG63qKX_}RQx(-1kDqp|?C?x_M$6cJafQO=hqhQ3hIUo{sUJynA*k<!E
z5|knVZhE&wEx<z=sGoY2tPQ{=00wu%K}Fz;C?KJ4<*o-bwE?|Y8uDg<lmd`2ON<i+
zh{6EJQ9iz4AhrNt&^fkJ`SqZYW*2<hse)z|48Njwlr5>KFNu|v7?%)MpZNiUoYNhr
zLUpnJkOEezC>iRnCldfrie$WXdvX<wrE0`t<<ihp4x&GLh;MkEowu%crs{mu0bnB_
zeCAq2yoD`NnK;txy8Qkjk%v8b=?i>{do#6qD^R+(Y<1%EpWbMfWnh+;cDA>_7z}FK
zI(@Qwb&cqE=(W0f_9tBB5`Xr!dy_v(>T#4V(fQZ@iBqErj;TztSm%{*>AII~<Tsa`
zbG(E4&KNkDVatrpH%+TB^+c+IH%T*7c<BD@jqB#R$OWO=x-8K2RE;1EDO&y5h48nb
zAPqdAcM}f)#~t2%pM^*XBRyg^rh;ycAXmDj_kl=v{Y+l~u-D=jgMDg&jF19=R%sNt
zNrU~gk42=Hn4|CRN+0>9qg<>e|6so+jV8!7lEcqYxIS7_D1Neu6YMDdS)Eg+*UTe6
z%ZEy=-|{1sst@&FC(@EWq0T5WB8^c}dMBRcL;~kzIA0nmb3xoWPoEwUT?#+<SR#WN
zuOU)aPtrUY#%-y`d93PpM=`-pryZ$03P?p?#&_yUW=A!fseYztg<`7Z*<y9d$){Zd
zBn#7-N{gobKCr2#%9ZU-*LhGcr>j9nBznKIkVp?clMovo(0s2TLYCz+{+_p<qHdV2
z=}|plN79+g)cYVJx+v{lGC86kO>QSH4L!IK+~kX1H8%&p4DB+RJQ-oES-&7}R<+>=
zfp*$IgDlQck_m7<5xxbwbb4``uteyfJU?9$R7wz&aYz6CTi7h!tj_FnbG|*1nj#rJ
zHomN}WmnH;62)v0?gyiB+o<h<ZRu_HZJuLW5XxEI_wBjGrjYrkrv|J$G&?do#BTXg
z8kTPg%~z|-V6?L7Hku7(X!Cbw3Dxv6FA5bWW0>>}D;mmk=GZ)#J@8N7eGh1-UCrS#
z$(y#C9pBUap7)*bG>C#So-t-55h27{#6rS?U=3n<mv2<bI+wya0+W!J+|S=Fpf2#X
zgoDxGmyF0ypd5o7fgE<QIIO0&vUaVO-15Y79F|^VZ}F;8$5Ps2tA^}zZk=T<>fycx
zy9J^~xjLa1QSA+bybZU$N+Px%Fd8t0zBMa-@37T5%uVjX+7c|n<4IW;{~}6uP~iRK
z(#t?_NB$LOM~Z4~O_?H8+CbW>#r!1O9YV5|?|WVxU_!PlRF!uUp1j6zB)loiUMXXQ
zk#w{3tj*2Kh1coTnbqm;cgZy#RG})nv^%UXqNp@R3<T{2brI><yQp1@C5zQ6P!*H0
zs?u4bSgmITds&B8P|x$K!~BwN`^XX_@2a(suHC;Juf(rlS6+Xs&U7i@#4Qy3#5f8p
zi^`C!ueS=P_z3Qo8T9dhI<&$m$0=_k7Kxcrc{8u`j}CR}lVQ6p{w=Rzui-ZW!~#45
zhQ>dP4-3tWF_prV%0|jYBa;)8<&z5qvPG(ri&DEqyG4sd>uruU!8Ri3YV=1p{pM|(
zO6x;(=kqulMe7Q<rOo3Ood#6H{e_N(jw;$Jj7gSBUC9Dspy?NS3|-U4+O+e97}tpQ
zLcelCI5Q*Kh8hPBv%0cgwZHo3N|l5%VTr1a(}pwL|AQZc(j%p{LTvFN<kiq=?w&XT
zK1DP2MS2TOKQ27@1Z7b)r;A`lVEkiF5>t{4qsPVa)WnL|hgV0twhaQ#d>?Ow&Yq!)
zT8jD)s&VfQ<qn@5Cak=kka;a!VlC}IJunLWYPK^2A%C4C;kjNPZVEMxLbUGkiMxm+
ztr`}a+A4x-@lHp~YQNR|UT>*wsmQNMtyw!v$oIC5wvC$(c$YZDtwSUDZd`9dZ)Sk1
z$om8c!lWpTDBC+1=b<lodeR_DIJe);O9vXMr5FuHjr8b-=$*a>9*@zV84W!6-Fz$(
z`-Cd)VfgojX%eSzPALQ76oPRHaaQq=G@nE`+l*wMY|zXK)PG&;uU4+sS`k9hRPmjt
zfh<_9R_v+DvV6NtJ7ieQ-Fjjkj)LnyGvrVd12@V&f7ZQzd2o`jiP|LJ3S-U}AARf-
zQikn6jjLj+5u+1P%2hW?G*QU@SAzyz&6Ilom_F!!8YrZnvme1LJ|j1i_yuYQ<}x|f
z0~?2C{zP`lhVTzbSBUIjVg+k5I*lleR*RHMm<j{Ay`)r?x+G&Vkr}?4wLHdIXv7c1
zH}}Btdbhad>!If<ZQ@)xBx9Z}8TEIqML}rI%I`26p+zuBGs4Zvy=OA%lVWaN$%Dy*
ziIz{e4Ef@t*Ea`r{d60d=!$qlD^X7Px32o9ip?s%Avn+$<J|}GkBIA4J|?5VUh}4P
z2UdBF;aviu#-363eV?NHS_wS&$t^)Uaj*7Q77req!o{uETb=&KquA9Llad=5aLjp(
z5nqH>f_K;xyg&66tYm+vsT;(&PPxyz?|oYnEwQD_t<C$ENA;X?Y54=?`lAmAXTP;^
z;(3!oxQ-Y!6!WG><zuH?)fjeC83KC?JB9s)$sNwFwmL1IKc6{d59|Cb^K%|&+ljwY
z8|%hd5?C6~yU)8_=wGzpA&p73d*`_qUXL40ynGKv2xkeeNcgDGi<r;zoU^Jh^Yn!W
z`?^#k_qwl9M?s>La+H?;6c2Cm`Dle~@8QYT@m!Qj%Ymt3^&2E}_X0T)oZ?S43y%wR
ziYy8*Y@Kb}O()c57JjUSUh3lRQ5VaoSce##z{Z{|??umLA&#d~Q8}nr;bMPQ9C797
zuw57I`crD}qjs8rjaluRlwIXcWvt!t$AIHz+z!3fN2|hjN)BE>0)D7mXU&<8bQE_K
zHe&2NF^8dDtzqu?J@yiL?CNP($S<awh4~Ui)zz+(P;dMY{%oCTHQZ0|3OnAh6eM`q
z&}rJ~aH@Wwu>@UBn1|S2uY5ULiFn!@RC+SAqKhYvi<!Dco~2Vm@1_@|kH=HS@0Td3
zDJpJ4{I2#ctDsY^QvxN-*X7sajm%8q;pg-Fi`bd`nX<g&ysRFtmlan(diPo1;fDK%
zxw^-Kr6ui8A6*>u1+4^e9=%zoHr;;79U*k%@#p=S#dOt07Hd}44H04f$)$_5DDL*(
z<aW_BHwJ*v`v4Fb0|0++Zn*~lFC+lqha&(e<pTh-cc#OT4ge6I>uYJif|q|5i~1iM
zu&9R|xekFIY0=OU@Rrl&+g$e~dEAXoPRwW2sD}@N^7k00R*VGHBQuWW{HM&aGjcah
z|17Wmp2Fkt6qOT@mqbED40k|gr2ii`BU&$s-faY{*m$D+d17gzRcc!0h9`DUWQ=pn
z2f9!01%mBk@ZCbmo>n>L<2!z>Hb}h)@!|Yy7SVCPHr#C@tDmW5#y@7UIjgu^>}l`V
zFmD54OW~jGN%eo;vC5S`@~vRf{Y|R?(j;CXZE^pFF1XbvlnLjwn5M^I5}zO?2#VmF
ze4E%`6DZ>q=gaZN#~EfhkFCC<=l9<@=SUy93P0+i=26(qHb42Z!je_Fr^?t^TN_AT
z=(hC6I}-D+b%eH=UA?P8jmY?{%mHTA24|1dpOcw7(OoArJ7+KuZg@f3$3j2i%b=Yw
zP}b=Az5{pgVPWy9t)-=#wVVT+3-LK0NVl}n)5S;`5qboHK-@YFGv7*M$U9cCHn_AN
zk@0x1N7Gmp!%TiuAtLpO!SYZ=u75;G3H91DUgY)9>hSK*=9ZQ?hmzf^vrM5&PcIXC
z<iTzi{@^dZ6T)!)^+UL?iT8zJ=5|Pg(4}xhCOF63OJ7PsuHh#RhZBNO#cfqh9|S%~
z&l`8^m_s^2;UY{i7t9RTA_u<ZEjQsxsKSwSvDjH86?M&3xVXWlLN_MVI=2FE-=2S>
zBAoZ|Tq(rO8r^ZT)Nr?nCRzk;h*F-)3MomTag}{hFOu9}2lw2O$Sk(b^fy?rvF_;(
zHf-5wvJ(>@u3KnFbWz6f@s$)hqiq_J%l}<leDTWIY0fK63ViE0#k_VdRfH+!+Sav)
zH*JeHi{DH^VAhmMZJReoHo08JI03b+v(YjNTeKM4o*e?iEpuQ7dzngn5Rk*?)mWOh
z9hl#Rx7$~OQ|_VU1HWZjiyNG75?xwaB53tZxeq!rD5HzMI*DvW*Vd+xgeXhOAKGc~
zcFD(wnpX!rpsQI}o^+qX+zxs#pmyKpn$&BhhA^&8k%OvbNhG<vs37q7dZ3mdHrssv
z?C&NS1>N3*{Hfg*TarfHz_4`HK1uJdst&rE&;4BTYZnKBVM?gyTq;^T8F~p+jQ8%{
zJ6PpqFd<WH*)G2L`&V;=f5v89;(Ajp&PKtHTWPoC&yj=>5uGrIATCl<o#%fKhGW`N
lnunx9T0H*^qr3<b;O9<9ffeJ}`0c#_^tDa28Z{lG{|{Kz3%39O

diff --git a/theme/quiz-report-form.tpl.php b/theme/quiz-report-form.tpl.php
index 6fee1ef..7b4f628 100644
--- a/theme/quiz-report-form.tpl.php
+++ b/theme/quiz-report-form.tpl.php
@@ -3,39 +3,40 @@
  * @file
  * Themes the question report
  *
- */
-/*
  * Available variables:
  * $form - FAPI array
  *
  * All questions are in form[x] where x is an integer.
  * Useful values:
- * $form[x]['question'] - the question as a FAPI array(usually a form field of type "markup")
- * $form[x]['score'] - the users score on the current question.(FAPI array usually of type "markup" or "textfield")
- * $form[x]['max_score'] - the max score for the current question.(FAPI array of type "value")
- * $form[x]['response'] - the users response, usually a FAPI array of type markup.
+ *  $form[x]['question'] - the question as a FAPI array(usually a form field of type "markup")
+ *  $form[x]['score'] - the users score on the current question.(FAPI array usually of type "markup" or "textfield")
+ *  $form[x]['max_score'] - the max score for the current question.(FAPI array of type "value")
+ *  $form[x]['response'] - the users response, usually a FAPI array of type markup.
  */
 ?>
 <h2><?php print t('Question Results'); ?></h2>
 <div class="quiz-report">
-  <?php
-  foreach ($form as $key => $sub_form):
-    if (!is_numeric($key) || isset($sub_form['#no_report']))
-      continue;
-    unset($form[$key]);
-    ?>
+  <?php foreach ($form as $key => $sub_form): ?>
+    <?php if (!is_numeric($key) || isset($sub_form['#no_report'])): ?>
+      <?php continue; ?>
+    <?php endif; ?>
+    <?php unset($form[$key]); ?>
+
     <div class="quiz-report-question dt">
       <?php print drupal_render($sub_form['score_display']); ?>
       <h3><strong><?php print t('Question') ?></strong></h3>
       <?php print drupal_render($sub_form['question']); ?>
     </div>
+
     <div class="quiz-report-response dd">
       <h3><strong><?php print t('Response') ?>:</strong></h3>
       <?php print drupal_render($sub_form['response']); ?>
     </div>
+
     <div class="quiz-report-question-feedback dd">
       <?php print drupal_render($sub_form['question_feedback']); ?>
     </div>
+
     <div class="quiz-report-score-feedback dd">
       <?php print drupal_render($sub_form['score']); ?>
       <?php print drupal_render($sub_form['answer_feedback']); ?>
@@ -43,4 +44,7 @@
     <hr/>
   <?php endforeach; ?>
 </div>
-<div class="quiz-score-submit"><?php print drupal_render_children($form); ?></div>
+
+<div class="quiz-score-submit">
+  <?php print drupal_render_children($form); ?>
+</div>
diff --git a/theme/quiz_confirm.js b/theme/quiz_confirm.js
deleted file mode 100644
index 2573b8d..0000000
--- a/theme/quiz_confirm.js
+++ /dev/null
@@ -1,13 +0,0 @@
-(function ($) {
-  Drupal.behaviors.quizAnswerConfirm = {
-    attach : function(context, settings) {
-      $('form.quiz-answer-confirm #edit-submit').once(function() {
-        var $form = $(this);
-        $form.submit(function(){
-          // Return false to avoid submitting if user aborts
-          return confirm($form.data('confirm-message'));
-        });
-      });
-    }
-  };
-})(jQuery);
diff --git a/theme/quiz_node_form.js b/theme/quiz_node_form.js
deleted file mode 100644
index fb11315..0000000
--- a/theme/quiz_node_form.js
+++ /dev/null
@@ -1,24 +0,0 @@
-(function($) {
-/**
- * @file
- * JS enabling one filter fieldset to controll filter formats for all textareas in alternatives.
- */
-Drupal.behaviors.quizFormBehavior = {
-  attach: function(context) {
-    $('.quiz-filter:first :radio').click(function(){
-    var myValue = $(this).val();
-      $('.quiz-filter:not(:first) :radio[value='+myValue+']').click();
-      $('.quiz-filter:not(:first) :radio[value='+myValue+']').change();
-    });
-    var defaultValue = $('.quiz-filter:first :radio[checked=1]').val();
-    $('.quiz-filter:not(:first):not(.quizFormBehavior-processed) :radio[value='+defaultValue+']').click().change().addClass('quizFormBehavior-processed');
-    $('.quiz-filter:not(:first)').hide().addClass('quizStayHidden');
-    var oldToggle = Drupal.toggleFieldset;
-    Drupal.toggleFieldset = function(context) {
-      oldToggle(context);
-      $('.quizStayHidden').hide();
-    };
-  }
-};
-
-})(jQuery);
diff --git a/theme/quiz_take.js b/theme/quiz_take.js
deleted file mode 100644
index 3548745..0000000
--- a/theme/quiz_take.js
+++ /dev/null
@@ -1,19 +0,0 @@
-(function ($) {
-  Drupal.behaviors.quizQuestionBrowserBehavior = {
-    attach: function(context, settings) {
-      //  $('div.checkbox div.form-item').hide();
-      var $cell = $('div.mark-doubtful');
-      var $checkbox = $(':checkbox', $cell);
-      var $switch = $('.toggle', $cell);
-      if ($checkbox.is(':checked')) {
-	$switch.toggleClass('off');
-      }
-      $switch.click(function() {
-	$checkbox.click();
-      });
-      $checkbox.click(function() {
-	$switch.toggleClass('off');
-      });
-    }
-  };
-})(jQuery);
