CMS  Version 3.9
abstract_questionnaire_create_manager.inc
Go to the documentation of this file.
1 <?php
7 Fakoli::using("questionnaire", "text_lookup");
8 Fakoli::usingFile("/cms/components/questionnaire/abstract_questionnaire_manager.inc");
9 
10 /*
11  * Provides an interface between a custom questionnaire
12 * component and the QuestionnaireForm and Question Renderers
13 * and Results View classes. Revies an object of class
14 * Question or Survey or other DataItem class that can
15 * access the questions and answers.
16 *
17 * This class defines the methods that are required for components that
18 * use the QuestionnaireForm for the user survey-create interface.
19 *
20 * Instantiating this class requires the questionnaire or
21 * survey object.
22 */
23 abstract class AbstractQuestionnaireCreateManager extends AbstractQuestionnaireManager
24 {
25  var $item; //< The questionnaire or survey object
26  var $validation_msg;
27 
28  function AbstractQuestionnaireCreateManager($item)
29  {
30  $this->item = $item;
31  }
32 
36  abstract function getComponentName();
37 
41  abstract function getQuestionClass();
42 
49  abstract function getXrefClass();
50 
54  function getTitleField()
55  {
56  return "title";
57  }
58 
67  function getAnswers()
68  {
69  return null;
70  }
71 
72  function getQuestionDeleteHandler()
73  {
74  return "question_delete";
75  }
76 
77  function getQuestionRemoveHandler()
78  {
79  return "question_remove";
80  }
81 
82  function getReorderHandler()
83  {
84  return "reorder_questions";
85  }
86 
87  function getClassName()
88  {
89  return $this->item->prettifyClassName();
90  }
91 
99  function getQuestionListIdentifier()
100  {
101  return "{$this->item->table}_questions";
102  }
103 
109  function getQuestionForm()
110  {
111  return "{$this->item->table}_question_form";
112  }
113 
120  function getQuestionnaireFormIdentifier()
121  {
122  if(!$this->item)
123  {
124  return;
125  }
126  return "{$this->item->table}_form";
127  }
128 
134  function formatQuestionLink($question)
135  {
136  $question_form_id = $this->getQuestionForm();
137  $qPk = $question->getPrimaryKey();
138 
139  return $question->format("<a href='{$question_form_id}?{$qPk}={{$qPk}}'>{question}</a>\n");
140  }
141 
149  function cloneQuestionnaire($title_field, $cloneFields = array())
150  {
151  $src = $this->item;
152  $itemClass = get_class($src);
153  $dst = new $itemClass();
154 
155  if(is_callable(array($dst, setDefaults)))
156  {
157  $dst->setDefaults();
158  }
159  $dst->$title_field = $src->$title_field;
160 
161  if(count($cloneFields) > 0)
162  {
163  foreach($cloneFields as $field)
164  {
165  $dst->$field = $src->$field;
166  }
167  }
168 
169  $dst->save();
170 
171  $questions = $this->getQuestions();
172  if(count($questions) == 0) return;
173 
174  $xref_class = $this->getXrefClass();
175  $sort_field = $this->getSortField();
176  $sort_order = 1;
177 
178  $qPk = $questions[0]->getPrimaryKey();
179  $itemPk = $dst->getPrimaryKey();
180 
185  foreach($questions as $question)
186  {
187  $obj = ($xref_class) ? new $xref_class() : clone $question;
188  $obj->$qPk = ($xref_class) ? $question->$qPk : null;
189  if($xref_class)
190  {
191  $obj->$sort_field = $sort_order;
192  }
193  $obj->$itemPk = $dst->$itemPk;
194  $obj->save();
195  $sort_order++;
196  }
197 
198  return $dst;
199  }
200 
205  function deleteQuestionnaire()
206  {
207  $item = $this->item;
208 
209  if(!$item->allowDelete())
210  {
211  return false;
212  }
213  $itemPk = $item->getPrimaryKey();
214  $obj = $this->getSortableObj();
215 
216  $questions = $this->getQuestions();
217  $qPk = $this->getQuestionKey();
218 
219  $tx = new DataTransaction();
220 
221  try
222  {
223  if(count($questions) > 0)
224  {
225  // Either deleting all questions linked to the questionnaire or all
226  // question xrefs linked to the questionnaire
227  $obj->delete("WHERE {$itemPk}={$item->$itemPk}");
228  foreach($questions as $question)
229  {
230  $question->joinTransaction($tx);
231  $this->deleteOrphanQuestion($question);
232  }
233  }
234  $item->joinTransaction($tx);
235  $item->delete();
236  $tx->commit();
237  }
238  catch(Exception $e)
239  {
240  Fakoli::end($e->getMessage());
241  }
242 
243  return true;
244  }
245 
246  function logicalDeleteQuestionnaire()
247  {
248  $this->item->filter = null;
249 
250  if($this->item->hasField("deleted"))
251  {
252  $this->item->deleted = true;
253  $this->item->filter = new InclusionFilter("deleted");
254  $this->item->save();
255  }
256  }
257 
258 
265  function removeQuestionXref($xref_id)
266  {
267  $item = $this->item;
268  $itemPk = $item->getPrimaryKey();
269  $xref_class = $this->getXrefClass();
270 
271  try
272  {
273  $xref = new $xref_class($xref_id);
274  $xrefPk = $xref->getPrimaryKey();
275 
276  if ($item->$itemPk != $xref->$itemPk)
277  {
278  throw new FakoliException("QuestionnaireManager removeQuestion Data Mismatch");
279  }
280  $xref->delete("WHERE $xrefPk={$xref_id}");
281  $this->reNumberQuestions();
282  }
283  catch(Exception $e)
284  {
285  Fakoli::end($e->getMessage());
286  }
287 
288  $qPk = $this->getQuestionKey();
289  $qClass = $this->getQuestionClass();
290  $question = new $qClass($xref->$qPk);
291  $this->deleteOrphanQuestion($question);
292  }
293 
300  function removeQuestion($question_id)
301  {
302  $item = $this->item;
303  $itemPk = $item->getPrimaryKey();
304  $qClass = $this->getQuestionClass();
305  $question = new $qClass($question_id);
306  $qPk = $question->getPrimaryKey();
307  $xref = $this->getSortableObj();
308 
309  try
310  {
311  $xref->delete("WHERE {$itemPk}={$item->$itemPk} AND {$qPk}={$question->$qpK}");
312  $this->reNumberQuestions();
313  }
314  catch(Exception $e)
315  {
316  Fakoli::end($e->getMessage());
317  }
318 
319  $this->deleteOrphanQuestion($question);
320  }
321 
330  function saveQuestionXref($question, $sort_order = 0)
331  {
332  $item = $this->item;
333  $xref_class = $this->getXrefClass();
334  $itemPk = $this->getPrimaryKey();
335  $qPk = $question->getPrimaryKey();
336 
337  if(!$xref_class)
338  {
339  return;
340  }
341 
342  $xref = new $xref_class();
343 
344  if($xref->exists("WHERE {$itemPk}={$item->$itemPk} AND {$qPk}={$question->$qPk}"))
345  {
346  return;
347  }
348 
349  $xref->$itemPk = $item->$itemPk;
350  $xref->sort_order = (!$sort_order) ? $this->getNextSortOrder() : $sort_order;
351  $xref->$qPk = $question->$qPk;
352  $xref->save();
353  }
354 
361  function deleteOrphanQuestion($question)
362  {
363  $xrefClass = $this->getXrefClass();
364  $qPk = $this->getQuestionKey();
365 
366  if(!$xrefClass)
367  {
368  return;
369  }
370 
371  $count = Query::create($xrefClass, "WHERE {$qPk}=:{$qPk}")
372  ->bind(":{$qPk}", $question->$qPk)
373  ->executeValue("COUNT(1)");
374 
375  if($count == 0)
376  {
377  $question->delete();
378  }
379  }
380 
389  function deleteQuestion($question_id)
390  {
391  $item = $this->item;
392  $qClass = $this->getQuestionClass();
393  $itemPk = $item->getPrimaryKey();
394  $question = new $qClass($question_id);
395  $qPk = $question->getPrimaryKey();
396  $item_class = $this->item->prettifyClassName();
397  $question_class = $question->prettifyClassName();
398 
399  if(!$question->allowDelete())
400  {
401  Fakoli::end("Delete not permitted for question id {$question->$qPk} {$question->question}");
402  }
403 
404  try
405  {
406  if ($item->$itemPk != $question->$itemPk)
407  {
408  throw new FakoliException("QuestionnaireManager deleteQuestion Data Mismatch $item_class {$itemPk} {$item->$itemPk} does not match question $question_class {$itemPk} {$question->$itemPk} {$qPk} {$question->$qPk}");
409  }
410 
411  $question->delete();
412  $this->reNumberQuestions();
413  }
414  catch(Exception $e)
415  {
416  Fakoli::end($e->getMessage());
417  }
418 
419  }
420 
421 
431  function reOrderQuestions()
432  {
433  global $_POST;
434 
435  $item = $this->item;
436  $sortableClass = $this->getSortableClass();
437  $sortableObj = new $sortableClass();
438  $pk = $sortableObj->getPrimaryKey();
439  $itemPk = $item->getPrimaryKey();
440  $sort_field = $this->getSortField();
441 
442  // reorder
443  foreach($_POST as $name => $sort_order)
444  {
445  if (!strncmp($name, "question_", 9))
446  {
447  $id = substr($name, 9);
448  checkNumeric($id);
449  checkNumeric($sort_order);
450 
451  $sortableObj = new $sortableClass($id);
452  $sortableObj->filter = new InclusionFilter($pk, $sort_field);
453  $sortableObj->$itemPk = $item->$itemPk;
454  $sortableObj->$pk = $id;
455  $sortableObj->$sort_field = $sort_order;
456  $sortableObj->save();
457  }
458  }
459 
460  $this->reNumberQuestions();
461  }
462 
463 
473  function reNumberQuestions()
474  {
475  $item = $this->item;
476  $sortableClass = $this->getSortableClass();
477  $sortableObj = new $sortableClass();
478  $pk = $sortableObj->getPrimaryKey();
479  $sort_field = $this->getSortField();
480 
481  $objs = $this->getSortableItems();
482  $sort_order = 1;
483 
484  foreach($objs as $obj)
485  {
486  $obj->filter = new InclusionFilter($sort_field);
487  $obj->$sort_field = $sort_order;
488  $obj->save();
489  $sort_order++;
490  }
491  }
492 
493 
503  function getQuestionSets()
504  {
505  $itemPk = $this->item->getPrimaryKey();
506  $qPk = $this->getQuestionKey();
507  $xref = $this->getSortableObj();
508  $xref_class = get_class($xref);
509 
510  if(!$xref)
511  {
512  return;
513  }
514 
515  $query = GroupedQuery::create($xref_class, "", "{$itemPk}");
516  if($this->item->$itemPk)
517  {
518  $query->constraints = "WHERE {$qPk} NOT IN (SELECT {$qPk} FROM {$xref->table} WHERE {$itemPk} = :{$itemPk})";
519  $query->bind(":{$itemPk}", $this->item->$itemPk);
520  }
521 
522  return $query->execute();
523  }
524 
539  function buildQuestionSelectForm($items = null)
540  {
541  $class = $this->getClassName();
542  $this->item->filter = new InclusionFilter();
543  $form = new AutoForm($this->item);
544  $itemPk = $this->getPrimaryKey();
545  $title_field = $this->getTitleField();
546 
547  if(!$items)
548  {
549  $items = $this->getQuestionnaires();
550  }
551 
552  $questionSets = $this->getQuestionSets();
553 
554  $table = new GroupedDataListView($questionSets, "QuestionSets");
555  $table->mode = "fixed";
556  $table->selector();
557  $table->column("Question", "{Question.question}", false)
558  ->column("Options", array(QuestionTableHelper, formatOptions), false)
559  ->column("Required", array(QuestionTableHelper, formatRequired), false, "text-align: center")
560  ;
561 
562  $table->emptyMessage = "There are no $class questions to select.";
563 
564  foreach(array_keys($questionSets) as $item_id)
565  {
566  if(!array_key_exists($item_id, $items)) continue;
567 
568  $item = $items[$item_id];
569  $table->group($item->$title_field, $item_id);
570  }
571 
572  $instructions = TextLookup::getText("question_select_instructions");
573  $questionSelect = new DataListFieldRenderer($form, $table, "survey_question_select", "<h3>Select Survey Questions</h3>$instructions");
574  $questionSelect->hideLabel = true;
575  $form->submitLabel = "Select";
576  $form->buttons_at_top = true;
577 
578  if(!count($questionSets))
579  {
580  $form->readOnlyForm = true;
581  }
582 
583  $questionSelect->onPostProcess = array($this, saveSelectedQuestions);
584 
585  return $form;
586  }
587 
588  /*
589  * Called by DataListFieldRenderer onPostProcess
590  * from the question select page.
591  *
592  * Save questions selected
593  *
594  * It is possible for the user to select the same question
595  * if that question is part of 2 or more surveys.
596  */
597  function saveSelectedQuestions($questionSelect, $field)
598  {
599  global $_POST;
600 
601  $xref_class = $this->getXrefClass();
602  if(!$xref_class)
603  {
604  return;
605  }
606 
607  $xref = new $xref_class();
608  $xrefPk = $xref->getPrimaryKey();
609  $questionClass = $this->getQuestionClass();
610  $qPk = $this->getQuestionKey();
611 
612  $questionIds = $_POST[$xrefPk];
613 
614  if(isset($questionIds) AND count($questionIds) > 0)
615  {
616  $list = implode(",", array_values($questionIds));
617  // Given the set of xref ids selected, retrieve the questions from the question table
618  $questions = Query::create($questionClass, "WHERE {$qPk} IN (SELECT {$qPk} FROM {$xref->table} WHERE {$xrefPk} IN ($list))")
619  ->execute();
620 
621  if(count($questions) > 0)
622  {
623  $sort_order = $this->getNextSortOrder();
624 
625  foreach($questions as $question)
626  {
627  $this->saveQuestionXref($question, $sort_order);
628  }
629  }
630  }
631  return true;
632  }
633 
634  function filterActions(&$actions)
635  {
636  if(!count($actions)) return;
637  $item = $this->item;
638 
639  if(!is_callable(array($item, isAuthor))) return;
640 
641  if(!$item->isAuthor())
642  {
643  unset($actions["edit"], $actions["send"], $actions["open"],
644  $actions["close"], $actions["send_reminders"], $actions["reopen"],
645  $actions["share"], $actions["unshare"], $actions["delete"], $actions["send_test"]);
646  }
647  }
648 
654  function enableDragReorder(&$table)
655  {
656  $component_name = $this->getComponentName();
657  $reorder_handler = $this->getReorderHandler();
658  $itemPk = $this->getPrimaryKey();
659 
660  $table->enableDragReorder("/action/{$component_name}/{$reorder_handler}?{$itemPk}={$this->item->$itemPk}");
661  $table->dragText = "<span style='font-size: 10px'>Click and drag to change the order of the questions</span>";
662  }
663 
674  function saveDraggableQuestionOrder()
675  {
676  $item = $this->item;
677  $itemPk = $item->getPrimaryKey();
678  $sortableClass = $this->getSortableClass();
679  $sortableObj = new $sortableClass();
680  $sort_field = $this->getSortField();
681 
682  $pk = $sortableObj->getPrimaryKey();
683  $tx = new DataTransaction();
684 
685  try
686  {
687  foreach($_GET[$pk] as $id => $sort_order)
688  {
689  checkNumeric($id);
690  checkNumeric($sort_order);
691 
692  $sortableObj = new $sortableClass();
693  $sortableObj->joinTransaction($tx);
694 
695  $sortableObj->load($id);
696 
697  if ($item->$itemPk != $sortableObj->$itemPk)
698  {
699  throw new FakoliException("Data Mismatch");
700  }
701 
702  $sortableObj->filter = new InclusionFilter($pk, $sort_field);
703  $sortableObj->$sort_field = $sort_order;
704  $sortableObj->save();
705  }
706 
707  $tx->commit();
708  }
709  catch(Exception $e)
710  {
711  $tx->rollback();
712  Fakoli::end($e->getMessage());
713  }
714  }
715 
725  function getNextSortOrder()
726  {
727  $item = $this->item;
728  $sortableClass = $this->getSortableClass();
729  $itemPk = $item->getPrimaryKey();
730  $sort_field = $this->getSortField();
731 
732  return Query::create($sortableClass, "WHERE {$itemPk}=:{$itemPk}")
733  ->bind(":{$itemPk}", $item->$itemPk)
734  ->executeValue("MAX($sort_field)") + 1;
735  }
736 
737  function validateQuestionnaire()
738  {
739  $pk = $this->getPrimaryKey();
740  $id = $this->item->$pk;
741 
742  if(count($this->getQuestions()) == 0)
743  {
744  $identifier = $this->getQuestionListIdentifier();
745  $msg = "Your <a href='{$identifier}?$pk=$id'>questionnaire</a> must have at least one question.";
746  }
747 
748  return $msg;
749  }
750 
751 
752  function writeQuestionnaireValidationMsg()
753  {
754  $msg = $this->validateQuestionnaire();
755  echo "<div id='warning'>{$msg}</div>\n";
756  }
757 
764  function getJSManagerName()
765  {
766  return "questionnaireMgr";
767  }
768 
781  function writeScript()
782  {
783  $item = $this->item;
784  $qPk = $this->getQuestionKey();
785  $itemPk = $item->getPrimaryKey();
786  $item_id = $item->$itemPk;
787  $component_name = $this->getComponentName();
788  $question_delete_handler = $this->getQuestionDeleteHandler();
789  $question_remove_handler = $this->getQuestionRemoveHandler();
790  $question_list_identifier = $this->getQuestionListIdentifier();
791  $mgrName = $this->getJSManagerName();
792 
793  $xrefPk = "";
794  $xref = $this->getSortableObj();
795  if($xref)
796  {
797  $xrefPk = $xref->getPrimaryKey();
798  }
799 
800  ob_start();
801  ?>
802 <script type="text/javascript" src="/components/questionnaire/js/questionnaire_create.js"></script>
803 <script type="text/javascript">
804 var <?php echo $mgrName ?>;
805 
806 window.addEvent('domready', function()
807 {
808  <?php echo $mgrName ?> = new QuestionnaireCreateManager(
809  '<?php echo $qPk ?>',
810  '<?php echo $itemPk ?>',
811  <?php echo $item_id ?>,
812  '<?php echo $xrefPk ?>',
813  '<?php echo $component_name ?>',
814  '<?php echo $question_delete_handler ?>',
815  '<?php echo $question_remove_handler ?>',
816  '<?php echo $question_list_identifier ?>'
817  );
818 });
819 
820 </script>
821 <?
822  $script .= ob_get_contents();
823  ob_end_clean();
824 
825  return $script;
826  }
827 
828 } // end AbstractQuestionnaireCreateManager
829 ?>
$form
$_POST["owner_id"]
Definition: blog_form.inc:54
$src
Definition: page.inc:37
$name
Definition: upload.inc:54
Questionnaire/Survey Implementation instructions.
getQuestionKey()
Returns the primar key name of the question obj.
getSortableItems()
Retrieves the table that has the question sort order field either Question or a QuestionXref.
getQuestions()
Retrieves questions through the questionnaire or survey DataItem object.
FakoliException is the base exception class for all Fakoli errors.
Definition: core.inc:53
static using()
Import the datamodels, views and manifest for the specified component(s).
Definition: core.inc:116
static end($message="")
Use this method to terminate execution of a script instead of using the php keywords exit() or die().
Definition: core.inc:1149
static usingFile()
Uses the specified framework file(s) from the framework directory.
Definition: core.inc:369
static getText($code, $obj=null, $blank=false)
Retrieves text for display on a page, given the code.
Definition: text_lookup.inc:85
$xref_class
$list
Definition: list_images.inc:41
$question_id
$question
$identifier
Definition: rss.inc:37
$msg
Definition: save.inc:10
$questionSets