Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
page_online_quiz.inc
1 <?php
18 require_once SQ_PACKAGES_PATH.'/cms/page_templates/page_asset_listing/page_asset_listing.inc';
19 require_once SQ_LIB_PATH.'/html_form/html_form.inc';
20 
21 
22 define('SQ_ONLINE_QUIZ_SESSION_VAR', 'SQ_ONLINE_QUIZ');
23 define('SQ_ONLINE_QUIZ_FLAG_COMPLETE', 'is_complete');
24 
25 
69 {
70 
71 
76  var $_bodycopies;
77 
78 
83  var $_error_stack = Array(
84  'errors' => Array(),
85  'warnings' => Array(),
86  );
87 
88 
97  function __construct($assetid=0)
98  {
99  parent::__construct($assetid);
100 
101  $this->_bodycopies = Array(
102  'page_contents' => translate('cms_page_contents'),
103  'no_results' => translate('cms_page_contents_no_results'),
104  'thank_you' => translate('online_quiz_bodycopy_thank_you'),
105  'already_taken' => translate('online_quiz_bodycopy_already_taken'),
106  'user_details' => translate('online_quiz_bodycopy_user_details'),
107  );
108 
109  }//end constructor
110 
111 
119  function _getAllowedLinks()
120  {
121  $page_links = parent::_getAllowedLinks();
122 
123  $page_links[SQ_LINK_TYPE_1]['folder'] = Array('card' => 'M');
124  $page_links[SQ_LINK_TYPE_1]['online_quiz_question'] = Array('card' => 'M');
125  $page_links[SQ_LINK_TYPE_1]['online_quiz_question_group'] = Array('card' => 'M');
126  $page_links[SQ_LINK_TYPE_2]['bodycopy'] = Array('card' => 'M');
127  $page_links[SQ_LINK_TYPE_3]['bodycopy'] = Array('card' => 'M');
128  $page_links[SQ_LINK_TYPE_2]['simple_form'] = Array('card' => 'M');
129  $page_links[SQ_LINK_TYPE_3]['simple_form'] = Array('card' => 'M');
130  $page_links[SQ_LINK_NOTICE]['user'] = Array('card' => 'M');
131 
132  return $page_links;
133 
134  }//end _getAllowedLinks()
135 
136 
137 //-- CREATION --//
138 
139 
150  function _createAdditional(&$link)
151  {
152  if (!parent::_createAdditional($link)) return FALSE;
153 
154  // create the user_details form (to go with the matching bodycopy)
155  if (!$this->createForm()) return FALSE;
156  if (!$this->_createSubmissionsFolder()) return FALSE;
157 
158  // create the type
159  return $this->createTypeFormat('online_quiz_question');
160 
161  }//end _createAdditional()
162 
163 
172  function _createBodycopies()
173  {
174  $bodycopies = $this->_bodycopies;
175 
176  // leave out no_results for this - it's created later
177  unset($bodycopies['no_results']);
178 
179  // only create the other bodycopies if they're needed at the moment
180  if ($this->attr('after_submit_action') != 'thank_you') {
181  unset($bodycopies['thank_you']);
182  }
183  if ($this->attr('can_retake')) {
184  unset($bodycopies['already_taken']);
185  }
186 
187 
188  foreach ($bodycopies as $bodycopy_code => $bodycopy_name) {
189  if (!$this->createBodycopy($bodycopy_code)) {
190  return FALSE;
191  }
192  }
193 
194  if (!$this->createNoResultsBodycopy(TRUE)) {
195  return FALSE;
196  }
197 
198  return TRUE;
199 
200  }//end _createBodycopies()
201 
202 
210  {
211  $existing = $this->getSubmissionsFolder();
212  if (!empty($existing)) return TRUE;
213  $GLOBALS['SQ_SYSTEM']->am->includeAsset('folder');
214  $folder = new Folder();
215  $folder->setAttrValue('name', translate('submissions'));
216  $folder_link = Array('asset' => &$this, 'link_type' => SQ_LINK_TYPE_2, 'value' => 'submissions_folder', 'is_dependant' => 1, 'is_exclusive' => 1);
217  if (!$folder->create($folder_link)) {
218  trigger_localised_error('CMS0070', E_USER_WARNING);
219  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
220  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
221  return FALSE;
222  }
223 
224  // Deny public read access to the submissions folder by default
225  if (!$GLOBALS['SQ_SYSTEM']->am->acquireLock($folder->id, 'permissions')) {
226  return FALSE;
227  }
228  if (!$GLOBALS['SQ_SYSTEM']->am->setPermission($folder->id, $GLOBALS['SQ_SYSTEM']->am->getSystemAssetid('public_user'), SQ_PERMISSION_READ, 0, TRUE, TRUE)) {
229  return FALSE;
230  }
231  $GLOBALS['SQ_SYSTEM']->am->releaseLock($folder->id, 'permissions');
232  return TRUE;
233 
234  }//end _createSubmissionsFolder()
235 
236 
244  function _getCacheKey()
245  {
246  return '';
247 
248  }//end _getCacheKey()
249 
250 
257  function &createSubmission()
258  {
259  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
260  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
261  $GLOBALS['SQ_SYSTEM']->am->includeAsset('online_quiz_submission');
262 
263  // create the form submission asset
264  $submission = new Online_Quiz_Submission();
265  $submissions_folder = $this->getSubmissionsFolder();
266  if (is_null($submissions_folder)) {
267  trigger_localised_error('CMS0069', E_USER_WARNING);
268  $null = NULL;
269  return $null;
270  }
271  $copy_link = Array('asset' => &$submissions_folder, 'link_type' => SQ_LINK_TYPE_3, 'is_dependant' => 0, 'is_exclusive' => 0);
272 
273  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
274  if (!$submission->create($copy_link)) {
275  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
276  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
277  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
278  return FALSE;
279  }
280  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
281  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
282  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
283 
284  return $submission;
285 
286  }//end createSubmission()
287 
288 
296  {
297  $res = NULL;
298  $link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'folder', TRUE, 'submissions_folder');
299  if (!empty($link)) {
300  $res = $GLOBALS['SQ_SYSTEM']->am->getAsset($link['minorid'], 'folder');
301  }
302  return $res;
303 
304  }//end getSubmissionsFolder()
305 
306 
315  function _getDefaultBodycopyContent($bodycopy_code)
316  {
317  $content = '';
318 
319  switch ($bodycopy_code) {
320  // regular bodycopies
321  case 'page_contents':
322  $content = '%asset_listing% %submit_button%';
323  break;
324  case 'no_results':
325  return '<p>'.translate('online_quiz_bodycopy_no_result_default').'</p>';
326  break;
327  case 'thank_you':
328  $content = '<p>'.translate('online_quiz_bodycopy_thank_you_default').'</p>'."\n";
329  $content .= '%results_table_full%';
330  break;
331  case 'user_details':
332  $content = '<p>'.translate('online_quiz_bodycopy_user_details_default').'</p>'."\n";
333  $content .= '%form_contents%';
334  break;
335  case 'already_taken':
336  $content = '<p>'.translate('online_quiz_bodycopy_already_taken_default').'</p>';
337  break;
338 
339  // type formats
340  case 'online_quiz_question':
341  $content .= '<div>%question_text%</div>'."\n";
342  $content .= '%response_form%';
343  break;
344  }
345 
346  if (!$content) {
347  $content = parent::_getDefaultBodycopyContent($bodycopy_code);
348  }
349 
350  return $content;
351 
352  }//end _getDefaultBodycopyContent()
353 
354 
355 //-- Bodycopy-handling --//
356 
357 
367  function createBodycopy($bodycopy_code, $enable_on_create=TRUE)
368  {
369  $bodycopy_link = $this->getBodycopyLink($bodycopy_code, SQ_LINK_TYPE_2 | SQ_LINK_TYPE_3);
370 
371  // we already have a bodycopy link: bail out
372  if ($bodycopy_link) {
373  return FALSE;
374  } else {
375  $GLOBALS['SQ_SYSTEM']->am->includeAsset('bodycopy');
376 
377  $link_type = ($enable_on_create ? SQ_LINK_TYPE_2 : SQ_LINK_TYPE_3);
378 
379  $asset = new Bodycopy();
380  $copy_link = Array(
381  'asset' => &$this,
382  'value' => $bodycopy_code,
383  'link_type' => $link_type,
384  'is_dependant' => 1,
385  'is_exclusive' => 1,
386  );
387 
388  $asset->setAttrValue('name', $this->_bodycopies[$bodycopy_code]);
389  $args = Array(
390  'content' => $this->_getDefaultBodycopyContent($bodycopy_code),
391  );
392 
393  if (!$asset->create($copy_link, $args)) return FALSE;
394  unset($asset);
395  }
396 
397  return TRUE;
398 
399  }//end createBodycopy()
400 
401 
413  function &getBodycopy($bodycopy_code, $only_if_enabled=TRUE)
414  {
415  $asset = NULL;
416 
417  if ($only_if_enabled) {
418  $link_types = SQ_LINK_TYPE_2;
419  } else {
420  $link_types = SQ_LINK_TYPE_2 | SQ_LINK_TYPE_3;
421  }
422 
423  $bodycopy_link = $this->getBodycopyLink($bodycopy_code, $link_types);
424  if ($bodycopy_link) {
425  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($bodycopy_link['minorid'], 'bodycopy');
426  }
427 
428  return $asset;
429 
430  }//end getBodycopy()
431 
432 
442  function getBodycopyLink($bodycopy_code, $link_type)
443  {
444  $tmp_bodycopy = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, $link_type, 'bodycopy', FALSE, 'major', $bodycopy_code);
445  return reset($tmp_bodycopy);
446 
447  }//end getBodycopyLink()
448 
449 
459  function setBodycopy($bodycopy_code, $enabled)
460  {
461  $bodycopy_link = $this->getBodycopyLink($bodycopy_code, SQ_LINK_TYPE_2 | SQ_LINK_TYPE_3);
462 
463  if (!$bodycopy_link) {
464  // no bodycopy yet? If we're trying to set to disabled, then we don't
465  // need to do anything - if not then we need to create it
466  if ($enabled) {
467  if (!$this->createBodycopy($bodycopy_code)) {
468  return FALSE;
469  }
470  }
471  } else {
472  // set link type to either TYPE_2 if enabled or TYPE_3 if disabled
473  $new_link_type = ($enabled) ? SQ_LINK_TYPE_2 : SQ_LINK_TYPE_3;
474  if ($bodycopy_link['link_type'] != $new_link_type) {
475  $GLOBALS['SQ_SYSTEM']->am->updateLink($bodycopy_link['linkid'], $new_link_type);
476  }
477  }
478 
479  return TRUE;
480 
481  }//end setBodycopy()
482 
483 
490  function printBody()
491  {
492  if ($this->getFlag(SQ_ONLINE_QUIZ_FLAG_COMPLETE) && !$this->attr('can_retake')) {
493  $this->printBodycopy('already_taken');
494  } else if ($this->attr('interactive_mode')){
495  $this->printContents();
496  } else {
497  parent::printBody();
498  }
499 
500  return TRUE;
501 
502  }//end printBody()
503 
504 
513  function printBodycopy($bodycopy_code)
514  {
515  $bodycopy_printed = FALSE;
516  $bodycopy = $this->getBodycopy($bodycopy_code);
517 
518  if (!is_null($bodycopy)) {
519  // keywords
520  require_once SQ_FUDGE_PATH.'/general/text.inc';
521  $keywords = $bodycopy->getKeywords();
522  $replacements = Array();
523  foreach ($keywords as $keyword) {
524  $replacements[$keyword] = $this->getKeywordReplacement($keyword);
525  }
526  $bodycopy->setKeywordReplacements($replacements);
527 
528  $bodycopy->printBody();
529  $bodycopy_printed = TRUE;
530  }
531 
532  return $bodycopy_printed;
533 
534  }//end printBodycopy()
535 
536 
537 //-- Form-manipulation --//
538 
539 
546  function &getForm()
547  {
548  $form = NULL;
549 
550  $form_link = $this->getFormLink(SQ_LINK_TYPE_2);
551  if (!empty($form_link['minorid'])) {
552  $form = $GLOBALS['SQ_SYSTEM']->am->getAsset($form_link['minorid'], 'simple_form');
553  }
554 
555  return $form;
556 
557  }//end getForm()
558 
559 
568  function getFormLink($link_type)
569  {
570  $tmp_form = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, $link_type, 'simple_form', FALSE, 'major', 'user_details');
571  return reset($tmp_form);
572 
573  }//end getFormLink()
574 
575 
584  function createForm($link_type=SQ_LINK_TYPE_2)
585  {
586  $form_link = $this->getFormLink(SQ_LINK_TYPE_2 | SQ_LINK_TYPE_3);
587  if (empty($form_link)) {
588  $GLOBALS['SQ_SYSTEM']->am->includeAsset('simple_form');
589  $form = new Simple_Form();
590  $form->setAttrValue('name', translate('online_quiz_form_user_details'));
591  $link = Array(
592  'asset' => &$this,
593  'link_type' => $link_type,
594  'value' => 'user_details',
595  'is_dependant' => 1,
596  'is_exclusive' => 1,
597  );
598  if (!$form->create($link)) return FALSE;
599  }
600 
601  return TRUE;
602 
603  }//end createForm()
604 
605 
614  function setForm($enabled)
615  {
616  $form_link = $this->getFormLink(SQ_LINK_TYPE_2 | SQ_LINK_TYPE_3);
617 
618  if (empty($form_link)) {
619  // no bodycopy yet? If we're trying to set to disabled, then we don't
620  // need to do anything - if not then we need to create it
621  if (($enabled) && (!$this->createForm())) {
622  return FALSE;
623  }
624  } else {
625  // set link type to either TYPE_2 if enabled or TYPE_3 if disabled
626  $new_link_type = ($enabled) ? SQ_LINK_TYPE_2 : SQ_LINK_TYPE_3;
627  if ($form_link['link_type'] != $new_link_type) {
628  return $GLOBALS['SQ_SYSTEM']->am->updateLink($form_link['linkid'], $new_link_type);
629  }
630  }
631 
632  return TRUE;
633 
634  }//end setForm()
635 
636 
644  {
645  $form = $this->getForm();
646  if (!is_null($form)) {
647  ob_start();
648  $form->printBody(TRUE, TRUE, $_SERVER['PHP_SELF'].'?'.$this->id.'_formid='.$form->id);
649  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($form);
650  return ob_get_clean();
651  }
652 
653  return '';
654 
655  }//end getFormContentsKeywordReplacement()
656 
657 
665  {
666  if (!empty($this->_tmp['form_errors'])) {
667  ob_start();
668  echo '<ul>';
669  foreach ($this->_tmp['form_errors'] as $error) {
670  echo '<li>';
671  echo $error;
672  echo '</li>';
673  }
674  echo '</ul>';
675  return ob_get_clean();
676  }
677 
678  return '';
679 
680  }//end getFormErrorsKeywordReplacement()
681 
682 
683  //-- Type-Format Auto-creation --//
684 
685 
694  function createTypeFormat($new_type)
695  {
696  $edit_fns = $this->getEditFns();
697  $folder = $this->getFolder('type_formats');
698  $types = $edit_fns->_getAvailableTypes($this);
699 
700  // check that the new type is valid
701  if ($new_type != '' && !isset($types[$new_type])) {
702  trigger_localised_error('CMS0045', E_USER_WARNING, $new_type);
703  return FALSE;
704  }
705 
706  // check that this type isnt already customised
707  $current_link = $GLOBALS['SQ_SYSTEM']->am->getLink($folder->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, $new_type);
708  if (empty($current_link)) {
709  // create a new format bodycopy for this type
710  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
711  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
712  $GLOBALS['SQ_SYSTEM']->am->includeAsset('bodycopy');
713 
714  $copy_link = Array(
715  'asset' => &$folder,
716  'value' => $new_type,
717  'link_type' => SQ_LINK_TYPE_2,
718  'is_dependant' => 1,
719  'is_exclusive' => 1,
720  );
721 
722  $bodycopy = new Bodycopy();
723  $bodycopy->setAttrValue('name', $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($new_type, 'name').' Format');
724  $args = Array('content' => $this->_getDefaultBodycopyContent($new_type));
725  if (!$bodycopy->create($copy_link, $args)) {
726  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
727  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
728  return FALSE;
729  }
730  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
731  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
732  } else {
733  trigger_localised_error('CMS0046', E_USER_WARNING, $new_type);
734  return FALSE;
735  }
736 
737  return TRUE;
738 
739  }//end createTypeFormat()
740 
741 
742 //-- PAINTING --//
743 
744 
752  {
753  $base_url = ($this->attr('submit_to_page_url') ? $this->getURL() : current_url());
754  $form_method = strtolower($this->_getFormSubmitMethod());
755 
756  $replacements = Array(
757  $this->id.'_formid' => NULL,
758  );
759 
760  if ($form_method == 'get') {
761  // no point retaining query string vars for a GET form action because the browser
762  // won't submit them - instead we will preserve them as hidden fields later
763  $url = $base_url;
764  } else {
765  $url = replace_query_string_vars($replacements, $base_url);
766  }
767 
768  return Array(
769  'url' => $url,
770  'url_base' => $base_url,
771  );
772 
773  }//end _getFormUrlComponents()
774 
775 
782  function printFrontend()
783  {
784 
785  // Tie the process and following paint together using db2 in order to
786  // avoid possible replication slowdown on db1
787  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
788 
789  $can_take_quiz = TRUE;
790  if ($this->getFlag(SQ_ONLINE_QUIZ_FLAG_COMPLETE)) {
791  if (!$this->attr('can_retake')) {
792  $can_take_quiz = FALSE;
793  }
794  }
795 
796  if ($can_take_quiz) {
797  if ($this->attr('interactive_mode')){
799  } else {
800  $this->processQuestions();
801  $this->processAssetSelections();
802  }
803  }
804  parent::printFrontend();
805  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
806 
807  }//end printFrontend()
808 
809 
816  function printContents()
817  {
818  // collect the user details - don't let the user see the test until we have what we need
819  // if handleUserDetailsCollection() returns FALSE, we haven't managed to finish acquiring the user's details,
820  if (!$this->handleUserDetailsCollection()) return;
821 
822  $flag_complete = $this->getFlag(SQ_ONLINE_QUIZ_FLAG_COMPLETE);
823 
824  // submit the quiz for marking if the submit button was processed
825  if (isset($_REQUEST[$this->getPrefix().'_submit_button']) && !$flag_complete) {
826  // gather the points for each answer
827  $questions = $this->getQuestions();
828  $results = Array(
829  'questions' => Array(),
830  'totals' => Array(
831  'points' => 0,
832  'available_points' => 0,
833  ),
834  );
835  foreach ($questions as $id => $details) {
836  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($id);
837  if (!is_null($question)) {
838  $question_results = $question->getResults();
839 
840  $points = $question->getPoints();
841  $available = $question->getAvailablePoints();
842  $position = $question->getPosition();
843 
844  $results['questions'][$position] = $question_results;
845  $results['totals']['points'] += $points;
846  $results['totals']['available_points'] += $available;
847 
848  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
849  }
850  }
851 
852  // sort the results by question number
853  ksort($results['questions'], SORT_NUMERIC);
854 
855  if ($this->attr('interactive_mode')){
856  //we need to adjust the points such that the retried questions don't count
857  foreach (array_keys($results['questions']) as $position){
858  if (strpos($position, ' retry ') !== FALSE){
859  $components = explode(' retry ', $position);
860  if ($components[1] > 1){
861  $previous_position = $components[0].' retry '.--$components[1];
862  } else {
863  $previous_position = $components[0];
864  }
865 
866  if (isset($results['questions'][$previous_position])){
867  $results['totals']['points'] -= $results['questions'][$previous_position]['points'];
868  $results['questions'][$previous_position]['points'] = 0;
869  $results['totals']['available_points'] -= $results['questions'][$previous_position]['available_points'];
870  $results['questions'][$previous_position]['available_points'] = 0;
871  }
872  }
873  }
874  ksort($results['questions'], SORT_STRING);
875  }
876 
877  // set up the results for the thank you page and email later
878  $this->setResults($results);
879 
880  // Log the Quiz Submission if required. This comprises both the User Details Form and the Quiz responses.
881  if ($this->attr('log_submissions')) {
882  $submission_asset = $this->createSubmission();
883 
884  $user_details_form = $this->getForm();
885  $user_details_form_questions = $user_details_form->getQuestions();
886 
887  $user_details = $this->getUserDetails();
888  $quiz_results = $this->getResults();
889  $question_results = $quiz_results['questions'];
890 
891  // Log the answers given for each User Details question
892  foreach ($user_details as $question_id => $user_response) {
893  $question_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_id);
894  $submission_asset->setUserDetailsQuestion($question_id, $question_asset->getVal('title'));
895  $submission_asset->setUserDetailsAnswer($question_id, $user_response);
896  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question_asset);
897  }
898 
899  foreach ($question_results as $question_position => $question_result) {
900  $question_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_result['assetid']);
901 
902  $question_text = strip_tags($question_asset->getQuestionText());
903  $question_result['correct_summary'] = trim(strip_tags($question_result['correct_summary']));
904  $question_result['summary'] = trim(strip_tags($question_result['summary']));
905 
906  $submission_asset->setQuizQuestion($question_result['assetid'], trim($question_text));
907  $submission_asset->setQuizAnswer($question_result['assetid'], $question_result);
908  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question_asset);
909  }
910 
911  $submission_asset->setScore($this->getScoreKeywordReplacement());
912  $submission_asset->setIP($_SERVER['REMOTE_ADDR']);
913  $submission_asset->setAttrValue('complete', TRUE);
914  $submission_asset->saveAttributes();
915  }//end if
916 
917  // send the results off to the user
918  $this->sendResultsEmail();
919 
920  // if appropriate, print the Thank You bodycopy
921  switch ($this->attr('after_submit_action')) {
922  case 'thank_you':
923  $this->printBodycopy('thank_you');
924 
925  // clear the questions and results
926  $this->clearQuizData(array_keys($questions));
927  $this->setFlag(SQ_ONLINE_QUIZ_FLAG_COMPLETE, TRUE, TRUE);
928  return;
929  break;
930 
931  case 'return_to_quiz':
932  // clear the questions and results
933  $this->clearQuizData(array_keys($questions));
934  $this->setFlag(SQ_ONLINE_QUIZ_FLAG_COMPLETE, TRUE, TRUE);
935 
936  // collect the user details - don't let the user see the test until we have what we need
937  // if handleUserDetailsCollection() returns FALSE, we haven't managed to finish acquiring the user's details,
938  if (!$this->handleUserDetailsCollection()) return;
939 
940  // in the extreme instance where the following settings are set:
941  // - cannot retake
942  // - return to quiz (no thank you page)
943  // - user details source is the current user
944  // ... the following code is necessary to plug the gap (otherwise
945  // the user is presented with the quiz again)
946  if (!$this->attr('can_retake')) {
947  $this->printBodycopy('already_taken');
948  return;
949  }
950  break;
951  }
952 
953  }//end if (isset($_REQUEST(submit button))
954 
955  // whether or not a user can retake the quiz is handled in printFrontend()
956  // all we're doing here is making sure that the Complete flag is switched
957  // off so the user can complete the quiz
958  if ($flag_complete) {
959  $this->setFlag(SQ_ONLINE_QUIZ_FLAG_COMPLETE, FALSE, TRUE);
960  }
961 
962  if ($this->attr('interactive_mode')){
963  $question_pagination = $this->loadQuestionPagination();
964  if (empty($question_pagination['pages_to_questions'])){
965  $this->printBodycopy('no_results');
966  } else {
967  // Make a clean URL to submit to
968  $form_method = strtolower($this->_getFormSubmitMethod());
969  $form_url_components = $this->_getFormUrlComponents();
970  $form_url = htmlspecialchars($form_url_components['url']);
971  ?> <form id="<?php echo $this->getPrefix() ?>" method="<?php echo $form_method ?>" action="<?php echo $form_url; ?>"> <?php
972  $this->printBodycopy('page_contents');
973  ?> </form> <?php
974  }
975 
976  return;
977  }
978 
980 
981  // print the old_result_page, so we can keep track of which page we just came from when changing page
982  $this->registerFormField('old_result_page');
983  hidden_field('quiz_'.$this->id.'_old_result_page', $this->getCurrentPage());
984 
985  // print the result_page as a hidden form, so that it shows up in $_REQUEST when the form is submitted by the pagination vars
986  $this->registerFormField('result_page');
987  hidden_field('quiz_'.$this->id.'_result_page', '');
988 
989  //provide the listing engine with the current page to display.
990  $_REQUEST['result_'.$this->id.'_result_page'] = $this->getCurrentPage();
991 
992  // Alrighty, if we're not using the %asset_listing% keyword, we have to make sure that the appropriate keywords are replaced, as Listing Engine will not do this for us
993  $page_contents_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'page_contents');
994  if (!empty($page_contents_link)) {
995  $format_bodycopy = $GLOBALS['SQ_SYSTEM']->am->getAsset($page_contents_link['minorid'], $page_contents_link['minor_type_code']);
996 
997  require_once SQ_FUDGE_PATH.'/general/text.inc';
998  $keywords = $format_bodycopy->getKeywords();
999 
1000  if (!in_array('asset_listing', $keywords)) {
1001  $replacements = Array();
1002  foreach ($keywords as $word) {
1003  $replacements[$word] = $this->getKeywordReplacement($word);
1004  }
1005 
1006  $format_bodycopy->setKeywordReplacements($replacements);
1007  }
1008  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($format_bodycopy);
1009 
1010  }
1011 
1012  parent::printContents();
1013 
1014  }//end printContents()
1015 
1016 
1034  function getAssetList()
1035  {
1036  $assets_to_list = Array();
1037  $questions = $this->getQuestions();
1038 
1039  if (!empty($questions)) {
1040  foreach ($questions as $id => $details) {
1041  // compatibility with different listing formats
1042  if ($this->attr('group_by') == 'letter') {
1043  $assets_to_list[$id] = $details;
1044  } else {
1045  $assets_to_list[$id] = Array( 0 => Array('type_code' => $details['type_code']));
1046  }
1047  }
1048  }
1049 
1050  return $assets_to_list;
1051 
1052  }//end getAssetList()
1053 
1054 
1072  function getQuestions($refresh=NULL, $all_interactive=FALSE)
1073  {
1074 
1075  if ($this->attr('interactive_mode')){
1076  if ($all_interactive){
1077  //used by the edit interface
1078  $interactive_questions = $this->getInteractiveQuestions();
1079  } else {
1080  //while undertaking the quiz, need to always refresh the question list to add the retry questions
1081  $question_pagination = $this->loadQuestionPagination();
1082  $interactive_questions = $question_pagination['questions_to_pages'];
1083  }
1084 
1085  $this->saveQuestionList(array_keys($interactive_questions));
1086  $questions = $this->loadQuestions();
1087  return $questions;
1088  }
1089 
1090  // if $refresh is TRUE, pretend we have no questions to force the regeneration of questions
1091  if ($refresh === TRUE) {
1092  $questions = Array();
1093  } else {
1094  $questions = $this->loadQuestions();
1095  }
1096 
1097  // only refresh if $refresh is TRUE or NULL
1098  if (empty($questions) && $refresh !== FALSE) {
1099 
1100  $assets_to_list = $this->getManuallyConfiguredQuestions();
1101  $manually_configured_questions = !empty($assets_to_list);
1102 
1103  if (empty($assets_to_list)) {
1104  $assets_to_list = parent::getAssetList();
1105  parent::filterAssetTypes($assets_to_list);
1106  }
1107 
1108  if (empty($assets_to_list)) return Array();
1109 
1110  // work out how many assets we need to list
1111  $attr_num_questions = $this->attr('num_questions');
1112  if ($attr_num_questions <= 0 || $attr_num_questions > count($assets_to_list)) {
1113  $num_questions = count($assets_to_list);
1114  } else {
1115  $num_questions = $attr_num_questions;
1116  }
1117 
1118  // Don't randomise question display if we have set them up manually!
1119  if ($manually_configured_questions) {
1120  $random_asset_list =& $assets_to_list;
1121  } else {
1122  // Randomize the order
1123  // array_rand(array, 1) will produce a string instead of an array - compensate
1124  $random_asset_list = Array();
1125 
1126  // Fix for #4058 Online quiz with specific questions asked first and random number of questions
1127  // now before we randomly drop out the questions we need to make sure we do not drop out the compulsory ones
1128  // viz-a-viz the ones in Asset Positions under List Position Settings
1129  $mandatory_listing_assets = $this->attr('asset_positions');
1130  foreach ($mandatory_listing_assets as $index => $info) {
1131  if (isset($assets_to_list[$info['id']])) unset ($assets_to_list[$info['id']]);
1132  }
1133  // while we are at it, lets make space for the assets from asset_positions. and from here we can pick number of questions apart from compulsory listing ones
1134  $num_questions = $num_questions - count($mandatory_listing_assets);
1135 
1136  $random_asset_list_keys = array_rand($assets_to_list, $num_questions);
1137  if ($num_questions == 1) {
1138  $random_asset_list_keys = Array($random_asset_list_keys);
1139  }
1140 
1141  foreach($mandatory_listing_assets as $index => $info) {
1142  $random_asset_list[$info['id']] = Array ( '0' => Array ( 'type_code' => $info['type'] ));
1143  }
1144 
1145  foreach ($random_asset_list_keys as $key) {
1146  $random_asset_list[$key] = $assets_to_list[$key];
1147  }
1148  }
1149 
1150  // modify the asset list to conform to the question format
1151  foreach ($random_asset_list as $assetid => $type) {
1152  // the 'letter' group_by return the 'type' as an array,
1153  // containing extra information, eg. first_letter
1154  // mix these extra elements in with the rest for compatibility's sake
1155  if ($this->attr('group_by') == 'letter') {
1156  $questions[$assetid] = $type;
1157  $questions[$assetid]['value'] = NULL;
1158  $questions[$assetid]['position'] = NULL;
1159  } else {
1160  $questions[$assetid] = Array(
1161  'type_code' => $type,
1162  'value' => NULL,
1163  'position' => NULL,
1164  );
1165  }
1166  }
1167 
1168  // fixate the questions now that we've generated the list
1169  $this->saveQuestions($questions);
1170  $this->saveQuestionList(array_keys($questions));
1171 
1172  }//end if ($refresh != FALSE)
1173 
1174 
1175  // verify that the current questions still exist
1176  $existing_questions = array_flip($GLOBALS['SQ_SYSTEM']->am->assetExists(array_keys($questions)));
1177  foreach ($questions as $id => $question) {
1178  if (!isset($existing_questions[$id])) {
1179  unset($questions[$id]);
1180  }
1181  }
1182 
1183  return $questions;
1184 
1185  }//end getQuestions()
1186 
1187 
1196  function getInteractiveQuestions($grouped = FALSE)
1197  {
1198  require_once SQ_FUDGE_PATH.'/general/general.inc';
1199 
1200  $interactive_questions = Array ();
1201 
1202  $pool_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_LINK_NOTICE, '', FALSE, 'major', 'root');
1203  foreach ($pool_links as $link){
1204  $questions = $GLOBALS['SQ_SYSTEM']->am->getChildren($link['minorid'], 'online_quiz_question_multichoice', TRUE, NULL, NULL, SQ_PERMISSION_READ);
1205  if (!empty($questions)){
1206  $questions = dal_array_flatten($questions);
1207 
1208  //filter out what we don't have read access to
1209  foreach ($questions as $question_id => $type_code){
1210  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_id);
1211  if (is_null($question) || !$question->readAccess()) {
1212  unset($questions[$question_id]);
1213  }
1214  }
1215 
1216  //randomising
1217  $keys = array_rand($questions, count($questions));
1218  if (!is_array($keys)) $keys = Array($keys);
1219  $randomised_questions = Array();
1220  foreach ($keys as $key){
1221  $randomised_questions[$key] = $questions[$key];
1222  }
1223 
1224  if ($grouped){
1225  $interactive_questions[$link['minorid']] = $randomised_questions;
1226  } else {
1227  $interactive_questions += $randomised_questions;
1228  }
1229  }
1230  }
1231 
1232  return $interactive_questions;
1233 
1234  }//end getInteractiveQuestions()
1235 
1236 
1245  {
1246  $assets_to_list = Array();
1247 
1248  $page_contents_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'page_contents');
1249  if (!empty($page_contents_link)) {
1250  $format_bodycopy = $GLOBALS['SQ_SYSTEM']->am->getAsset($page_contents_link['minorid'], $page_contents_link['minor_type_code']);
1251  require_once SQ_FUDGE_PATH.'/general/text.inc';
1252  $keywords = $format_bodycopy->getKeywords();
1253  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($format_bodycopy);
1254 
1255  // Question display was manually configured. These questions will be scored and tracked as available questions when the
1256  // question response is shown (%question_X_response_form%)
1257  if (!in_array('asset_listing', $keywords)) {
1258  $replacements = Array();
1259  foreach ($keywords as $keyword) {
1260  if (preg_match('/^(question)_([0-9]*)_(response_form)$/', $keyword, $matches)) {
1261  if (isset($matches[1]) && isset($matches[2]) && isset($matches[3])) {
1262  // Add this question to a list of quiz questions
1263  $question_asset_id = $matches[2];
1264  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_asset_id);
1265 
1266  // Check again to ensure that it is a question
1267  $question_type = $question->type();
1268  if ($question_type == 'online_quiz_question_multichoice') {
1269  if (isset($matches[1]) && isset($matches[2])) {
1270  // Check permissions to this asset before listing (read permission must be there)
1271  if ($question->readAccess()) {
1272  $assets_to_list[$question_asset_id] = $question_type;
1273  }
1274  }
1275  }
1276 
1277  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
1278  }
1279  }
1280  }
1281  }
1282  }//end if
1283 
1284  return $assets_to_list;
1285 
1286  }//end getManuallyConfiguredQuestions()
1287 
1288 
1295  function sendResultsEmail()
1296  {
1297  $email_attr = $this->getAttribute('results_email_format');
1298  $value = unserialize($email_attr->value);
1299 
1300  // make sure the proper version of the results table is used in the plaintext version of the email
1301  $value['text_format'] = str_replace('%results_table_full%', '%results_table_full_text%', $value['text_format']);
1302  $value['text_format'] = str_replace('%results_table_simple%', '%results_table_simple_text%', $value['text_format']);
1303 
1304  // ...and for Question and Question Group result HTML-based keywords. Use a "_plain" suffix if the last bit is "_text" to avoid "%fred_text_text%"
1305  $result_keywords = Array(
1306  'question_text', 'answer_text', 'correct_answer_text', 'question_note',
1307  );
1308  foreach ($result_keywords as $result_keyword) {
1309  $value['text_format'] = preg_replace('/%(result)_([0-9]*)_('.$result_keyword.')%/', '%result_$2_$3_plain%', $value['text_format']);
1310  }
1311  $value['text_format'] = preg_replace('/%(result)_([0-9]*)_(response_supplement)%/', '%result_$2_$3_text%', $value['text_format']);
1312  $value['text_format'] = preg_replace('/%(question_group)_([0-9]*)_(score_category_supplement)%/', '%question_group_$2_$3_text%', $value['text_format']);
1313 
1314  $email_attr->value = serialize($value);
1315 
1316  $keywords = $email_attr->getKeywords();
1317 
1318  $replacements = Array();
1319  foreach ($keywords as $keyword) {
1320  $replacements[$keyword] = $this->getKeywordReplacement($keyword);
1321  }
1322 
1323  // email_format already checks for us if there are no 'To' addresses
1324  return $email_attr->sendMail($replacements);
1325 
1326  }//end sendResultsEmail()
1327 
1328 
1337  function clearQuizData($questions=NULL)
1338  {
1339  if (is_null($questions)) {
1340  $questions = array_keys($this->getQuestions());
1341  }
1342 
1343  if (is_array($questions)) {
1344  foreach ($questions as $assetid) {
1345  unset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['question'][$assetid]);
1346  }
1347  }
1348 
1349  // unset all the quiz data (results, flags, question list)
1350  unset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]);
1351 
1352  }//end clearQuizData()
1353 
1354 
1361  function getUserDetails()
1362  {
1363  $details = Array();
1364  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['user_details'])) {
1365  $details = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['user_details'];
1366  }
1367 
1368  return $details;
1369 
1370  }//end getUserDetails()
1371 
1372 
1381  function setUserDetails($details)
1382  {
1383  if (is_array($details)) {
1384  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['user_details'] = $details;
1385  return TRUE;
1386  }
1387 
1388  return FALSE;
1389 
1390  }//end setUserDetails()
1391 
1392 
1403  {
1404  // if we're using the current user's details collect user data during quiz submission, so
1405  // we can just make calls to the user's data on the fly, rather than attempting to
1406  // precache everything (eg. attributes, metadata)
1407 
1408  // always enforce the use of a form if the current user is the public user;
1409  // "Public User" doesn't tend to look good on quiz results
1410  if ($this->attr('user_details_source') == 'form' || $GLOBALS['SQ_SYSTEM']->userPublic()) {
1411  $user_details = $this->getUserDetails();
1412  if (empty($user_details)) {
1413  // determine whether we need to get details from the current user, or a form
1414  // fill in dummy values for public user
1415  $form = $this->getForm();
1416  if (!is_null($form)) {
1417  // check to see if we need to process
1418  if (array_get_index($_REQUEST, $this->id.'_formid') && $form->process()) {
1419  // process, then let through to the rest of the function to print the questions, etc.
1420  $this->setUserDetails($form->getAnswerSummaries());
1421  } else {
1422  // either not processed or details missing, so display the form again and bail out
1423  $this->_tmp['form_errors'] = $form->getErrors();
1424  $this->printBodycopy('user_details');
1425  return FALSE;
1426  }
1427  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($form);
1428  }
1429  }
1430  }
1431 
1432  return TRUE;
1433 
1434  }//end handleUserDetailsCollection()
1435 
1436 
1445  function getFlag($flag)
1446  {
1447  $return = NULL;
1448 
1449  // check links first, unless we're the public user
1450  if (!$GLOBALS['SQ_SYSTEM']->userPublic()) {
1451  $link = $GLOBALS['SQ_SYSTEM']->am->getLinkByAsset($this->id, $GLOBALS['SQ_SYSTEM']->user->id, SQ_LINK_NOTICE);
1452  if (!empty($link)) {
1453  $link_value = unserialize($link['value']);
1454  $return = array_get_index($link_value, $flag, NULL);
1455  }
1456  }
1457 
1458  // fallback to the session
1459  if (is_null($return)) {
1460  if (isset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['flags'][$flag])) {
1461  $return = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['flags'][$flag];
1462  }
1463  }
1464 
1465  return $return;
1466 
1467  }//end getFlag()
1468 
1469 
1480  function setFlag($flag, $value, $permanent=FALSE)
1481  {
1482  if (empty($flag)) return FALSE;
1483 
1484  // only allow permanent flags if the user is logged in - the best we can
1485  // do for the public_user is the session
1486  if ($permanent && !$GLOBALS['SQ_SYSTEM']->userPublic()) {
1487 
1488  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
1489 
1490  $link = $GLOBALS['SQ_SYSTEM']->am->getLinkByAsset($this->id, $GLOBALS['SQ_SYSTEM']->user->id, SQ_LINK_NOTICE);
1491  if (!empty($link)) {
1492  // update instead of create
1493  $link_value = unserialize($link['value']);
1494  $link_value[$flag] = $value;
1495  $new_link_value = serialize($link_value);
1496  $res = $GLOBALS['SQ_SYSTEM']->am->updateLink($link['linkid'], SQ_LINK_NOTICE, $new_link_value);
1497  } else {
1498  $new_value = Array(
1499  $flag => $value,
1500  );
1501  $new_link_value = serialize($new_value);
1502  $res = $GLOBALS['SQ_SYSTEM']->am->createAssetLink($this, $GLOBALS['SQ_SYSTEM']->user, SQ_LINK_NOTICE, $new_link_value);
1503  }
1504 
1505  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
1506 
1507  return $res;
1508  }
1509 
1510  // fall back to just storing the flag in an array in the session
1511  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['flags'][$flag] = $value;
1512 
1513  return TRUE;
1514 
1515  }//end setFlag()
1516 
1517 
1541  function getResults()
1542  {
1543  $results = Array();
1544  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['results'])) {
1545  $results = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['results'];
1546  }
1547 
1548  return $results;
1549 
1550  }//end getResults()
1551 
1552 
1562  function setResults($results)
1563  {
1564  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['results'] = $results;
1565  return TRUE;
1566 
1567  }//end setResults()
1568 
1569 
1576  function loadQuestions()
1577  {
1578  $questions = Array();
1579 
1580  // get the question list first, then load the data for each
1581  $question_list = $this->loadQuestionList();
1582  if (!empty($question_list)) {
1583  foreach ($question_list as $assetid) {
1584  // load the premade question entries, and manually construct the rest
1585  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['question'][$assetid])) {
1586  $questions[$assetid] = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['question'][$assetid];
1587  } else {
1588  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1589  if (!is_null($question)) {
1590  $questions[$assetid] = $question->getQuestionData();
1591  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
1592  }
1593  }
1594  }
1595  }
1596 
1597  return $questions;
1598 
1599  }//end loadQuestions()
1600 
1601 
1610  function saveQuestions($questions)
1611  {
1612  if (!empty($questions)) {
1613  foreach ($questions as $assetid => $question_data) {
1614  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1615  if (!is_null($question)) {
1616  $questions[$assetid] = $question->setValue($question_data['value']);
1617  $questions[$assetid] = $question->setPosition($question_data['position']);
1618  $question->saveQuestion();
1619  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
1620  }
1621  }
1622  }
1623 
1624  }//end saveQuestions()
1625 
1626 
1633  function loadQuestionList()
1634  {
1635  $question_list = Array();
1636 
1637  // load everything from the session
1638  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['question_list'])) {
1639  $question_list = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['question_list'];
1640  }
1641 
1642  return $question_list;
1643 
1644  }//end loadQuestionList()
1645 
1646 
1655  function saveQuestionList($question_list)
1656  {
1657  if (!is_array($question_list)) {
1658  $question_list = Array();
1659  }
1660 
1661  // load everything from the session
1662  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['question_list'] = $question_list;
1663 
1664  return TRUE;
1665 
1666  }//end saveQuestionList()
1667 
1668 
1677  {
1678  $question_pagination = Array();
1679  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['question_pagination'])) {
1680  $question_pagination = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['question_pagination'];
1681  }
1682 
1683  if (empty($question_pagination) || empty($question_pagination['pages_to_questions'])) {
1684  // regenerate the question pagination cache
1685  if ($this->attr('interactive_mode')){
1699  $question_pool = $this->getInteractiveQuestions(TRUE);
1700  $pool_setup = $this->attr('question_pool_setup');
1701 
1702  $page_no = 1;
1703  foreach ($pool_setup as $pool_id => $info){
1704  if (isset($question_pool[$pool_id])){
1705  $questions = $question_pool[$pool_id];
1706  } else {
1707  continue;
1708  }
1709  //give one question per page upto the pool size
1710  for ($size = 0; $size < $info['size']; $size++){
1711  reset($questions);
1712  $question_id = key($questions);
1713  if (empty($question_id)) {
1714  trigger_localised_error('CMS0112', E_USER_WARNING, $pool_id);
1715  break;
1716  }
1717  $question_pagination['pages_to_questions'][$page_no][$question_id] = $questions[$question_id]['type_code'];
1718  $question_pagination['questions_to_pages'][$question_id] = Array(
1719  'page' => $page_no,
1720  'list_position' => $page_no
1721  );
1722  $question_pagination['pages_to_status'][$page_no] = 'submit_question';
1723  unset($questions[$question_id]);
1724 
1725  //give this page the required retry questions
1726  for ($retries = 0; $retries < $info['retries']; $retries++){
1727  reset($questions);
1728  $question_id = key($questions);
1729  if (empty($question_id)) {
1730  trigger_localised_error('CMS0112', E_USER_WARNING, $pool_id);
1731  break;
1732  }
1733  $question_pagination['pages_to_retry_questions'][$page_no][$question_id] = $questions[$question_id]['type_code'];
1734  unset($questions[$question_id]);
1735  }
1736  $page_no++;
1737  }
1738  }
1739 
1740  } else {
1741  // grab the list of question assets for chunking later
1742  $children = $this->getAssetList();
1743  $this->filterAssetList($children);
1744  $replacements = Array();
1745  $question_pagination = Array(
1746  'pages_to_questions' => Array(),
1747  'questions_to_pages' => Array(),
1748  );
1749 
1750  // as an initial attempt, load the current page of assets, and store it in the question pagination array
1751  // check $replacements['total_pages'] to see how many more chunks we have to get
1752 
1753  // assumption: any assets that are of interest to us (online_quiz_questions) will never be parents
1754  // of other assets that interest us (other quiz questions)
1755  // eg. the following code will break if the below is encountered:
1756  // [quiz:1]
1757  // |--- [quiz:2]
1758  // '--- [quiz:3]
1759  $current_result_page = $this->getCurrentPage();
1760  require_once SQ_FUDGE_PATH.'/general/general.inc';
1761  $current_page_assets = dal_array_flatten($this->getPageAssets($children, $replacements, $current_result_page), 'type_code');
1762  $total_pages = $replacements['total_pages'];
1763 
1764  if (!empty($current_page_assets)) {
1765  $question_pagination['pages_to_questions'][$current_result_page] = $current_page_assets;
1766 
1767  // load all the pages, skipping over anything that's already been loaded (ie. the current page)
1768  for ($ii = 1; $ii <= $total_pages; $ii++) {
1769  if (isset($question_pagination['pages_to_questions'][$ii])) {
1770  $page_assets = $question_pagination['pages_to_questions'][$ii];
1771  } else {
1772  $page_assets = dal_array_flatten($this->getPageAssets($children, $replacements, $ii), 'type_code');
1773  $question_pagination['pages_to_questions'][$ii] = $page_assets;
1774  }
1775 
1776  $asset_count = 1;
1777  $num_per_page = $this->_getNumPerPage();
1778  foreach ($page_assets as $assetid => $type) {
1779  $question_pagination['questions_to_pages'][$assetid] = Array(
1780  'page' => $ii,
1781  'list_position' => ($ii - 1) * $num_per_page + $asset_count,
1782  );
1783  $asset_count++;
1784  }
1785  }
1786  }
1787  }//end else (not interactive mode)
1788 
1789  $this->saveQuestionPagination($question_pagination);
1790 
1791  }//end if (empty($question_pagination))
1792 
1793  return $question_pagination;
1794 
1795  }//end loadQuestionPagination()
1796 
1797 
1807  function saveQuestionPagination($question_pagination)
1808  {
1809  // load everything from the session
1810  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['question_pagination'] = $question_pagination;
1811 
1812  return TRUE;
1813 
1814  }//end saveQuestionPagination()
1815 
1816 
1825  {
1826 
1827  // figure out which assets are on what pages
1828  $question_pagination = $this->loadQuestionPagination();
1829  if (empty($question_pagination['pages_to_questions'])) return TRUE;
1830 
1831  // figure out which assets are on the current page
1832  $current_result_page = $this->getCurrentPage();
1833  $current_question_id = key($question_pagination['pages_to_questions'][$current_result_page]);
1834 
1835  $current_status = $question_pagination['pages_to_status'][$current_result_page];
1836 
1837  $prefix = $this->getPrefix();
1838 
1839  if (isset($_POST[$prefix.'_submit_next_button']) && $current_status == 'feedback_next'){
1840  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_page'] = $current_result_page + 1;
1841  $question_pagination['pages_to_status'][$current_result_page] = 'answered';
1842  } else if (isset($_POST[$prefix.'_submit_retry_button']) && $current_status == 'feedback_retry'){
1843  //shouldn't be empty, just double checking
1844  if (!empty($question_pagination['pages_to_retry_questions'][$current_result_page])) {
1845  //grab the next question in the same pool
1846  $question_id = key($question_pagination['pages_to_retry_questions'][$current_result_page]);
1847  $question_type = $question_pagination['pages_to_retry_questions'][$current_result_page][$question_id];
1848  unset($question_pagination['pages_to_retry_questions'][$current_result_page][$question_id]);
1849  $question_pagination['pages_to_questions'][$current_result_page] = Array ($question_id => $question_type);
1850 
1851  //add the retry question to the 'questions_to_pages' and give it its retry position.
1852  $question_pagination['questions_to_pages'][$question_id] = $question_pagination['questions_to_pages'][$current_question_id];
1853  $question_list_position = $question_pagination['questions_to_pages'][$question_id]['list_position'];
1854  if (strpos($question_list_position, ' retry ') !== FALSE){
1855  $components = explode(' retry ', $question_list_position);
1856  $question_list_position = $components[0].' retry '.++$components[1];
1857  } else {
1858  $question_list_position .= ' retry 1';
1859  }
1860  $question_pagination['questions_to_pages'][$question_id]['list_position'] = $question_list_position;
1861 
1862  //update the page status
1863  $question_pagination['pages_to_status'][$current_result_page] = 'submit_question_retry';
1864  } else {
1865  $question_pagination['pages_to_status'][$current_result_page] = 'feedback_next';
1866  }
1867  } else if (isset($_POST[$prefix.'_submit_question_button']) && strpos($current_status, 'submit_question') === 0){
1868  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($current_question_id);
1869  if (!is_null($question)) {
1870  if ($current_status == 'submit_question_retry'){
1871  if (isset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['retries'])){
1872  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['retries']++;
1873  } else {
1874  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['retries'] = 1;
1875  }
1876  } else {
1877  if (isset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['total_questions'])){
1878  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['total_questions']++;
1879  } else {
1880  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['total_questions'] = 1;
1881  }
1882  }
1883 
1884  $errors = $question->processResponseForm();
1885  if ($current_result_page == count($question_pagination['pages_to_questions'])) {
1886  $question_pagination['pages_to_status'][$current_result_page] = 'feedback_last';
1887  } else {
1888  $question_pagination['pages_to_status'][$current_result_page] = 'feedback_next';
1889  }
1890 
1891  $value = $question->getValue();
1892  if (!is_array($value)) $value = Array($value);
1893  if (array_intersect($value, $question->getCorrectValue()) === Array()){
1894  if(!empty($question_pagination['pages_to_retry_questions'][$current_result_page])){
1895  $question_pagination['pages_to_status'][$current_result_page] = 'feedback_retry';
1896  }
1897  } else {
1898  if (isset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['correct_answers'])){
1899  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['correct_answers']++;
1900  } else {
1901  $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['correct_answers'] = 1;
1902  }
1903  }
1904  }
1905  }
1906 
1907  $this->saveQuestionPagination($question_pagination);
1908 
1909  //set and save the position of the current page question. Need to use up to date data.
1910  $question_pagination = $this->loadQuestionPagination();
1911  $current_result_page = $this->getCurrentPage();
1912  $current_question_id = key($question_pagination['pages_to_questions'][$current_result_page]);
1913 
1914  if (!empty($question_pagination['questions_to_pages'][$current_question_id]['list_position'])) {
1915  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($current_question_id);
1916  if (!is_null($question)) {
1917  // set the asset position
1918  $question->setPosition($question_pagination['questions_to_pages'][$current_question_id]['list_position']);
1919  $question->saveQuestion();
1920  }
1921  }
1922 
1923  return TRUE;
1924 
1925  }//end processInteractiveQuestions()
1926 
1927 
1934  function processQuestions()
1935  {
1936  // load all questions, and separate them into three: questions from the page we just came from,
1937  // questions from the page we're about to display, and the rest of the questions.
1938  // process and collect errors from the first group, leave the second, and collect
1939  // errors from the third by means of validateValue()
1940 
1941  // load all the questions attached to the quiz
1942  $questions = $this->getQuestions();
1943 
1944  // set up the error stack for later use
1945  $error_stack = Array(
1946  'errors' => Array(),
1947  'warnings' => Array(),
1948  );
1949 
1950  // figure out which assets are on what pages
1951  $question_pagination = $this->loadQuestionPagination();
1952 
1953  // figure out which assets are on the current page
1954  $current_result_page = $this->getCurrentPage();
1955  $current_page_assets =& $question_pagination['pages_to_questions'][$current_result_page];
1956 
1957  // now grab the previous page of assets (if applicable)
1958  $previous_page_assets = Array();
1959  $old_result_page = array_get_index($_REQUEST, 'quiz_'.$this->id.'_old_result_page', NULL);
1960  if (!is_null($old_result_page)) {
1961  $previous_page_assets =& $question_pagination['pages_to_questions'][$old_result_page];
1962  }
1963 
1964  foreach ($questions as $questionid => $question_details) {
1965  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($questionid);
1966  if (!is_null($question)) {
1967  // set the asset position
1968  if (!empty($question_pagination['questions_to_pages'][$questionid]['list_position'])) {
1969  $question->setPosition($question_pagination['questions_to_pages'][$questionid]['list_position']);
1970  }
1971 
1972  // Figure out what to process
1973  if (isset($previous_page_assets[$questionid])) {
1974  // process any questions that were on the page we just came from
1975  $errors = $question->processResponseForm();
1976  } else {
1977  // just get the warnings
1978  $errors = $question->validateValue();
1979  }
1980 
1981  // only get the warnings if the questions aren't on the current page, or warn_on_missing_answers is off
1982  // otherwise, present warnings to the user via javascript
1983  if (!isset($current_page_assets[$questionid]) || !$this->attr('clientside_validation')) {
1984  if (!empty($errors['errors'])) {
1985  $error_stack['errors'] = array_merge($error_stack['errors'], $errors['errors']);
1986  }
1987  if (!empty($errors['warnings'])) {
1988  $error_stack['warnings'] = array_merge($error_stack['warnings'], $errors['warnings']);
1989  }
1990  }
1991 
1992  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
1993  }//end if (!is_null($question))
1994  }//end foreach ($questions)
1995 
1996  // set the error_stack, to be used later when the doing keyword replacements (eg. submit button prints the warnings)
1997  $this->_error_stack = $error_stack;
1998 
1999  return TRUE;
2000 
2001  }//end processQuestions()
2002 
2003 
2022  function &getChunk(&$children, &$replacements, $keywords, $result_page, $num_per_page, $group_by=NULL)
2023  {
2024  $todo =& parent::getChunk($children, $replacements, $keywords, $result_page, $num_per_page, $group_by);
2025 
2026  // if the page keywords are present, deconstruct them, add the javascript to force the processing of the form,
2027  // and reconstruct them in the form '<a href="url"' + javascript + '>link text</a>'
2028 
2029  // the following javascript:
2030  // - sets the value of the #_result_page hidden field, to control which page we're going to end up at
2031  // - removes #_result_page from the GET vars of the form action, to avoid conflicts
2032  // - submits the form
2033  $link_js = ' onClick="document.getElementById(\'quiz_'.$this->id.'_result_page\').value=\'%s\'; '
2034  .'frm=document.getElementById(\''.$this->getPrefix().'\'); '
2035  .'frm.action=\''.htmlentities(replace_query_string_vars(Array('quiz_'.$this->id.'_result_page' => NULL))).'\'; '
2036  .'frm.submit(); return false;" ';
2037 
2038  // we have the luxury of assuming that #_result_page will always exist if a link is present
2039  $link_regex = '|(<a href=")(([^"]*result_'.$this->id.'_result_page=)([[:alnum:]]*))([^"]*")(>[^<]*</a>)|';
2040 
2041  $url_reps = Array(
2042  $this->id.'_formid' => NULL,
2043  );
2044 
2045  // next_page keyword
2046  preg_match($link_regex, $replacements['next_page'], $matches);
2047  if (!empty($matches)) {
2048  // remove the formid query var
2049  list($base, $query) = explode('?', $matches[2]);
2050  $url = replace_query_string_vars($url_reps, $base, $query);
2051 
2052  // sub the destination page into the javascript
2053  $dest_page = $matches[4];
2054  $replacements['next_page'] = $matches[1].htmlentities($url).$matches[5].sprintf($link_js, $dest_page).$matches[6];
2055  }
2056 
2057  // previous_page keyword
2058  preg_match($link_regex, $replacements['previous_page'], $matches);
2059  if (!empty($matches)) {
2060  // remove the formid query var
2061  list($base, $query) = explode('?', $matches[2]);
2062  $url = replace_query_string_vars($url_reps, $base, $query);
2063 
2064  // sub the destination page into the javascript
2065  $dest_page = $matches[4];
2066  $replacements['previous_page'] = $matches[1].htmlentities($url).$matches[5].sprintf($link_js, $dest_page).$matches[6];
2067  }
2068 
2069 
2070  // page_list keywords
2071  // do some trickery with the keyword replacements - replace %d in $link_js with a regex special character,
2072  // to get preg_replace to sub in the target page number for us
2073  $replacements['page_list'] = preg_replace(
2074  '|(<a href="[^"]*result_'.$this->id.'_result_page=)([[:alnum:]]*)([^"]*")([^>]*>)|',
2075  '$1$2$3'.sprintf($link_js, '$2').'$4',
2076  $replacements['page_list']
2077  );
2078  $replacements['page_list_without_unused'] = preg_replace(
2079  '|(<a href="[^"]*result_'.$this->id.'_result_page=)([[:alnum:]]*)([^"]*")([^>]*>)|',
2080  '$1$2$3'.sprintf($link_js, '$2').'$4',
2081  $replacements['page_list']
2082  );
2083 
2084 
2085  // unset the next_page_href and previous_page_href for safety (allows for the form
2086  // to change pages without submitting the form to save the questions)
2087  unset($replacements['next_page_href']);
2088  unset($replacements['previous_page_href']);
2089 
2090  return $todo;
2091 
2092  }//end getChunk()
2093 
2094 
2095 //-- Keyword Listings --//
2096 
2097 
2107  function onRequestKeywords(&$broadcaster, $vars=Array())
2108  {
2109  if (!isset($vars['keywords'])) return;
2110  if (!($broadcaster instanceof Content_Type)) return;
2111 
2112  $parents = $GLOBALS['SQ_SYSTEM']->am->getParents($broadcaster->id, 'bodycopy', TRUE);
2113 
2114  // the broadcaster could be our page contents bodycopy, in which case
2115  // we want to supply our own keyword replacements
2116  $page_contents_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'page_contents');
2117  if (!empty($page_contents_link)) {
2118  if (isset($parents[$page_contents_link['minorid']])) {
2119  $vars['keywords'] += $this->_getContentsKeywords();
2120  $vars['keywords'] += $this->_getQuestionKeywords();
2121  if ($this->attr('interactive_mode')){
2122  $vars['keywords'] += $this->_getInteractiveKeywords();
2123  }
2124  return;
2125  }
2126  }
2127 
2128  // and the same for the thank_you page
2129  $thank_you_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'thank_you');
2130  if (!empty($thank_you_link)) {
2131  if (isset($parents[$thank_you_link['minorid']])) {
2132  $vars['keywords'] += $this->_getResultsKeywords();
2133  $vars['keywords'] += $this->_getUserDetailsKeywords();
2134  $vars['keywords'] += $this->_getQuestionResultKeywords();
2135  $vars['keywords'] += $this->_getQuestionGroupResultKeywords();
2136  $vars['keywords'] += Array ('quiz_score' => translate('online_quiz_keyword_quiz_score'));
2137  return;
2138  }
2139  }
2140 
2141  // and for the user_details page
2142  $user_details_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'user_details');
2143  if (!empty($user_details_link)) {
2144  if (isset($parents[$user_details_link['minorid']])) {
2145  $vars['keywords'] += $this->_getFormKeywords();
2146  return;
2147  }
2148  }
2149 
2150 
2151  // and the already_taken page
2152  $user_details_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'already_taken');
2153  if (!empty($user_details_link)) {
2154  if (isset($parents[$user_details_link['minorid']])) {
2155  return;
2156  }
2157  }
2158 
2159  // type folder links
2160  $folder = $this->getFolder();
2161  $type_formats = Array();
2162  if (!is_null($folder)) {
2163  $type_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($folder->id, SQ_LINK_TYPE_2, 'bodycopy');
2164  foreach ($type_links as $link_info) {
2165  if (isset($parents[$link_info['minorid']])) {
2166  $type_formats[] = $link_info['value'];
2167  }
2168  }
2169  }
2170 
2171  // if it's not the page contents bodycopy, it's something that we've
2172  // inherited from the listing engine, so pass it on
2173  parent::onRequestKeywords($broadcaster, $vars);
2174 
2175  // if any type formats present, override the asset_position keyword
2176  if (count($type_formats) > 0) {
2177  // set up the 'question_number' alias (mimics asset_position)
2178  $vars['keywords']['asset_position'] = translate('online_quiz_keyword_question_number');
2179  }
2180 
2181  }//end onRequestKeywords()
2182 
2183 
2197  function _getContentsKeywords()
2198  {
2199  $keywords = parent::_getContentsKeywords();
2200  $GLOBALS['SQ_SYSTEM']->lm->includeAssetStrings('page_asset_listing');
2201 
2202  $keywords += Array(
2203  'error_list' => translate('online_quiz_keyword_error_list'),
2204  'warning_list' => translate('online_quiz_keyword_warning_list'),
2205  'question_range' => translate('online_quiz_keyword_question_range'),
2206  'question_count' => translate('online_quiz_keyword_question_count'),
2207  );
2208 
2209  // unset the next_page_href and previous_page_href for safety (allows for the form
2210  // to change pages without submitting the form to save the questions)
2211  unset($keywords['next_page_href']);
2212  unset($keywords['previous_page_href']);
2213 
2214  return $keywords;
2215 
2216  }//end _getContentsKeywords()
2217 
2218 
2226  function _getFormKeywords()
2227  {
2228  $keywords = Array();
2229 
2230  $keywords['form_contents'] = translate('cms_form_contents');
2231  $keywords['form_errors'] = translate('cms_form_errors');
2232 
2233  return $keywords;
2234 
2235  }//end _getFormKeywords()
2236 
2237 
2244  function _getUserDetailsKeywords()
2245  {
2246  $keywords = Array();
2247 
2248  // grab the questions attached to the form
2249  $form = $this->getForm();
2250  if (!is_null($form)) {
2251  $questions = $form->getQuestions(TRUE);
2252  foreach ($questions as $assetid => $question_details) {
2253  list($bridgeid, $shadowid) = explode(':', $assetid);
2254  $keywords['user_details_form_'.$bridgeid.'-'.$shadowid] = 'Form Field: #'.$assetid.' ('.$question_details['name'].')';
2255  }
2256  }
2257 
2258  // get the generic user to provide us with a list of keywords
2259  $GLOBALS['SQ_SYSTEM']->am->includeAsset('user');
2260  $user = new User();
2261  $user_keywords = $user->getAvailableKeywords();
2262  foreach ($user_keywords as $keyword => $description) {
2263  $keywords['user_details_current_user_'.$keyword] = 'Current User: '.$description;
2264  }
2265 
2266  return $keywords;
2267 
2268  }//end _getUserDetailsKeywords()
2269 
2270 
2277  function _getResultsKeywords()
2278  {
2279  $keywords = Array();
2280 
2281  $keywords['results_table_full'] = translate('online_quiz_keyword_results_table_full');
2282  $keywords['results_table_simple'] = translate('online_quiz_keyword_results_table_simple');
2283 
2284  return $keywords;
2285 
2286  }//end _getResultsKeywords()
2287 
2288 
2295  function _getInteractiveKeywords()
2296  {
2297  $keywords = Array();
2298 
2299  $keywords['interactive_question_response_form'] = translate('online_quiz_keyword_interactive_question_response_form');
2300  $keywords['interactive_question_text'] = translate('online_quiz_keyword_interactive_question_text');
2301  $keywords['interactive_question_note'] = translate('online_quiz_keyword_interactive_question_note');
2302  $keywords['interactive_question_number'] = translate('online_quiz_keyword_interactive_question_number');
2303  $keywords['interactive_button'] = translate('online_quiz_keyword_interactive_button');
2304  $keywords['quiz_score'] = translate('online_quiz_keyword_quiz_score');
2305 
2306  return $keywords;
2307 
2308  }//end _getInteractiveKeywords()
2309 
2310 
2317  function _getQuestionKeywords()
2318  {
2319  $keywords = Array();
2320 
2321  // Refresh the question list, if we are manually configuring the page's question content and adding/removing/editing this order.
2322  // Only do this if the %asset_listing% keyword is missing from the Page Contents bodycopy.
2323  $page_contents_link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_TYPE_2, 'bodycopy', TRUE, 'page_contents');
2324  if (!empty($page_contents_link)) {
2325  $format_bodycopy = $GLOBALS['SQ_SYSTEM']->am->getAsset($page_contents_link['minorid'], $page_contents_link['minor_type_code']);
2326 
2327  require_once SQ_FUDGE_PATH.'/general/text.inc';
2328  $keywords = $format_bodycopy->getKeywords();
2329 
2330  if (!in_array('asset_listing', $keywords)) {
2331  $form = $this->getForm();
2332  if (!is_null($form)) {
2333  $questions = $form->getQuestions(TRUE);
2334  }
2335  }
2336  }
2337 
2338  // Obtain the available keywords for a quiz question
2339  $GLOBALS['SQ_SYSTEM']->am->includeAsset('online_quiz_question');
2340  $question = new Online_Quiz_Question();
2341  $question_keywords = $question->getAvailableKeywords();
2342  foreach ($question_keywords as $keyword => $description) {
2343  $keywords['question_X_'.$keyword] = 'Question X: '.$description;
2344  }
2345 
2346  return $keywords;
2347 
2348  }//end _getQuestionKeywords()
2349 
2350 
2358  function _getQuestionResultKeywords()
2359  {
2360  $keywords = Array();
2361  $available_keywords = Array(
2362  'question_number' => translate('online_quiz_results_header_question_num'),
2363  'question_text' => translate('online_quiz_results_header_question_text'),
2364  'answer' => translate('online_quiz_results_header_value'),
2365  'answer_text' => translate('online_quiz_results_header_summary'),
2366  'correct_answer' => translate('online_quiz_results_header_correct_value'),
2367  'correct_answer_text' => translate('online_quiz_results_header_correct_summary'),
2368  'points' => translate('online_quiz_results_header_points'),
2369  'available_points' => translate('online_quiz_results_header_available_points'),
2370  'response_supplement' => translate('online_quiz_results_response_supplement'),
2371  'question_note' => translate('online_quiz_results_question_note'),
2372  );
2373 
2374  $locale_text = translate('online_quiz_results_question_x');
2375  foreach ($available_keywords as $keyword => $description) {
2376  $keywords['result_X_'.$keyword] = $locale_text.': '.$description;
2377  }
2378 
2379  // And some more general keywords...
2380  $keywords['quiz_total_points'] = translate('online_quiz_results_quiz_total_points_obtained');
2381  $keywords['quiz_total_available_points'] = translate('online_quiz_results_quiz_maximum_total_points');
2382 
2383  return $keywords;
2384 
2385  }//end _getQuestionResultKeywords()
2386 
2387 
2395  function _getQuestionGroupResultKeywords()
2396  {
2397  $keywords = Array();
2398  $keywords['question_group_X_score_category_name'] = translate('online_quiz_results_question_group_x').': '.translate('online_quiz_results_score_category_name');
2399  $keywords['question_group_X_score_category_supplement'] = translate('online_quiz_results_question_group_x').': '.translate('online_quiz_results_score_category_supplement');
2400 
2401  return $keywords;
2402 
2403  }//end _getQuestionGroupResultKeywords()
2404 
2405 
2406 //-- Keyword Replacements --//
2407 
2408 
2424  function getKeywordReplacement($keyword)
2425  {
2426  if (empty($keyword)) return '';
2427 
2428  $keyword = parse_keyword($keyword, $modifiers);
2429 
2430  if ($this->attr('interactive_mode') && strpos($keyword, 'interactive_') === 0){
2431  if ($keyword == 'interactive_button'){
2432  $replacement = $this->getInteractiveButtonKeywordReplacement();
2433  } else {
2434  $replacement = $this->getInteractiveQuestionKeywordReplacement(substr($keyword, strlen('interactive_')));
2435  }
2436  if (!is_null($replacement)) apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $this->id));
2437  return $replacement;
2438  }
2439 
2440  if ($keyword == 'quiz_score'){
2441  $replacement = $this->getScoreKeywordReplacement();
2442  apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $this->id));
2443  return $replacement;
2444  }
2445 
2446  // check the keyword against the dynamic keywords
2447  // in this case - user details:
2448 
2449  if (preg_match('/^(user_details_form)_([0-9]*-q[0-9]*)$/', $keyword, $matches)) {
2450  if (isset($matches[1]) && isset($matches[2])) {
2451  // arg = question assetid
2452  $arg = str_replace('-', ':', $matches[2]);
2453  $keyword_stem = $matches[1];
2454  }
2455  }
2456 
2457  if (preg_match('/^(user_details_current_user)_(.*)$/', $keyword, $matches)) {
2458  if (isset($matches[1]) && isset($matches[2])) {
2459  // if the current user is the public user, the keyword replacements that would've
2460  // been collected below would have been invalid
2461  // return a blank replacement instead
2462  if ($GLOBALS['SQ_SYSTEM']->userPublic()) return '';
2463 
2464  // arg = user attribute
2465  $arg = $matches[2];
2466  $keyword_stem = $matches[1];
2467  }
2468  }
2469 
2470  if (preg_match('/^(question)_([0-9]*)_(.*)$/', $keyword, $matches)) {
2471  if (isset($matches[1]) && isset($matches[2]) && isset($matches[3])) {
2472  // Keyword match example: question_123_response_form becomes (question, 123, response_form)
2473  $keyword_stem = $matches[1];
2474  $asset_id = $matches[2];
2475  $attr = $matches[3];
2476 
2477  // Grab the question keyword
2478  if (!empty($keyword_stem) && !empty($asset_id) && !empty($attr)) {
2479  return $this->getQuestionKeywordReplacement($asset_id, $attr);
2480  }
2481  }
2482  }
2483 
2484  // Question and result keywords for the "Thank You" bodycopy
2485  if (preg_match('/^(result)_([0-9]*)_(.*)$/', $keyword, $matches)) {
2486  if (isset($matches[1]) && isset($matches[2]) && isset($matches[3])) {
2487  // Keyword match example: result_123_answer becomes (result, 123, answer)
2488  $keyword_stem = $matches[1];
2489  $asset_id = $matches[2];
2490  $attr = $matches[3];
2491 
2492  // Grab the question result (answers etc.) keyword
2493  if (!empty($keyword_stem) && !empty($asset_id) && !empty($attr)) {
2494  return $this->getQuestionResultKeywordReplacement($asset_id, $attr);
2495  }
2496  }
2497  }
2498 
2499  // Question Group result keywords for the "Thank You" bodycopy
2500  if (preg_match('/^(question_group)_([0-9]*)_(.*)$/', $keyword, $matches)) {
2501  if (isset($matches[1]) && isset($matches[2]) && isset($matches[3])) {
2502  $keyword_stem = $matches[1];
2503  $asset_id = $matches[2];
2504  $attr = $matches[3];
2505 
2506  // Grab the question result (answers etc.) keyword
2507  if (!empty($keyword_stem) && !empty($asset_id) && !empty($attr)) {
2508  return $this->getQuestionGroupResultKeywordReplacement($asset_id, $attr);
2509  }
2510  }
2511  }
2512 
2513  // setup the replacement function call
2514  if (!empty($keyword_stem) && !empty($arg)) {
2515  $func_name = 'get'.ucwords_no_space($keyword_stem).'KeywordReplacement';
2516  if (method_exists($this, $func_name)) {
2517  return $this->$func_name($arg);
2518  }
2519  }
2520 
2521  // otherwise, fall back to the parent
2522  $replacement = parent::getKeywordReplacement($keyword);
2523  if (!is_null($replacement)) apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $this->id));
2524 
2525  return $replacement;
2526 
2527  }//end getKeywordReplacement()
2528 
2529 
2538  function getInteractiveQuestionKeywordReplacement($keyword)
2539  {
2540  $replacement = NULL;
2541 
2542  $question_pagination = $this->loadQuestionPagination();
2543  $current_result_page = $this->getCurrentPage();
2544 
2545  $question_id = key($question_pagination['pages_to_questions'][$current_result_page]);
2546  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_id, 'online_quiz_question_multichoice');
2547  if ((is_null($question))) return $replacement;
2548 
2549  switch ($keyword){
2550  case 'question_response_form':
2551  $current_status = $question_pagination['pages_to_status'][$current_result_page];
2552  if (strpos($current_status, 'feedback') !== FALSE){
2553  //print the question but with disabled fields and add special classes & tags.
2554  $replacement = $question->getResponseFormKeywordReplacement($this->attr('question_option_format'), TRUE);
2555  } else {
2556  $replacement = $question->getResponseFormKeywordReplacement($this->attr('question_option_format'));
2557  }
2558  break;
2559  case 'question_note':
2560  $replacement = $question->getQuestionNoteKeywordReplacement();
2561  break;
2562  case 'question_text':
2563  $replacement = $question->getQuestionTextKeywordReplacement();
2564  break;
2565  case 'question_number':
2566  $replacement = $question->getQuestionNumberKeywordReplacement();
2567  break;
2568  }
2569 
2570  return $replacement;
2571 
2572  }//end getInteractiveQuestionKeywordReplacement()
2573 
2574 
2582  function getInteractiveButtonKeywordReplacement()
2583  {
2584  $prefix = $this->getPrefix();
2585  $current_result_page = $this->getCurrentPage();
2586  $question_pagination = $this->loadQuestionPagination();
2587 
2588  ob_start();
2589 
2590  $current_status = $question_pagination['pages_to_status'][$current_result_page];
2591  if ($current_status == 'feedback_retry'){
2592  submit_button($prefix.'_submit_retry_button', $this->attr('retry_button_text'));
2593  } else if ($current_status == 'feedback_last'){
2594  submit_button($prefix.'_submit_button', $this->attr('submit_button_text'));
2595  } else if ($current_status == 'feedback_next'){
2596  submit_button($prefix.'_submit_next_button', $this->attr('next_button_text'));
2597  } else if (strpos($current_status, 'submit_question') === 0){
2598  //using this so we can have JS validation for the currently displayed question.
2599  echo $this->getSubmitButtonKeywordReplacement($prefix.'_submit_question_button', $this->attr('submit_question_button_text'));
2600  }
2601 
2602  return ob_get_clean();
2603 
2604 
2605  }//end getInteractiveButtonKeywordReplacement()
2606 
2607 
2615  function getScoreKeywordReplacement()
2616  {
2617  $replacement = $this->attr('score_format');
2618 
2619  if ($this->attr('interactive_mode')) {
2620 
2621  //Number of questions done so far
2622  $total_questions = 0;
2623  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['total_questions'])){
2624  $total_questions = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['total_questions'];
2625  }
2626 
2627  //Don't display the score if we havn't answered any questions yet. i.e first page.
2628  if ($total_questions < 1) return NULL;
2629 
2630  //Number of questions answered correctly.
2631  $correct_answers = 0;
2632  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['correct_answers'])){
2633  $correct_answers = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['correct_answers'];
2634  }
2635 
2636  //Number of question retries so far
2637  $retries = 0;
2638  if (!empty($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['retries'])){
2639  $retries = $_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_score']['retries'];
2640  }
2641 
2642  $total_questions_plus_retries = $total_questions + $retries;
2643 
2644  $replacement = str_replace('%correct_answers%', $correct_answers, $replacement);
2645  $replacement = str_replace('%total_questions%', $total_questions, $replacement);
2646  $replacement = str_replace('%total_questions_plus_retries%', $total_questions_plus_retries, $replacement);
2647  $replacement = str_replace('%total_retries%', $retries, $replacement);
2648  }
2649 
2650  //these only work in the thank you bodycopy.
2651  $question_results = $this->getResults();
2652  if (isset($question_results['totals']['points'])){
2653  $replacement = str_replace('%quiz_total_points%', $question_results['totals']['points'], $replacement);
2654  }
2655  if (isset($question_results['totals']['available_points'])){
2656  $replacement = str_replace('%quiz_total_available_points%', $question_results['totals']['available_points'], $replacement);
2657  }
2658 
2659  return $replacement;
2660 
2661  }//end getScoreKeywordReplacement()
2662 
2663 
2672  function getUserDetailsFormKeywordReplacement($questionid)
2673  {
2674  $user_details = $this->getUserDetails();
2675  $return = '';
2676  if (!empty($user_details)) {
2677  $return = array_get_index($user_details, $questionid, '');
2678  }
2679 
2680  return $return;
2681 
2682  }//end getUserDetailsFormKeywordReplacement()
2683 
2684 
2693  function getUserDetailsCurrentUserKeywordReplacement($keyword)
2694  {
2695  // ask the user asset to get the replacement for us
2696  return $GLOBALS['SQ_SYSTEM']->user->getKeywordReplacement('asset_'.$keyword);
2697 
2698  }//end getUserDetailsCurrentUserKeywordReplacement()
2699 
2700 
2712  function getSubmitButtonKeywordReplacement($field_name = '', $button_text = '')
2713  {
2714  if (empty($field_name)){
2715  $prefix = $this->getPrefix();
2716  $field_name = $prefix.'_submit_button';
2717  }
2718 
2719  $this->registerFormField($field_name);
2720 
2721  if (empty($button_text)) {
2722  $button_text = $this->attr('submit_button_text');
2723  }
2724 
2725  // construct the javascript here
2726  $button_js = '';
2727  ob_start();
2728  if ($this->attr('clientside_validation')) {
2729  ?>
2730  <script type="text/javascript">
2731  //<![CDATA[
2732 
2738  function print_error_summary()
2739  {
2740  form = document.getElementById("<?php echo $this->getPrefix(); ?>");
2741  if (!form) {
2742  return true;
2743  }
2744 
2745  var summary = "";
2746  var box_type = "";
2747  var error_stack = new Array();
2748 
2749  error_stack["errors"] = new Array();
2750  error_stack["warnings"] = new Array();
2751 
2752  <?php
2753  $errors = array_get_index($this->_error_stack, 'errors', Array());
2754  foreach ($errors as $idx => $error) {
2755  ?>
2756  error_stack["errors"][<?php echo $idx; ?>] = "<?php echo str_replace('"', '\"', $error); ?>";
2757  <?php
2758  }
2759 
2760  $warnings = array_get_index($this->_error_stack, 'warnings', Array());
2761  foreach ($warnings as $idx => $warning) {
2762  ?>
2763  error_stack["warnings"][<?php echo $idx; ?>] = "<?php echo str_replace('"', '\"', $warning); ?>";
2764  <?php
2765  }
2766 
2767  // getQuestionPagination allows us to skip
2768  // reloading and chunking the question assets
2769  $pages = array_get_index($this->loadQuestionPagination(), 'pages_to_questions', Array());
2770  $current_questions = array_get_index($pages, $this->getCurrentPage(), Array());
2771  foreach ($current_questions as $assetid => $type) {
2772  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
2773  if (!is_null($question)) {
2774  echo $question->getValidationJS();
2775  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
2776  }
2777  }
2778  ?>
2779 
2780  if (error_stack["errors"].length) {
2781  summary += "The following errors are present:\n";
2782  for (var idx in error_stack["errors"]) {
2783  summary += " - " + error_stack["errors"][idx] + "\n";
2784  }
2785  summary += "\n";
2786  box_type = "alert";
2787  }
2788 
2789  if (error_stack["warnings"].length > 0) {
2790  summary += "The following warnings are present:\n";
2791  for (var idx in error_stack["warnings"]) {
2792  summary += " - " + error_stack["warnings"][idx] + "\n";
2793  }
2794  summary += "\n";
2795  if (!box_type.length) {
2796  summary += "\nAre you sure you want to continue?";
2797  box_type = "confirm";
2798  }
2799  }
2800 
2801  if (box_type == "alert") {
2802  alert(summary);
2803  return false;
2804  } else if (box_type == "confirm") {
2805  return confirm(summary);
2806  } else {
2807  return true;
2808  }
2809 
2810  }//end function print_error_summary()
2811  //]]>
2812  </script>
2813  <?php
2814 
2815  $button_js = 'return print_error_summary();';
2816  }//end if (clientside_validation)
2817 
2818  submit_button($field_name, $button_text, $button_js);
2819  return ob_get_clean();
2820 
2821  }//end getSubmitButtonKeywordReplacement()
2822 
2823 
2830  function getErrorListKeywordReplacement()
2831  {
2832  $errors = array_get_index($this->_error_stack, 'errors', Array());
2833  if (!empty($errors)) {
2834  ob_start();
2835  ?><ul><?php
2836  foreach ($errors as $error) {
2837  ?><li><?php echo $error; ?></li><?php
2838  }
2839  ?></ul><?php
2840  return ob_get_clean();
2841  }
2842 
2843  return '';
2844 
2845  }//end getErrorListKeywordReplacement()
2846 
2847 
2854  function getWarningListKeywordReplacement()
2855  {
2856  $warnings = array_get_index($this->_error_stack, 'warnings', Array());
2857  if (!empty($warnings)) {
2858  ob_start();
2859  ?><ul><?php
2860  foreach ($warnings as $warning) {
2861  ?><li><?php echo $warning; ?></li><?php
2862  }
2863  ?></ul><?php
2864  return ob_get_clean();
2865  }
2866 
2867  return '';
2868 
2869  }//end getWarningListKeywordReplacement()
2870 
2871 
2878  function getQuestionRangeKeywordReplacement()
2879  {
2880  $question_pagination = $this->loadQuestionPagination();
2881  $pages_to_questions = array_get_index($question_pagination, 'pages_to_questions', Array());
2882  $questions_to_pages = array_get_index($question_pagination, 'questions_to_pages', Array());
2883  $current_page_assets = array_get_index($pages_to_questions, $this->getCurrentPage(), Array());
2884 
2885  $first = NULL;
2886  $last = NULL;
2887  foreach ($current_page_assets as $assetid => $type) {
2888  if (is_null($first)) {
2889  $first_asset = array_get_index($questions_to_pages, $assetid, Array());
2890  $first = array_get_index($first_asset, 'list_position', NULL);
2891  }
2892 
2893  // keep grabbing items, in case it's the last one
2894  $last_asset = array_get_index($questions_to_pages, $assetid, Array());
2895  $last = array_get_index($last_asset, 'list_position', NULL);
2896  }
2897 
2898  if (is_null($first) || is_null($last)) {
2899  return NULL;
2900  }
2901 
2902  return $first.' - '.$last;
2903 
2904  }//end getQuestionRangeKeywordReplacement()
2905 
2906 
2913  function getQuestionCountKeywordReplacement()
2914  {
2915  if ($this->attr('interactive_mode')){
2916  $question_pagination = $this->loadQuestionPagination();
2917  //one question per page so number of questions = number of pages.
2918  return count($question_pagination['pages_to_questions']);
2919  } else {
2920  return count($this->getQuestions());
2921  }
2922 
2923  }//end getQuestionCountKeywordReplacement()
2924 
2925 
2936  function getResultsTableKeywordReplacement($show_columns=NULL, $show_totals=TRUE, $html=TRUE)
2937  {
2938  if (!is_array($show_columns)) {
2939  // set to defaults
2940  $show_columns = Array(
2941  'question_num' => translate('online_quiz_results_header_question_num'),
2942  'points' => translate('online_quiz_results_header_points'),
2943  'available_points' => translate('online_quiz_results_header_available_points'),
2944  );
2945  }
2946 
2947  // slight hack - tabs show up when printing plain text, but not when displaying as html
2948  $available_columns = Array(
2949  'question_num' => translate('online_quiz_results_header_question_num'),
2950  'assetid' => translate('online_quiz_results_header_assetid'),
2951  'question_text' => translate('online_quiz_results_header_question_text'),
2952  'value' => translate('online_quiz_results_header_value'),
2953  'summary' => translate('online_quiz_results_header_summary'),
2954  'correct_value' => translate('online_quiz_results_header_correct_value'),
2955  'correct_summary' => translate('online_quiz_results_header_correct_summary'),
2956  'points' => translate('online_quiz_results_header_points'),
2957  'available_points' => translate('online_quiz_results_header_available_points'),
2958  );
2959 
2960  // plain-text column formatting:
2961  $column_name_suffix = ': ';
2962  $longest_column_length = 0;
2963 
2964  $columns = Array();
2965  foreach ($show_columns as $column => $column_title) {
2966  if (isset($available_columns[$column])) {
2967  $columns[$column] = $column_title;
2968  if (strlen($column_title) > $longest_column_length) {
2969  $longest_column_length = strlen($column_title);
2970  }
2971  }
2972  }
2973 
2974  $results = $this->getResults();
2975  $return = '';
2976  if (!empty($results) && !empty($columns)) {
2977  ob_start();
2978 
2979  // HACK: hardcode a short name for the CSS class, to get around problems with long class names and Mac IE
2980  $css_class_prefix = str_replace('_', '-', 'quiz_res');
2981 
2982  if ($html) {
2983  ?>
2984  <table class="<?php echo $css_class_prefix; ?>" id="<?php echo $css_class_prefix.'-'.$this->id; ?>">
2985  <tr>
2986  <?php
2987 
2988  $column_counter = 1;
2989  foreach ($columns as $column => $column_name) {
2990  ?><th class="<?php echo $css_class_prefix.'-c'.$column_counter; ?>"><?php echo $column_name; ?></th><?php
2991  $column_counter++;
2992  }
2993 
2994  ?>
2995  </tr>
2996  <?php
2997  }
2998 
2999  $row_counter = 1;
3000  foreach (array_get_index($results, 'questions', Array()) as $question_num => $question_results) {
3001  if ($html) {
3002  ?>
3003  <tr>
3004  <?php
3005  }
3006 
3007  $column_counter = 1;
3008  foreach ($columns as $column => $column_name) {
3009  $alignment_padding = '';
3010  if ($html) {
3011  ?><td class="<?php echo $css_class_prefix.'-c'.$column_counter; ?> <?php echo $css_class_prefix.'-r'.$row_counter; ?>"><?php
3012  } else {
3013  // pad
3014  echo $column_name;
3015  echo $column_name_suffix;
3016  for ($ii = 0; $ii < ($longest_column_length - strlen($column_name)); $ii++) {
3017  $alignment_padding .= ' ';
3018  echo ' ';
3019  }
3020  for ($ii = 0; $ii < strlen($column_name.$column_name_suffix); $ii++) {
3021  $alignment_padding .= ' ';
3022  }
3023  }
3024 
3025  ob_start();
3026 
3027  switch ($column) {
3028  case 'question_num':
3029  echo $question_num;
3030  break;
3031 
3032  case 'question_text':
3033  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_results['assetid']);
3034  if (!is_null($question)) {
3035  ob_start();
3036  echo $question->getQuestionText();
3037  $question_text = ob_get_clean();
3038  if (!$html) {
3039  // a hack to make bodycopy-derived question text appear normal when sending a plain-text email
3040  $question_text = trim(strip_tags($question_text));
3041  $question_text = html_entity_decode($question_text);
3042 
3043  // indent each line
3044  $question_text = str_replace("\n", "\n".$alignment_padding, $question_text);
3045  }
3046  echo $question_text;
3047  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
3048  }
3049  break;
3050 
3051  case 'assetid':
3052  case 'value':
3053  case 'correct_value':
3054  case 'points':
3055  case 'available_points':
3056  echo array_get_index($question_results, $column);
3057 
3058  break;
3059 
3060  case 'summary':
3061  case 'correct_summary':
3062  if ($html) {
3063  // explode into <li>
3064  $summary_text = array_get_index($question_results, $column, '');
3065  if (!empty($summary_text)) {
3066  $summary_array = explode("\n", array_get_index($question_results, $column, ''));
3067  if (!empty($summary_array)) {
3068  ?><ul><?php
3069  foreach ($summary_array as $summary_item) {
3070  ?><li><?php
3071  echo $summary_item;
3072  ?></li><?php
3073  }
3074  ?></ul><?php
3075  }
3076  }
3077  } else {
3078  // indent each new line
3079  echo str_replace("\n", "\n".$alignment_padding, array_get_index($question_results, $column));
3080  }
3081  break;
3082  }//end switch
3083 
3084  $cell_contents = ob_get_clean();
3085  if ($html && !strlen($cell_contents)) {
3086  echo translate('online_quiz_results_empty_cell');
3087  } else {
3088  echo $cell_contents;
3089  }
3090 
3091  if ($html) {
3092  ?></td><?php
3093  } else {
3094  echo "\n";
3095  }
3096  $column_counter++;
3097  }//end foreach ($columns)
3098 
3099  if ($html) {
3100  ?>
3101  </tr>
3102  <?php
3103  } else {
3104  echo "\n";
3105  }
3106 
3107  $row_counter++;
3108  }//end foreach ($results['questions'])
3109 
3110  // check if either points or available_points is set, if so
3111  // replace question_num, assetid or the column to the left
3112  // of the first points column with "Total"
3113  if ($show_totals) {
3114 
3115  $points_columns_counter = 0;
3116  $points_column = FALSE;
3117  $available_points_column = FALSE;
3118  if (isset($columns['points'])) {
3119  $points_columns_counter++;
3120  $points_column = TRUE;
3121  }
3122  if (isset($columns['available_points'])) {
3123  $points_columns_counter++;
3124  $available_points_column = TRUE;
3125  }
3126 
3127  // if we have at least one point column
3128  if ($points_columns_counter) {
3129  if ($html) {
3130 
3131  ?><tr><?php
3132  $column_counter = 0;
3133  foreach ($columns as $column => $column_name) {
3134  ?><td class="<?php echo $css_class_prefix.'_c'.($column_counter + 1); ?>"><?php
3135 
3136  // is this the first column, and are there enough columns to support a "Total" label?
3137  // also make sure the first column isn't a points column
3138  if ($column == 'points' || $column == 'available_points') {
3139  echo $results['totals'][$column];
3140  } else {
3141  if ($column_counter == 0 && count($columns) > $points_columns_counter + 1) {
3142  // print "Totals"
3143  echo translate('online_quiz_results_header_totals');
3144  } else {
3145  // otherwise print an "empty" cell
3146  echo translate('online_quiz_results_empty_cell');
3147  }
3148  }
3149 
3150  $column_counter++;
3151 
3152  ?></td><?php
3153  }//end for (columns)
3154  ?></tr><?php
3155 
3156  } else {
3157  // plain-text column formatting:
3158  $longest_column_length = 0;
3159  $totals_columns = Array(
3160  translate('online_quiz_results_header_total_points'),
3161  translate('online_quiz_results_header_total_available_points'),
3162  );
3163  foreach ($totals_columns as $column_name) {
3164  if (strlen($column_name) > $longest_column_length) {
3165  $longest_column_length = strlen($column_name);
3166  }
3167  }
3168 
3169  foreach ($columns as $column => $column_name) {
3170  if ($column == 'points' || $column == 'available_points') {
3171  echo translate('online_quiz_results_header_total_'.$column);
3172  echo $column_name_suffix;
3173  for ($ii = 0; $ii < ($longest_column_length - strlen($column_name)); $ii++) {
3174  echo ' ';
3175  }
3176  echo $results['totals'][$column];
3177  echo "\n";
3178  }
3179  }
3180 
3181  }
3182  }//end if ($points_columns_counter)
3183  }//end if ($show_totals)
3184 
3185  if ($html) {
3186  ?>
3187  </table>
3188  <?php
3189  }
3190  $return = ob_get_clean();
3191  }//end if (!empty($results) && !empty($columns))
3192 
3193  return $return;
3194 
3195  }//end getResultsTableKeywordReplacement()
3196 
3197 
3206  function getResultsTableFullKeywordReplacement($html=TRUE)
3207  {
3208  $columns = Array(
3209  'question_num' => translate('online_quiz_results_header_question_num'),
3210  'question_text' => translate('online_quiz_results_header_question_text'),
3211  'value' => translate('online_quiz_results_header_value'),
3212  'summary' => translate('online_quiz_results_header_summary'),
3213  'correct_value' => translate('online_quiz_results_header_correct_value'),
3214  'correct_summary' => translate('online_quiz_results_header_correct_summary'),
3215  'points' => translate('online_quiz_results_header_points'),
3216  'available_points' => translate('online_quiz_results_header_available_points'),
3217  'response_supplement' => translate('online_quiz_results_header_available_points'),
3218  );
3219  return $this->getResultsTableKeywordReplacement($columns, TRUE, $html);
3220 
3221  }//end getResultsTableFullKeywordReplacement()
3222 
3223 
3230  function getResultsTableFullTextKeywordReplacement()
3231  {
3232  return $this->getResultsTableFullKeywordReplacement(FALSE);
3233 
3234  }//end getResultsTableFullTextKeywordReplacement()
3235 
3236 
3245  function getResultsTableSimpleKeywordReplacement($html=TRUE)
3246  {
3247  $columns = Array(
3248  'question_num' => translate('online_quiz_results_header_question_num'),
3249  'value' => translate('online_quiz_results_header_value'),
3250  'correct_value' => translate('online_quiz_results_header_correct_value'),
3251  'points' => translate('online_quiz_results_header_points'),
3252  'available_points' => translate('online_quiz_results_header_available_points'),
3253  );
3254  return $this->getResultsTableKeywordReplacement($columns, TRUE, $html);
3255 
3256  }//end getResultsTableSimpleKeywordReplacement()
3257 
3258 
3265  function getResultsTableSimpleTextKeywordReplacement()
3266  {
3267  return $this->getResultsTableSimpleKeywordReplacement(FALSE);
3268 
3269  }//end getResultsTableSimpleTextKeywordReplacement()
3270 
3271 
3281  function getQuestionKeywordReplacement($question_asset_id, $keyword)
3282  {
3283  $replacement = '%question_'.$question_asset_id.'_'.$keyword.'%';
3284 
3285  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_asset_id, 'online_quiz_question_multichoice');
3286  if ((!is_null($question)) && ($question->readAccess())) {
3287  $replacement = $question->getKeywordReplacement($keyword);
3288  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
3289  }
3290 
3291  return $replacement;
3292 
3293  }//end getQuestionKeywordReplacement()
3294 
3295 
3305  function getQuestionResultKeywordReplacement($question_asset_id, $keyword)
3306  {
3307  $replacement = '%result_'.$question_asset_id.'_'.$keyword.'%';
3308 
3309  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_asset_id, 'online_quiz_question_multichoice');
3310  if ((!is_null($question)) && ($question->readAccess())) {
3311  $question_results = $question->getResults();
3312 
3313  // Some replacements are not provided in the question results, so we have to ask for these...
3314  $keyword_replaced = FALSE;
3315 
3316  switch ($keyword) {
3317  case 'question_number': $replacement = $question->getPosition();
3318  $keyword_replaced = TRUE;
3319  break;
3320 
3321  case 'question_text': $replacement = $question->getKeywordReplacement('question_text');
3322  $keyword_replaced = TRUE;
3323  break;
3324 
3325  case 'question_text_plain': $replacement = trim(strip_tags($question->getKeywordReplacement('question_text')));
3326  $keyword_replaced = TRUE;
3327  break;
3328 
3329  case 'question_note': $replacement = $question->getQuestionNote();
3330  $keyword_replaced = TRUE;
3331  break;
3332 
3333  case 'question_note_plain': $replacement = trim(strip_tags($question->getQuestionNote()));
3334  $keyword_replaced = TRUE;
3335  break;
3336 
3337  case 'answer': $keyword = 'value';
3338  break;
3339 
3340  case 'answer_text': $keyword = 'summary';
3341  break;
3342 
3343  case 'answer_text_plain': if (isset($question_results['summary'])) {
3344  $replacement = trim(strip_tags($question_results['summary']));
3345  $keyword_replaced = TRUE;
3346  }
3347  break;
3348 
3349  case 'correct_answer': $keyword = 'correct_value';
3350  break;
3351 
3352  case 'correct_answer_text': $keyword = 'correct_summary';
3353  break;
3354 
3355  case 'correct_answer_text_plain': if (isset($question_results['correct_summary'])) {
3356  $replacement = trim(strip_tags($question_results['correct_summary']));
3357  $keyword_replaced = TRUE;
3358  }
3359  break;
3360 
3361  case 'response_supplement': $replacement = $question->getResponseSupplement();
3362  break;
3363 
3364  case 'response_supplement_text': $replacement = trim(strip_tags($question->getResponseSupplement()));
3365  break;
3366  }//end switch
3367 
3368  // This keyword is obtained from the results...
3369  if (!$keyword_replaced) {
3370  if (isset($question_results[$keyword])) {
3371  $replacement = $question_results[$keyword];
3372  }
3373  }
3374 
3375  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($question);
3376  }//end if
3377 
3378  return $replacement;
3379 
3380  }//end getQuestionResultKeywordReplacement()
3381 
3382 
3391  function getQuizTotalPointsKeywordReplacement()
3392  {
3393  $total_points = '';
3394 
3395  $quiz_result = $this->getResults();
3396  $total_points = $quiz_result['totals']['points'];
3397 
3398  return $total_points;
3399 
3400  }//end getQuizTotalPointsKeywordReplacement()
3401 
3402 
3411  function getQuizTotalAvailablePointsKeywordReplacement()
3412  {
3413  $total_points = '';
3414 
3415  $quiz_result = $this->getResults();
3416  $total_points = $quiz_result['totals']['available_points'];
3417 
3418  return $total_points;
3419 
3420  }//end getQuizTotalAvailablePointsKeywordReplacement()
3421 
3422 
3432  function getQuestionGroupResultKeywordReplacement($question_group_asset_id, $keyword)
3433  {
3434  $replacement = '%question_group_'.$question_group_asset_id.'_'.$keyword.'%';
3435 
3436  $question_group = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_group_asset_id, 'online_quiz_question_group');
3437  if ((!is_null($question_group)) && ($question_group->readAccess())) {
3438  $group_questions = array_keys($question_group->getQuestions());
3439  $quiz_questions = array_keys($this->getQuestions());
3440 
3441  // Add up the totals for the questions which have been asked
3442  $questions_asked = array_intersect($quiz_questions, $group_questions);
3443 
3444  // Free memory
3445  $group_questions = NULL;
3446  $quiz_questions = NULL;
3447 
3448  $section_total = 0;
3449  foreach ($questions_asked as $question_asset_id) {
3450  $question = $GLOBALS['SQ_SYSTEM']->am->getAsset($question_asset_id);
3451  if (!is_null($question)) {
3452  $section_total += $question->getPoints();
3453  }
3454  }
3455 
3456  $score_category = $question_group->getScoreCategoryForScore($section_total);
3457  if (!is_null($score_category)) {
3458  switch ($keyword) {
3459  case 'score_category_name': $replacement = $score_category['name'];
3460  break;
3461 
3462  case 'score_category_supplement': $replacement = $score_category['content'];
3463 
3464  // replace internal links to pages and images with their full URLs
3465  $matches = Array();
3466  preg_match_all('|\./\?a=([0-9]+)|', $replacement, $matches);
3467  $urls = $GLOBALS['SQ_SYSTEM']->am->getAssetURL(array_unique($matches[1]));
3468  foreach ($urls as $assetid => $url) {
3469  $replacement = preg_replace('|\./\?a='.$assetid.'([^0-9])|', $url.'\\1', $replacement);
3470  }
3471  break;
3472 
3473  case 'score_category_supplement_text': $replacement = strip_tags($score_category['content']);
3474  break;
3475  }
3476  }
3477  }//end if
3478 
3479  return $replacement;
3480 
3481  }//end getQuestionGroupResultKeywordReplacement()
3482 
3483 
3490  function _getFormSubmitMethod()
3491  {
3492  return 'post';
3493 
3494  }//end _getFormSubmitMethod()
3495 
3496 
3497 //-- Listing Functions --//
3498 
3499 
3508  function getCurrentPage()
3509  {
3510  if ($this->attr('interactive_mode')){
3511  //using sessions
3512  $result_page = 1;
3513  if (isset($_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_page'])) {
3514  $result_page = (int)$_SESSION[SQ_ONLINE_QUIZ_SESSION_VAR]['quiz'][$this->id]['interactive_page'];
3515  }
3516 
3517  if ($result_page <= 0) $result_page = 1;
3518 
3519  return $result_page;
3520 
3521  } else {
3522  $asset_result_page_var = 'quiz_'.$this->id.'_result_page';
3523  $generic_result_page_var = 'result_page';
3524 
3525  // have [assetid]_result_page take precedence over result_page
3526  $result_page = 1;
3527  if (isset($_REQUEST[$generic_result_page_var])) {
3528  $result_page = (int)$_REQUEST[$generic_result_page_var];
3529  }
3530  if (isset($_REQUEST[$asset_result_page_var])) {
3531  $result_page = (int)$_REQUEST[$asset_result_page_var];
3532  }
3533 
3534  if ($result_page <= 0) $result_page = 1;
3535 
3536  return $result_page;
3537  }
3538 
3539  }//end getCurrentPage()
3540 
3541 
3555  function &getPageAssets(&$children, &$replacements, $result_page=NULL)
3556  {
3557  // the list is sorted and positions set, now we can split to groups
3558  if ($this->attr('group_by') == 'grouped') {
3559  $this->groupAssetsRecursively($this->attr('asset_grouping'), $children, $children);
3560  $this->sortGroups($this->attr('asset_grouping'), $children, $children);
3561  }
3562 
3563  $num_per_page = $this->_getNumPerPage();
3564 
3565  if (is_null($result_page) || $result_page <= 0) {
3566  $result_page = $this->getCurrentPage();
3567  }
3568  $result_page = (int)$result_page;
3569 
3570  // get our page chunk here
3571  $todo =& $this->getChunk($children, $replacements, Array(), $result_page, $num_per_page);
3572 
3573  //Bug #5283: Need to extract the page ids from the grouping structure
3574  if ($this->attr('group_by') == 'grouped') {
3575  for ($i = 0; $i < count($this->attr('asset_grouping')); $i++){ //group levels
3576  $todo_pages = Array();
3577  foreach($todo as $group_key => $group_data){
3578  foreach($group_data as $key => $value){
3579  $todo_pages[$key] = $value;
3580  }
3581  }
3582  $todo = $todo_pages; //$todo will be the final traversal
3583  }
3584  }
3585 
3586  return $todo;
3587 
3588  }//end getPageAssets()
3589 
3590 
3591 }//end class
3592 
3593 ?>