MediaWiki  REL1_31
HTMLFormFieldCloner.php
Go to the documentation of this file.
1 <?php
2 
39  private static $counter = 0;
40 
46  protected $uniqueId;
47 
48  public function __construct( $params ) {
49  $this->uniqueId = static::class . ++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'] + [
60  'formnovalidate' => true,
61  'cssclass' => $class
62  ];
63  unset( $info['name'], $info['class'] );
64 
65  if ( !isset( $info['type'] ) || $info['type'] !== 'submit' ) {
66  throw new MWException(
67  'HTMLFormFieldCloner delete field, if specified, must be of type "submit"'
68  );
69  }
70 
71  if ( !in_array( $class, explode( ' ', $info['cssclass'] ) ) ) {
72  $info['cssclass'] .= " $class";
73  }
74 
75  $this->mParams['fields']['delete'] = $info;
76  }
77  }
78 
86  protected function createFieldsForKey( $key ) {
87  $fields = [];
88  foreach ( $this->mParams['fields'] as $fieldname => $info ) {
89  $name = "{$this->mName}[$key][$fieldname]";
90  if ( isset( $info['name'] ) ) {
91  $info['name'] = "{$this->mName}[$key][{$info['name']}]";
92  } else {
93  $info['name'] = $name;
94  }
95  if ( isset( $info['id'] ) ) {
96  $info['id'] = Sanitizer::escapeIdForAttribute( "{$this->mID}--$key--{$info['id']}" );
97  } else {
98  $info['id'] = Sanitizer::escapeIdForAttribute( "{$this->mID}--$key--$fieldname" );
99  }
100  // Copy the hide-if rules to "child" fields, so that the JavaScript code handling them
101  // (resources/src/mediawiki/htmlform/hide-if.js) doesn't have to handle nested fields.
102  if ( $this->mHideIf ) {
103  if ( isset( $info['hide-if'] ) ) {
104  // Hide child field if either its rules say it's hidden, or parent's rules say it's hidden
105  $info['hide-if'] = [ 'OR', $info['hide-if'], $this->mHideIf ];
106  } else {
107  // Hide child field if parent's rules say it's hidden
108  $info['hide-if'] = $this->mHideIf;
109  }
110  }
111  $field = HTMLForm::loadInputFromParameters( $name, $info, $this->mParent );
112  $fields[$fieldname] = $field;
113  }
114  return $fields;
115  }
116 
125  protected function rekeyValuesArray( $key, $values ) {
126  $data = [];
127  foreach ( $values as $fieldname => $value ) {
128  $name = "{$this->mName}[$key][$fieldname]";
129  $data[$name] = $value;
130  }
131  return $data;
132  }
133 
134  protected function needsLabel() {
135  return false;
136  }
137 
138  public function loadDataFromRequest( $request ) {
139  // It's possible that this might be posted with no fields. Detect that
140  // by looking for an edit token.
141  if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) {
142  return $this->getDefault();
143  }
144 
145  $values = $request->getArray( $this->mName );
146  if ( $values === null ) {
147  $values = [];
148  }
149 
150  $ret = [];
151  foreach ( $values as $key => $value ) {
152  if ( $key === 'create' || isset( $value['delete'] ) ) {
153  $ret['nonjs'] = 1;
154  continue;
155  }
156 
157  // Add back in $request->getValues() so things that look for e.g.
158  // wpEditToken don't fail.
159  $data = $this->rekeyValuesArray( $key, $value ) + $request->getValues();
160 
161  $fields = $this->createFieldsForKey( $key );
162  $subrequest = new DerivativeRequest( $request, $data, $request->wasPosted() );
163  $row = [];
164  foreach ( $fields as $fieldname => $field ) {
165  if ( $field->skipLoadData( $subrequest ) ) {
166  continue;
167  } elseif ( !empty( $field->mParams['disabled'] ) ) {
168  $row[$fieldname] = $field->getDefault();
169  } else {
170  $row[$fieldname] = $field->loadDataFromRequest( $subrequest );
171  }
172  }
173  $ret[] = $row;
174  }
175 
176  if ( isset( $values['create'] ) ) {
177  // Non-JS client clicked the "create" button.
178  $fields = $this->createFieldsForKey( $this->uniqueId );
179  $row = [];
180  foreach ( $fields as $fieldname => $field ) {
181  if ( !empty( $field->mParams['nodata'] ) ) {
182  continue;
183  } else {
184  $row[$fieldname] = $field->getDefault();
185  }
186  }
187  $ret[] = $row;
188  }
189 
190  return $ret;
191  }
192 
193  public function getDefault() {
194  $ret = parent::getDefault();
195 
196  // The default default is one entry with all subfields at their
197  // defaults.
198  if ( $ret === null ) {
199  $fields = $this->createFieldsForKey( $this->uniqueId );
200  $row = [];
201  foreach ( $fields as $fieldname => $field ) {
202  if ( !empty( $field->mParams['nodata'] ) ) {
203  continue;
204  } else {
205  $row[$fieldname] = $field->getDefault();
206  }
207  }
208  $ret = [ $row ];
209  }
210 
211  return $ret;
212  }
213 
214  public function cancelSubmit( $values, $alldata ) {
215  if ( isset( $values['nonjs'] ) ) {
216  return true;
217  }
218 
219  foreach ( $values as $key => $value ) {
220  $fields = $this->createFieldsForKey( $key );
221  foreach ( $fields as $fieldname => $field ) {
222  if ( !array_key_exists( $fieldname, $value ) ) {
223  continue;
224  }
225  if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
226  return true;
227  }
228  }
229  }
230 
231  return parent::cancelSubmit( $values, $alldata );
232  }
233 
234  public function validate( $values, $alldata ) {
235  if ( isset( $this->mParams['required'] )
236  && $this->mParams['required'] !== false
237  && !$values
238  ) {
239  return $this->msg( 'htmlform-cloner-required' );
240  }
241 
242  if ( isset( $values['nonjs'] ) ) {
243  // The submission was a non-JS create/delete click, so fail
244  // validation in case cancelSubmit() somehow didn't already handle
245  // it.
246  return false;
247  }
248 
249  foreach ( $values as $key => $value ) {
250  $fields = $this->createFieldsForKey( $key );
251  foreach ( $fields as $fieldname => $field ) {
252  if ( !array_key_exists( $fieldname, $value ) ) {
253  continue;
254  }
255  if ( $field->isHidden( $alldata ) ) {
256  continue;
257  }
258  $ok = $field->validate( $value[$fieldname], $alldata );
259  if ( $ok !== true ) {
260  return false;
261  }
262  }
263  }
264 
265  return parent::validate( $values, $alldata );
266  }
267 
275  protected function getInputHTMLForKey( $key, array $values ) {
276  $displayFormat = isset( $this->mParams['format'] )
277  ? $this->mParams['format']
278  : $this->mParent->getDisplayFormat();
279 
280  // Conveniently, PHP method names are case-insensitive.
281  $getFieldHtmlMethod = $displayFormat == 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
282 
283  $html = '';
284  $hidden = '';
285  $hasLabel = false;
286 
287  $fields = $this->createFieldsForKey( $key );
288  foreach ( $fields as $fieldname => $field ) {
289  $v = array_key_exists( $fieldname, $values )
290  ? $values[$fieldname]
291  : $field->getDefault();
292 
293  if ( $field instanceof HTMLHiddenField ) {
294  // HTMLHiddenField doesn't generate its own HTML
295  list( $name, $value, $params ) = $field->getHiddenFieldData( $v );
296  $hidden .= Html::hidden( $name, $value, $params ) . "\n";
297  } else {
298  $html .= $field->$getFieldHtmlMethod( $v );
299 
300  $labelValue = trim( $field->getLabel() );
301  if ( $labelValue != '&#160;' && $labelValue !== '' ) {
302  $hasLabel = true;
303  }
304  }
305  }
306 
307  if ( !isset( $fields['delete'] ) ) {
308  $name = "{$this->mName}[$key][delete]";
309  $label = isset( $this->mParams['delete-button-message'] )
310  ? $this->mParams['delete-button-message']
311  : 'htmlform-cloner-delete';
313  'type' => 'submit',
314  'formnovalidate' => true,
315  'name' => $name,
316  'id' => Sanitizer::escapeIdForAttribute( "{$this->mID}--$key--delete" ),
317  'cssclass' => 'mw-htmlform-cloner-delete-button',
318  'default' => $this->getMessage( $label )->text(),
319  ], $this->mParent );
320  $v = $field->getDefault();
321 
322  if ( $displayFormat === 'table' ) {
323  $html .= $field->$getFieldHtmlMethod( $v );
324  } else {
325  $html .= $field->getInputHTML( $v );
326  }
327  }
328 
329  if ( $displayFormat !== 'raw' ) {
330  $classes = [
331  'mw-htmlform-cloner-row',
332  ];
333 
334  if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
335  $classes[] = 'mw-htmlform-nolabel';
336  }
337 
338  $attribs = [
339  'class' => implode( ' ', $classes ),
340  ];
341 
342  if ( $displayFormat === 'table' ) {
343  $html = Html::rawElement( 'table',
344  $attribs,
345  Html::rawElement( 'tbody', [], "\n$html\n" ) ) . "\n";
346  } else {
347  $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
348  }
349  }
350 
351  $html .= $hidden;
352 
353  if ( !empty( $this->mParams['row-legend'] ) ) {
354  $legend = $this->msg( $this->mParams['row-legend'] )->text();
355  $html = Xml::fieldset( $legend, $html );
356  }
357 
358  return $html;
359  }
360 
361  public function getInputHTML( $values ) {
362  $html = '';
363 
364  foreach ( (array)$values as $key => $value ) {
365  if ( $key === 'nonjs' ) {
366  continue;
367  }
368  $html .= Html::rawElement( 'li', [ 'class' => 'mw-htmlform-cloner-li' ],
369  $this->getInputHTMLForKey( $key, $value )
370  );
371  }
372 
373  $template = $this->getInputHTMLForKey( $this->uniqueId, [] );
374  $html = Html::rawElement( 'ul', [
375  'id' => "mw-htmlform-cloner-list-{$this->mID}",
376  'class' => 'mw-htmlform-cloner-ul',
377  'data-template' => $template,
378  'data-unique-id' => $this->uniqueId,
379  ], $html );
380 
381  $name = "{$this->mName}[create]";
382  $label = isset( $this->mParams['create-button-message'] )
383  ? $this->mParams['create-button-message']
384  : 'htmlform-cloner-create';
386  'type' => 'submit',
387  'formnovalidate' => true,
388  'name' => $name,
389  'id' => Sanitizer::escapeIdForAttribute( "{$this->mID}--create" ),
390  'cssclass' => 'mw-htmlform-cloner-create-button',
391  'default' => $this->getMessage( $label )->text(),
392  ], $this->mParent );
393  $html .= $field->getInputHTML( $field->getDefault() );
394 
395  return $html;
396  }
397 }
DerivativeRequest
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
Definition: DerivativeRequest.php:34
HTMLFormFieldCloner\getDefault
getDefault()
Definition: HTMLFormFieldCloner.php:193
$html
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:2013
array
the array() calling protocol came about after MediaWiki 1.4rc1.
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:18
Sanitizer\escapeIdForAttribute
static escapeIdForAttribute( $id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:1261
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:2005
$template
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition: hooks.txt:831
$params
$params
Definition: styleTest.css.php:40
HTMLFormField\getMessage
getMessage( $value)
Turns a *-message parameter (which could be a MessageSpecifier, or a message name,...
Definition: HTMLFormField.php:1165
HTMLFormFieldCloner\createFieldsForKey
createFieldsForKey( $key)
Create the HTMLFormFields that go inside this element, using the specified key.
Definition: HTMLFormFieldCloner.php:86
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
Xml\fieldset
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition: Xml.php:610
HTMLFormFieldCloner\$counter
static $counter
Definition: HTMLFormFieldCloner.php:39
MWException
MediaWiki exception.
Definition: MWException.php:26
HTMLFormField
The parent class to generate form fields.
Definition: HTMLFormField.php:7
HTMLForm\loadInputFromParameters
static loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent=null)
Initialise a new Object for the field.
Definition: HTMLForm.php:477
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
HTMLFormFieldCloner\rekeyValuesArray
rekeyValuesArray( $key, $values)
Re-key the specified values array to match the names applied by createFieldsForKey().
Definition: HTMLFormFieldCloner.php:125
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:774
$attribs
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:2014
HTMLFormFieldCloner\__construct
__construct( $params)
Initialise the object.
Definition: HTMLFormFieldCloner.php:48
$value
$value
Definition: styleTest.css.php:45
HTMLFormFieldCloner\$uniqueId
string $uniqueId
String uniquely identifying this cloner instance and unlikely to exist otherwise in the generated HTM...
Definition: HTMLFormFieldCloner.php:46
HTMLFormField\msg
msg()
Get a translated interface message.
Definition: HTMLFormField.php:77
HTMLHiddenField
Definition: HTMLHiddenField.php:3
HTMLFormFieldCloner\getInputHTMLForKey
getInputHTMLForKey( $key, array $values)
Get the input HTML for the specified key.
Definition: HTMLFormFieldCloner.php:275
HTMLFormFieldCloner\needsLabel
needsLabel()
Should this field have a label, or is there no input element with the appropriate id for the label to...
Definition: HTMLFormFieldCloner.php:134
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
HTMLFormFieldCloner\validate
validate( $values, $alldata)
Override this function to add specific validation checks on the field input.
Definition: HTMLFormFieldCloner.php:234
HTMLFormFieldCloner\loadDataFromRequest
loadDataFromRequest( $request)
Get the value that this input has been set to from a posted form, or the input's default value if it ...
Definition: HTMLFormFieldCloner.php:138
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:22
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
HTMLFormFieldCloner\getInputHTML
getInputHTML( $values)
This function must be implemented to return the HTML to generate the input object itself.
Definition: HTMLFormFieldCloner.php:361
HTMLFormField\$mHideIf
$mHideIf
Definition: HTMLFormField.php:22
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2806
HTMLFormFieldCloner\cancelSubmit
cancelSubmit( $values, $alldata)
Override this function if the control can somehow trigger a form submission that shouldn't actually s...
Definition: HTMLFormFieldCloner.php:214
HTMLFormFieldCloner
A container for HTMLFormFields that allows for multiple copies of the set of fields to be displayed t...
Definition: HTMLFormFieldCloner.php:38