5use InvalidArgumentException;
50 private static $counter = 0;
60 protected $mFields = [];
62 private bool $nonJsUpdate =
false;
70 parent::__construct( $params );
72 if ( empty( $this->mParams[
'fields'] ) || !is_array( $this->mParams[
'fields'] ) ) {
73 throw new InvalidArgumentException(
'HTMLFormFieldCloner called without any fields' );
77 if ( isset( $this->mParams[
'fields'][
'delete'] ) ) {
78 $class =
'mw-htmlform-cloner-delete-button';
79 $info = $this->mParams[
'fields'][
'delete'] + [
80 'formnovalidate' =>
true,
83 unset( $info[
'name'], $info[
'class'] );
85 if ( !isset( $info[
'type'] ) || $info[
'type'] !==
'submit' ) {
86 throw new InvalidArgumentException(
87 'HTMLFormFieldCloner delete field, if specified, must be of type "submit"'
91 if ( !in_array( $class, explode(
' ', $info[
'cssclass'] ) ) ) {
92 $info[
'cssclass'] .=
" $class";
95 $this->mParams[
'fields'][
'delete'] = $info;
104 if ( !isset( $this->mFields[$key] ) ) {
107 return $this->mFields[$key];
119 foreach ( $this->mParams[
'fields'] as $fieldname => $info ) {
120 $name =
"{$this->mName}[$key][$fieldname]";
121 if ( isset( $info[
'name'] ) ) {
122 $info[
'name'] =
"{$this->mName}[$key][{$info['name']}]";
124 $info[
'name'] = $name;
126 if ( isset( $info[
'id'] ) ) {
127 $info[
'id'] = Sanitizer::escapeIdForAttribute(
"{$this->mID}--$key--{$info['id']}" );
129 $info[
'id'] = Sanitizer::escapeIdForAttribute(
"{$this->mID}--$key--$fieldname" );
133 if ( $this->mCondState ) {
134 foreach ( [
'hide',
'disable' ] as $type ) {
135 if ( !isset( $this->mCondState[$type] ) ) {
138 $param = $type .
'-if';
139 if ( isset( $info[$param] ) ) {
141 $info[$param] = [
'OR', $info[$param], $this->mCondState[$type] ];
144 $info[$param] = $this->mCondState[$type];
149 $info[
'cloner'] = &$cloner;
150 $info[
'cloner-key'] = $key;
152 $fields[$fieldname] = $field;
167 foreach ( $values as $fieldname => $value ) {
168 $name =
"{$this->mName}[$key][$fieldname]";
169 $data[$name] = $value;
180 while ( preg_match(
'/^(.+)\[([^\]]+)\]$/', $name, $m ) ) {
181 array_unshift( $fieldKeys, $m[2] );
184 array_unshift( $fieldKeys, $name );
200 if ( count( $findPath ) > 1 ) {
203 if ( !isset( $this->mParams[
'fields'][$find] ) ) {
204 $cloner = $this->mParams[
'cloner'] ??
null;
205 if ( $cloner instanceof
self ) {
206 return $cloner->findNearestField( $this, $find );
211 return $fields[$find];
219 $path = [ $this->mParams[
'fieldname'], $field->mParams[
'cloner-key'] ];
220 $cloner = $this->mParams[
'cloner'] ??
null;
221 if ( $cloner instanceof
self ) {
222 $path = array_merge( $cloner->getFieldPath( $this ),
$path );
237 count( $alldata ) === 0 ||
240 $field->mParams[
'cloner-key'] === $this->uniqueId
242 return $field->getDefault();
246 $alldata = $alldata[$key];
248 return $alldata[$field->mParams[
'fieldname']];
260 if ( !$request->getCheck(
'wpEditToken' ) && $request->getArray( $this->mName ) ===
null ) {
264 $values = $request->getArray( $this->mName ) ?? [];
267 foreach ( $values as $key => $value ) {
268 if ( $key ===
'create' || isset( $value[
'delete'] ) ) {
269 $this->nonJsUpdate =
true;
280 foreach ( $fields as $fieldname => $field ) {
281 if ( $field->skipLoadData( $subrequest ) ) {
284 if ( !empty( $field->mParams[
'disabled'] ) ) {
285 $row[$fieldname] = $field->getDefault();
287 $row[$fieldname] = $field->loadDataFromRequest( $subrequest );
293 if ( isset( $values[
'create'] ) ) {
297 foreach ( $fields as $fieldname => $field ) {
298 if ( !empty( $field->mParams[
'nodata'] ) ) {
301 $row[$fieldname] = $field->getDefault();
310 public function filter( $values, $alldata ) {
312 foreach ( $values as $key => &$fieldsValue ) {
314 foreach ( $fieldsValue as $fieldname => &$value ) {
316 if ( $fields[$fieldname]->
isDisabled( $alldata ) ) {
317 $value = $fields[$fieldname]->getDefault();
321 $value = $fields[$fieldname]->filter( $value, $alldata );
326 return parent::filter( $values, $alldata );
331 $ret = parent::getDefault();
335 if ( $ret !==
null && !is_array( $ret ) ) {
336 $type = gettype( $ret );
337 wfDeprecated( __CLASS__ .
" with non-array default ($type given)",
'1.45' );
341 if ( $ret ===
null || !is_array( $ret ) ) {
344 foreach ( $fields as $fieldname => $field ) {
345 if ( !empty( $field->mParams[
'nodata'] ) ) {
348 $row[$fieldname] = $field->getDefault();
361 if ( $this->nonJsUpdate ) {
365 foreach ( $values as $key => $value ) {
367 foreach ( $fields as $fieldname => $field ) {
368 if ( !array_key_exists( $fieldname, $value ) ) {
371 if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
377 return parent::cancelSubmit( $values, $alldata );
385 if ( isset( $this->mParams[
'required'] )
386 && $this->mParams[
'required'] !==
false
389 return $this->
msg(
'htmlform-cloner-required' );
392 if ( $this->nonJsUpdate ) {
399 foreach ( $values as $key => $value ) {
401 foreach ( $fields as $fieldname => $field ) {
402 if ( !array_key_exists( $fieldname, $value ) || $field->isHidden( $alldata ) ) {
405 $ok = $field->validate( $value[$fieldname], $alldata );
406 if ( $ok !==
true ) {
412 return parent::validate( $values, $alldata );
423 $displayFormat = $this->mParams[
'format'] ?? $this->mParent->getDisplayFormat();
426 $getFieldHtmlMethod = $displayFormat ==
'table' ?
'getTableRow' : (
'get' . $displayFormat );
433 foreach ( $fields as $fieldname => $field ) {
434 $v = array_key_exists( $fieldname, $values )
435 ? $values[$fieldname]
436 : $field->getDefault();
440 [ $name, $value, $params ] = $field->getHiddenFieldData( $v );
441 $hidden .= Html::hidden( $name, $value, $params ) .
"\n";
443 $html .= $field->$getFieldHtmlMethod( $v );
445 $labelValue = trim( $field->getLabel() );
446 if ( $labelValue !==
"\u{00A0}" && $labelValue !==
' ' && $labelValue !==
'' ) {
452 if ( !isset( $fields[
'delete'] ) ) {
455 if ( $displayFormat ===
'table' ) {
456 $html .= $field->$getFieldHtmlMethod( $field->getDefault() );
458 $html .= $field->getInputHTML( $field->getDefault() );
462 if ( $displayFormat !==
'raw' ) {
463 $classes = [
'mw-htmlform-cloner-row' ];
466 $classes[] =
'mw-htmlform-nolabel';
469 $attribs = [
'class' => $classes ];
471 if ( $displayFormat ===
'table' ) {
472 $html = Html::rawElement(
'table',
474 Html::rawElement(
'tbody', [],
"\n$html\n" ) ) .
"\n";
476 $html = Html::rawElement(
'div', $attribs,
"\n$html\n" );
482 if ( !empty( $this->mParams[
'row-legend'] ) ) {
483 $legend = $this->
msg( $this->mParams[
'row-legend'] )->text();
484 $legend = $legend ?
Html::element(
'legend', [], $legend ) :
'';
485 $html = Html::rawElement(
500 $name =
"{$this->mName}[$key][delete]";
501 $label = $this->mParams[
'delete-button-message'] ??
'htmlform-cloner-delete';
504 'formnovalidate' =>
true,
506 'id' => Sanitizer::escapeIdForAttribute(
"{$this->mID}--$key--delete" ),
507 'cssclass' =>
'mw-htmlform-cloner-delete-button',
508 'default' => $this->
getMessage( $label )->text(),
509 'disabled' => $this->mParams[
'disabled'] ??
false,
515 $name =
"{$this->mName}[create]";
516 $label = $this->mParams[
'create-button-message'] ??
'htmlform-cloner-create';
519 'formnovalidate' =>
true,
521 'id' => Sanitizer::escapeIdForAttribute(
"{$this->mID}--create" ),
522 'cssclass' =>
'mw-htmlform-cloner-create-button',
523 'default' => $this->getMessage( $label )->text(),
524 'disabled' => $this->mParams[
'disabled'] ??
false,
532 foreach ( (array)$values as $key => $value ) {
533 $html .= Html::rawElement(
'li', [
'class' =>
'mw-htmlform-cloner-li' ],
534 $this->getInputHTMLForKey( $key, $value )
538 $template = $this->getInputHTMLForKey( $this->uniqueId, [] );
539 $html = Html::rawElement(
'ul', [
540 'id' =>
"mw-htmlform-cloner-list-{$this->mID}",
541 'class' =>
'mw-htmlform-cloner-ul',
542 'data-template' => $template,
543 'data-unique-id' => $this->uniqueId,
546 $field = $this->getCreateButtonHtml();
547 $html .= $field->getInputHTML( $field->getDefault() );
563 $fields = $this->getFieldsForKey( $key );
564 foreach ( $fields as $fieldname => $field ) {
565 $v = array_key_exists( $fieldname, $values )
566 ? $values[$fieldname]
567 : $field->getDefault();
571 [ $name, $value, $params ] = $field->getHiddenFieldData( $v );
572 $hidden .= Html::hidden( $name, $value, $params ) .
"\n";
574 $html .= $field->getOOUI( $v );
578 if ( !isset( $fields[
'delete'] ) ) {
579 $field = $this->getDeleteButtonHtml( $key );
580 $fieldHtml = $field->getInputOOUI( $field->getDefault() );
581 $fieldHtml->setInfusable(
true );
586 $html = Html::rawElement(
'div', [
'class' =>
'mw-htmlform-cloner-row' ],
"\n$html\n" );
590 if ( !empty( $this->mParams[
'row-legend'] ) ) {
591 $legend = $this->msg( $this->mParams[
'row-legend'] )->text();
592 $legend = $legend ? Html::element(
'legend', [], $legend ) :
'';
593 $html = Html::rawElement(
607 foreach ( (array)$values as $key => $value ) {
608 $html .= Html::rawElement(
'li', [
'class' =>
'mw-htmlform-cloner-li' ],
609 $this->getInputOOUIForKey( $key, $value )
613 $template = $this->getInputOOUIForKey( $this->uniqueId, [] );
614 $html = Html::rawElement(
'ul', [
615 'id' =>
"mw-htmlform-cloner-list-{$this->mID}",
616 'class' =>
'mw-htmlform-cloner-ul',
617 'data-template' => $template,
618 'data-unique-id' => $this->uniqueId,
621 $field = $this->getCreateButtonHtml();
622 $fieldHtml = $field->getInputOOUI( $field->getDefault() );
623 $fieldHtml->setInfusable(
true );
632class_alias( HTMLFormFieldCloner::class,
'HTMLFormFieldCloner' );
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.