[ Index ]

PHP Cross Reference of Unnamed Project

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      public function __construct($elementName=null, $elementLabel=null, $attributes=null) {
  64          parent::__construct($elementName, $elementLabel, $attributes);
  65      }
  66  
  67      /**
  68       * Old syntax of class constructor. Deprecated in PHP7.
  69       *
  70       * @deprecated since Moodle 3.1
  71       */
  72      public function MoodleQuickForm_rubriceditor($elementName=null, $elementLabel=null, $attributes=null) {
  73          debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
  74          self::__construct($elementName, $elementLabel, $attributes);
  75      }
  76  
  77      /**
  78       * get html for help button
  79       *
  80       * @return string html for help button
  81       */
  82      public function getHelpButton() {
  83          return $this->_helpbutton;
  84      }
  85  
  86      /**
  87       * The renderer will take care itself about different display in normal and frozen states
  88       *
  89       * @return string
  90       */
  91      public function getElementTemplateType() {
  92          return 'default';
  93      }
  94  
  95      /**
  96       * Specifies that confirmation about re-grading needs to be added to this rubric editor.
  97       * $changelevel is saved in $this->regradeconfirmation and retrieved in toHtml()
  98       *
  99       * @see gradingform_rubric_controller::update_or_check_rubric()
 100       * @param int $changelevel
 101       */
 102      public function add_regrade_confirmation($changelevel) {
 103          $this->regradeconfirmation = $changelevel;
 104      }
 105  
 106      /**
 107       * Returns html string to display this element
 108       *
 109       * @return string
 110       */
 111      public function toHtml() {
 112          global $PAGE;
 113          $html = $this->_getTabs();
 114          $renderer = $PAGE->get_renderer('gradingform_rubric');
 115          $data = $this->prepare_data(null, $this->wasvalidated);
 116          if (!$this->_flagFrozen) {
 117              $mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL;
 118              $module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
 119                  'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
 120                  'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'),
 121                      array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric')
 122                  ));
 123              $PAGE->requires->js_init_call('M.gradingform_rubriceditor.init', array(
 124                  array('name' => $this->getName(),
 125                      'criteriontemplate' => $renderer->criterion_template($mode, $data['options'], $this->getName()),
 126                      'leveltemplate' => $renderer->level_template($mode, $data['options'], $this->getName())
 127                     )),
 128                  true, $module);
 129          } else {
 130              // Rubric is frozen, no javascript needed
 131              if ($this->_persistantFreeze) {
 132                  $mode = gradingform_rubric_controller::DISPLAY_EDIT_FROZEN;
 133              } else {
 134                  $mode = gradingform_rubric_controller::DISPLAY_PREVIEW;
 135              }
 136          }
 137          if ($this->regradeconfirmation) {
 138              if (!isset($data['regrade'])) {
 139                  $data['regrade'] = 1;
 140              }
 141              $html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']);
 142          }
 143          if ($this->validationerrors) {
 144              $html .= html_writer::div($renderer->notification($this->validationerrors));
 145          }
 146          $html .= $renderer->display_rubric($data['criteria'], $data['options'], $mode, $this->getName());
 147          return $html;
 148      }
 149  
 150      /**
 151       * Prepares the data passed in $_POST:
 152       * - processes the pressed buttons 'addlevel', 'addcriterion', 'moveup', 'movedown', 'delete' (when JavaScript is disabled)
 153       *   sets $this->nonjsbuttonpressed to true/false if such button was pressed
 154       * - if options not passed (i.e. we create a new rubric) fills the options array with the default values
 155       * - if options are passed completes the options array with unchecked checkboxes
 156       * - if $withvalidation is set, adds 'error_xxx' attributes to elements that contain errors and creates an error string
 157       *   and stores it in $this->validationerrors
 158       *
 159       * @param array $value
 160       * @param boolean $withvalidation whether to enable data validation
 161       * @return array
 162       */
 163      protected function prepare_data($value = null, $withvalidation = false) {
 164          if (null === $value) {
 165              $value = $this->getValue();
 166          }
 167          if ($this->nonjsbuttonpressed === null) {
 168              $this->nonjsbuttonpressed = false;
 169          }
 170          $totalscore = 0;
 171          $errors = array();
 172          $return = array('criteria' => array(), 'options' => gradingform_rubric_controller::get_default_options());
 173          if (!isset($value['criteria'])) {
 174              $value['criteria'] = array();
 175              $errors['err_nocriteria'] = 1;
 176          }
 177          // If options are present in $value, replace default values with submitted values
 178          if (!empty($value['options'])) {
 179              foreach (array_keys($return['options']) as $option) {
 180                  // special treatment for checkboxes
 181                  if (!empty($value['options'][$option])) {
 182                      $return['options'][$option] = $value['options'][$option];
 183                  } else {
 184                      $return['options'][$option] = null;
 185                  }
 186              }
 187          }
 188          if (is_array($value)) {
 189              // for other array keys of $value no special treatmeant neeeded, copy them to return value as is
 190              foreach (array_keys($value) as $key) {
 191                  if ($key != 'options' && $key != 'criteria') {
 192                      $return[$key] = $value[$key];
 193                  }
 194              }
 195          }
 196  
 197          // iterate through criteria
 198          $lastaction = null;
 199          $lastid = null;
 200          foreach ($value['criteria'] as $id => $criterion) {
 201              if ($id == 'addcriterion') {
 202                  $id = $this->get_next_id(array_keys($value['criteria']));
 203                  $criterion = array('description' => '', 'levels' => array());
 204                  $i = 0;
 205                  // when adding new criterion copy the number of levels and their scores from the last criterion
 206                  if (!empty($value['criteria'][$lastid]['levels'])) {
 207                      foreach ($value['criteria'][$lastid]['levels'] as $lastlevel) {
 208                          $criterion['levels']['NEWID'.($i++)]['score'] = $lastlevel['score'];
 209                      }
 210                  } else {
 211                      $criterion['levels']['NEWID'.($i++)]['score'] = 0;
 212                  }
 213                  // add more levels so there are at least 3 in the new criterion. Increment by 1 the score for each next one
 214                  for ($i=$i; $i<3; $i++) {
 215                      $criterion['levels']['NEWID'.$i]['score'] = $criterion['levels']['NEWID'.($i-1)]['score'] + 1;
 216                  }
 217                  // set other necessary fields (definition) for the levels in the new criterion
 218                  foreach (array_keys($criterion['levels']) as $i) {
 219                      $criterion['levels'][$i]['definition'] = '';
 220                  }
 221                  $this->nonjsbuttonpressed = true;
 222              }
 223              $levels = array();
 224              $maxscore = null;
 225              if (array_key_exists('levels', $criterion)) {
 226                  foreach ($criterion['levels'] as $levelid => $level) {
 227                      if ($levelid == 'addlevel') {
 228                          $levelid = $this->get_next_id(array_keys($criterion['levels']));
 229                          $level = array(
 230                              'definition' => '',
 231                              'score' => 0,
 232                          );
 233                          foreach ($criterion['levels'] as $lastlevel) {
 234                              if (isset($lastlevel['score']) && $level['score'] < $lastlevel['score'] + 1) {
 235                                  $level['score'] = $lastlevel['score'] + 1;
 236                              }
 237                          }
 238                          $this->nonjsbuttonpressed = true;
 239                      }
 240                      if (!array_key_exists('delete', $level)) {
 241                          if ($withvalidation) {
 242                              if (!strlen(trim($level['definition']))) {
 243                                  $errors['err_nodefinition'] = 1;
 244                                  $level['error_definition'] = true;
 245                              }
 246                              if (!preg_match('#^[\+]?\d*$#', trim($level['score'])) && !preg_match('#^[\+]?\d*[\.,]\d+$#', trim($level['score']))) {
 247                                  $errors['err_scoreformat'] = 1;
 248                                  $level['error_score'] = true;
 249                              }
 250                          }
 251                          $levels[$levelid] = $level;
 252                          if ($maxscore === null || (float)$level['score'] > $maxscore) {
 253                              $maxscore = (float)$level['score'];
 254                          }
 255                      } else {
 256                          $this->nonjsbuttonpressed = true;
 257                      }
 258                  }
 259              }
 260              $totalscore += (float)$maxscore;
 261              $criterion['levels'] = $levels;
 262              if ($withvalidation && !array_key_exists('delete', $criterion)) {
 263                  if (count($levels)<2) {
 264                      $errors['err_mintwolevels'] = 1;
 265                      $criterion['error_levels'] = true;
 266                  }
 267                  if (!strlen(trim($criterion['description']))) {
 268                      $errors['err_nodescription'] = 1;
 269                      $criterion['error_description'] = true;
 270                  }
 271              }
 272              if (array_key_exists('moveup', $criterion) || $lastaction == 'movedown') {
 273                  unset($criterion['moveup']);
 274                  if ($lastid !== null) {
 275                      $lastcriterion = $return['criteria'][$lastid];
 276                      unset($return['criteria'][$lastid]);
 277                      $return['criteria'][$id] = $criterion;
 278                      $return['criteria'][$lastid] = $lastcriterion;
 279                  } else {
 280                      $return['criteria'][$id] = $criterion;
 281                  }
 282                  $lastaction = null;
 283                  $lastid = $id;
 284                  $this->nonjsbuttonpressed = true;
 285              } else if (array_key_exists('delete', $criterion)) {
 286                  $this->nonjsbuttonpressed = true;
 287              } else {
 288                  if (array_key_exists('movedown', $criterion)) {
 289                      unset($criterion['movedown']);
 290                      $lastaction = 'movedown';
 291                      $this->nonjsbuttonpressed = true;
 292                  }
 293                  $return['criteria'][$id] = $criterion;
 294                  $lastid = $id;
 295              }
 296          }
 297  
 298          if ($totalscore <= 0) {
 299              $errors['err_totalscore'] = 1;
 300          }
 301  
 302          // add sort order field to criteria
 303          $csortorder = 1;
 304          foreach (array_keys($return['criteria']) as $id) {
 305              $return['criteria'][$id]['sortorder'] = $csortorder++;
 306          }
 307  
 308          // create validation error string (if needed)
 309          if ($withvalidation) {
 310              if (count($errors)) {
 311                  $rv = array();
 312                  foreach ($errors as $error => $v) {
 313                      $rv[] = get_string($error, 'gradingform_rubric');
 314                  }
 315                  $this->validationerrors = join('<br/ >', $rv);
 316              } else {
 317                  $this->validationerrors = false;
 318              }
 319              $this->wasvalidated = true;
 320          }
 321          return $return;
 322      }
 323  
 324      /**
 325       * Scans array $ids to find the biggest element ! NEWID*, increments it by 1 and returns
 326       *
 327       * @param array $ids
 328       * @return string
 329       */
 330      protected function get_next_id($ids) {
 331          $maxid = 0;
 332          foreach ($ids as $id) {
 333              if (preg_match('/^NEWID(\d+)$/', $id, $matches) && ((int)$matches[1]) > $maxid) {
 334                  $maxid = (int)$matches[1];
 335              }
 336          }
 337          return 'NEWID'.($maxid+1);
 338      }
 339  
 340      /**
 341       * Checks if a submit button was pressed which is supposed to be processed on client side by JS
 342       * but user seem to have disabled JS in the browser.
 343       * (buttons 'add criteria', 'add level', 'move up', 'move down', etc.)
 344       * In this case the form containing this element is prevented from being submitted
 345       *
 346       * @param array $value
 347       * @return boolean true if non-submit button was pressed and not processed by JS
 348       */
 349      public function non_js_button_pressed($value) {
 350          if ($this->nonjsbuttonpressed === null) {
 351              $this->prepare_data($value);
 352          }
 353          return $this->nonjsbuttonpressed;
 354      }
 355  
 356      /**
 357       * Validates that rubric has at least one criterion, at least two levels within one criterion,
 358       * each level has a valid score, all levels have filled definitions and all criteria
 359       * have filled descriptions
 360       *
 361       * @param array $value
 362       * @return string|false error text or false if no errors found
 363       */
 364      public function validate($value) {
 365          if (!$this->wasvalidated) {
 366              $this->prepare_data($value, true);
 367          }
 368          return $this->validationerrors;
 369      }
 370  
 371      /**
 372       * Prepares the data for saving
 373       *
 374       * @see prepare_data()
 375       * @param array $submitValues
 376       * @param boolean $assoc
 377       * @return array
 378       */
 379      public function exportValue(&$submitValues, $assoc = false) {
 380          $value =  $this->prepare_data($this->_findValue($submitValues));
 381          return $this->_prepareValue($value, $assoc);
 382      }
 383  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1