[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/grade/grading/form/rubric/ -> rubriceditor.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * File contains definition of class MoodleQuickForm_rubriceditor
  19   *
  20   * @package    gradingform_rubric
  21   * @copyright  2011 Marina Glancy
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once("HTML/QuickForm/input.php");
  28  
  29  /**
  30   * Form element for handling rubric editor
  31   *
  32   * The rubric editor is defined as a separate form element. This allows us to render
  33   * criteria, levels and buttons using the rubric's own renderer. Also, the required
  34   * Javascript library is included, which processes, on the client, buttons needed
  35   * for reordering, adding and deleting criteria.
  36   *
  37   * If Javascript is disabled when one of those special buttons is pressed, the form
  38   * element is not validated and, instead of submitting the form, we process button presses.
  39   *
  40   * @package    gradingform_rubric
  41   * @copyright  2011 Marina Glancy
  42   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   */
  44  class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
  45      /** @var string help message */
  46      public $_helpbutton = '';
  47      /** @var string|bool stores the result of the last validation: null - undefined, false - no errors, string - error(s) text */
  48      protected $validationerrors = null;
  49      /** @var bool if element has already been validated **/
  50      protected $wasvalidated = false;
  51      /** @var bool If non-submit (JS) button was pressed: null - unknown, true/false - button was/wasn't pressed */
  52      protected $nonjsbuttonpressed = false;
  53      /** @var bool Message to display in front of the editor (that there exist grades on this rubric being edited) */
  54      protected $regradeconfirmation = false;
  55  
  56      /**
  57       * Constructor for rubric editor
  58       *
  59       * @param string $elementName
  60       * @param string $elementLabel
  61       * @param array $attributes
  62       */
  63      function MoodleQuickForm_rubriceditor($elementName=null, $elementLabel=null, $attributes=null) {
  64          parent::HTML_QuickForm_input($elementName, $elementLabel, $attributes);
  65      }
  66  
  67      /**
  68       * get html for help button
  69       *
  70       * @return string html for help button
  71       */
  72      public function getHelpButton() {
  73          return $this->_helpbutton;
  74      }
  75  
  76      /**
  77       * The renderer will take care itself about different display in normal and frozen states
  78       *
  79       * @return string
  80       */
  81      public function getElementTemplateType() {
  82          return 'default';
  83      }
  84  
  85      /**
  86       * Specifies that confirmation about re-grading needs to be added to this rubric editor.
  87       * $changelevel is saved in $this->regradeconfirmation and retrieved in toHtml()
  88       *
  89       * @see gradingform_rubric_controller::update_or_check_rubric()
  90       * @param int $changelevel
  91       */
  92      public function add_regrade_confirmation($changelevel) {
  93          $this->regradeconfirmation = $changelevel;
  94      }
  95  
  96      /**
  97       * Returns html string to display this element
  98       *
  99       * @return string
 100       */
 101      public function toHtml() {
 102          global $PAGE;
 103          $html = $this->_getTabs();
 104          $renderer = $PAGE->get_renderer('gradingform_rubric');
 105          $data = $this->prepare_data(null, $this->wasvalidated);
 106          if (!$this->_flagFrozen) {
 107              $mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL;
 108              $module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
 109                  'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
 110                  'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'),
 111                      array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric')
 112                      ));
 113              $PAGE->requires->js_init_call('M.gradingform_rubriceditor.init', array(
 114                  array('name' => $this->getName(),
 115                      'criteriontemplate' => $renderer->criterion_template($mode, $data['options'], $this->getName()),
 116                      'leveltemplate' => $renderer->level_template($mode, $data['options'], $this->getName())
 117                     )),
 118                  true, $module);
 119          } else {
 120              // Rubric is frozen, no javascript needed
 121              if ($this->_persistantFreeze) {
 122                  $mode = gradingform_rubric_controller::DISPLAY_EDIT_FROZEN;
 123              } else {
 124                  $mode = gradingform_rubric_controller::DISPLAY_PREVIEW;
 125              }
 126          }
 127          if ($this->regradeconfirmation) {
 128              if (!isset($data['regrade'])) {
 129                  $data['regrade'] = 1;
 130              }
 131              $html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']);
 132          }
 133          if ($this->validationerrors) {
 134              $html .= $renderer->notification($this->validationerrors, 'error');
 135          }
 136          $html .= $renderer->display_rubric($data['criteria'], $data['options'], $mode, $this->getName());
 137          return $html;
 138      }
 139  
 140      /**
 141       * Prepares the data passed in $_POST:
 142       * - processes the pressed buttons 'addlevel', 'addcriterion', 'moveup', 'movedown', 'delete' (when JavaScript is disabled)
 143       *   sets $this->nonjsbuttonpressed to true/false if such button was pressed
 144       * - if options not passed (i.e. we create a new rubric) fills the options array with the default values
 145       * - if options are passed completes the options array with unchecked checkboxes
 146       * - if $withvalidation is set, adds 'error_xxx' attributes to elements that contain errors and creates an error string
 147       *   and stores it in $this->validationerrors
 148       *
 149       * @param array $value
 150       * @param boolean $withvalidation whether to enable data validation
 151       * @return array
 152       */
 153      protected function prepare_data($value = null, $withvalidation = false) {
 154          if (null === $value) {
 155              $value = $this->getValue();
 156          }
 157          if ($this->nonjsbuttonpressed === null) {
 158              $this->nonjsbuttonpressed = false;
 159          }
 160          $totalscore = 0;
 161          $errors = array();
 162          $return = array('criteria' => array(), 'options' => gradingform_rubric_controller::get_default_options());
 163          if (!isset($value['criteria'])) {
 164              $value['criteria'] = array();
 165              $errors['err_nocriteria'] = 1;
 166          }
 167          // If options are present in $value, replace default values with submitted values
 168          if (!empty($value['options'])) {
 169              foreach (array_keys($return['options']) as $option) {
 170                  // special treatment for checkboxes
 171                  if (!empty($value['options'][$option])) {
 172                      $return['options'][$option] = $value['options'][$option];
 173                  } else {
 174                      $return['options'][$option] = null;
 175                  }
 176              }
 177          }
 178          if (is_array($value)) {
 179              // for other array keys of $value no special treatmeant neeeded, copy them to return value as is
 180              foreach (array_keys($value) as $key) {
 181                  if ($key != 'options' && $key != 'criteria') {
 182                      $return[$key] = $value[$key];
 183                  }
 184              }
 185          }
 186  
 187          // iterate through criteria
 188          $lastaction = null;
 189          $lastid = null;
 190          foreach ($value['criteria'] as $id => $criterion) {
 191              if ($id == 'addcriterion') {
 192                  $id = $this->get_next_id(array_keys($value['criteria']));
 193                  $criterion = array('description' => '', 'levels' => array());
 194                  $i = 0;
 195                  // when adding new criterion copy the number of levels and their scores from the last criterion
 196                  if (!empty($value['criteria'][$lastid]['levels'])) {
 197                      foreach ($value['criteria'][$lastid]['levels'] as $lastlevel) {
 198                          $criterion['levels']['NEWID'.($i++)]['score'] = $lastlevel['score'];
 199                      }
 200                  } else {
 201                      $criterion['levels']['NEWID'.($i++)]['score'] = 0;
 202                  }
 203                  // add more levels so there are at least 3 in the new criterion. Increment by 1 the score for each next one
 204                  for ($i=$i; $i<3; $i++) {
 205                      $criterion['levels']['NEWID'.$i]['score'] = $criterion['levels']['NEWID'.($i-1)]['score'] + 1;
 206                  }
 207                  // set other necessary fields (definition) for the levels in the new criterion
 208                  foreach (array_keys($criterion['levels']) as $i) {
 209                      $criterion['levels'][$i]['definition'] = '';
 210                  }
 211                  $this->nonjsbuttonpressed = true;
 212              }
 213              $levels = array();
 214              $maxscore = null;
 215              if (array_key_exists('levels', $criterion)) {
 216                  foreach ($criterion['levels'] as $levelid => $level) {
 217                      if ($levelid == 'addlevel') {
 218                          $levelid = $this->get_next_id(array_keys($criterion['levels']));
 219                          $level = array(
 220                              'definition' => '',
 221                              'score' => 0,
 222                          );
 223                          foreach ($criterion['levels'] as $lastlevel) {
 224                              if (isset($lastlevel['score']) && $level['score'] < $lastlevel['score'] + 1) {
 225                                  $level['score'] = $lastlevel['score'] + 1;
 226                              }
 227                          }
 228                          $this->nonjsbuttonpressed = true;
 229                      }
 230                      if (!array_key_exists('delete', $level)) {
 231                          if ($withvalidation) {
 232                              if (!strlen(trim($level['definition']))) {
 233                                  $errors['err_nodefinition'] = 1;
 234                                  $level['error_definition'] = true;
 235                              }
 236                              if (!preg_match('#^[\+]?\d*$#', trim($level['score'])) && !preg_match('#^[\+]?\d*[\.,]\d+$#', trim($level['score']))) {
 237                                  $errors['err_scoreformat'] = 1;
 238                                  $level['error_score'] = true;
 239                              }
 240                          }
 241                          $levels[$levelid] = $level;
 242                          if ($maxscore === null || (float)$level['score'] > $maxscore) {
 243                              $maxscore = (float)$level['score'];
 244                          }
 245                      } else {
 246                          $this->nonjsbuttonpressed = true;
 247                      }
 248                  }
 249              }
 250              $totalscore += (float)$maxscore;
 251              $criterion['levels'] = $levels;
 252              if ($withvalidation && !array_key_exists('delete', $criterion)) {
 253                  if (count($levels)<2) {
 254                      $errors['err_mintwolevels'] = 1;
 255                      $criterion['error_levels'] = true;
 256                  }
 257                  if (!strlen(trim($criterion['description']))) {
 258                      $errors['err_nodescription'] = 1;
 259                      $criterion['error_description'] = true;
 260                  }
 261              }
 262              if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') {
 263                  unset($criterion['moveup']);
 264                  if ($lastid !== null) {
 265                      $lastcriterion = $return['criteria'][$lastid];
 266                      unset($return['criteria'][$lastid]);
 267                      $return['criteria'][$id] = $criterion;
 268                      $return['criteria'][$lastid] = $lastcriterion;
 269                  } else {
 270                      $return['criteria'][$id] = $criterion;
 271                  }
 272                  $lastaction = null;
 273                  $lastid = $id;
 274                  $this->nonjsbuttonpressed = true;
 275              } else if (array_key_exists('delete', $criterion)) {
 276                  $this->nonjsbuttonpressed = true;
 277              } else {
 278                  if (array_key_exists('movedown', $criterion)) {
 279                      unset($criterion['movedown']);
 280                      $lastaction = 'movedown';
 281                      $this->nonjsbuttonpressed = true;
 282                  }
 283                  $return['criteria'][$id] = $criterion;
 284                  $lastid = $id;
 285              }
 286          }
 287  
 288          if ($totalscore <= 0) {
 289              $errors['err_totalscore'] = 1;
 290          }
 291  
 292          // add sort order field to criteria
 293          $csortorder = 1;
 294          foreach (array_keys($return['criteria']) as $id) {
 295              $return['criteria'][$id]['sortorder'] = $csortorder++;
 296          }
 297  
 298          // create validation error string (if needed)
 299          if ($withvalidation) {
 300              if (count($errors)) {
 301                  $rv = array();
 302                  foreach ($errors as $error => $v) {
 303                      $rv[] = get_string($error, 'gradingform_rubric');
 304                  }
 305                  $this->validationerrors = join('<br/ >', $rv);
 306              } else {
 307                  $this->validationerrors = false;
 308              }
 309              $this->wasvalidated = true;
 310          }
 311          return $return;
 312      }
 313  
 314      /**
 315       * Scans array $ids to find the biggest element ! NEWID*, increments it by 1 and returns
 316       *
 317       * @param array $ids
 318       * @return string
 319       */
 320      protected function get_next_id($ids) {
 321          $maxid = 0;
 322          foreach ($ids as $id) {
 323              if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) {
 324                  $maxid = (int)$matches[1];
 325              }
 326          }
 327          return 'NEWID'.($maxid+1);
 328      }
 329  
 330      /**
 331       * Checks if a submit button was pressed which is supposed to be processed on client side by JS
 332       * but user seem to have disabled JS in the browser.
 333       * (buttons 'add criteria', 'add level', 'move up', 'move down', etc.)
 334       * In this case the form containing this element is prevented from being submitted
 335       *
 336       * @param array $value
 337       * @return boolean true if non-submit button was pressed and not processed by JS
 338       */
 339      public function non_js_button_pressed($value) {
 340          if ($this->nonjsbuttonpressed === null) {
 341              $this->prepare_data($value);
 342          }
 343          return $this->nonjsbuttonpressed;
 344      }
 345  
 346      /**
 347       * Validates that rubric has at least one criterion, at least two levels within one criterion,
 348       * each level has a valid score, all levels have filled definitions and all criteria
 349       * have filled descriptions
 350       *
 351       * @param array $value
 352       * @return string|false error text or false if no errors found
 353       */
 354      public function validate($value) {
 355          if (!$this->wasvalidated) {
 356              $this->prepare_data($value, true);
 357          }
 358          return $this->validationerrors;
 359      }
 360  
 361      /**
 362       * Prepares the data for saving
 363       *
 364       * @see prepare_data()
 365       * @param array $submitValues
 366       * @param boolean $assoc
 367       * @return array
 368       */
 369      public function exportValue(&$submitValues, $assoc = false) {
 370          $value =  $this->prepare_data($this->_findValue($submitValues));
 371          return $this->_prepareValue($value, $assoc);
 372      }
 373  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1