1<?php 2 /* 3 +----------------------------------------------------------------------------+ 4 | ILIAS open source | 5 +----------------------------------------------------------------------------+ 6 | Copyright (c) 1998-2001 ILIAS open source, University of Cologne | 7 | | 8 | This program is free software; you can redistribute it and/or | 9 | modify it under the terms of the GNU General Public License | 10 | as published by the Free Software Foundation; either version 2 | 11 | of the License, or (at your option) any later version. | 12 | | 13 | This program is distributed in the hope that it will be useful, | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 | GNU General Public License for more details. | 17 | | 18 | You should have received a copy of the GNU General Public License | 19 | along with this program; if not, write to the Free Software | 20 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | 21 +----------------------------------------------------------------------------+ 22*/ 23 24include_once "./Modules/SurveyQuestionPool/classes/class.SurveyQuestion.php"; 25 26/** 27* SingleChoice survey question 28* 29* The SurveySingleChoiceQuestion class defines and encapsulates basic methods and attributes 30* for single choice survey question types. 31* 32* @author Helmut Schottmüller <helmut.schottmueller@mac.com> 33* @version $Id$ 34* @extends SurveyQuestion 35* @ingroup ModulesSurveyQuestionPool 36*/ 37class SurveySingleChoiceQuestion extends SurveyQuestion 38{ 39 /** 40 * Categories contained in this question 41 * 42 * @var array 43 */ 44 public $categories; 45 46 /** 47 * SurveySingleChoiceQuestion constructor 48 * 49 * The constructor takes possible arguments an creates an instance of the SurveySingleChoiceQuestion object. 50 * 51 * @param string $title A title string to describe the question 52 * @param string $description A description string to describe the question 53 * @param string $author A string containing the name of the questions author 54 * @param integer $owner A numerical ID to identify the owner/creator 55 * @access public 56 */ 57 public function __construct($title = "", $description = "", $author = "", $questiontext = "", $owner = -1, $orientation = 1) 58 { 59 global $DIC; 60 61 $this->db = $DIC->database(); 62 $this->user = $DIC->user(); 63 $this->lng = $DIC->language(); 64 parent::__construct($title, $description, $author, $questiontext, $owner); 65 66 include_once "./Modules/SurveyQuestionPool/classes/class.SurveyCategories.php"; 67 $this->orientation = $orientation; 68 $this->categories = new SurveyCategories(); 69 } 70 71 /** 72 * Gets the available categories for a given phrase 73 * 74 * @param integer $phrase_id The database id of the given phrase 75 * @result array All available categories 76 * @access public 77 */ 78 public function &getCategoriesForPhrase($phrase_id) 79 { 80 $ilDB = $this->db; 81 $categories = array(); 82 $result = $ilDB->queryF( 83 "SELECT svy_category.* FROM svy_category, svy_phrase_cat WHERE svy_phrase_cat.category_fi = svy_category.category_id AND svy_phrase_cat.phrase_fi = %s ORDER BY svy_phrase_cat.sequence", 84 array('integer'), 85 array($phrase_id) 86 ); 87 while ($row = $ilDB->fetchAssoc($result)) { 88 if (($row["defaultvalue"] == 1) and ($row["owner_fi"] == 0)) { 89 $categories[$row["category_id"]] = $this->lng->txt($row["title"]); 90 } else { 91 $categories[$row["category_id"]] = $row["title"]; 92 } 93 } 94 return $categories; 95 } 96 97 /** 98 * Adds a phrase to the question 99 * 100 * @param integer $phrase_id The database id of the given phrase 101 * @access public 102 */ 103 public function addPhrase($phrase_id) 104 { 105 $ilUser = $this->user; 106 $ilDB = $this->db; 107 108 $result = $ilDB->queryF( 109 "SELECT svy_category.* FROM svy_category, svy_phrase_cat WHERE svy_phrase_cat.category_fi = svy_category.category_id AND svy_phrase_cat.phrase_fi = %s AND (svy_category.owner_fi = 0 OR svy_category.owner_fi = %s) ORDER BY svy_phrase_cat.sequence", 110 array('integer', 'integer'), 111 array($phrase_id, $ilUser->getId()) 112 ); 113 while ($row = $ilDB->fetchAssoc($result)) { 114 $neutral = $row["neutral"]; 115 if (($row["defaultvalue"] == 1) and ($row["owner_fi"] == 0)) { 116 $this->categories->addCategory($this->lng->txt($row["title"]), 0, $neutral); 117 } else { 118 $this->categories->addCategory($row["title"], 0, $neutral); 119 } 120 } 121 } 122 123 /** 124 * Returns the question data fields from the database 125 * 126 * @param integer $id The question ID from the database 127 * @return array Array containing the question fields and data from the database 128 * @access public 129 */ 130 public function getQuestionDataArray($id) 131 { 132 $ilDB = $this->db; 133 134 $result = $ilDB->queryF( 135 "SELECT svy_question.*, " . $this->getAdditionalTableName() . ".* FROM svy_question, " . $this->getAdditionalTableName() . " WHERE svy_question.question_id = %s AND svy_question.question_id = " . $this->getAdditionalTableName() . ".question_fi", 136 array('integer'), 137 array($id) 138 ); 139 if ($result->numRows() == 1) { 140 return $ilDB->fetchAssoc($result); 141 } else { 142 return array(); 143 } 144 } 145 146 /** 147 * Loads a SurveySingleChoiceQuestion object from the database 148 * 149 * @param integer $id The database id of the single choice survey question 150 * @access public 151 */ 152 public function loadFromDb($id) 153 { 154 $ilDB = $this->db; 155 156 $result = $ilDB->queryF( 157 "SELECT svy_question.*, " . $this->getAdditionalTableName() . ".* FROM svy_question LEFT JOIN " . $this->getAdditionalTableName() . " ON " . $this->getAdditionalTableName() . ".question_fi = svy_question.question_id WHERE svy_question.question_id = %s", 158 array('integer'), 159 array($id) 160 ); 161 if ($result->numRows() == 1) { 162 $data = $ilDB->fetchAssoc($result); 163 $this->setId($data["question_id"]); 164 $this->setTitle($data["title"]); 165 $this->label = $data['label']; 166 $this->setDescription($data["description"]); 167 $this->setObjId($data["obj_fi"]); 168 $this->setAuthor($data["author"]); 169 $this->setOwner($data["owner_fi"]); 170 include_once("./Services/RTE/classes/class.ilRTE.php"); 171 $this->setQuestiontext(ilRTE::_replaceMediaObjectImageSrc($data["questiontext"], 1)); 172 $this->setObligatory($data["obligatory"]); 173 $this->setComplete($data["complete"]); 174 $this->setOriginalId($data["original_id"]); 175 $this->setOrientation($data["orientation"]); 176 177 $this->categories->flushCategories(); 178 $result = $ilDB->queryF( 179 "SELECT svy_variable.*, svy_category.title, svy_category.neutral FROM svy_variable, svy_category WHERE svy_variable.question_fi = %s AND svy_variable.category_fi = svy_category.category_id ORDER BY sequence ASC", 180 array('integer'), 181 array($id) 182 ); 183 if ($result->numRows() > 0) { 184 while ($data = $ilDB->fetchAssoc($result)) { 185 $this->categories->addCategory($data["title"], $data["other"], $data["neutral"], null, ($data['scale']) ? $data['scale'] : ($data['sequence'] + 1)); 186 } 187 } 188 } 189 parent::loadFromDb($id); 190 } 191 192 /** 193 * Returns true if the question is complete for use 194 * 195 * @result boolean True if the question is complete for use, otherwise false 196 * @access public 197 */ 198 public function isComplete() 199 { 200 if ( 201 strlen($this->getTitle()) && 202 strlen($this->getAuthor()) && 203 strlen($this->getQuestiontext()) && 204 $this->categories->getCategoryCount() 205 ) { 206 return 1; 207 } else { 208 return 0; 209 } 210 } 211 212 /** 213 * Saves a SurveySingleChoiceQuestion object to a database 214 * 215 * @access public 216 */ 217 public function saveToDb($original_id = "") 218 { 219 $ilDB = $this->db; 220 221 $affectedRows = parent::saveToDb($original_id); 222 if ($affectedRows == 1) { 223 $this->log->debug("Before save Category-> DELETE from svy_qst_sc WHERE question_fi = " . $this->getId() . " AND INSERT again the same id and orientation in svy_qst_sc"); 224 $affectedRows = $ilDB->manipulateF( 225 "DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s", 226 array('integer'), 227 array($this->getId()) 228 ); 229 $affectedRows = $ilDB->manipulateF( 230 "INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, orientation) VALUES (%s, %s)", 231 array('integer', 'text'), 232 array( 233 $this->getId(), 234 $this->getOrientation() 235 ) 236 ); 237 238 $this->saveMaterial(); 239 $this->saveCategoriesToDb(); 240 } 241 } 242 243 public function saveCategoriesToDb() 244 { 245 $ilDB = $this->db; 246 247 $this->log->debug("DELETE from svy_variable before the INSERT into svy_variable. if scale > 0 we get scale value else we get null"); 248 249 $affectedRows = $ilDB->manipulateF( 250 "DELETE FROM svy_variable WHERE question_fi = %s", 251 array('integer'), 252 array($this->getId()) 253 ); 254 255 for ($i = 0; $i < $this->categories->getCategoryCount(); $i++) { 256 $cat = $this->categories->getCategory($i); 257 $category_id = $this->saveCategoryToDb($cat->title, $cat->neutral); 258 $next_id = $ilDB->nextId('svy_variable'); 259 $affectedRows = $ilDB->manipulateF( 260 "INSERT INTO svy_variable (variable_id, category_fi, question_fi, value1, other, sequence, scale, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)", 261 array('integer','integer','integer','float','integer','integer', 'integer','integer'), 262 array($next_id, $category_id, $this->getId(), ($i + 1), $cat->other, $i, ($cat->scale > 0) ? $cat->scale : null, time()) 263 ); 264 265 $debug_scale = ($cat->scale > 0) ? $cat->scale : null; 266 $this->log->debug("INSERT INTO svy_variable category_fi= " . $category_id . " question_fi= " . $this->getId() . " value1= " . ($i + 1) . " other= " . $cat->other . " sequence= " . $i . " scale =" . $debug_scale); 267 } 268 $this->saveCompletionStatus(); 269 } 270 271 /** 272 * Returns an xml representation of the question 273 * 274 * @return string The xml representation of the question 275 * @access public 276 */ 277 public function toXML($a_include_header = true, $obligatory_state = "") 278 { 279 include_once("./Services/Xml/classes/class.ilXmlWriter.php"); 280 $a_xml_writer = new ilXmlWriter; 281 $a_xml_writer->xmlHeader(); 282 $this->insertXML($a_xml_writer, $a_include_header, $obligatory_state); 283 $xml = $a_xml_writer->xmlDumpMem(false); 284 if (!$a_include_header) { 285 $pos = strpos($xml, "?>"); 286 $xml = substr($xml, $pos + 2); 287 } 288 return $xml; 289 } 290 291 /** 292 * Adds the question XML to a given XMLWriter object 293 * 294 * @param object $a_xml_writer The XMLWriter object 295 * @param boolean $a_include_header Determines wheather or not the XML should be used 296 * @access public 297 */ 298 public function insertXML(&$a_xml_writer, $a_include_header = true) 299 { 300 $attrs = array( 301 "id" => $this->getId(), 302 "title" => $this->getTitle(), 303 "type" => $this->getQuestiontype(), 304 "obligatory" => $this->getObligatory() 305 ); 306 $a_xml_writer->xmlStartTag("question", $attrs); 307 308 $a_xml_writer->xmlElement("description", null, $this->getDescription()); 309 $a_xml_writer->xmlElement("author", null, $this->getAuthor()); 310 if (strlen($this->label)) { 311 $attrs = array( 312 "label" => $this->label, 313 ); 314 } else { 315 $attrs = array(); 316 } 317 $a_xml_writer->xmlStartTag("questiontext", $attrs); 318 $this->addMaterialTag($a_xml_writer, $this->getQuestiontext()); 319 $a_xml_writer->xmlEndTag("questiontext"); 320 321 $a_xml_writer->xmlStartTag("responses"); 322 323 for ($i = 0; $i < $this->categories->getCategoryCount(); $i++) { 324 $attrs = array( 325 "id" => $i 326 ); 327 if (strlen($this->categories->getCategory($i)->other)) { 328 $attrs['other'] = $this->categories->getCategory($i)->other; 329 } 330 if (strlen($this->categories->getCategory($i)->neutral)) { 331 $attrs['neutral'] = $this->categories->getCategory($i)->neutral; 332 } 333 if (strlen($this->categories->getCategory($i)->label)) { 334 $attrs['label'] = $this->categories->getCategory($i)->label; 335 } 336 if (strlen($this->categories->getCategory($i)->scale)) { 337 $attrs['scale'] = $this->categories->getCategory($i)->scale; 338 } 339 $a_xml_writer->xmlStartTag("response_single", $attrs); 340 $this->addMaterialTag($a_xml_writer, $this->categories->getCategory($i)->title); 341 $a_xml_writer->xmlEndTag("response_single"); 342 } 343 344 $a_xml_writer->xmlEndTag("responses"); 345 346 if (count($this->material)) { 347 if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $this->material["internal_link"], $matches)) { 348 $attrs = array( 349 "label" => $this->material["title"] 350 ); 351 $a_xml_writer->xmlStartTag("material", $attrs); 352 $intlink = "il_" . IL_INST_ID . "_" . $matches[2] . "_" . $matches[3]; 353 if (strcmp($matches[1], "") != 0) { 354 $intlink = $this->material["internal_link"]; 355 } 356 $a_xml_writer->xmlElement("mattext", null, $intlink); 357 $a_xml_writer->xmlEndTag("material"); 358 } 359 } 360 361 $a_xml_writer->xmlStartTag("metadata"); 362 $a_xml_writer->xmlStartTag("metadatafield"); 363 $a_xml_writer->xmlElement("fieldlabel", null, "orientation"); 364 $a_xml_writer->xmlElement("fieldentry", null, $this->getOrientation()); 365 $a_xml_writer->xmlEndTag("metadatafield"); 366 $a_xml_writer->xmlEndTag("metadata"); 367 368 $a_xml_writer->xmlEndTag("question"); 369 } 370 371 /** 372 * Import additional meta data from the question import file. Usually 373 * the meta data section is used to store question elements which are not 374 * part of the standard XML schema. 375 * 376 * @return array $a_meta Array containing the additional meta data 377 * @access public 378 */ 379 public function importAdditionalMetadata($a_meta) 380 { 381 foreach ($a_meta as $key => $value) { 382 switch ($value["label"]) { 383 case "orientation": 384 $this->setOrientation($value["entry"]); 385 break; 386 } 387 } 388 } 389 390 /** 391 * Adds standard numbers as categories 392 * 393 * @param integer $lower_limit The lower limit 394 * @param integer $upper_limit The upper limit 395 * @access public 396 */ 397 public function addStandardNumbers($lower_limit, $upper_limit) 398 { 399 for ($i = $lower_limit; $i <= $upper_limit; $i++) { 400 $this->categories->addCategory($i); 401 } 402 } 403 404 /** 405 * Saves a set of categories to a default phrase 406 * 407 * @param array $phrases The database ids of the seleted phrases 408 * @param string $title The title of the default phrase 409 * @access public 410 */ 411 public function savePhrase($title) 412 { 413 $ilUser = $this->user; 414 $ilDB = $this->db; 415 416 $next_id = $ilDB->nextId('svy_phrase'); 417 $affectedRows = $ilDB->manipulateF( 418 "INSERT INTO svy_phrase (phrase_id, title, defaultvalue, owner_fi, tstamp) VALUES (%s, %s, %s, %s, %s)", 419 array('integer','text','text','integer','integer'), 420 array($next_id, $title, 1, $ilUser->getId(), time()) 421 ); 422 $phrase_id = $next_id; 423 424 $counter = 1; 425 foreach ($_SESSION['save_phrase_data'] as $data) { 426 $next_id = $ilDB->nextId('svy_category'); 427 $affectedRows = $ilDB->manipulateF( 428 "INSERT INTO svy_category (category_id, title, defaultvalue, owner_fi, tstamp, neutral) VALUES (%s, %s, %s, %s, %s, %s)", 429 array('integer','text','text','integer','integer','text'), 430 array($next_id, $data['answer'], 1, $ilUser->getId(), time(), $data['neutral']) 431 ); 432 $category_id = $next_id; 433 $next_id = $ilDB->nextId('svy_phrase_cat'); 434 $affectedRows = $ilDB->manipulateF( 435 "INSERT INTO svy_phrase_cat (phrase_category_id, phrase_fi, category_fi, sequence, other, scale) VALUES (%s, %s, %s, %s, %s, %s)", 436 array('integer', 'integer', 'integer','integer', 'integer', 'integer'), 437 array($next_id, $phrase_id, $category_id, $counter, ($data['other']) ? 1 : 0, $data['scale']) 438 ); 439 $counter++; 440 } 441 } 442 443 /** 444 * Returns the question type of the question 445 * 446 * @return integer The question type of the question 447 * @access public 448 */ 449 public function getQuestionType() 450 { 451 return "SurveySingleChoiceQuestion"; 452 } 453 454 /** 455 * Returns the name of the additional question data table in the database 456 * 457 * @return string The additional table name 458 * @access public 459 */ 460 public function getAdditionalTableName() 461 { 462 return "svy_qst_sc"; 463 } 464 465 /** 466 * Creates the user data of the svy_answer table from the POST data 467 * 468 * @return array User data according to the svy_answer table 469 * @access public 470 */ 471 public function &getWorkingDataFromUserInput($post_data) 472 { 473 $entered_value = $post_data[$this->getId() . "_value"]; 474 $data = array(); 475 if (strlen($entered_value)) { 476 array_push($data, array("value" => $entered_value, "textanswer" => $post_data[$this->getId() . '_' . $entered_value . '_other'])); 477 } 478 for ($i = 0; $i < $this->categories->getCategoryCount(); $i++) { 479 $cat = $this->categories->getCategory($i); 480 if ($cat->other) { 481 if ($i != $entered_value) { 482 if (strlen($post_data[$this->getId() . "_" . $i . "_other"])) { 483 array_push($data, array("value" => $i, "textanswer" => $post_data[$this->getId() . '_' . $i . '_other'], "uncheck" => true)); 484 } 485 } 486 } 487 } 488 return $data; 489 } 490 491 /** 492 * Checks the input of the active user for obligatory status 493 * and entered values 494 * 495 * @param array $post_data The contents of the $_POST array 496 * @param integer $survey_id The database ID of the active survey 497 * @return string Empty string if the input is ok, an error message otherwise 498 * @access public 499 */ 500 public function checkUserInput($post_data, $survey_id) 501 { 502 $entered_value = $post_data[$this->getId() . "_value"]; 503 504 $this->log->debug("Entered value = " . $entered_value); 505 506 if ((!$this->getObligatory($survey_id)) && (strlen($entered_value) == 0)) { 507 return ""; 508 } 509 510 if (strlen($entered_value) == 0) { 511 return $this->lng->txt("question_not_checked"); 512 } 513 514 for ($i = 0; $i < $this->categories->getCategoryCount(); $i++) { 515 $cat = $this->categories->getCategory($i); 516 if ($cat->other) { 517 if ($i == $entered_value) { 518 if (array_key_exists($this->getId() . "_" . $entered_value . "_other", $post_data) && !strlen($post_data[$this->getId() . "_" . $entered_value . "_other"])) { 519 return $this->lng->txt("question_mr_no_other_answer"); 520 } 521 } else { 522 if (strlen($post_data[$this->getId() . "_" . $i . "_other"])) { 523 return $this->lng->txt("question_sr_no_other_answer_checked"); 524 } 525 } 526 } 527 } 528 529 return ""; 530 } 531 532 public function saveUserInput($post_data, $active_id, $a_return = false) 533 { 534 $ilDB = $this->db; 535 536 $entered_value = $post_data[$this->getId() . "_value"]; 537 538 if ($a_return) { 539 return array(array("value" => $entered_value, 540 "textanswer" => $post_data[$this->getId() . "_" . $entered_value . "_other"])); 541 } 542 if (strlen($entered_value) == 0) { 543 return; 544 } 545 546 $next_id = $ilDB->nextId('svy_answer'); 547 #20216 548 $fields = array(); 549 $fields['answer_id'] = array("integer", $next_id); 550 $fields['question_fi'] = array("integer", $this->getId()); 551 $fields['active_fi'] = array("integer", $active_id); 552 $fields['value'] = array("float", (strlen($entered_value)) ? $entered_value : null); 553 $fields['textanswer'] = array("clob", ($post_data[$this->getId() . "_" . $entered_value . "_other"]) ? 554 $this->stripSlashesAddSpaceFallback($post_data[$this->getId() . "_" . $entered_value . "_other"]) : null); 555 $fields['tstamp'] = array("integer", time()); 556 557 $affectedRows = $ilDB->insert("svy_answer", $fields); 558 559 $debug_value = (strlen($entered_value)) ? $entered_value : "NULL"; 560 $debug_answer = ($post_data[$this->getId() . "_" . $entered_value . "_other"]) ? $post_data[$this->getId() . "_" . $entered_value . "_other"] : "NULL"; 561 $this->log->debug("INSERT svy_answer answer_id=" . $next_id . " question_fi=" . $this->getId() . " active_fi=" . $active_id . " value=" . $debug_value . " textanswer=" . $debug_answer); 562 } 563 564 /** 565 * Import response data from the question import file 566 * 567 * @return array $a_data Array containing the response data 568 * @access public 569 */ 570 public function importResponses($a_data) 571 { 572 foreach ($a_data as $id => $data) { 573 $categorytext = ""; 574 foreach ($data["material"] as $material) { 575 $categorytext .= $material["text"]; 576 } 577 $this->categories->addCategory( 578 $categorytext, 579 strlen($data['other']) ? $data['other'] : 0, 580 strlen($data['neutral']) ? $data['neutral'] : 0, 581 strlen($data['label']) ? $data['label'] : null, 582 strlen($data['scale']) ? $data['scale'] : null 583 ); 584 } 585 } 586 587 /** 588 * Returns if the question is usable for preconditions 589 * 590 * @return boolean TRUE if the question is usable for a precondition, FALSE otherwise 591 * @access public 592 */ 593 public function usableForPrecondition() 594 { 595 return true; 596 } 597 598 /** 599 * Returns the available relations for the question 600 * 601 * @return array An array containing the available relations 602 * @access public 603 */ 604 public function getAvailableRelations() 605 { 606 return array("<", "<=", "=", "<>", ">=", ">"); 607 } 608 609 /** 610 * Returns the options for preconditions 611 * 612 * @return array 613 */ 614 public function getPreconditionOptions() 615 { 616 $lng = $this->lng; 617 618 $options = array(); 619 for ($i = 0; $i < $this->categories->getCategoryCount(); $i++) { 620 $category = $this->categories->getCategory($i); 621 $options[$category->scale - 1] = $category->scale . " - " . $category->title; 622 } 623 return $options; 624 } 625 626 /** 627 * Creates a form property for the precondition value 628 * 629 * @return The ILIAS form element 630 * @access public 631 */ 632 public function getPreconditionSelectValue($default = "", $title, $variable) 633 { 634 include_once "./Services/Form/classes/class.ilSelectInputGUI.php"; 635 $step3 = new ilSelectInputGUI($title, $variable); 636 $options = $this->getPreconditionOptions(); 637 $step3->setOptions($options); 638 $step3->setValue($default); 639 return $step3; 640 } 641 642 /** 643 * Returns the output for a precondition value 644 * 645 * @param string $value The precondition value 646 * @return string The output of the precondition value 647 * @access public 648 */ 649 public function getPreconditionValueOutput($value) 650 { 651 // #18136 652 $category = $this->categories->getCategoryForScale($value + 1); 653 654 // #17895 - see getPreconditionOptions() 655 return $category->scale . 656 " - " . 657 ((strlen($category->title)) ? $category->title : $this->lng->txt('other_answer')); 658 } 659 660 public function getCategories() 661 { 662 return $this->categories; 663 } 664} 665