[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/htmlform/ -> HTMLFormFieldCloner.php (source)

   1  <?php
   2  
   3  /**
   4   * A container for HTMLFormFields that allows for multiple copies of the set of
   5   * fields to be displayed to and entered by the user.
   6   *
   7   * Recognized parameters, besides the general ones, include:
   8   *   fields - HTMLFormField descriptors for the subfields this cloner manages.
   9   *     The format is just like for the HTMLForm. A field with key 'delete' is
  10   *     special: it must have type = submit and will serve to delete the group
  11   *     of fields.
  12   *   required - If specified, at least one group of fields must be submitted.
  13   *   format - HTMLForm display format to use when displaying the subfields:
  14   *     'table', 'div', or 'raw'.
  15   *   row-legend - If non-empty, each group of subfields will be enclosed in a
  16   *     fieldset. The value is the name of a message key to use as the legend.
  17   *   create-button-message - Message key to use as the text of the button to
  18   *     add an additional group of fields.
  19   *   delete-button-message - Message key to use as the text of automatically-
  20   *     generated 'delete' button. Ignored if 'delete' is included in 'fields'.
  21   *
  22   * In the generated HTML, the subfields will be named along the lines of
  23   * "clonerName[index][fieldname]", with ids "clonerId--index--fieldid". 'index'
  24   * may be a number or an arbitrary string, and may likely change when the page
  25   * is resubmitted. Cloners may be nested, resulting in field names along the
  26   * lines of "cloner1Name[index1][cloner2Name][index2][fieldname]" and
  27   * corresponding ids.
  28   *
  29   * Use of cloner may result in submissions of the page that are not submissions
  30   * of the HTMLForm, when non-JavaScript clients use the create or remove buttons.
  31   *
  32   * The result is an array, with values being arrays mapping subfield names to
  33   * their values. On non-HTMLForm-submission page loads, there may also be
  34   * additional (string) keys present with other types of values.
  35   *
  36   * @since 1.23
  37   */
  38  class HTMLFormFieldCloner extends HTMLFormField {
  39      private static $counter = 0;
  40  
  41      /**
  42       * @var string String uniquely identifying this cloner instance and
  43       * unlikely to exist otherwise in the generated HTML, while still being
  44       * valid as part of an HTML id.
  45       */
  46      protected $uniqueId;
  47  
  48  	public function __construct( $params ) {
  49          $this->uniqueId = get_class( $this ) . ++self::$counter . 'x';
  50          parent::__construct( $params );
  51  
  52          if ( empty( $this->mParams['fields'] ) || !is_array( $this->mParams['fields'] ) ) {
  53              throw new MWException( 'HTMLFormFieldCloner called without any fields' );
  54          }
  55  
  56          // Make sure the delete button, if explicitly specified, is sane
  57          if ( isset( $this->mParams['fields']['delete'] ) ) {
  58              $class = 'mw-htmlform-cloner-delete-button';
  59              $info = $this->mParams['fields']['delete'] + array(
  60                  'cssclass' => $class
  61              );
  62              unset( $info['name'], $info['class'] );
  63  
  64              if ( !isset( $info['type'] ) || $info['type'] !== 'submit' ) {
  65                  throw new MWException(
  66                      'HTMLFormFieldCloner delete field, if specified, must be of type "submit"'
  67                  );
  68              }
  69  
  70              if ( !in_array( $class, explode( ' ', $info['cssclass'] ) ) ) {
  71                  $info['cssclass'] .= " $class";
  72              }
  73  
  74              $this->mParams['fields']['delete'] = $info;
  75          }
  76      }
  77  
  78      /**
  79       * Create the HTMLFormFields that go inside this element, using the
  80       * specified key.
  81       *
  82       * @param string $key Array key under which these fields should be named
  83       * @return HTMLFormField[]
  84       */
  85  	protected function createFieldsForKey( $key ) {
  86          $fields = array();
  87          foreach ( $this->mParams['fields'] as $fieldname => $info ) {
  88              $name = "{$this->mName}[$key][$fieldname]";
  89              if ( isset( $info['name'] ) ) {
  90                  $info['name'] = "{$this->mName}[$key][{$info['name']}]";
  91              } else {
  92                  $info['name'] = $name;
  93              }
  94              if ( isset( $info['id'] ) ) {
  95                  $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--{$info['id']}" );
  96              } else {
  97                  $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--$fieldname" );
  98              }
  99              $field = HTMLForm::loadInputFromParameters( $name, $info );
 100              $field->mParent = $this->mParent;
 101              $fields[$fieldname] = $field;
 102          }
 103          return $fields;
 104      }
 105  
 106      /**
 107       * Re-key the specified values array to match the names applied by
 108       * createFieldsForKey().
 109       *
 110       * @param string $key Array key under which these fields should be named
 111       * @param array $values Values array from the request
 112       * @return array
 113       */
 114  	protected function rekeyValuesArray( $key, $values ) {
 115          $data = array();
 116          foreach ( $values as $fieldname => $value ) {
 117              $name = "{$this->mName}[$key][$fieldname]";
 118              $data[$name] = $value;
 119          }
 120          return $data;
 121      }
 122  
 123  	protected function needsLabel() {
 124          return false;
 125      }
 126  
 127  	public function loadDataFromRequest( $request ) {
 128          // It's possible that this might be posted with no fields. Detect that
 129          // by looking for an edit token.
 130          if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) {
 131              return $this->getDefault();
 132          }
 133  
 134          $values = $request->getArray( $this->mName );
 135          if ( $values === null ) {
 136              $values = array();
 137          }
 138  
 139          $ret = array();
 140          foreach ( $values as $key => $value ) {
 141              if ( $key === 'create' || isset( $value['delete'] ) ) {
 142                  $ret['nonjs'] = 1;
 143                  continue;
 144              }
 145  
 146              // Add back in $request->getValues() so things that look for e.g.
 147              // wpEditToken don't fail.
 148              $data = $this->rekeyValuesArray( $key, $value ) + $request->getValues();
 149  
 150              $fields = $this->createFieldsForKey( $key );
 151              $subrequest = new DerivativeRequest( $request, $data, $request->wasPosted() );
 152              $row = array();
 153              foreach ( $fields as $fieldname => $field ) {
 154                  if ( !empty( $field->mParams['nodata'] ) ) {
 155                      continue;
 156                  } elseif ( !empty( $field->mParams['disabled'] ) ) {
 157                      $row[$fieldname] = $field->getDefault();
 158                  } else {
 159                      $row[$fieldname] = $field->loadDataFromRequest( $subrequest );
 160                  }
 161              }
 162              $ret[] = $row;
 163          }
 164  
 165          if ( isset( $values['create'] ) ) {
 166              // Non-JS client clicked the "create" button.
 167              $fields = $this->createFieldsForKey( $this->uniqueId );
 168              $row = array();
 169              foreach ( $fields as $fieldname => $field ) {
 170                  if ( !empty( $field->mParams['nodata'] ) ) {
 171                      continue;
 172                  } else {
 173                      $row[$fieldname] = $field->getDefault();
 174                  }
 175              }
 176              $ret[] = $row;
 177          }
 178  
 179          return $ret;
 180      }
 181  
 182  	public function getDefault() {
 183          $ret = parent::getDefault();
 184  
 185          // The default default is one entry with all subfields at their
 186          // defaults.
 187          if ( $ret === null ) {
 188              $fields = $this->createFieldsForKey( $this->uniqueId );
 189              $row = array();
 190              foreach ( $fields as $fieldname => $field ) {
 191                  if ( !empty( $field->mParams['nodata'] ) ) {
 192                      continue;
 193                  } else {
 194                      $row[$fieldname] = $field->getDefault();
 195                  }
 196              }
 197              $ret = array( $row );
 198          }
 199  
 200          return $ret;
 201      }
 202  
 203  	public function cancelSubmit( $values, $alldata ) {
 204          if ( isset( $values['nonjs'] ) ) {
 205              return true;
 206          }
 207  
 208          foreach ( $values as $key => $value ) {
 209              $fields = $this->createFieldsForKey( $key );
 210              foreach ( $fields as $fieldname => $field ) {
 211                  if ( !empty( $field->mParams['nodata'] ) ) {
 212                      continue;
 213                  }
 214                  if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
 215                      return true;
 216                  }
 217              }
 218          }
 219  
 220          return parent::cancelSubmit( $values, $alldata );
 221      }
 222  
 223  	public function validate( $values, $alldata ) {
 224          if ( isset( $this->mParams['required'] )
 225              && $this->mParams['required'] !== false
 226              && !$values
 227          ) {
 228              return $this->msg( 'htmlform-cloner-required' )->parseAsBlock();
 229          }
 230  
 231          if ( isset( $values['nonjs'] ) ) {
 232              // The submission was a non-JS create/delete click, so fail
 233              // validation in case cancelSubmit() somehow didn't already handle
 234              // it.
 235              return false;
 236          }
 237  
 238          foreach ( $values as $key => $value ) {
 239              $fields = $this->createFieldsForKey( $key );
 240              foreach ( $fields as $fieldname => $field ) {
 241                  if ( !empty( $field->mParams['nodata'] ) ) {
 242                      continue;
 243                  }
 244                  $ok = $field->validate( $value[$fieldname], $alldata );
 245                  if ( $ok !== true ) {
 246                      return false;
 247                  }
 248              }
 249          }
 250  
 251          return parent::validate( $values, $alldata );
 252      }
 253  
 254      /**
 255       * Get the input HTML for the specified key.
 256       *
 257       * @param string $key Array key under which the fields should be named
 258       * @param array $values
 259       * @return string
 260       */
 261  	protected function getInputHTMLForKey( $key, $values ) {
 262          $displayFormat = isset( $this->mParams['format'] )
 263              ? $this->mParams['format']
 264              : $this->mParent->getDisplayFormat();
 265  
 266          switch ( $displayFormat ) {
 267              case 'table':
 268                  $getFieldHtmlMethod = 'getTableRow';
 269                  break;
 270              case 'vform':
 271                  // Close enough to a div.
 272                  $getFieldHtmlMethod = 'getDiv';
 273                  break;
 274              default:
 275                  $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat );
 276          }
 277  
 278          $html = '';
 279          $hasLabel = false;
 280  
 281          $fields = $this->createFieldsForKey( $key );
 282          foreach ( $fields as $fieldname => $field ) {
 283              $v = ( empty( $field->mParams['nodata'] ) && $values !== null )
 284                  ? $values[$fieldname]
 285                  : $field->getDefault();
 286              $html .= $field->$getFieldHtmlMethod( $v );
 287  
 288              $labelValue = trim( $field->getLabel() );
 289              if ( $labelValue != '&#160;' && $labelValue !== '' ) {
 290                  $hasLabel = true;
 291              }
 292          }
 293  
 294          if ( !isset( $fields['delete'] ) ) {
 295              $name = "{$this->mName}[$key][delete]";
 296              $label = isset( $this->mParams['delete-button-message'] )
 297                  ? $this->mParams['delete-button-message']
 298                  : 'htmlform-cloner-delete';
 299              $field = HTMLForm::loadInputFromParameters( $name, array(
 300                  'type' => 'submit',
 301                  'name' => $name,
 302                  'id' => Sanitizer::escapeId( "{$this->mID}--$key--delete" ),
 303                  'cssclass' => 'mw-htmlform-cloner-delete-button',
 304                  'default' => $this->msg( $label )->text(),
 305              ) );
 306              $field->mParent = $this->mParent;
 307              $v = $field->getDefault();
 308  
 309              if ( $displayFormat === 'table' ) {
 310                  $html .= $field->$getFieldHtmlMethod( $v );
 311              } else {
 312                  $html .= $field->getInputHTML( $v );
 313              }
 314          }
 315  
 316          if ( $displayFormat !== 'raw' ) {
 317              $classes = array(
 318                  'mw-htmlform-cloner-row',
 319              );
 320  
 321              if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
 322                  $classes[] = 'mw-htmlform-nolabel';
 323              }
 324  
 325              $attribs = array(
 326                  'class' => implode( ' ', $classes ),
 327              );
 328  
 329              if ( $displayFormat === 'table' ) {
 330                  $html = Html::rawElement( 'table',
 331                      $attribs,
 332                      Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
 333              } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
 334                  $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
 335              }
 336          }
 337  
 338          if ( !empty( $this->mParams['row-legend'] ) ) {
 339              $legend = $this->msg( $this->mParams['row-legend'] )->text();
 340              $html = Xml::fieldset( $legend, $html );
 341          }
 342  
 343          return $html;
 344      }
 345  
 346  	public function getInputHTML( $values ) {
 347          $html = '';
 348  
 349          foreach ( (array)$values as $key => $value ) {
 350              if ( $key === 'nonjs' ) {
 351                  continue;
 352              }
 353              $html .= Html::rawElement( 'li', array( 'class' => 'mw-htmlform-cloner-li' ),
 354                  $this->getInputHTMLForKey( $key, $value )
 355              );
 356          }
 357  
 358          $template = $this->getInputHTMLForKey( $this->uniqueId, null );
 359          $html = Html::rawElement( 'ul', array(
 360              'id' => "mw-htmlform-cloner-list-{$this->mID}",
 361              'class' => 'mw-htmlform-cloner-ul',
 362              'data-template' => $template,
 363              'data-unique-id' => $this->uniqueId,
 364          ), $html );
 365  
 366          $name = "{$this->mName}[create]";
 367          $label = isset( $this->mParams['create-button-message'] )
 368              ? $this->mParams['create-button-message']
 369              : 'htmlform-cloner-create';
 370          $field = HTMLForm::loadInputFromParameters( $name, array(
 371              'type' => 'submit',
 372              'name' => $name,
 373              'id' => Sanitizer::escapeId( "{$this->mID}--create" ),
 374              'cssclass' => 'mw-htmlform-cloner-create-button',
 375              'default' => $this->msg( $label )->text(),
 376          ) );
 377          $field->mParent = $this->mParent;
 378          $html .= $field->getInputHTML( $field->getDefault() );
 379  
 380          return $html;
 381      }
 382  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1