ILIAS  Release_4_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.SurveyMetricQuestion.php
Go to the documentation of this file.
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 
24 include_once "./Modules/SurveyQuestionPool/classes/class.SurveyQuestion.php";
25 include_once "./Modules/Survey/classes/inc.SurveyConstants.php";
26 
27 define("SUBTYPE_NON_RATIO", 3);
28 define("SUBTYPE_RATIO_NON_ABSOLUTE", 4);
29 define("SUBTYPE_RATIO_ABSOLUTE", 5);
30 
43 {
51  var $subtype;
52 
58  var $minimum;
59 
65  var $maximum;
66 
79  $title = "",
80  $description = "",
81  $author = "",
82  $questiontext = "",
83  $owner = -1,
85  )
86  {
88  $this->subtype = $subtype;
89  $this->minimum = "";
90  $this->maximum = "";
91  }
92 
101  {
102  $this->subtype = $subtype;
103  }
104 
112  function setMinimum($minimum = 0)
113  {
114  $this->minimum = $minimum;
115  }
116 
124  function setMaximum($maximum = "")
125  {
126  $this->maximum = $maximum;
127  }
128 
136  function getSubtype()
137  {
138  return $this->subtype;
139  }
140 
148  function getMinimum()
149  {
150  if ((strlen($this->minimum) == 0) && ($this->getSubtype() > 3))
151  {
152  $this->minimum = 0;
153  }
154  return (strlen($this->minimum)) ? $this->minimum : NULL;
155  }
156 
164  function getMaximum()
165  {
166  return (strlen($this->maximum)) ? $this->maximum : NULL;
167  }
168 
177  {
178  global $ilDB;
179 
180  $result = $ilDB->queryF("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",
181  array('integer'),
182  array($id)
183  );
184  if ($result->numRows() == 1)
185  {
186  return $ilDB->fetchAssoc($result);
187  }
188  else
189  {
190  return array();
191  }
192  }
193 
200  function loadFromDb($id)
201  {
202  global $ilDB;
203 
204  $result = $ilDB->queryF("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",
205  array('integer'),
206  array($id)
207  );
208  if ($result->numRows() == 1)
209  {
210  $data = $ilDB->fetchAssoc($result);
211  $this->setId($data["question_id"]);
212  $this->setTitle($data["title"]);
213  $this->setDescription($data["description"]);
214  $this->setObjId($data["obj_fi"]);
215  $this->setAuthor($data["author"]);
216  $this->setOwner($data["owner_fi"]);
217  include_once("./Services/RTE/classes/class.ilRTE.php");
218  $this->setQuestiontext(ilRTE::_replaceMediaObjectImageSrc($data["questiontext"], 1));
219  $this->setObligatory($data["obligatory"]);
220  $this->setComplete($data["complete"]);
221  $this->setOriginalId($data["original_id"]);
222  $this->setSubtype($data["subtype"]);
223 
224  $result = $ilDB->queryF("SELECT svy_variable.* FROM svy_variable WHERE svy_variable.question_fi = %s",
225  array('integer'),
226  array($id)
227  );
228  if ($result->numRows() > 0)
229  {
230  if ($data = $ilDB->fetchAssoc($result))
231  {
232  $this->minimum = $data["value1"];
233  if (($data["value2"] < 0) or (strcmp($data["value2"], "") == 0))
234  {
235  $this->maximum = "";
236  }
237  else
238  {
239  $this->maximum = $data["value2"];
240  }
241  }
242  }
243  }
245  }
246 
253  function isComplete()
254  {
255  if ($this->title and $this->author and $this->questiontext)
256  {
257  return 1;
258  }
259  else
260  {
261  return 0;
262  }
263  }
264 
270  function saveToDb($original_id = "")
271  {
272  global $ilDB;
273 
274  $affectedRows = parent::saveToDb($original_id);
275  if ($affectedRows == 1)
276  {
277  $affectedRows = $ilDB->manipulateF("DELETE FROM " . $this->getAdditionalTableName() . " WHERE question_fi = %s",
278  array('integer'),
279  array($this->getId())
280  );
281  $affectedRows = $ilDB->manipulateF("INSERT INTO " . $this->getAdditionalTableName() . " (question_fi, subtype) VALUES (%s, %s)",
282  array('integer', 'text'),
283  array($this->getId(), $this->getSubType())
284  );
285 
286  // saving material uris in the database
287  $this->saveMaterial();
288 
289  // save categories
290  $affectedRows = $ilDB->manipulateF("DELETE FROM svy_variable WHERE question_fi = %s",
291  array('integer'),
292  array($this->getId())
293  );
294 
295  if (preg_match("/[\D]/", $this->maximum) or (strcmp($this->maximum, "&infin;") == 0))
296  {
297  $max = -1;
298  }
299  else
300  {
301  $max = $this->getMaximum();
302  }
303  $next_id = $ilDB->nextId('svy_variable');
304  $affectedRows = $ilDB->manipulateF("INSERT INTO svy_variable (variable_id, category_fi, question_fi, value1, value2, sequence, tstamp) VALUES (%s, %s, %s, %s, %s, %s, %s)",
305  array('integer','integer','integer','float','float','integer','integer'),
306  array($next_id, 0, $this->getId(), $this->getMinimum(), $max, 0, time())
307  );
308  }
309  }
310 
317  function toXML($a_include_header = TRUE, $obligatory_state = "")
318  {
319  include_once("./classes/class.ilXmlWriter.php");
320  $a_xml_writer = new ilXmlWriter;
321  $a_xml_writer->xmlHeader();
322  $this->insertXML($a_xml_writer, $a_include_header, $obligatory_state);
323  $xml = $a_xml_writer->xmlDumpMem(FALSE);
324  if (!$a_include_header)
325  {
326  $pos = strpos($xml, "?>");
327  $xml = substr($xml, $pos + 2);
328  }
329  return $xml;
330  }
331 
340  function insertXML(&$a_xml_writer, $a_include_header = TRUE, $obligatory_state = "")
341  {
342  $attrs = array(
343  "id" => $this->getId(),
344  "title" => $this->getTitle(),
345  "type" => $this->getQuestiontype(),
346  "subtype" => $this->getSubtype(),
347  "obligatory" => $this->getObligatory()
348  );
349  $a_xml_writer->xmlStartTag("question", $attrs);
350 
351  $a_xml_writer->xmlElement("description", NULL, $this->getDescription());
352  $a_xml_writer->xmlElement("author", NULL, $this->getAuthor());
353  $a_xml_writer->xmlStartTag("questiontext");
354  $this->addMaterialTag($a_xml_writer, $this->getQuestiontext());
355  $a_xml_writer->xmlEndTag("questiontext");
356 
357  $a_xml_writer->xmlStartTag("responses");
358  switch ($this->getSubtype())
359  {
360  case 3:
361  $attrs = array(
362  "id" => "0",
363  "format" => "double"
364  );
365  if (strlen($this->getMinimum()))
366  {
367  $attrs["min"] = $this->getMinimum();
368  }
369  if (strlen($this->getMaximum()))
370  {
371  $attrs["max"] = $this->getMaximum();
372  }
373  break;
374  case 4:
375  $attrs = array(
376  "id" => "0",
377  "format" => "double"
378  );
379  if (strlen($this->getMinimum()))
380  {
381  $attrs["min"] = $this->getMinimum();
382  }
383  if (strlen($this->getMaximum()))
384  {
385  $attrs["max"] = $this->getMaximum();
386  }
387  break;
388  case 5:
389  $attrs = array(
390  "id" => "0",
391  "format" => "integer"
392  );
393  if (strlen($this->getMinimum()))
394  {
395  $attrs["min"] = $this->getMinimum();
396  }
397  if (strlen($this->getMaximum()))
398  {
399  $attrs["max"] = $this->getMaximum();
400  }
401  break;
402  }
403  $a_xml_writer->xmlStartTag("response_num", $attrs);
404  $a_xml_writer->xmlEndTag("response_num");
405 
406  $a_xml_writer->xmlEndTag("responses");
407 
408  if (count($this->material))
409  {
410  if (preg_match("/il_(\d*?)_(\w+)_(\d+)/", $this->material["internal_link"], $matches))
411  {
412  $attrs = array(
413  "label" => $this->material["title"]
414  );
415  $a_xml_writer->xmlStartTag("material", $attrs);
416  $intlink = "il_" . IL_INST_ID . "_" . $matches[2] . "_" . $matches[3];
417  if (strcmp($matches[1], "") != 0)
418  {
419  $intlink = $this->material["internal_link"];
420  }
421  $a_xml_writer->xmlElement("mattext", NULL, $intlink);
422  $a_xml_writer->xmlEndTag("material");
423  }
424  }
425 
426  $a_xml_writer->xmlEndTag("question");
427  }
428 
435  function getQuestionTypeID()
436  {
437  global $ilDB;
438  $result = $ilDB->queryF("SELECT questiontype_id FROM svy_qtype WHERE type_tag = %s",
439  array('text'),
440  array($this->getQuestionType())
441  );
442  $row = $ilDB->fetchAssoc($result);
443  return $row["questiontype_id"];
444  }
445 
452  function getQuestionType()
453  {
454  return "SurveyMetricQuestion";
455  }
456 
464  {
465  return "svy_qst_metric";
466  }
467 
474  function &getWorkingDataFromUserInput($post_data)
475  {
476  $entered_value = $post_data[$this->getId() . "_metric_question"];
477  $data = array();
478  if (strlen($entered_value))
479  {
480  array_push($data, array("value" => $entered_value));
481  }
482  return $data;
483  }
484 
494  function checkUserInput($post_data, $survey_id)
495  {
496  $entered_value = $post_data[$this->getId() . "_metric_question"];
497  // replace german notation with international notation
498  $entered_value = str_replace(",", ".", $entered_value);
499 
500  if ((!$this->getObligatory($survey_id)) && (strlen($entered_value) == 0)) return "";
501 
502  if (strlen($entered_value) == 0) return $this->lng->txt("survey_question_obligatory");
503 
504  if (strlen($this->getMinimum()))
505  {
506  if ($entered_value < $this->getMinimum())
507  {
508  return $this->lng->txt("metric_question_out_of_bounds");
509  }
510  }
511 
512  if (strlen($this->getMaximum()))
513  {
514  if (($this->getMaximum() == 1) && ($this->getMaximum() < $this->getMinimum()))
515  {
516  // old &infty; values as maximum
517  }
518  else
519  {
520  if ($entered_value > $this->getMaximum())
521  {
522  return $this->lng->txt("metric_question_out_of_bounds");
523  }
524  }
525  }
526 
527  if (!is_numeric($entered_value))
528  {
529  return $this->lng->txt("metric_question_not_a_value");
530  }
531 
532  if (($this->getSubType() == SUBTYPE_RATIO_ABSOLUTE) && (intval($entered_value) != doubleval($entered_value)))
533  {
534  return $this->lng->txt("metric_question_floating_point");
535  }
536  return "";
537  }
538 
544  public function saveRandomData($active_id)
545  {
546  global $ilDB;
547  // single response
548  $number = rand($this->getMinimum(), (strlen($this->getMaximum())) ? $this->getMaximum() : 100);
549  $next_id = $ilDB->nextId('svy_answer');
550  $affectedRows = $ilDB->manipulateF("INSERT INTO svy_answer (answer_id, question_fi, active_fi, value, textanswer, tstamp) VALUES (%s, %s, %s, %s, %s, %s)",
551  array('integer','integer','integer','float','text','integer'),
552  array($next_id, $this->getId(), $active_id, $number, NULL, time())
553  );
554  }
555 
556  function saveUserInput($post_data, $active_id)
557  {
558  global $ilDB;
559 
560  $entered_value = $post_data[$this->getId() . "_metric_question"];
561  if (strlen($entered_value) == 0) return;
562  // replace german notation with international notation
563  $entered_value = str_replace(",", ".", $entered_value);
564  $next_id = $ilDB->nextId('svy_answer');
565  $affectedRows = $ilDB->manipulateF("INSERT INTO svy_answer (answer_id, question_fi, active_fi, value, textanswer, tstamp) VALUES (%s, %s, %s, %s, %s, %s)",
566  array('integer','integer','integer','float','text','integer'),
567  array($next_id, $this->getId(), $active_id, (strlen($entered_value)) ? $entered_value : NULL, NULL, time())
568  );
569  }
570 
571  function &getCumulatedResults($survey_id, $nr_of_users)
572  {
573  global $ilDB;
574 
575  $question_id = $this->getId();
576 
577  $result_array = array();
578  $cumulated = array();
579 
580  $result = $ilDB->queryF("SELECT svy_answer.* FROM svy_answer, svy_finished WHERE svy_answer.question_fi = %s AND svy_finished.survey_fi = %s AND svy_finished.finished_id = svy_answer.active_fi",
581  array('integer', 'integer'),
582  array($question_id, $survey_id)
583  );
584 
585  while ($row = $ilDB->fetchAssoc($result))
586  {
587  $cumulated[$row["value"]]++;
588  }
589  asort($cumulated, SORT_NUMERIC);
590  end($cumulated);
591  $numrows = $result->numRows();
592  $result_array["USERS_ANSWERED"] = $result->numRows();
593  $result_array["USERS_SKIPPED"] = $nr_of_users - $result->numRows();
594  $result_array["MODE"] = key($cumulated);
595  $result_array["MODE_VALUE"] = key($cumulated);
596  $result_array["MODE_NR_OF_SELECTIONS"] = $cumulated[key($cumulated)];
597  ksort($cumulated, SORT_NUMERIC);
598  $counter = 0;
599  foreach ($cumulated as $value => $nr_of_users)
600  {
601  $percentage = 0;
602  if ($numrows > 0)
603  {
604  $percentage = (float)($nr_of_users/$numrows);
605  }
606  $result_array["values"][$counter++] = array("value" => $value, "selected" => (int)$nr_of_users, "percentage" => $percentage);
607  }
608  $median = array();
609  $total = 0;
610  $x_i = 0;
611  $p_i = 1;
612  $x_i_inv = 0;
613  $sum_part_zero = false;
614  foreach ($cumulated as $value => $key)
615  {
616  $total += $key;
617  for ($i = 0; $i < $key; $i++)
618  {
619  array_push($median, $value);
620  $x_i += $value;
621  $p_i *= $value;
622  if ($value != 0)
623  {
624  $sum_part_zero = true;
625  $x_i_inv += 1/$value;
626  }
627  }
628  }
629  if ($total > 0)
630  {
631  if (($total % 2) == 0)
632  {
633  $median_value = 0.5 * ($median[($total/2)-1] + $median[($total/2)]);
634  }
635  else
636  {
637  $median_value = $median[(($total+1)/2)-1];
638  }
639  }
640  else
641  {
642  $median_value = "";
643  }
644  if ($total > 0)
645  {
646  if (($x_i/$total) == (int)($x_i/$total))
647  {
648  $result_array["ARITHMETIC_MEAN"] = $x_i/$total;
649  }
650  else
651  {
652  $result_array["ARITHMETIC_MEAN"] = sprintf("%.2f", $x_i/$total);
653  }
654  }
655  else
656  {
657  $result_array["ARITHMETIC_MEAN"] = "";
658  }
659  $result_array["MEDIAN"] = $median_value;
660  $result_array["QUESTION_TYPE"] = "SurveyMetricQuestion";
661  return $result_array;
662  }
663 
673  function setExportDetailsXLS(&$workbook, &$format_title, &$format_bold, &$eval_data)
674  {
675  include_once ("./Services/Excel/classes/class.ilExcelUtils.php");
676  $worksheet =& $workbook->addWorksheet();
677  $worksheet->writeString(0, 0, ilExcelUtils::_convert_text($this->lng->txt("title")), $format_bold);
678  $worksheet->writeString(0, 1, ilExcelUtils::_convert_text($this->getTitle()));
679  $worksheet->writeString(1, 0, ilExcelUtils::_convert_text($this->lng->txt("question")), $format_bold);
680  $worksheet->writeString(1, 1, ilExcelUtils::_convert_text($this->getQuestiontext()));
681  $worksheet->writeString(2, 0, ilExcelUtils::_convert_text($this->lng->txt("question_type")), $format_bold);
682  $worksheet->writeString(2, 1, ilExcelUtils::_convert_text($this->lng->txt($this->getQuestionType())));
683  $worksheet->writeString(3, 0, ilExcelUtils::_convert_text($this->lng->txt("users_answered")), $format_bold);
684  $worksheet->write(3, 1, $eval_data["USERS_ANSWERED"]);
685  $worksheet->writeString(4, 0, ilExcelUtils::_convert_text($this->lng->txt("users_skipped")), $format_bold);
686  $worksheet->write(4, 1, $eval_data["USERS_SKIPPED"]);
687  $rowcounter = 5;
688 
689  $worksheet->write($rowcounter, 0, $this->lng->txt("subtype"), $format_bold);
690  switch ($this->getSubtype())
691  {
692  case SUBTYPE_NON_RATIO:
693  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($this->lng->txt("non_ratio")), $format_bold);
694  break;
696  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($this->lng->txt("ratio_non_absolute")), $format_bold);
697  break;
699  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($this->lng->txt("ratio_absolute")), $format_bold);
700  break;
701  }
702  $worksheet->write($rowcounter, 0, ilExcelUtils::_convert_text($this->lng->txt("mode")), $format_bold);
703  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($eval_data["MODE"]));
704  $worksheet->write($rowcounter, 0, ilExcelUtils::_convert_text($this->lng->txt("mode_text")), $format_bold);
705  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($eval_data["MODE"]));
706  $worksheet->write($rowcounter, 0, ilExcelUtils::_convert_text($this->lng->txt("mode_nr_of_selections")), $format_bold);
707  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($eval_data["MODE_NR_OF_SELECTIONS"]));
708  $worksheet->write($rowcounter, 0, ilExcelUtils::_convert_text($this->lng->txt("median")), $format_bold);
709  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($eval_data["MEDIAN"]));
710  $worksheet->write($rowcounter, 0, ilExcelUtils::_convert_text($this->lng->txt("arithmetic_mean")), $format_bold);
711  $worksheet->write($rowcounter++, 1, ilExcelUtils::_convert_text($eval_data["ARITHMETIC_MEAN"]));
712  $worksheet->write($rowcounter, 0, ilExcelUtils::_convert_text($this->lng->txt("values")), $format_bold);
713  $worksheet->write($rowcounter, 1, ilExcelUtils::_convert_text($this->lng->txt("value")), $format_title);
714  $worksheet->write($rowcounter, 2, ilExcelUtils::_convert_text($this->lng->txt("category_nr_selected")), $format_title);
715  $worksheet->write($rowcounter++, 3, ilExcelUtils::_convert_text($this->lng->txt("percentage_of_selections")), $format_title);
716  $values = "";
717  if (is_array($eval_data["values"]))
718  {
719  foreach ($eval_data["values"] as $key => $value)
720  {
721  $worksheet->write($rowcounter, 1, ilExcelUtils::_convert_text($value["value"]));
722  $worksheet->write($rowcounter, 2, ilExcelUtils::_convert_text($value["selected"]));
723  $worksheet->write($rowcounter++, 3, ilExcelUtils::_convert_text($value["percentage"]), $format_percent);
724  }
725  }
726  }
727 
735  function addUserSpecificResultsData(&$a_array, &$resultset)
736  {
737  if (count($resultset["answers"][$this->getId()]))
738  {
739  foreach ($resultset["answers"][$this->getId()] as $key => $answer)
740  {
741  array_push($a_array, $answer["value"]);
742  }
743  }
744  else
745  {
746  array_push($a_array, $this->lng->txt("skipped"));
747  }
748  }
749 
758  {
759  global $ilDB;
760 
761  $answers = array();
762 
763  $result = $ilDB->queryF("SELECT svy_answer.* FROM svy_answer, svy_finished WHERE svy_finished.survey_fi = %s AND svy_answer.question_fi = %s AND svy_finished.finished_id = svy_answer.active_fi",
764  array('integer','integer'),
765  array($survey_id, $this->getId())
766  );
767  while ($row = $ilDB->fetchAssoc($result))
768  {
769  $answers[$row["active_fi"]] = $row["value"];
770  }
771  return $answers;
772  }
773 
780  function importResponses($a_data)
781  {
782  foreach ($a_data as $id => $data)
783  {
784  $this->setMinimum($data["min"]);
785  $this->setMaximum($data["max"]);
786  }
787  }
788 
796  {
797  return TRUE;
798  }
799 
807  {
808  return array("<", "<=", "=", "<>", ">=", ">");
809  }
810 
817  function outPreconditionSelectValue(&$template)
818  {
819  $template->setCurrentBlock("textfield");
820  $template->setVariable("TEXTFIELD_VALUE", "");
821  $template->parseCurrentBlock();
822  }
823 
830  function getPreconditionSelectValue($default = "")
831  {
832  global $lng;
833 
834  include_once "./classes/class.ilTemplate.php";
835  $template = new ilTemplate("tpl.il_svy_svy_precondition_select_value_textfield.html", TRUE, TRUE, "Modules/Survey");
836  if (strlen($default))
837  {
838  $template->setCurrentBlock("textfield");
839  $template->setVariable("TEXTFIELD_VALUE", " value=\"" . ilUtil::prepareFormOutput($default) . "\"");
840  $template->parseCurrentBlock();
841  }
842  else
843  {
844  $template->touchBlock("textfield");
845  }
846  $template->setVariable("SELECT_VALUE", $lng->txt("step") . " 3: " . $lng->txt("enter_value"));
847  return $template->get();
848  }
849 
858  function outChart($survey_id, $type = "")
859  {
860  if (count($this->cumulated) == 0)
861  {
862  include_once "./Modules/Survey/classes/class.ilObjSurvey.php";
864  $this->cumulated =& $this->getCumulatedResults($survey_id, $nr_of_users);
865  }
866 
867  foreach ($this->cumulated["values"] as $key => $value)
868  {
869  foreach ($value as $key2 => $value2)
870  {
871  $this->cumulated["variables"][$key][$key2] = utf8_decode($value2);
872  }
873  }
874  include_once "./Modules/SurveyQuestionPool/classes/class.SurveyChart.php";
875  $b1 = new SurveyChart("bars",400,250,utf8_decode($this->getTitle()),utf8_decode($this->lng->txt("answers")),utf8_decode($this->lng->txt("users_answered")),$this->cumulated["values"]);
876  }
877 
884  function getMinMaxText()
885  {
886  $min = $this->getMinimum();
887  $max = $this->getMaximum();
888  if (strlen($min) && strlen($max))
889  {
890  return "(" . $min . " " . strtolower($this->lng->txt("to")) . " " . $max . ")";
891  }
892  else if (strlen($min))
893  {
894  return "(&gt;= " . $min . ")";
895  }
896  else if (strlen($max))
897  {
898  return "(&lt;= " . $max . ")";
899  }
900  else
901  {
902  return "";
903  }
904  }
905 }
906 ?>