MediaWiki
REL1_24
|
00001 <?php 00002 00038 class HTMLFormFieldCloner extends HTMLFormField { 00039 private static $counter = 0; 00040 00046 protected $uniqueId; 00047 00048 public function __construct( $params ) { 00049 $this->uniqueId = get_class( $this ) . ++self::$counter . 'x'; 00050 parent::__construct( $params ); 00051 00052 if ( empty( $this->mParams['fields'] ) || !is_array( $this->mParams['fields'] ) ) { 00053 throw new MWException( 'HTMLFormFieldCloner called without any fields' ); 00054 } 00055 00056 // Make sure the delete button, if explicitly specified, is sane 00057 if ( isset( $this->mParams['fields']['delete'] ) ) { 00058 $class = 'mw-htmlform-cloner-delete-button'; 00059 $info = $this->mParams['fields']['delete'] + array( 00060 'cssclass' => $class 00061 ); 00062 unset( $info['name'], $info['class'] ); 00063 00064 if ( !isset( $info['type'] ) || $info['type'] !== 'submit' ) { 00065 throw new MWException( 00066 'HTMLFormFieldCloner delete field, if specified, must be of type "submit"' 00067 ); 00068 } 00069 00070 if ( !in_array( $class, explode( ' ', $info['cssclass'] ) ) ) { 00071 $info['cssclass'] .= " $class"; 00072 } 00073 00074 $this->mParams['fields']['delete'] = $info; 00075 } 00076 } 00077 00085 protected function createFieldsForKey( $key ) { 00086 $fields = array(); 00087 foreach ( $this->mParams['fields'] as $fieldname => $info ) { 00088 $name = "{$this->mName}[$key][$fieldname]"; 00089 if ( isset( $info['name'] ) ) { 00090 $info['name'] = "{$this->mName}[$key][{$info['name']}]"; 00091 } else { 00092 $info['name'] = $name; 00093 } 00094 if ( isset( $info['id'] ) ) { 00095 $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--{$info['id']}" ); 00096 } else { 00097 $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--$fieldname" ); 00098 } 00099 $field = HTMLForm::loadInputFromParameters( $name, $info ); 00100 $field->mParent = $this->mParent; 00101 $fields[$fieldname] = $field; 00102 } 00103 return $fields; 00104 } 00105 00114 protected function rekeyValuesArray( $key, $values ) { 00115 $data = array(); 00116 foreach ( $values as $fieldname => $value ) { 00117 $name = "{$this->mName}[$key][$fieldname]"; 00118 $data[$name] = $value; 00119 } 00120 return $data; 00121 } 00122 00123 protected function needsLabel() { 00124 return false; 00125 } 00126 00127 public function loadDataFromRequest( $request ) { 00128 // It's possible that this might be posted with no fields. Detect that 00129 // by looking for an edit token. 00130 if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) { 00131 return $this->getDefault(); 00132 } 00133 00134 $values = $request->getArray( $this->mName ); 00135 if ( $values === null ) { 00136 $values = array(); 00137 } 00138 00139 $ret = array(); 00140 foreach ( $values as $key => $value ) { 00141 if ( $key === 'create' || isset( $value['delete'] ) ) { 00142 $ret['nonjs'] = 1; 00143 continue; 00144 } 00145 00146 // Add back in $request->getValues() so things that look for e.g. 00147 // wpEditToken don't fail. 00148 $data = $this->rekeyValuesArray( $key, $value ) + $request->getValues(); 00149 00150 $fields = $this->createFieldsForKey( $key ); 00151 $subrequest = new DerivativeRequest( $request, $data, $request->wasPosted() ); 00152 $row = array(); 00153 foreach ( $fields as $fieldname => $field ) { 00154 if ( !empty( $field->mParams['nodata'] ) ) { 00155 continue; 00156 } elseif ( !empty( $field->mParams['disabled'] ) ) { 00157 $row[$fieldname] = $field->getDefault(); 00158 } else { 00159 $row[$fieldname] = $field->loadDataFromRequest( $subrequest ); 00160 } 00161 } 00162 $ret[] = $row; 00163 } 00164 00165 if ( isset( $values['create'] ) ) { 00166 // Non-JS client clicked the "create" button. 00167 $fields = $this->createFieldsForKey( $this->uniqueId ); 00168 $row = array(); 00169 foreach ( $fields as $fieldname => $field ) { 00170 if ( !empty( $field->mParams['nodata'] ) ) { 00171 continue; 00172 } else { 00173 $row[$fieldname] = $field->getDefault(); 00174 } 00175 } 00176 $ret[] = $row; 00177 } 00178 00179 return $ret; 00180 } 00181 00182 public function getDefault() { 00183 $ret = parent::getDefault(); 00184 00185 // The default default is one entry with all subfields at their 00186 // defaults. 00187 if ( $ret === null ) { 00188 $fields = $this->createFieldsForKey( $this->uniqueId ); 00189 $row = array(); 00190 foreach ( $fields as $fieldname => $field ) { 00191 if ( !empty( $field->mParams['nodata'] ) ) { 00192 continue; 00193 } else { 00194 $row[$fieldname] = $field->getDefault(); 00195 } 00196 } 00197 $ret = array( $row ); 00198 } 00199 00200 return $ret; 00201 } 00202 00203 public function cancelSubmit( $values, $alldata ) { 00204 if ( isset( $values['nonjs'] ) ) { 00205 return true; 00206 } 00207 00208 foreach ( $values as $key => $value ) { 00209 $fields = $this->createFieldsForKey( $key ); 00210 foreach ( $fields as $fieldname => $field ) { 00211 if ( !empty( $field->mParams['nodata'] ) ) { 00212 continue; 00213 } 00214 if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) { 00215 return true; 00216 } 00217 } 00218 } 00219 00220 return parent::cancelSubmit( $values, $alldata ); 00221 } 00222 00223 public function validate( $values, $alldata ) { 00224 if ( isset( $this->mParams['required'] ) 00225 && $this->mParams['required'] !== false 00226 && !$values 00227 ) { 00228 return $this->msg( 'htmlform-cloner-required' )->parseAsBlock(); 00229 } 00230 00231 if ( isset( $values['nonjs'] ) ) { 00232 // The submission was a non-JS create/delete click, so fail 00233 // validation in case cancelSubmit() somehow didn't already handle 00234 // it. 00235 return false; 00236 } 00237 00238 foreach ( $values as $key => $value ) { 00239 $fields = $this->createFieldsForKey( $key ); 00240 foreach ( $fields as $fieldname => $field ) { 00241 if ( !empty( $field->mParams['nodata'] ) ) { 00242 continue; 00243 } 00244 $ok = $field->validate( $value[$fieldname], $alldata ); 00245 if ( $ok !== true ) { 00246 return false; 00247 } 00248 } 00249 } 00250 00251 return parent::validate( $values, $alldata ); 00252 } 00253 00261 protected function getInputHTMLForKey( $key, $values ) { 00262 $displayFormat = isset( $this->mParams['format'] ) 00263 ? $this->mParams['format'] 00264 : $this->mParent->getDisplayFormat(); 00265 00266 switch ( $displayFormat ) { 00267 case 'table': 00268 $getFieldHtmlMethod = 'getTableRow'; 00269 break; 00270 case 'vform': 00271 // Close enough to a div. 00272 $getFieldHtmlMethod = 'getDiv'; 00273 break; 00274 default: 00275 $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat ); 00276 } 00277 00278 $html = ''; 00279 $hasLabel = false; 00280 00281 $fields = $this->createFieldsForKey( $key ); 00282 foreach ( $fields as $fieldname => $field ) { 00283 $v = ( empty( $field->mParams['nodata'] ) && $values !== null ) 00284 ? $values[$fieldname] 00285 : $field->getDefault(); 00286 $html .= $field->$getFieldHtmlMethod( $v ); 00287 00288 $labelValue = trim( $field->getLabel() ); 00289 if ( $labelValue != ' ' && $labelValue !== '' ) { 00290 $hasLabel = true; 00291 } 00292 } 00293 00294 if ( !isset( $fields['delete'] ) ) { 00295 $name = "{$this->mName}[$key][delete]"; 00296 $label = isset( $this->mParams['delete-button-message'] ) 00297 ? $this->mParams['delete-button-message'] 00298 : 'htmlform-cloner-delete'; 00299 $field = HTMLForm::loadInputFromParameters( $name, array( 00300 'type' => 'submit', 00301 'name' => $name, 00302 'id' => Sanitizer::escapeId( "{$this->mID}--$key--delete" ), 00303 'cssclass' => 'mw-htmlform-cloner-delete-button', 00304 'default' => $this->msg( $label )->text(), 00305 ) ); 00306 $field->mParent = $this->mParent; 00307 $v = $field->getDefault(); 00308 00309 if ( $displayFormat === 'table' ) { 00310 $html .= $field->$getFieldHtmlMethod( $v ); 00311 } else { 00312 $html .= $field->getInputHTML( $v ); 00313 } 00314 } 00315 00316 if ( $displayFormat !== 'raw' ) { 00317 $classes = array( 00318 'mw-htmlform-cloner-row', 00319 ); 00320 00321 if ( !$hasLabel ) { // Avoid strange spacing when no labels exist 00322 $classes[] = 'mw-htmlform-nolabel'; 00323 } 00324 00325 $attribs = array( 00326 'class' => implode( ' ', $classes ), 00327 ); 00328 00329 if ( $displayFormat === 'table' ) { 00330 $html = Html::rawElement( 'table', 00331 $attribs, 00332 Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n"; 00333 } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) { 00334 $html = Html::rawElement( 'div', $attribs, "\n$html\n" ); 00335 } 00336 } 00337 00338 if ( !empty( $this->mParams['row-legend'] ) ) { 00339 $legend = $this->msg( $this->mParams['row-legend'] )->text(); 00340 $html = Xml::fieldset( $legend, $html ); 00341 } 00342 00343 return $html; 00344 } 00345 00346 public function getInputHTML( $values ) { 00347 $html = ''; 00348 00349 foreach ( (array)$values as $key => $value ) { 00350 if ( $key === 'nonjs' ) { 00351 continue; 00352 } 00353 $html .= Html::rawElement( 'li', array( 'class' => 'mw-htmlform-cloner-li' ), 00354 $this->getInputHTMLForKey( $key, $value ) 00355 ); 00356 } 00357 00358 $template = $this->getInputHTMLForKey( $this->uniqueId, null ); 00359 $html = Html::rawElement( 'ul', array( 00360 'id' => "mw-htmlform-cloner-list-{$this->mID}", 00361 'class' => 'mw-htmlform-cloner-ul', 00362 'data-template' => $template, 00363 'data-unique-id' => $this->uniqueId, 00364 ), $html ); 00365 00366 $name = "{$this->mName}[create]"; 00367 $label = isset( $this->mParams['create-button-message'] ) 00368 ? $this->mParams['create-button-message'] 00369 : 'htmlform-cloner-create'; 00370 $field = HTMLForm::loadInputFromParameters( $name, array( 00371 'type' => 'submit', 00372 'name' => $name, 00373 'id' => Sanitizer::escapeId( "{$this->mID}--create" ), 00374 'cssclass' => 'mw-htmlform-cloner-create-button', 00375 'default' => $this->msg( $label )->text(), 00376 ) ); 00377 $field->mParent = $this->mParent; 00378 $html .= $field->getInputHTML( $field->getDefault() ); 00379 00380 return $html; 00381 } 00382 }