Parent Directory
|
Revision Log
|
Revision Graph
Appending destination = $_GET['q'] to url in quiz node form page for automatic redirection after enabling/disabling quiz addon.
| 1 | <?php |
| 2 | |
| 3 | // $Id: quiz.module,v 1.142 2009/08/13 16:48:17 sivaji Exp $ |
| 4 | |
| 5 | /** |
| 6 | * @file |
| 7 | * Quiz Module |
| 8 | * |
| 9 | * This module allows the creation of interactive quizzes for site visitors. |
| 10 | */ |
| 11 | |
| 12 | // This module is structured as follows: |
| 13 | // |
| 14 | // The main module file: |
| 15 | // * Defines and general includes are at the top. |
| 16 | // * Hook implementations come immediately after. |
| 17 | // * Public functions come next. |
| 18 | // * Private functions are at the bottom. |
| 19 | // |
| 20 | // Where possible, user pages are located in quiz.pages.inc, and admin pages |
| 21 | // are in quiz.admin.inc. Most utility functions have been left here, even if they |
| 22 | // are only used by a function in one of the other files. quiz_datetime.inc holds |
| 23 | // some additional date/time functions. |
| 24 | // |
| 25 | // Themes are in quiz.pages.inc unless they clearly only apply to admin screens. |
| 26 | // Then they are in quiz.admin.inc. |
| 27 | // |
| 28 | // Views support is included in includes/views/quiz.views.inc |
| 29 | define('QUIZ_VIEWS_DIR', drupal_get_path('module', 'quiz') . '/includes/views'); |
| 30 | |
| 31 | include(drupal_get_path('module', 'quiz') .'/quiz_datetime.inc'); |
| 32 | |
| 33 | /* |
| 34 | * Define question statuses... |
| 35 | */ |
| 36 | define('QUESTION_RANDOM', 0); |
| 37 | define('QUESTION_ALWAYS', 1); |
| 38 | define('QUESTION_NEVER', 2); |
| 39 | |
| 40 | /** |
| 41 | * Quiz name. |
| 42 | */ |
| 43 | define('QUIZ_NAME', _quiz_get_quiz_name()); |
| 44 | |
| 45 | /** |
| 46 | * Define feedback statuses. |
| 47 | */ |
| 48 | define('QUIZ_FEEDBACK_END', 0); |
| 49 | define('QUIZ_FEEDBACK_QUESTION', 1); |
| 50 | define('QUIZ_FEEDBACK_NEVER', 2); |
| 51 | |
| 52 | /** |
| 53 | * Quiz perms. |
| 54 | * |
| 55 | * TODO: Simply adding the new quiz config perm for now - refactor other perms |
| 56 | * to constants in the future. |
| 57 | */ |
| 58 | define('QUIZ_PERM_ADMIN_CONFIG', 'administer quiz configuration'); |
| 59 | |
| 60 | /** |
| 61 | * Implementation of hook_help(). |
| 62 | */ |
| 63 | function quiz_help($path, $arg) { |
| 64 | // This is moved on an experimental basis. |
| 65 | include_once drupal_get_path('module', 'quiz') . '/quiz.help.inc'; |
| 66 | return _quiz_help($path, $arg); |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * This module is Views 2.0 enabled. |
| 71 | * Implementation of hook_views_api(). |
| 72 | */ |
| 73 | function quiz_views_api() { |
| 74 | return array( |
| 75 | 'api' => 2, |
| 76 | 'path' => QUIZ_VIEWS_DIR, |
| 77 | ); |
| 78 | } |
| 79 | |
| 80 | /** |
| 81 | * Implementation of hook_perm(). |
| 82 | */ |
| 83 | function quiz_perm() { |
| 84 | return array(QUIZ_PERM_ADMIN_CONFIG, |
| 85 | // Administrating quizzes: |
| 86 | 'administer quiz', |
| 87 | // Managing quizzes: |
| 88 | 'access quiz', 'create quiz', 'edit own quiz', 'edit any quiz', 'delete any quiz', 'delete own quiz', |
| 89 | // Managing results: |
| 90 | 'view user results', 'view own results', |
| 91 | // Allow a quiz question to be viewed outside of a test. |
| 92 | 'view quiz question outside of a quiz', |
| 93 | // Allow someone to see the answers to a quiz question |
| 94 | 'view quiz question solutions' |
| 95 | ); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Implementation of hook_access(). |
| 100 | */ |
| 101 | function quiz_access($op, $node, $account) { |
| 102 | |
| 103 | // Admin can do all of this. |
| 104 | if (user_access('administer quiz', $account)) { |
| 105 | return TRUE; |
| 106 | } |
| 107 | |
| 108 | if (!user_access('access quiz')) { |
| 109 | // If you can't access, you get NOTHING! |
| 110 | // Otherwise, we allow further permission checking. |
| 111 | return FALSE; |
| 112 | } |
| 113 | |
| 114 | switch ($op) { |
| 115 | case 'create': |
| 116 | return user_access('create quiz', $account); |
| 117 | case 'update': |
| 118 | return (user_access('edit any quiz', $account) || (user_access('edit own quiz', $account) && $account->uid == $node->uid)); |
| 119 | case 'delete': |
| 120 | return (user_access('delete any quiz', $account) || (user_access('delete own quiz', $account) && $account->uid == $node->uid)); |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Implementation of hook_node_info(). |
| 126 | */ |
| 127 | function quiz_node_info() { |
| 128 | return array( |
| 129 | 'quiz' => array( |
| 130 | 'name' => t('@quiz', array("@quiz" => QUIZ_NAME)), |
| 131 | 'module' => 'quiz', |
| 132 | 'description' => 'Create interactive quizzes for site visitors', |
| 133 | ) |
| 134 | ); |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Implementation of hook_init(). |
| 139 | * |
| 140 | * Add quiz-specific styling. |
| 141 | */ |
| 142 | function quiz_init() { |
| 143 | // MPB FIXME: Probably don't want to add this to _every_ page. |
| 144 | drupal_add_css(drupal_get_path('module', 'quiz') .'/quiz.css', 'module', 'all'); |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Implementation of hook_menu(). |
| 149 | */ |
| 150 | function quiz_menu() { |
| 151 | |
| 152 | // ADMIN // |
| 153 | $items['admin/quiz'] = array( |
| 154 | 'title' => t('@quiz management', array('@quiz' => QUIZ_NAME)), |
| 155 | 'description' => t('View @quiz results, score tests, run reports.', array('@quiz' => QUIZ_NAME)), |
| 156 | 'page callback' => 'system_admin_menu_block_page', |
| 157 | 'access arguments' => array(QUIZ_PERM_ADMIN_CONFIG), |
| 158 | 'type' => MENU_NORMAL_ITEM, // MENU_CALLBACK, MENU_SUGGESTED_ITEM, MENU_LOCAL_TASK, MENU_DEFAULT_LOCAL_TASK |
| 159 | 'file' => 'system.admin.inc', |
| 160 | 'file path' => drupal_get_path('module', 'system'), |
| 161 | //'weight' => -1, |
| 162 | //'position' => 'right', |
| 163 | //'menu_name' => 'name of menu', |
| 164 | ); |
| 165 | |
| 166 | $items['admin/quiz/settings'] = array( |
| 167 | 'title' => t('@quiz configuration', array('@quiz' => QUIZ_NAME)), |
| 168 | 'description' => t('Configure @quiz options.', array('@quiz' => QUIZ_NAME)), |
| 169 | 'page callback' => 'drupal_get_form', |
| 170 | 'page arguments' => array('quiz_admin_settings'), |
| 171 | 'access arguments' => array(QUIZ_PERM_ADMIN_CONFIG), |
| 172 | 'type' => MENU_NORMAL_ITEM, // optional |
| 173 | 'file' => 'quiz.admin.inc', |
| 174 | ); |
| 175 | |
| 176 | $items['admin/quiz/reports'] = array( |
| 177 | 'title' => t('@quiz reports', array('@quiz' => QUIZ_NAME)), |
| 178 | 'description' => t('View @quiz reports.', array('@quiz' => QUIZ_NAME)), |
| 179 | 'page callback' => 'system_admin_menu_block_page', |
| 180 | 'access arguments' => array(QUIZ_PERM_ADMIN_CONFIG), |
| 181 | 'type' => MENU_NORMAL_ITEM, |
| 182 | 'file' => 'system.admin.inc', |
| 183 | 'file path' => drupal_get_path('module', 'system'), |
| 184 | ); |
| 185 | |
| 186 | $items['admin/quiz/reports/results'] = array( |
| 187 | 'title' => t('@quiz results', array('@quiz' => QUIZ_NAME)), |
| 188 | 'description' => 'View results.', |
| 189 | 'page callback' => 'quiz_admin_quizzes', |
| 190 | 'access arguments' => array(QUIZ_PERM_ADMIN_CONFIG), |
| 191 | 'type' => MENU_NORMAL_ITEM, |
| 192 | 'file' => 'quiz.admin.inc', |
| 193 | ); |
| 194 | |
| 195 | $items['admin/quiz/reports/%/results'] = array( |
| 196 | 'title' => t('View @quiz', array('@quiz' => QUIZ_NAME)), |
| 197 | 'description' => t('View results for the given quiz.'), |
| 198 | 'page callback' => 'quiz_admin_results', |
| 199 | 'page arguments' => array(3), |
| 200 | 'type' => MENU_NORMAL_ITEM, // MENU_CALLBACK, MENU_SUGGESTED_ITEM, MENU_LOCAL_TASK, MENU_DEFAULT_LOCAL_TASK |
| 201 | 'file' => 'quiz.admin.inc', |
| 202 | 'access arguments' => array(QUIZ_PERM_ADMIN_CONFIG), |
| 203 | ); |
| 204 | |
| 205 | $items['admin/quiz/%/view'] = array( |
| 206 | 'title' => t('View @quiz', array('@quiz' => QUIZ_NAME)), |
| 207 | 'page callback' => 'quiz_admin', |
| 208 | 'page arguments' => array(2), |
| 209 | 'access arguments' => array('administer quiz'), |
| 210 | 'type' => MENU_CALLBACK, |
| 211 | 'file' => 'quiz.admin.inc', |
| 212 | ); |
| 213 | |
| 214 | $items['admin/quiz/%/delete'] = array( |
| 215 | 'title' => t('Delete @quiz', array('@quiz' => QUIZ_NAME)), |
| 216 | 'page callback' => 'quiz_admin_result_delete', |
| 217 | //'page arguments' => array(2), |
| 218 | 'access arguments' => array('administer quiz'), |
| 219 | 'type' => MENU_CALLBACK, |
| 220 | 'file' => 'quiz.admin.inc', |
| 221 | ); |
| 222 | |
| 223 | // JSON callback |
| 224 | $items['admin/quiz/listquestions'] = array( |
| 225 | 'title' => t('List Quiz Questions'), |
| 226 | 'description' => t('Auto-completion question listing.'), |
| 227 | 'page callback' => 'quiz_admin_list_questions_ac', |
| 228 | 'page arguments' => array(''), |
| 229 | 'type' => MENU_CALLBACK, |
| 230 | //'access callback' => 'user_access', |
| 231 | 'access arguments' => array('create quiz'), |
| 232 | 'file' => 'quiz.admin.inc' |
| 233 | ); |
| 234 | // AHAH callback |
| 235 | $items['admin/quiz/newquestion'] = array( |
| 236 | 'title' => t('Add a Question to a Quiz'), |
| 237 | 'description' => t('AHAH Callback for adding a quiz question'), |
| 238 | 'page callback' => 'quiz_admin_add_question_ahah', |
| 239 | 'type' => MENU_CALLBACK, |
| 240 | 'access arguments' => array('create quiz'), |
| 241 | 'file' => 'quiz.admin.inc', |
| 242 | ); |
| 243 | |
| 244 | // Menu item for adding questions to quiz. |
| 245 | $items['node/%quiz_type_access/questions'] = array( |
| 246 | 'title' => t('Manage questions'), |
| 247 | 'page callback' => 'quiz_questions', |
| 248 | 'page arguments' => array(1), |
| 249 | 'access callback' => 'node_access', |
| 250 | 'access arguments' => array('update', 1), |
| 251 | 'type' => MENU_LOCAL_TASK, |
| 252 | 'file' => 'quiz.admin.inc', |
| 253 | ); |
| 254 | |
| 255 | $items['node/%quiz_type_access/admin'] = array( |
| 256 | 'title' => t('Quiz admin', array('@quiz' => QUIZ_NAME)), |
| 257 | 'page callback' => 'theme', |
| 258 | 'page arguments' => array('quiz_view', 1), |
| 259 | 'access arguments' => array('administer quiz'), |
| 260 | 'type' => MENU_LOCAL_TASK, |
| 261 | 'file' => 'quiz.admin.inc', |
| 262 | |
| 263 | ); |
| 264 | |
| 265 | // USER // |
| 266 | $items['user/%/myresults'] = array( |
| 267 | 'title' => t('My results'), |
| 268 | 'page callback' => 'quiz_get_user_results', |
| 269 | 'page arguments' => array(1), |
| 270 | 'access arguments' => array('view own results'), |
| 271 | 'type' => MENU_LOCAL_TASK, |
| 272 | 'file' => 'quiz.pages.inc', |
| 273 | ); |
| 274 | |
| 275 | $items['user/quiz/%/userresults'] = array( |
| 276 | 'title' => t('User results'), |
| 277 | 'page callback' => 'quiz_user_results', |
| 278 | 'page arguments' => array(2), |
| 279 | 'access arguments' => array('view own results'), |
| 280 | 'type' => MENU_CALLBACK, |
| 281 | 'file' => 'quiz.pages.inc', |
| 282 | ); |
| 283 | |
| 284 | return $items; |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * Implementation of hook_theme(). |
| 289 | */ |
| 290 | function quiz_theme() { |
| 291 | return array( |
| 292 | 'quiz_availability' => array( |
| 293 | 'arguments' => array('node' => NULL), |
| 294 | 'file' => 'quiz.pages.inc', |
| 295 | ), |
| 296 | 'quiz_view' => array( |
| 297 | 'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' => FALSE), |
| 298 | 'file' => 'quiz.pages.inc', |
| 299 | ), |
| 300 | 'quiz_get_user_results' => array( |
| 301 | 'arguments' => array('results' => NULL), |
| 302 | 'file' => 'quiz.pages.inc', |
| 303 | ), |
| 304 | 'quiz_question_table' => array( |
| 305 | 'arguments' => array('questions' => NULL, 'quiz_id' => NULL), |
| 306 | 'file' => 'quiz.pages.inc', |
| 307 | ), |
| 308 | 'quiz_filtered_questions' => array( |
| 309 | 'arguments' => array('form' => NULL), |
| 310 | 'file' => 'quiz.pages.inc', |
| 311 | ), |
| 312 | 'quiz_take_question' => array( |
| 313 | 'arguments' => array('quiz' => NULL, 'question_node' => NULL), |
| 314 | 'file' => 'quiz.pages.inc', |
| 315 | ), |
| 316 | 'quiz_take_summary' => array( |
| 317 | 'arguments' => array('quiz' => NULL, 'questions' => NULL, 'score' => 0, 'summary' => ''), |
| 318 | 'file' => 'quiz.pages.inc', |
| 319 | ), |
| 320 | 'quiz_admin' => array( |
| 321 | 'arguments' => array('results' => NULL), |
| 322 | 'file' => 'quiz.admin.inc', |
| 323 | ), |
| 324 | 'quiz_admin_summary' => array( |
| 325 | 'arguments' => array('quiz' => NULL, 'questions' => NULL, 'score' => NULL, 'summary' => NULL), |
| 326 | 'file' => 'quiz.admin.inc', |
| 327 | ), |
| 328 | 'quiz_user_summary' => array( |
| 329 | 'arguments' => array('quiz' => NULL, 'questions' => NULL, 'score' => NULL, 'summary' => NULL), |
| 330 | 'file' => 'quiz.pages.inc', |
| 331 | ), |
| 332 | 'quiz_feedback' => array( |
| 333 | 'arguments' => array('questions' => NULL, 'showpoints' => TRUE, 'showfeedback' => FALSE), |
| 334 | 'file' => 'quiz.pages.inc', |
| 335 | ), |
| 336 | 'quiz_single_question_feedback' => array( |
| 337 | 'arguments' => array('quiz' => NULL, 'report' => NULL), |
| 338 | 'file' => 'quiz.pages.inc', |
| 339 | ), |
| 340 | 'quiz_questions' => array( |
| 341 | 'arguments' => array('form' => NULL), |
| 342 | 'file' => 'quiz.pages.inc', |
| 343 | ), |
| 344 | 'quiz_progress' => array( |
| 345 | 'arguments' => array('question_number' => NULL, 'num_of_question' => NULL), |
| 346 | 'file' => 'quiz.pages.inc', |
| 347 | ), |
| 348 | 'quiz_question_table' => array( |
| 349 | 'arguments' => array('questions' => NULL, 'quiz_id' => NULL), |
| 350 | 'file' => 'quiz.pages.inc', |
| 351 | ), |
| 352 | 'quiz_no_feedback' => array( |
| 353 | 'file' => 'quiz.pages.inc', |
| 354 | 'arguments' => array(), |
| 355 | ), |
| 356 | 'quiz_admin_quizzes' => array( |
| 357 | 'file' => 'quiz.admin.inc', |
| 358 | 'arguments' => array('results' => NULL), |
| 359 | ), |
| 360 | 'quiz_single_question_node' => array( |
| 361 | 'file' => 'quiz.pages.inc', |
| 362 | 'arguments' => array('question_node' => NULL), |
| 363 | ), |
| 364 | 'question_selection_table' => array( |
| 365 | 'file' => 'quiz.admin.inc', |
| 366 | 'arguments' => array('form' => array()), |
| 367 | ), |
| 368 | 'quiz_score_correct' => array( |
| 369 | 'file' => 'quiz.pages.inc', |
| 370 | 'arguments' => array(), |
| 371 | ), |
| 372 | 'quiz_score_incorrect' => array( |
| 373 | 'file' => 'quiz.pages.inc', |
| 374 | 'arguments' => array(), |
| 375 | ), |
| 376 | ); |
| 377 | } |
| 378 | |
| 379 | /** |
| 380 | * Implementation of hook_form_alter(). |
| 381 | * |
| 382 | * Override settings in some existing forms. For example, we remove the |
| 383 | * preview button on a quiz. |
| 384 | */ |
| 385 | function quiz_form_alter(&$form, $form_state, $form_id) { |
| 386 | if ($form_id == 'quiz_node_form') { |
| 387 | // Remove preview button: |
| 388 | unset($form['buttons']['preview']); |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | /** |
| 393 | * Implementation of hook_insert(). |
| 394 | */ |
| 395 | function quiz_insert($node) { |
| 396 | quiz_translate_form_date($node, 'quiz_open'); |
| 397 | quiz_translate_form_date($node, 'quiz_close'); |
| 398 | |
| 399 | $tid = (isset($node->tid) ? $node->tid : 0); |
| 400 | |
| 401 | if (!isset($node->has_userpoints)) { |
| 402 | $node->has_userporints = 0; |
| 403 | } |
| 404 | |
| 405 | $sql = "INSERT INTO {quiz_node_properties} |
| 406 | (vid, nid, aid, number_of_random_questions, shuffle, |
| 407 | backwards_navigation, quiz_open, quiz_close, takes, time_limit, pass_rate, summary_pass, summary_default, quiz_always, feedback_time, tid, has_userpoints) |
| 408 | VALUES(%d, %d, '%s', %d, %d, %d, %d, %d, %d, %d, %d, '%s', '%s', %d, %d, %d, %d)"; |
| 409 | db_query($sql, $node->vid, $node->nid, $node->aid, $node->number_of_random_questions, $node->shuffle, $node->backwards_navigation, $node->quiz_open, $node->quiz_close, $node->takes, $node->time_limit, $node->pass_rate, $node->summary_pass, $node->summary_default, $node->quiz_always, $node->feedback_time, $node->has_userpoints, $tid); |
| 410 | _quiz_insert_resultoptions($node); |
| 411 | } |
| 412 | |
| 413 | /** |
| 414 | * Implementation of hook_update(). |
| 415 | */ |
| 416 | function quiz_update($node) { |
| 417 | // Quiz node vid (revision) was updated. |
| 418 | if ($node->revision) { |
| 419 | |
| 420 | // Insert a new row in the quiz_node_properties table. |
| 421 | quiz_insert($node); |
| 422 | |
| 423 | // Create new quiz-question relation entries in the quiz_node_relationship table. |
| 424 | quiz_update_quiz_question_relationship($node->old_vid, $node->vid, $node->nid); |
| 425 | } |
| 426 | |
| 427 | // Quiz node vid (revision) was not updated. |
| 428 | else { |
| 429 | // Update an existing row in the quiz_node_properties table. |
| 430 | quiz_translate_form_date($node, 'quiz_open'); |
| 431 | quiz_translate_form_date($node, 'quiz_close'); |
| 432 | $sql = "UPDATE {quiz_node_properties} |
| 433 | SET vid = %d, |
| 434 | aid='%s', |
| 435 | shuffle = %d, |
| 436 | backwards_navigation = %d, |
| 437 | quiz_open = %d, |
| 438 | quiz_close = %d, |
| 439 | takes = %d, |
| 440 | time_limit = '%d', |
| 441 | pass_rate = %d, |
| 442 | summary_pass = '%s', |
| 443 | summary_default = '%s', |
| 444 | quiz_always = %d, |
| 445 | feedback_time = %d, |
| 446 | number_of_random_questions = %d, |
| 447 | has_userpoints = %d |
| 448 | WHERE vid = %d |
| 449 | AND nid = %d"; |
| 450 | $resource = db_query($sql, $node->vid, $node->aid, $node->shuffle, $node->backwards_navigation, $node->quiz_open, $node->quiz_close, |
| 451 | $node->takes, $node->time_limit, $node->pass_rate, $node->summary_pass, $node->summary_default, $node->quiz_always, |
| 452 | $node->feedback_time, $node->number_of_random_questions, $node->has_userpoints, |
| 453 | $node->vid, $node->nid); |
| 454 | |
| 455 | // if (db_affected_rows($resource) == 0) { |
| 456 | // drupal_set_message('No quiz was found that could be modified.', 'status'); |
| 457 | // } |
| 458 | } |
| 459 | _quiz_update_resultoptions($node); |
| 460 | } |
| 461 | |
| 462 | /** |
| 463 | * Implementation of hook_delete(). |
| 464 | */ |
| 465 | function quiz_delete($node) { |
| 466 | // This first line should load all the vid's for the nid. |
| 467 | db_query('DELETE FROM {quiz_node_properties} WHERE vid = %d AND nid = %d', $node->vid, $node->nid); |
| 468 | db_query('DELETE FROM {quiz_node_relationship} WHERE parent_nid = %d', $node->nid); |
| 469 | db_query('DELETE FROM {quiz_node_results} WHERE vid = %d AND nid = %d', $node->vid, $node->nid); |
| 470 | db_query('DELETE FROM {quiz_node_result_options} WHERE vid = %d AND nid = %d', $node->vid, $node->nid); |
| 471 | } |
| 472 | |
| 473 | function _quiz_get_node_defaults() { |
| 474 | return array( |
| 475 | 'property_id' => NULL, |
| 476 | 'aid' => NULL, |
| 477 | 'number_of_random_questions' => 0, |
| 478 | 'pass_rate' => 75, |
| 479 | 'summary_pass' => '', |
| 480 | 'summary_default' => '', |
| 481 | 'shuffle' => 0, |
| 482 | 'backwards_navigation' => 0, |
| 483 | 'feedback_time' => 0, |
| 484 | 'quiz_open' => 0, |
| 485 | 'quiz_close' => 0, |
| 486 | 'takes' => 0, |
| 487 | 'time_limit' => 0, |
| 488 | 'quiz_always' => 0, |
| 489 | 'tid' => 0, |
| 490 | 'has_userpoints' => 0, |
| 491 | ); |
| 492 | } |
| 493 | |
| 494 | /** |
| 495 | * Implementation of hook_load(). |
| 496 | */ |
| 497 | function quiz_load($node) { |
| 498 | |
| 499 | $default_additions = _quiz_get_node_defaults(); |
| 500 | $fields = implode(', ', array_keys($default_additions)); |
| 501 | |
| 502 | $quiz_vid = $node->vid; |
| 503 | $sql = 'SELECT %s FROM {quiz_node_properties} WHERE vid = %d AND nid = %d ORDER BY property_id DESC'; |
| 504 | $fetched_additions = db_fetch_array(db_query($sql, $fields, $quiz_vid, $node->nid)); |
| 505 | |
| 506 | $additions = ($fetched_additions) ? (object)($fetched_additions += $default_additions) : NULL; |
| 507 | |
| 508 | /* |
| 509 | * This doesn't appear to have ever worked.... It just adds an empty item to $additions->status. |
| 510 | * Also, I can't find where this information is ever used, so there's probably no point in fixing it. |
| 511 | $results = db_query('SELECT nr.nid, qnr.question_status, qnr.child_nid |
| 512 | FROM {quiz_node_relationship} qnr |
| 513 | INNER JOIN {node_revisions} nr ON (qnr.parent_vid = nr.vid AND qnr.parent_nid = nr.nid) |
| 514 | WHERE qnr.parent_vid = %d AND qnr.parent_nid = %d', $quiz_vid, $node->nid); |
| 515 | |
| 516 | while ($question = db_fetch_object($results)) { |
| 517 | $additions->question_status[$question->child_nid] = $question->status; |
| 518 | $additions->status[$question->child_nid] = $question->status; |
| 519 | |
| 520 | }*/ |
| 521 | |
| 522 | $result_options = db_query('SELECT * FROM {quiz_node_result_options} WHERE nid = %d AND vid= %d', $node->nid, $node->vid); |
| 523 | while ($option = db_fetch_array($result_options)) { |
| 524 | $additions->resultoptions[$option['option_id']] = $option; |
| 525 | } |
| 526 | |
| 527 | return $additions; |
| 528 | } |
| 529 | |
| 530 | /** |
| 531 | * Implementation of hook_view(). |
| 532 | */ |
| 533 | function quiz_view($node, $teaser = FALSE, $page = FALSE) { |
| 534 | drupal_alter('quiz_view', $node, $teaser, $page); |
| 535 | if (!$teaser && $page) { |
| 536 | //load the questions in the view page |
| 537 | //$node->content['body']['#value'] = quiz_take_quiz($node); |
| 538 | $node->content = quiz_take_quiz($node); |
| 539 | } |
| 540 | else { |
| 541 | $node = node_prepare($node, $teaser); |
| 542 | } |
| 543 | return $node; |
| 544 | } |
| 545 | |
| 546 | // QUIZ FORM |
| 547 | |
| 548 | /** |
| 549 | * Implementation of hook_form(). |
| 550 | * |
| 551 | * This is an admin form used to build a new quiz. It is called as part of the node edit form. |
| 552 | */ |
| 553 | function quiz_form(&$node) { |
| 554 | $form = array(); |
| 555 | $form['title'] = array( |
| 556 | '#type' => 'textfield', |
| 557 | '#title' => t('Title'), |
| 558 | '#default_value' => $node->title, |
| 559 | '#description' => t('The name of the @quiz.', array('@quiz' => QUIZ_NAME)), |
| 560 | '#required' => TRUE, |
| 561 | ); |
| 562 | |
| 563 | $form['body_field']['body'] = array( |
| 564 | '#type' => 'textarea', |
| 565 | '#title' => t('Description'), |
| 566 | '#default_value' => $node->body, |
| 567 | '#description' => t('A description of what the @quiz entails', array('@quiz' => QUIZ_NAME)), |
| 568 | '#required' => FALSE, |
| 569 | ); |
| 570 | $form['body_field']['format'] = filter_form($node->format); |
| 571 | |
| 572 | $form['shuffle'] = array( |
| 573 | '#type' => 'checkbox', |
| 574 | '#title' => t('Shuffle questions'), |
| 575 | '#default_value' => (isset($node->shuffle) ? $node->shuffle : 1), |
| 576 | '#description' => t('Whether to shuffle/randomize the questions on the @quiz', array('@quiz' => QUIZ_NAME)), |
| 577 | ); |
| 578 | |
| 579 | /* |
| 580 | * Added the action as a dropdown for selection with specific quizzes |
| 581 | * This allows you to choose a defined action from the actions module for use when |
| 582 | * a user completes the quiz. |
| 583 | */ |
| 584 | $form['aid'] = array( |
| 585 | '#title' => t('Assign Action'), |
| 586 | '#description' => t('Select an action to be preformed after a user has completed this @quiz.', array('@quiz' => QUIZ_NAME)), |
| 587 | '#type' => 'select', |
| 588 | /* |
| 589 | * An idea here would be to add a system conf variable into the quiz_action_options() function that |
| 590 | * could filter the type of actions you could display on your quizzes. For Example: you create |
| 591 | * a custom module that defines some actions that you only want a user to choose when creating |
| 592 | * a quiz and selecting an action from the dropdown. You setup your actions with type 'quiz' and |
| 593 | * then add in that variable into the function and it will automatically filter and show only |
| 594 | * those specific actions. @note: In doing this you loose your default "Choose an Action" |
| 595 | * option. Review actions and the quiz_action_options() function for further explaination. |
| 596 | */ |
| 597 | '#options' => quiz_action_options(variable_get('quiz_action_type', 'all')), |
| 598 | '#default_value' => MD5($node->aid), |
| 599 | ); |
| 600 | |
| 601 | $form['backwards_navigation'] = array( |
| 602 | '#type' => 'checkbox', |
| 603 | '#title' => t('Backwards navigation'), |
| 604 | '#default_value' => $node->backwards_navigation, |
| 605 | '#description' => t('Whether to allow user to go back and revisit their answers'), |
| 606 | ); |
| 607 | |
| 608 | $form['feedback_time'] = array( |
| 609 | '#title' => t('Feedback Time'), |
| 610 | '#type' => 'radios', |
| 611 | '#default_value' => (isset($node->feedback_time) ? $node->feedback_time : QUIZ_FEEDBACK_END), |
| 612 | '#options' => _quiz_get_feedback_options(), |
| 613 | '#description' => t('Indicates at what point feedback for each question will be given to the user'), |
| 614 | ); |
| 615 | |
| 616 | // Set up the availability options. |
| 617 | $form['quiz_availability'] = array( |
| 618 | '#type' => 'fieldset', |
| 619 | '#title' => t('Availability options'), |
| 620 | '#collapsed' => FALSE, |
| 621 | '#collapsible' => TRUE, |
| 622 | ); |
| 623 | $form['quiz_availability']['quiz_always'] = array( |
| 624 | '#type' => 'checkbox', |
| 625 | '#title' => t('Always Available'), |
| 626 | '#default_value' => $node->quiz_always, |
| 627 | '#description' => t('Click this option to ignore the open and close dates.'), |
| 628 | ); |
| 629 | $form['quiz_availability']['quiz_open'] = array( |
| 630 | '#type' => 'date', |
| 631 | '#title' => t('Open Date'), |
| 632 | '#default_value' => _quiz_form_prepare_date($node->quiz_open), |
| 633 | '#description' => t('The date this @quiz will become available.', array('@quiz' => QUIZ_NAME)), |
| 634 | ); |
| 635 | $form['quiz_availability']['quiz_close'] = array( |
| 636 | '#type' => 'date', |
| 637 | '#title' => t('Close Date'), |
| 638 | '#default_value' => _quiz_form_prepare_date($node->quiz_close, variable_get('quiz_default_close', 30)), |
| 639 | '#description' => t('The date this @quiz will cease to be available.', array('@quiz' => QUIZ_NAME)), |
| 640 | ); |
| 641 | |
| 642 | $options = array(t('Unlimited')); |
| 643 | for ($i = 1; $i < 10; $i++) { |
| 644 | $options[$i] = $i; |
| 645 | } |
| 646 | $form['takes'] = array( |
| 647 | '#type' => 'select', |
| 648 | '#title' => t('Number of takes'), |
| 649 | '#default_value' => $node->takes, |
| 650 | '#options' => $options, |
| 651 | '#description' => t('The number of times a user is allowed to take the @quiz', array('@quiz' => QUIZ_NAME)), |
| 652 | ); |
| 653 | |
| 654 | $form['addons'] = array( |
| 655 | '#type' => 'fieldset', |
| 656 | '#title' => t('Quiz Addons Properties'), |
| 657 | '#description' => t('Configure Quiz !url and their Properties', array('!url' => l(t('Addons'), 'admin/quiz/settings', array('query' => array('destination' => $_GET['q']))))), |
| 658 | '#collapsible' => TRUE, |
| 659 | '#collapsed' => FALSE, |
| 660 | ); |
| 661 | |
| 662 | |
| 663 | if (function_exists('jquery_countdown_add') && variable_get('quiz_has_timer', 0)) { |
| 664 | $form['addons']['time_limit'] = array( |
| 665 | '#type' => 'textfield', |
| 666 | '#title' => t(' Time Limit'), |
| 667 | '#default_value' => isset($node->time_limit) ? $node->time_limit : 0, |
| 668 | '#description' => t('Set the maximum allowed time in seconds for this @quiz. Use 0 for no limit.', array('@quiz' => QUIZ_NAME)), |
| 669 | ); |
| 670 | } |
| 671 | else { |
| 672 | $form['addons']['time_limit'] = array( |
| 673 | '#type' => 'value', |
| 674 | '#value' => 0, |
| 675 | ); |
| 676 | } |
| 677 | |
| 678 | |
| 679 | if (module_exists('userpoints') && variable_get('quiz_has_userpoints', 0)) { |
| 680 | $form['addons']['has_userpoints'] = array( |
| 681 | '#type' => 'checkbox', |
| 682 | '#default_value' => (isset($node->has_userpoints) ? $node->has_userpoints : 1), |
| 683 | '#title' => t('Enable UserPoints Module Integration'), |
| 684 | '#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)), |
| 685 | ); |
| 686 | } |
| 687 | |
| 688 | // Quiz summary options. |
| 689 | $form['summaryoptions'] = array( |
| 690 | '#type' => 'fieldset', |
| 691 | '#title' => t('@quiz Summary Options', array('@quiz' => QUIZ_NAME)), |
| 692 | '#collapsible' => TRUE, |
| 693 | '#collapsed' => FALSE, |
| 694 | ); |
| 695 | // If pass/fail option is checked, present the form elements. |
| 696 | if (variable_get('quiz_use_passfail', 1)) { |
| 697 | // New nodes get the default. |
| 698 | if (empty($node->nid)) { |
| 699 | $node->pass_rate = variable_get('quiz_default_pass_rate', 75); |
| 700 | } |
| 701 | $form['summaryoptions']['pass_rate'] = array( |
| 702 | '#type' => 'textfield', |
| 703 | '#title' => t('Pass rate for @quiz (%)', array('@quiz' => QUIZ_NAME)), |
| 704 | '#default_value' => $node->pass_rate, |
| 705 | '#description' => t('Pass rate for the @quiz as a percentage score. (For personality quiz enter 0, and use result options.)', array('@quiz' => QUIZ_NAME)), |
| 706 | '#required' => FALSE, |
| 707 | ); |
| 708 | $form['summaryoptions']['summary_pass'] = array( |
| 709 | '#type' => 'textarea', |
| 710 | '#title' => t('Summary text if passed'), |
| 711 | '#default_value' => $node->summary_pass, |
| 712 | '#cols' => 60, |
| 713 | '#description' => t("Summary for when the user gets enough correct answers to pass the @quiz. Leave blank if you don't want to give different summary text if they passed or if you are not using the 'percent to pass' option above. If you don't use the 'Percentage needed to pass' field above, this text will not be used.", array('@quiz' => QUIZ_NAME)), |
| 714 | ); |
| 715 | } |
| 716 | // If the pass/fail option is unchecked, use the default and hide it. |
| 717 | else { |
| 718 | $form['summaryoptions']['pass_rate'] = array( |
| 719 | '#type' => 'hidden', |
| 720 | '#value' => variable_get('quiz_default_pass_rate', 75), |
| 721 | '#required' => FALSE, |
| 722 | ); |
| 723 | } |
| 724 | $form['summaryoptions']['summary_default'] = array( |
| 725 | '#type' => 'textarea', |
| 726 | '#title' => t('Default summary text'), |
| 727 | '#default_value' => $node->summary_default, |
| 728 | '#cols' => 60, |
| 729 | '#description' => t("Default summary. Leave blank if you don't want to give a summary."), |
| 730 | ); |
| 731 | |
| 732 | $num_rand = (isset($node->number_of_random_questions)) ? $node->number_of_random_questions : 0; |
| 733 | $form['number_of_random_questions'] = array( |
| 734 | '#type' => 'value', |
| 735 | '#value' => $num_rand, |
| 736 | ); |
| 737 | |
| 738 | $form['resultoptions'] = array( |
| 739 | '#type' => 'fieldset', |
| 740 | '#title' => t('!quiz Results', array('!quiz' => QUIZ_NAME)), |
| 741 | '#collapsible' => TRUE, |
| 742 | '#collapsed' => TRUE, |
| 743 | '#tree' => TRUE, |
| 744 | ); |
| 745 | |
| 746 | $options = !empty($node->resultoptions) ? $node->resultoptions : array(); |
| 747 | $num_options = max(3, (!empty($options)) ? count($options) : variable_get('quiz_max_result_options', 5)); |
| 748 | |
| 749 | for ($i=0; $i < $num_options; $i++) { |
| 750 | $option = (count($options) > 0) ? array_shift($options) : NULL; // grab each option in the array |
| 751 | $form['resultoptions'][$i] = array( |
| 752 | '#type' => 'fieldset', |
| 753 | '#title' => t('Result Option ') . ($i + 1), |
| 754 | '#collapsible' => TRUE, |
| 755 | '#collapsed' => FALSE, |
| 756 | ); |
| 757 | $form['resultoptions'][$i]['option_name'] = array( |
| 758 | '#type' => 'textfield', |
| 759 | '#title' => t('The name of the result'), |
| 760 | '#description' => t('Not displayed on personality !quiz.', array('!quiz' => QUIZ_NAME)), |
| 761 | '#default_value' => $option['option_name'], |
| 762 | '#maxlength' => 40, |
| 763 | '#size' => 40, |
| 764 | ); |
| 765 | $form['resultoptions'][$i]['option_start'] = array( |
| 766 | '#type' => 'textfield', |
| 767 | '#title' => t('Percentage Start Range'), |
| 768 | '#description' => t('Show this result for scored quizzes in this range (0-100). Leave blank for personality quizzes.'), |
| 769 | '#default_value' => $option['option_start'], |
| 770 | '#size' => 5, |
| 771 | ); |
| 772 | $form['resultoptions'][$i]['option_end'] = array( |
| 773 | '#type' => 'textfield', |
| 774 | '#title' => t('Percentage End Range'), |
| 775 | '#description' => t('Show this result for scored quizzes in this range (0-100). Leave blank for personality quizzes.'), |
| 776 | '#default_value' => $option['option_end'], |
| 777 | '#size' => 5, |
| 778 | ); |
| 779 | $form['resultoptions'][$i]['option_summary'] = array( |
| 780 | '#type' => 'textarea', |
| 781 | '#title' => t('Display text for the result'), |
| 782 | '#default_value' => $option['option_summary'], |
| 783 | '#description' => t('Result summary. This is the summary that is displayed when the user falls in this result set determined by his/her responses.'), |
| 784 | ); |
| 785 | |
| 786 | if ($option['option_id']) { |
| 787 | $form['resultoptions'][$i]['option_id'] = array( |
| 788 | '#type' => 'hidden', |
| 789 | '#value' => $option['option_id'], |
| 790 | ); |
| 791 | } |
| 792 | } |
| 793 | return $form; |
| 794 | } |
| 795 | |
| 796 | /** |
| 797 | * Implementation of hook_validate(). |
| 798 | */ |
| 799 | function quiz_validate($node) { |
| 800 | if (!$node->nid && empty($_POST)) return; |
| 801 | |
| 802 | 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'])) { |
| 803 | form_set_error('quiz_close', t('Please make sure the close date is after the open date.')); |
| 804 | } |
| 805 | if (!is_numeric($node->pass_rate)) { |
| 806 | form_set_error('pass_rate', t('The pass rate value must be a number between 0% and 100%.')); |
| 807 | } |
| 808 | if ($node->pass_rate > 100) { |
| 809 | form_set_error('pass_rate', t('The pass rate value must not be more than 100%.')); |
| 810 | } |
| 811 | if ($node->pass_rate < 0) { |
| 812 | form_set_error('pass_rate', t('The pass rate value must not be less than 0%.')); |
| 813 | } |
| 814 | |
| 815 | if (isset($node->time_limit)) { |
| 816 | if ($node->time_limit < 0 || !is_numeric($node->time_limit)) { |
| 817 | form_set_error('time_limit', t('Time limit must be a non negative interger ')); |
| 818 | } |
| 819 | } |
| 820 | |
| 821 | $taken_values = array(); |
| 822 | $num_options =0; |
| 823 | foreach ($node->resultoptions as $option) { |
| 824 | if (!empty($option['option_name'])) { |
| 825 | $num_options++; |
| 826 | if (empty($option['option_summary'])) { |
| 827 | form_set_error('option_summary', t('Option has no summary text.')); |
| 828 | } |
| 829 | if ($node->pass_rate && (isset($option['option_start']) || isset($option['option_end']))) { |
| 830 | |
| 831 | // Check for a number between 0-100. |
| 832 | foreach (array('option_start' => 'start', 'option_end' => 'end') as $bound => $bound_text) { |
| 833 | if (!is_numeric($option[$bound])) { |
| 834 | form_set_error($bound, t('The range %start value must be a number between 0% and 100%.', array('%start' => $bound_text))); |
| 835 | } |
| 836 | if ($option[$bound] < 0) { |
| 837 | form_set_error($bound, t('The range %start value must not be less than 0%.', array('%start' => $bound_text))); |
| 838 | } |
| 839 | if ($option[$bound] > 100) { |
| 840 | form_set_error($bound, t('The range %start value must not be more than 100%.', array('%start' => $bound_text))); |
| 841 | } |
| 842 | } |
| 843 | |
| 844 | // Check that range end >= start. |
| 845 | if ($option['option_start'] > $option['option_end']) { |
| 846 | form_set_error('option_start', t('The start must be less than the end of the range.')); |
| 847 | } |
| 848 | |
| 849 | // Check that range doesn't collide with any other range. |
| 850 | $option_range = range($option['option_start'], $option['option_end']); |
| 851 | if ($intersect = array_intersect($taken_values, $option_range)) { |
| 852 | form_set_error('option_start', t('The ranges must not overlap each other. (%intersect)', array('%intersect' => implode(',', $intersect)))); |
| 853 | } |
| 854 | else { |
| 855 | $taken_values = array_merge($taken_values, $option_range); |
| 856 | } |
| 857 | } |
| 858 | } |
| 859 | elseif (!empty($option['option_summary'])) { |
| 860 | form_set_error('option_summary', t('Option has a summary, but no name.')); |
| 861 | } |
| 862 | } |
| 863 | if ($node->pass_rate == 0 && !$num_options) { |
| 864 | form_set_error('pass_rate', t('Unscored quiz, but no result options defined.')); |
| 865 | } |
| 866 | } |
| 867 | /** |
| 868 | * Implementation of hook_nodeapi() |
| 869 | */ |
| 870 | function quiz_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { |
| 871 | // We need to filter on node type to prevent this from overriding any other node |
| 872 | if ($node->type == 'quiz') { |
| 873 | switch ($op) { |
| 874 | case 'presave': |
| 875 | /* |
| 876 | * convert the action id to the actual id from the MD5 hash |
| 877 | * Why the actions module does this I do not know? Maybe to prevent invalid values put |
| 878 | * into the options value="" field. |
| 879 | */ |
| 880 | $node->aid = actions_function_lookup($node->aid); |
| 881 | break; |
| 882 | case 'prepare': |
| 883 | // Meet E_STRICT on new nodes. |
| 884 | $defaults = _quiz_get_node_defaults(); |
| 885 | if (!isset($node->nid)) { |
| 886 | //drupal_set_message('Building defaults'); |
| 887 | foreach ($defaults as $key => $value) { |
| 888 | if (!isset($node->$key)) { |
| 889 | $node->$key = $value; |
| 890 | } |
| 891 | } |
| 892 | } |
| 893 | } |
| 894 | } |
| 895 | /* If we want to pre-process question nodes before they get rendered, here's how to do it: |
| 896 | if ($op == 'view' && in_array($node->type, array_keys(_quiz_get_question_types())) && $a3) { |
| 897 | drupal_set_message("Do global munging of node content here."); |
| 898 | //var_dump($node->content); |
| 899 | } |
| 900 | if ($op == 'alter' && in_array($node->type, array_keys(_quiz_get_question_types())) && $a3) { |
| 901 | drupal_set_message("Do alter of node content here."); |
| 902 | //var_dump($node->content); |
| 903 | } |
| 904 | */ |
| 905 | } |
| 906 | |
| 907 | // END HOOKS |
| 908 | |
| 909 | /** |
| 910 | * Load a quiz node and validate it. |
| 911 | * |
| 912 | * @param $arg |
| 913 | * The Node ID |
| 914 | * @return |
| 915 | * A quiz node object or FALSE if a load failed. |
| 916 | */ |
| 917 | function quiz_type_access_load($arg) { |
| 918 | // Simple verification/load of the node. |
| 919 | return (($node = node_load($arg)) && $node->type == 'quiz') ? $node : FALSE; |
| 920 | } |
| 921 | |
| 922 | /** |
| 923 | * Finds out the number of questions for the quiz. |
| 924 | * |
| 925 | * Good example of usage could be to calculate the % of score. |
| 926 | * |
| 927 | * @param $nid |
| 928 | * Quiz ID |
| 929 | * @return integer |
| 930 | * Returns the number of quiz questions. |
| 931 | */ |
| 932 | function quiz_get_number_of_questions($vid, $nid) { |
| 933 | // PostgreSQL cannot handle addition of count() columns plus int columns, so we have to do this as two queries: |
| 934 | // $sql = 'SELECT COUNT(*) + (SELECT number_of_random_questions FROM {quiz_node_properties} WHERE vid = %d AND nid = %d) |
| 935 | // FROM {quiz_node_relationship} qnr |
| 936 | // WHERE qnr.parent_vid = %d |
| 937 | // AND qnr.parent_nid = %d |
| 938 | // AND question_status = %d'; |
| 939 | // return db_result(db_query($sql, $vid, $nid, $vid, $nid, QUESTION_ALWAYS)); |
| 940 | $sql = 'SELECT COUNT(*) FROM {quiz_node_relationship} qnr WHERE qnr.parent_vid = %d AND question_status = %d'; |
| 941 | $always_count = db_result(db_query($sql, $vid, QUESTION_ALWAYS)); |
| 942 | $rand_count = db_result(db_query('SELECT number_of_random_questions FROM {quiz_node_properties} WHERE vid = %d', $vid)); |
| 943 | return $always_count + (int)$rand_count; |
| 944 | } |
| 945 | |
| 946 | /** |
| 947 | * Finds out the pass rate for the quiz. |
| 948 | * |
| 949 | * @param $nid |
| 950 | * The quiz ID. |
| 951 | * @return integer |
| 952 | * Returns the passing percentage of the quiz. |
| 953 | */ |
| 954 | function quiz_get_pass_rate($nid, $vid) { |
| 955 | return db_result(db_query('SELECT pass_rate FROM {quiz_node_properties} WHERE nid = %d AND vid = %d', $nid, $vid)); |
| 956 | } |
| 957 | |
| 958 | /** |
| 959 | * Updates quiz-question relation entries in the quiz_node_relationship table. |
| 960 | * |
| 961 | * @access public |
| 962 | * @param integer $old_quiz_vid |
| 963 | * The quiz vid prior to a new revision. |
| 964 | * @param integer $new_quiz_vid |
| 965 | * The quiz vid of the latest revision. |
| 966 | */ |
| 967 | function quiz_update_quiz_question_relationship($old_quiz_vid, $new_quiz_vid, $quiz_nid) { |
| 968 | $sql = "INSERT INTO {quiz_node_relationship} (parent_nid, parent_vid, child_nid, child_vid, question_status) |
| 969 | SELECT src.parent_nid, %d, src.child_nid, src.child_vid, src.question_status |
| 970 | FROM {quiz_node_relationship} AS src |
| 971 | WHERE src.parent_vid = %d AND src.parent_nid = %d AND src.question_status != %d"; |
| 972 | db_query($sql, $new_quiz_vid, $old_quiz_vid, $quiz_nid, QUESTION_NEVER); |
| 973 | } |
| 974 | |
| 975 | |
| 976 | /** |
| 977 | * Handles quiz taking. |
| 978 | * |
| 979 | * This gets executed when the main quiz node is first loaded. |
| 980 | * |
| 981 | * @param $quiz |
| 982 | * The quiz node. |
| 983 | * |
| 984 | * @return |
| 985 | * HTML output for page. |
| 986 | */ |
| 987 | function quiz_take_quiz($quiz) { |
| 988 | global $user; |
| 989 | $allow_skipping = TRUE; |
| 990 | |
| 991 | // If no access, fail. |
| 992 | if (!user_access('access quiz')) { |
| 993 | drupal_access_denied(); |
| 994 | return; |
| 995 | } |
| 996 | if (!isset($quiz)) { |
| 997 | drupal_not_found(); |
| 998 | return; |
| 999 | } |
| 1000 | |
| 1001 | // If anonymous user and no unique hash, refresh with a unique string to prevent caching. |
| 1002 | if (!$quiz->name && arg(4) == NULL) { |
| 1003 | drupal_goto('node/'. $quiz->nid .'/quiz/start/'. md5(mt_rand() . time())); |
| 1004 | } |
| 1005 | |
| 1006 | if (!isset($_SESSION['quiz_'. $quiz->nid]['quiz_questions'])) { |
| 1007 | $rid = _quiz_active_result_id($user->uid, $quiz->nid, $quiz->vid); |
| 1008 | |
| 1009 | // Are we resuming an in-progress quiz? |
| 1010 | if ($rid > 0) { |
| 1011 | _quiz_resume_existing_quiz($quiz, $user->uid, $rid); |
| 1012 | } |
| 1013 | // First time running through quiz. |
| 1014 | elseif ($rid = quiz_start_actions($quiz)) { |
| 1015 | // Create question list. |
| 1016 | $questions = quiz_build_question_list($quiz); |
| 1017 | |
| 1018 | if ($questions === FALSE) { |
| 1019 | drupal_set_message(t('Not enough random questions were found. Please !add_more_questions before trying to take this @quiz.', |
| 1020 | array('@quiz' => QUIZ_NAME, '!add_more_questions' => l(t('add more questions'), 'node/'. arg(1) .'/questions'))), 'error'); |
| 1021 | return array('body' => array('#value' => '')); |
| 1022 | } |
| 1023 | |
| 1024 | if (count($questions) == 0) { |
| 1025 | drupal_set_message(t('No questions were found. Please !assign_questions before trying to take this @quiz.', |
| 1026 | array('@quiz' => QUIZ_NAME, '!assign_questions' => l(t('assign questions'), 'node/'. arg(1) .'/questions'))), 'error'); |
| 1027 | return array('body' => array('#value' => '')); |
| 1028 | } |
| 1029 | |
| 1030 | // Initialize session variables. |
| 1031 | $_SESSION['quiz_'. $quiz->nid]['quiz_questions'] = $questions; |
| 1032 | $_SESSION['quiz_'. $quiz->nid]['result_id'] = $rid; |
| 1033 | $_SESSION['quiz_'. $quiz->nid]['question_number'] = 0; |
| 1034 | $_SESSION['quiz_'. $quiz->nid]['question_start_time'] = time(); |
| 1035 | $_SESSION['quiz_'. $quiz->nid]['question_duration'] = $quiz->time_limit; |
| 1036 | } |
| 1037 | |
| 1038 | else { |
| 1039 | return array('body' => array('#value' => '')); |
| 1040 | } |
| 1041 | } |
| 1042 | |
| 1043 | if (!isset($_POST['op'])) { |
| 1044 | // Starting new quiz... Do we need to show instructions here? |
| 1045 | } |
| 1046 | // Navigate backwards |
| 1047 | elseif ($_POST['op'] == t('Back')) { |
| 1048 | unset($_POST['tries']); |
| 1049 | |
| 1050 | // We maintain two lists -- previous questions and upcomming questions. |
| 1051 | // When we go backward, we pop one from the previous and prepend it to |
| 1052 | // the upcomming. |
| 1053 | // TODO: This can be maintained more efficiently with a single array of |
| 1054 | // all questions and then a pointer to the current question. That makes |
| 1055 | // rewinding much easier. |
| 1056 | $quiz_id = 'quiz_' . $quiz->nid; |
| 1057 | $last_q = array_pop($_SESSION[$quiz_id]['previous_quiz_questions']); |
| 1058 | array_unshift($_SESSION[$quiz_id]['quiz_questions'], $last_q); |
| 1059 | } |
| 1060 | // Check for answer submission. |
| 1061 | elseif ($_POST['op'] == t('Submit') || $_POST['op'] == t('Next')) { |
| 1062 | if (!isset($_POST['tries'])) { |
| 1063 | // Moving skip logic here... |
| 1064 | |
| 1065 | if ($allow_skipping) { |
| 1066 | // Advance the question. |
| 1067 | $_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions'][] = $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][0]; |
| 1068 | |
| 1069 | // Load the last asked question. |
| 1070 | $former_question_array = array_shift($_SESSION['quiz_'. $quiz->nid]['quiz_questions']); |
| 1071 | $former_question = node_load(array('nid' => $former_question_array['nid'])); |
| 1072 | |
| 1073 | // Call hook_skip_question(). |
| 1074 | $module = quiz_module_for_type($former_question->type); |
| 1075 | $result = module_invoke($module, 'skip_question', $former_question, $_SESSION['quiz_'. $quiz->nid]['result_id']); |
| 1076 | |
| 1077 | // Report that the question was skipped: |
| 1078 | //quiz_store_question_result($former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_'. $quiz->nid]['result_id'], $result); |
| 1079 | quiz_store_question_result($result, array('set_msg' => TRUE)); |
| 1080 | } |
| 1081 | else { |
| 1082 | drupal_set_message(t('You must select an answer before you can progress to the next question!'), 'error'); |
| 1083 | } |
| 1084 | } |
| 1085 | else { |
| 1086 | //unset($_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions']); |
| 1087 | |
| 1088 | // Previous quiz questions: Questions that have been asked already. We save a record of all of them |
| 1089 | // so that a user can navigate backward all the way to the beginning of the quiz. |
| 1090 | $_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions'][] = $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][0]; |
| 1091 | |
| 1092 | $former_question_array = array_shift($_SESSION['quiz_'. $quiz->nid]['quiz_questions']); |
| 1093 | $former_question = node_load(array('nid' => $former_question_array['nid'])); |
| 1094 | |
| 1095 | // Call hook_evaluate_question(). |
| 1096 | $types = _quiz_get_question_types(); |
| 1097 | $module = $types[$former_question->type]['module']; |
| 1098 | //drupal_set_message($module . ' evaluate_question called'); |
| 1099 | $result = module_invoke($module, 'evaluate_question', $former_question, $_SESSION['quiz_'. $quiz->nid]['result_id']); |
| 1100 | |
| 1101 | //$result stdClass Object ( [score] => 0 [nid] => 3 [vid] => 3 [rid] => 27 [is_correct] => [is_evaluated] => 1 [is_skipped] => ) |
| 1102 | quiz_store_question_result($result, array('set_msg' => TRUE)); |
| 1103 | |
| 1104 | // Stash feedback in the session, since the $_POST gets cleared. |
| 1105 | if ($quiz->feedback_time == QUIZ_FEEDBACK_QUESTION) { |
| 1106 | // Invoke hook_get_report(). |
| 1107 | //$report = module_invoke($former_question->type, 'get_report', $former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_'. $quiz->nid]['result_id']); |
| 1108 | $report = module_invoke($module, 'get_report', $former_question_array['nid'], $former_question_array['vid'], $_SESSION['quiz_'. $quiz->nid]['result_id']); |
| 1109 | $_SESSION['quiz_'. $quiz->nid]['feedback'] = rawurlencode(quiz_get_feedback($quiz, $report)); |
| 1110 | } |
| 1111 | // If anonymous user, refresh url with unique hash to prevent caching. |
| 1112 | if (!$user->uid) { |
| 1113 | //drupal_goto('node/'. $quiz->nid .'/quiz/start/'. md5(mt_rand() . time())); #460550 |
| 1114 | drupal_goto('node/' . $quiz->nid, array('quizkey' => md5(mt_rand() . time()))); |
| 1115 | } |
| 1116 | } |
| 1117 | } |
| 1118 | // Check for a skip. |
| 1119 | // XXX: Deprecated. Causing too many UI issues. |
| 1120 | elseif ($_POST['op'] == t('Skip') && $allow_skipping) { |
| 1121 | |
| 1122 | // Advance the question. |
| 1123 | $_SESSION['quiz_'. $quiz->nid]['previous_quiz_questions'][] = $_SESSION['quiz_'. $quiz->nid]['quiz_questions'][0]; |
| 1124 | |
| 1125 | // Load the last asked question. |
| 1126 | $former_question_array = array_shift($_SESSION['quiz_'. $quiz->nid]['quiz_questions']); |
| 1127 | $former_question = node_load(array('nid' => $former_question_array['nid'])); |
| 1128 | |
| 1129 | // Call hook_skip_question(). |
| 1130 | $module< |