[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 != ' ' && $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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |