Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
91.70% |
994 / 1084 |
|
87.50% |
28 / 32 |
CRAP | |
0.00% |
0 / 1 |
| PFFormPrinter | |
91.70% |
994 / 1084 |
|
87.50% |
28 / 32 |
528.38 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
43 / 43 |
|
100.00% |
1 / 1 |
4 | |||
| setSemanticTypeHook | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setCargoTypeHook | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setInputTypeHook | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| registerInputType | |
100.00% |
39 / 39 |
|
100.00% |
1 / 1 |
13 | |||
| getInputType | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getDefaultInputTypeSMW | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| getDefaultInputTypeCargo | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| getPossibleInputTypesSMW | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| getPossibleInputTypesCargo | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| getAllInputTypes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| showDeletionLog | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
| strReplaceFirst | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| placeholderFormat | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| makePlaceholderInFormHTML | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| multipleTemplateStartHTML | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
| multipleTemplateInstanceTableHTML | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
| multipleTemplateInstanceHTML | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
1 | |||
| multipleTemplateEndHTML | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
2 | |||
| tableHTML | |
100.00% |
47 / 47 |
|
100.00% |
1 / 1 |
12 | |||
| getSpreadsheetAutocompleteAttributes | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
8 | |||
| spreadsheetHTML | |
100.00% |
64 / 64 |
|
100.00% |
1 / 1 |
20 | |||
| getStringForCurrentTime | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
8 | |||
| getStringFromPassedInArray | |
100.00% |
46 / 46 |
|
100.00% |
1 / 1 |
22 | |||
| displayLoadingImage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| formHTML | |
90.07% |
535 / 594 |
|
0.00% |
0 / 1 |
327.75 | |||
| getCargoBasedMapping | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
| formFieldHTML | |
100.00% |
42 / 42 |
|
100.00% |
1 / 1 |
17 | |||
| addTranslatableInput | |
20.00% |
2 / 10 |
|
0.00% |
0 / 1 |
24.43 | |||
| createFormFieldTranslateTag | |
15.38% |
2 / 13 |
|
0.00% |
0 / 1 |
70.58 | |||
| generateUUID | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
| getParsedValue | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Handles the creation and running of a user-created form. |
| 4 | * |
| 5 | * @author Yaron Koren |
| 6 | * @author Nils Oppermann |
| 7 | * @author Jeffrey Stuckman |
| 8 | * @author Harold Solbrig |
| 9 | * @author Daniel Hansch |
| 10 | * @author Stephan Gambke |
| 11 | * @author LY Meng |
| 12 | * @file |
| 13 | * @ingroup PF |
| 14 | */ |
| 15 | |
| 16 | use MediaWiki\EditPage\EditPage; |
| 17 | use MediaWiki\Html\Html; |
| 18 | use MediaWiki\MediaWikiServices; |
| 19 | use MediaWiki\Title\Title; |
| 20 | |
| 21 | class PFFormPrinter { |
| 22 | |
| 23 | public const CONTEXT_REGULAR = 0; |
| 24 | public const CONTEXT_QUERY = 1; |
| 25 | public const CONTEXT_EMBEDDED_QUERY = 2; |
| 26 | public const CONTEXT_AUTOEDIT = 3; |
| 27 | public const CONTEXT_AUTOCREATE = 4; |
| 28 | |
| 29 | public $mSemanticTypeHooks; |
| 30 | public $mCargoTypeHooks; |
| 31 | public $mInputTypeHooks; |
| 32 | public $standardInputsIncluded; |
| 33 | public $mPageTitle; |
| 34 | |
| 35 | private $mInputTypeClasses; |
| 36 | private $mDefaultInputForPropType; |
| 37 | private $mDefaultInputForPropTypeList; |
| 38 | private $mPossibleInputsForPropType; |
| 39 | private $mPossibleInputsForPropTypeList; |
| 40 | private $mDefaultInputForCargoType; |
| 41 | private $mDefaultInputForCargoTypeList; |
| 42 | private $mPossibleInputsForCargoType; |
| 43 | private $mPossibleInputsForCargoTypeList; |
| 44 | |
| 45 | private static $mParsedValues = []; |
| 46 | |
| 47 | public function __construct() { |
| 48 | global $wgPageFormsDisableOutsideServices; |
| 49 | // Initialize variables. |
| 50 | $this->mSemanticTypeHooks = []; |
| 51 | $this->mCargoTypeHooks = []; |
| 52 | $this->mInputTypeHooks = []; |
| 53 | $this->mInputTypeClasses = []; |
| 54 | $this->mDefaultInputForPropType = []; |
| 55 | $this->mDefaultInputForPropTypeList = []; |
| 56 | $this->mPossibleInputsForPropType = []; |
| 57 | $this->mPossibleInputsForPropTypeList = []; |
| 58 | $this->mDefaultInputForCargoType = []; |
| 59 | $this->mDefaultInputForCargoTypeList = []; |
| 60 | $this->mPossibleInputsForCargoType = []; |
| 61 | $this->mPossibleInputsForCargoTypeList = []; |
| 62 | |
| 63 | $this->standardInputsIncluded = false; |
| 64 | |
| 65 | $this->registerInputType( 'PFTextInput' ); |
| 66 | $this->registerInputType( 'PFTextWithAutocompleteInput' ); |
| 67 | $this->registerInputType( 'PFTextAreaInput' ); |
| 68 | $this->registerInputType( 'PFTextAreaWithAutocompleteInput' ); |
| 69 | $this->registerInputType( 'PFDateInput' ); |
| 70 | $this->registerInputType( 'PFStartDateInput' ); |
| 71 | $this->registerInputType( 'PFEndDateInput' ); |
| 72 | $this->registerInputType( 'PFDatePickerInput' ); |
| 73 | $this->registerInputType( 'PFDateTimePicker' ); |
| 74 | $this->registerInputType( 'PFDateTimeInput' ); |
| 75 | $this->registerInputType( 'PFStartDateTimeInput' ); |
| 76 | $this->registerInputType( 'PFEndDateTimeInput' ); |
| 77 | $this->registerInputType( 'PFYearInput' ); |
| 78 | $this->registerInputType( 'PFCheckboxInput' ); |
| 79 | $this->registerInputType( 'PFDropdownInput' ); |
| 80 | $this->registerInputType( 'PFRadioButtonInput' ); |
| 81 | $this->registerInputType( 'PFCheckboxesInput' ); |
| 82 | $this->registerInputType( 'PFListBoxInput' ); |
| 83 | $this->registerInputType( 'PFComboBoxInput' ); |
| 84 | $this->registerInputType( 'PFTreeInput' ); |
| 85 | $this->registerInputType( 'PFTokensInput' ); |
| 86 | $this->registerInputType( 'PFRegExpInput' ); |
| 87 | $this->registerInputType( 'PFRatingInput' ); |
| 88 | // Add this if the Semantic Maps extension is not |
| 89 | // included, or if it's SM (really Maps) v4.0 or higher. |
| 90 | if ( !$wgPageFormsDisableOutsideServices ) { |
| 91 | if ( !defined( 'SM_VERSION' ) || version_compare( SM_VERSION, '4.0', '>=' ) ) { |
| 92 | $this->registerInputType( 'PFGoogleMapsInput' ); |
| 93 | } |
| 94 | $this->registerInputType( 'PFOpenLayersInput' ); |
| 95 | $this->registerInputType( 'PFLeafletInput' ); |
| 96 | } |
| 97 | |
| 98 | // All-purpose setup hook. |
| 99 | // Avoid PHP 7.1 warning from passing $this by reference. |
| 100 | $formPrinterRef = $this; |
| 101 | MediaWikiServices::getInstance()->getHookContainer()->run( 'PageForms::FormPrinterSetup', [ &$formPrinterRef ] ); |
| 102 | } |
| 103 | |
| 104 | public function setSemanticTypeHook( $type, $is_list, $class_name, $default_args ) { |
| 105 | $this->mSemanticTypeHooks[$type][$is_list] = [ $class_name, $default_args ]; |
| 106 | } |
| 107 | |
| 108 | public function setCargoTypeHook( $type, $is_list, $class_name, $default_args ) { |
| 109 | $this->mCargoTypeHooks[$type][$is_list] = [ $class_name, $default_args ]; |
| 110 | } |
| 111 | |
| 112 | public function setInputTypeHook( $input_type, $class_name, $default_args ) { |
| 113 | $this->mInputTypeHooks[$input_type] = [ $class_name, $default_args ]; |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Register all information about the passed-in form input class. |
| 118 | * |
| 119 | * @param string $inputTypeClass The full qualified class name representing the new input. |
| 120 | * Must be derived from PFFormInput. |
| 121 | */ |
| 122 | public function registerInputType( $inputTypeClass ) { |
| 123 | $inputTypeName = call_user_func( [ $inputTypeClass, 'getName' ] ); |
| 124 | $this->mInputTypeClasses[$inputTypeName] = $inputTypeClass; |
| 125 | $this->setInputTypeHook( $inputTypeName, $inputTypeClass, [] ); |
| 126 | |
| 127 | $defaultProperties = call_user_func( [ $inputTypeClass, 'getDefaultPropTypes' ] ); |
| 128 | foreach ( $defaultProperties as $propertyType => $additionalValues ) { |
| 129 | $this->setSemanticTypeHook( $propertyType, false, $inputTypeClass, $additionalValues ); |
| 130 | $this->mDefaultInputForPropType[$propertyType] = $inputTypeName; |
| 131 | } |
| 132 | $defaultPropertyLists = call_user_func( [ $inputTypeClass, 'getDefaultPropTypeLists' ] ); |
| 133 | foreach ( $defaultPropertyLists as $propertyType => $additionalValues ) { |
| 134 | $this->setSemanticTypeHook( $propertyType, true, $inputTypeClass, $additionalValues ); |
| 135 | $this->mDefaultInputForPropTypeList[$propertyType] = $inputTypeName; |
| 136 | } |
| 137 | |
| 138 | $defaultCargoTypes = call_user_func( [ $inputTypeClass, 'getDefaultCargoTypes' ] ); |
| 139 | foreach ( $defaultCargoTypes as $fieldType => $additionalValues ) { |
| 140 | $this->setCargoTypeHook( $fieldType, false, $inputTypeClass, $additionalValues ); |
| 141 | $this->mDefaultInputForCargoType[$fieldType] = $inputTypeName; |
| 142 | } |
| 143 | $defaultCargoTypeLists = call_user_func( [ $inputTypeClass, 'getDefaultCargoTypeLists' ] ); |
| 144 | foreach ( $defaultCargoTypeLists as $fieldType => $additionalValues ) { |
| 145 | $this->setCargoTypeHook( $fieldType, true, $inputTypeClass, $additionalValues ); |
| 146 | $this->mDefaultInputForCargoTypeList[$fieldType] = $inputTypeName; |
| 147 | } |
| 148 | |
| 149 | $otherProperties = call_user_func( [ $inputTypeClass, 'getOtherPropTypesHandled' ] ); |
| 150 | foreach ( $otherProperties as $propertyTypeID ) { |
| 151 | if ( array_key_exists( $propertyTypeID, $this->mPossibleInputsForPropType ) ) { |
| 152 | $this->mPossibleInputsForPropType[$propertyTypeID][] = $inputTypeName; |
| 153 | } else { |
| 154 | $this->mPossibleInputsForPropType[$propertyTypeID] = [ $inputTypeName ]; |
| 155 | } |
| 156 | } |
| 157 | $otherPropertyLists = call_user_func( [ $inputTypeClass, 'getOtherPropTypeListsHandled' ] ); |
| 158 | foreach ( $otherPropertyLists as $propertyTypeID ) { |
| 159 | if ( array_key_exists( $propertyTypeID, $this->mPossibleInputsForPropTypeList ) ) { |
| 160 | $this->mPossibleInputsForPropTypeList[$propertyTypeID][] = $inputTypeName; |
| 161 | } else { |
| 162 | $this->mPossibleInputsForPropTypeList[$propertyTypeID] = [ $inputTypeName ]; |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | $otherCargoTypes = call_user_func( [ $inputTypeClass, 'getOtherCargoTypesHandled' ] ); |
| 167 | foreach ( $otherCargoTypes as $cargoType ) { |
| 168 | if ( array_key_exists( $cargoType, $this->mPossibleInputsForCargoType ) ) { |
| 169 | $this->mPossibleInputsForCargoType[$cargoType][] = $inputTypeName; |
| 170 | } else { |
| 171 | $this->mPossibleInputsForCargoType[$cargoType] = [ $inputTypeName ]; |
| 172 | } |
| 173 | } |
| 174 | $otherCargoTypeLists = call_user_func( [ $inputTypeClass, 'getOtherCargoTypeListsHandled' ] ); |
| 175 | foreach ( $otherCargoTypeLists as $cargoType ) { |
| 176 | if ( array_key_exists( $cargoType, $this->mPossibleInputsForCargoTypeList ) ) { |
| 177 | $this->mPossibleInputsForCargoTypeList[$cargoType][] = $inputTypeName; |
| 178 | } else { |
| 179 | $this->mPossibleInputsForCargoTypeList[$cargoType] = [ $inputTypeName ]; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | // FIXME: No need to register these functions explicitly. Instead |
| 184 | // formFieldHTML should call $someInput -> getJsInitFunctionData() and |
| 185 | // store its return value. formHTML should at some (late) point use the |
| 186 | // stored data. |
| 187 | // |
| 188 | // $initJSFunction = call_user_func( array( $inputTypeClass, 'getJsInitFunctionData' ) ); |
| 189 | // if ( !is_null( $initJSFunction ) ) { |
| 190 | // $wgPageFormsInitJSFunctions[] = $initJSFunction; |
| 191 | // } |
| 192 | // |
| 193 | // $validationJSFunctions = call_user_func( array( $inputTypeClass, 'getJsValidationFunctionData' ) ); |
| 194 | // if ( count( $validationJSFunctions ) > 0 ) { |
| 195 | // $wgPageFormsValidationJSFunctions = array_merge( $wgPageFormsValidationJSFunctions, $initJSFunction ); |
| 196 | // } |
| 197 | } |
| 198 | |
| 199 | public function getInputType( $inputTypeName ) { |
| 200 | if ( array_key_exists( $inputTypeName, $this->mInputTypeClasses ) ) { |
| 201 | return $this->mInputTypeClasses[$inputTypeName]; |
| 202 | } else { |
| 203 | return null; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | public function getDefaultInputTypeSMW( $isList, $propertyType ) { |
| 208 | if ( $isList ) { |
| 209 | if ( array_key_exists( $propertyType, $this->mDefaultInputForPropTypeList ) ) { |
| 210 | return $this->mDefaultInputForPropTypeList[$propertyType]; |
| 211 | } else { |
| 212 | return null; |
| 213 | } |
| 214 | } else { |
| 215 | if ( array_key_exists( $propertyType, $this->mDefaultInputForPropType ) ) { |
| 216 | return $this->mDefaultInputForPropType[$propertyType]; |
| 217 | } else { |
| 218 | return null; |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | public function getDefaultInputTypeCargo( $isList, $fieldType ) { |
| 224 | if ( $isList ) { |
| 225 | if ( array_key_exists( $fieldType, $this->mDefaultInputForCargoTypeList ) ) { |
| 226 | return $this->mDefaultInputForCargoTypeList[$fieldType]; |
| 227 | } else { |
| 228 | return null; |
| 229 | } |
| 230 | } else { |
| 231 | if ( array_key_exists( $fieldType, $this->mDefaultInputForCargoType ) ) { |
| 232 | return $this->mDefaultInputForCargoType[$fieldType]; |
| 233 | } else { |
| 234 | return null; |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | public function getPossibleInputTypesSMW( $isList, $propertyType ) { |
| 240 | if ( $isList ) { |
| 241 | if ( array_key_exists( $propertyType, $this->mPossibleInputsForPropTypeList ) ) { |
| 242 | return $this->mPossibleInputsForPropTypeList[$propertyType]; |
| 243 | } else { |
| 244 | return []; |
| 245 | } |
| 246 | } else { |
| 247 | if ( array_key_exists( $propertyType, $this->mPossibleInputsForPropType ) ) { |
| 248 | return $this->mPossibleInputsForPropType[$propertyType]; |
| 249 | } else { |
| 250 | return []; |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | public function getPossibleInputTypesCargo( $isList, $fieldType ) { |
| 256 | if ( $isList ) { |
| 257 | if ( array_key_exists( $fieldType, $this->mPossibleInputsForCargoTypeList ) ) { |
| 258 | return $this->mPossibleInputsForCargoTypeList[$fieldType]; |
| 259 | } else { |
| 260 | return []; |
| 261 | } |
| 262 | } else { |
| 263 | if ( array_key_exists( $fieldType, $this->mPossibleInputsForCargoType ) ) { |
| 264 | return $this->mPossibleInputsForCargoType[$fieldType]; |
| 265 | } else { |
| 266 | return []; |
| 267 | } |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | public function getAllInputTypes() { |
| 272 | return array_keys( $this->mInputTypeClasses ); |
| 273 | } |
| 274 | |
| 275 | /** |
| 276 | * Show the set of previous deletions for the page being edited. |
| 277 | * @param OutputPage $out |
| 278 | * @return true |
| 279 | */ |
| 280 | function showDeletionLog( $out ) { |
| 281 | LogEventsList::showLogExtract( $out, 'delete', $this->mPageTitle->getPrefixedText(), |
| 282 | '', [ 'lim' => 10, |
| 283 | 'conds' => [ "log_action != 'revision'" ], |
| 284 | 'showIfEmpty' => false, |
| 285 | 'msgKey' => [ 'moveddeleted-notice' ] ] |
| 286 | ); |
| 287 | return true; |
| 288 | } |
| 289 | |
| 290 | /** |
| 291 | * Like PHP's str_replace(), but only replaces the first found |
| 292 | * instance - unfortunately, str_replace() doesn't allow for that. |
| 293 | * This code is basically copied directly from |
| 294 | * http://www.php.net/manual/en/function.str-replace.php#86177 |
| 295 | * - this might make sense in the PFUtils class, if it's useful in |
| 296 | * other places. |
| 297 | * @param string $search |
| 298 | * @param string $replace |
| 299 | * @param string $subject |
| 300 | * @return string |
| 301 | */ |
| 302 | function strReplaceFirst( $search, $replace, $subject ) { |
| 303 | $firstChar = strpos( $subject, $search ); |
| 304 | if ( $firstChar !== false ) { |
| 305 | $beforeStr = substr( $subject, 0, $firstChar ); |
| 306 | $afterStr = substr( $subject, $firstChar + strlen( $search ) ); |
| 307 | return $beforeStr . $replace . $afterStr; |
| 308 | } else { |
| 309 | return $subject; |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | static function placeholderFormat( $templateName, $fieldName ) { |
| 314 | $templateName = str_replace( '_', ' ', $templateName ); |
| 315 | $fieldName = str_replace( '_', ' ', $fieldName ); |
| 316 | return $templateName . '___' . $fieldName; |
| 317 | } |
| 318 | |
| 319 | static function makePlaceholderInFormHTML( $str ) { |
| 320 | return '@insert"HTML_' . $str . '@'; |
| 321 | } |
| 322 | |
| 323 | function multipleTemplateStartHTML( $tif ) { |
| 324 | // If placeholder is set, it means we want to insert a |
| 325 | // multiple template form's HTML into the main form's HTML. |
| 326 | // So, the HTML will be stored in $text. |
| 327 | $text = "\t" . '<div class="multipleTemplateWrapper">' . "\n"; |
| 328 | $attrs = [ 'class' => 'multipleTemplateList' ]; |
| 329 | if ( $tif->getMinInstancesAllowed() !== null ) { |
| 330 | $attrs['minimumInstances'] = $tif->getMinInstancesAllowed(); |
| 331 | } |
| 332 | if ( $tif->getMaxInstancesAllowed() !== null ) { |
| 333 | $attrs['maximumInstances'] = $tif->getMaxInstancesAllowed(); |
| 334 | } |
| 335 | if ( $tif->getDisplayedFieldsWhenMinimized() != null ) { |
| 336 | $attrs['data-displayed-fields-when-minimized'] = $tif->getDisplayedFieldsWhenMinimized(); |
| 337 | } |
| 338 | $text .= "\t" . Html::openElement( 'div', $attrs ) . "\n"; |
| 339 | return $text; |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * Creates the HTML for the inner table for every instance of a |
| 344 | * multiple-instance template in the form. |
| 345 | * @param bool $form_is_disabled |
| 346 | * @param string $mainText |
| 347 | * @return string |
| 348 | */ |
| 349 | function multipleTemplateInstanceTableHTML( $form_is_disabled, $mainText ) { |
| 350 | if ( $form_is_disabled ) { |
| 351 | $addAboveButton = $removeButton = ''; |
| 352 | } else { |
| 353 | $addAboveButton = Html::element( 'a', [ 'class' => "addAboveButton", 'title' => wfMessage( 'pf_formedit_addanotherabove' )->text() ] ); |
| 354 | $removeButton = Html::element( 'a', [ 'class' => "removeButton", 'title' => wfMessage( 'pf_formedit_remove' )->text() ] ); |
| 355 | } |
| 356 | |
| 357 | $text = <<<END |
| 358 | <table class="multipleTemplateInstanceTable"> |
| 359 | <tr> |
| 360 | <td class="instanceRearranger"></td> |
| 361 | <td class="instanceMain">$mainText</td> |
| 362 | <td class="instanceAddAbove">$addAboveButton</td> |
| 363 | <td class="instanceRemove">$removeButton</td> |
| 364 | </tr> |
| 365 | </table> |
| 366 | END; |
| 367 | |
| 368 | return $text; |
| 369 | } |
| 370 | |
| 371 | /** |
| 372 | * Creates the HTML for a single instance of a multiple-instance |
| 373 | * template. |
| 374 | * @param PFTemplateInForm $template_in_form |
| 375 | * @param bool $form_is_disabled |
| 376 | * @param string &$section |
| 377 | * @return string |
| 378 | */ |
| 379 | function multipleTemplateInstanceHTML( $template_in_form, $form_is_disabled, &$section ) { |
| 380 | global $wgPageFormsCalendarHTML; |
| 381 | |
| 382 | $wgPageFormsCalendarHTML[$template_in_form->getTemplateName()] = str_replace( '[num]', "[cf]", $section ); |
| 383 | |
| 384 | // Add the character "a" onto the instance number of this input |
| 385 | // in the form, to differentiate the inputs the form starts out |
| 386 | // with from any inputs added by the Javascript. |
| 387 | $section = str_replace( '[num]', "[{$template_in_form->getInstanceNum()}a]", $section ); |
| 388 | // @TODO - this replacement should be |
| 389 | // case- and spacing-insensitive. |
| 390 | // Also, keeping the "id=" attribute should not be |
| 391 | // necessary; but currently it is, for "show on select". |
| 392 | $section = preg_replace_callback( |
| 393 | '/ id="(.*?)"/', |
| 394 | static function ( $matches ) { |
| 395 | $id = htmlspecialchars( $matches[1], ENT_QUOTES ); |
| 396 | return " id=\"$id\" data-origID=\"$id\" "; |
| 397 | }, |
| 398 | $section |
| 399 | ); |
| 400 | |
| 401 | $text = "\t\t" . Html::rawElement( 'div', |
| 402 | [ |
| 403 | // The "multipleTemplate" class is there for |
| 404 | // backwards-compatibility with any custom CSS on people's |
| 405 | // wikis before PF 2.0.9. |
| 406 | 'class' => "multipleTemplateInstance multipleTemplate" |
| 407 | ], |
| 408 | $this->multipleTemplateInstanceTableHTML( $form_is_disabled, $section ) |
| 409 | ) . "\n"; |
| 410 | |
| 411 | return $text; |
| 412 | } |
| 413 | |
| 414 | /** |
| 415 | * Creates the end of the HTML for a multiple-instance template - |
| 416 | * including the sections necessary for adding additional instances. |
| 417 | * @param PFTemplateInForm $template_in_form |
| 418 | * @param bool $form_is_disabled |
| 419 | * @param string $section |
| 420 | * @return string |
| 421 | */ |
| 422 | function multipleTemplateEndHTML( $template_in_form, $form_is_disabled, $section ) { |
| 423 | global $wgPageFormsTabIndex; |
| 424 | |
| 425 | $text = "\t\t" . Html::rawElement( 'div', |
| 426 | [ |
| 427 | 'class' => "multipleTemplateStarter", |
| 428 | 'style' => "display: none", |
| 429 | ], |
| 430 | $this->multipleTemplateInstanceTableHTML( $form_is_disabled, $section ) |
| 431 | ) . "\n"; |
| 432 | |
| 433 | $attributes = [ |
| 434 | 'tabIndex' => $wgPageFormsTabIndex, |
| 435 | 'classes' => [ 'multipleTemplateAdder' ], |
| 436 | 'label' => Sanitizer::decodeCharReferences( $template_in_form->getAddButtonText() ), |
| 437 | 'icon' => 'add' |
| 438 | ]; |
| 439 | if ( $form_is_disabled ) { |
| 440 | $attributes['disabled'] = true; |
| 441 | $attributes['classes'] = []; |
| 442 | } |
| 443 | $button = new OOUI\ButtonWidget( $attributes ); |
| 444 | $text .= <<<END |
| 445 | </div><!-- multipleTemplateList --> |
| 446 | <p>$button</p> |
| 447 | <div class="pfErrorMessages"></div> |
| 448 | </div><!-- multipleTemplateWrapper --> |
| 449 | </fieldset> |
| 450 | END; |
| 451 | return $text; |
| 452 | } |
| 453 | |
| 454 | function tableHTML( $tif, $instanceNum ) { |
| 455 | global $wgPageFormsFieldNum; |
| 456 | |
| 457 | $allGridValues = $tif->getGridValues(); |
| 458 | if ( array_key_exists( $instanceNum, $allGridValues ) ) { |
| 459 | $gridValues = $allGridValues[$instanceNum]; |
| 460 | } else { |
| 461 | $gridValues = null; |
| 462 | } |
| 463 | |
| 464 | $html = ''; |
| 465 | foreach ( $tif->getFields() as $formField ) { |
| 466 | $fieldName = $formField->getTemplateField()->getFieldName(); |
| 467 | if ( $gridValues == null ) { |
| 468 | $curValue = null; |
| 469 | } else { |
| 470 | $curValue = $gridValues[$fieldName]; |
| 471 | } |
| 472 | |
| 473 | if ( $formField->holdsTemplate() ) { |
| 474 | $attribs = []; |
| 475 | if ( $formField->hasFieldArg( 'class' ) ) { |
| 476 | $attribs['class'] = $formField->getFieldArg( 'class' ); |
| 477 | } |
| 478 | $html .= '</table>' . "\n"; |
| 479 | $html .= Html::hidden( $formField->getInputName(), $curValue, $attribs ); |
| 480 | $html .= $formField->additionalHTMLForInput( $curValue, $fieldName, $tif->getTemplateName() ); |
| 481 | $html .= '<table class="formtable">' . "\n"; |
| 482 | continue; |
| 483 | } |
| 484 | |
| 485 | if ( $formField->isHidden() ) { |
| 486 | $attribs = []; |
| 487 | if ( $formField->hasFieldArg( 'class' ) ) { |
| 488 | $attribs['class'] = $formField->getFieldArg( 'class' ); |
| 489 | } |
| 490 | $html .= Html::hidden( $formField->getInputName(), $curValue, $attribs ); |
| 491 | continue; |
| 492 | } |
| 493 | |
| 494 | $wgPageFormsFieldNum++; |
| 495 | if ( $formField->getLabel() !== null ) { |
| 496 | $labelText = $formField->getLabel(); |
| 497 | // Kind of a @HACK - for a checkbox within |
| 498 | // display=table, 'label' is used for two |
| 499 | // purposes: the label column, and the text |
| 500 | // after the checkbox. Unset the value here so |
| 501 | // that it's only used for the first purpose, |
| 502 | // and doesn't show up twice. |
| 503 | $formField->setFieldArg( 'label', '' ); |
| 504 | } elseif ( $formField->getLabelMsg() !== null ) { |
| 505 | $labelText = wfMessage( $formField->getLabelMsg() )->parse(); |
| 506 | } elseif ( $formField->getTemplateField()->getLabel() !== null ) { |
| 507 | $labelText = $formField->getTemplateField()->getLabel() . ':'; |
| 508 | } else { |
| 509 | $labelText = $fieldName . ': '; |
| 510 | } |
| 511 | $label = Html::element( 'label', |
| 512 | [ 'for' => "input_$wgPageFormsFieldNum" ], |
| 513 | $labelText ); |
| 514 | |
| 515 | $labelCellAttrs = []; |
| 516 | if ( $formField->hasFieldArg( 'tooltip' ) ) { |
| 517 | $labelCellAttrs['data-tooltip'] = $formField->getFieldArg( 'tooltip' ); |
| 518 | } |
| 519 | |
| 520 | $labelCell = Html::rawElement( 'th', $labelCellAttrs, $label ); |
| 521 | $inputHTML = $this->formFieldHTML( $formField, $curValue ); |
| 522 | $inputHTML .= $formField->additionalHTMLForInput( $curValue, $fieldName, $tif->getTemplateName() ); |
| 523 | $inputCell = Html::rawElement( 'td', null, $inputHTML ); |
| 524 | $html .= Html::rawElement( 'tr', null, $labelCell . $inputCell ) . "\n"; |
| 525 | } |
| 526 | |
| 527 | $html = Html::rawElement( 'table', [ 'class' => 'formtable' ], $html ); |
| 528 | |
| 529 | return $html; |
| 530 | } |
| 531 | |
| 532 | function getSpreadsheetAutocompleteAttributes( $formFieldArgs ) { |
| 533 | if ( array_key_exists( 'values from category', $formFieldArgs ) ) { |
| 534 | return [ 'category', $formFieldArgs[ 'values from category' ] ]; |
| 535 | } elseif ( array_key_exists( 'cargo table', $formFieldArgs ) ) { |
| 536 | $cargo_table = $formFieldArgs[ 'cargo table' ]; |
| 537 | $cargo_field = $formFieldArgs[ 'cargo field' ]; |
| 538 | return [ 'cargo field', $cargo_table . '|' . $cargo_field ]; |
| 539 | } elseif ( array_key_exists( 'values from property', $formFieldArgs ) ) { |
| 540 | return [ 'property', $formFieldArgs['values from property'] ]; |
| 541 | } elseif ( array_key_exists( 'values from concept', $formFieldArgs ) ) { |
| 542 | return [ 'concept', $formFieldArgs['values from concept'] ]; |
| 543 | } elseif ( array_key_exists( 'values dependent on', $formFieldArgs ) ) { |
| 544 | return [ 'dep_on', '' ]; |
| 545 | } elseif ( array_key_exists( 'values from external data', $formFieldArgs ) ) { |
| 546 | return [ 'external data', $formFieldArgs['origName'] ]; |
| 547 | } elseif ( array_key_exists( 'values from wikidata', $formFieldArgs ) ) { |
| 548 | return [ 'wikidata', $formFieldArgs['values from wikidata'] ]; |
| 549 | } else { |
| 550 | return [ '', '' ]; |
| 551 | } |
| 552 | } |
| 553 | |
| 554 | function spreadsheetHTML( $tif ) { |
| 555 | global $wgOut, $wgPageFormsGridValues, $wgPageFormsGridParams; |
| 556 | global $wgPageFormsScriptPath; |
| 557 | |
| 558 | if ( empty( $tif->getFields() ) ) { |
| 559 | return; |
| 560 | } |
| 561 | |
| 562 | $wgOut->addModules( 'ext.pageforms.spreadsheet' ); |
| 563 | |
| 564 | $gridParams = []; |
| 565 | foreach ( $tif->getFields() as $formField ) { |
| 566 | $templateField = $formField->getTemplateField(); |
| 567 | $formFieldArgs = $formField->getFieldArgs(); |
| 568 | $possibleValues = $formField->getPossibleValues(); |
| 569 | |
| 570 | $inputType = $formField->getInputType(); |
| 571 | $gridParamValues = [ 'name' => $templateField->getFieldName() ]; |
| 572 | [ $autocompletedatatype, $autocompletesettings ] = $this->getSpreadsheetAutocompleteAttributes( $formFieldArgs ); |
| 573 | if ( $formField->getLabel() !== null ) { |
| 574 | $gridParamValues['label'] = $formField->getLabel(); |
| 575 | } |
| 576 | if ( $formField->getDefaultValue() !== null ) { |
| 577 | $gridParamValues['default'] = $formField->getDefaultValue(); |
| 578 | } |
| 579 | // currently the spreadsheets in Page Forms doesn't support the tokens input |
| 580 | // so it's better to take a default jspreadsheet editor for tokens |
| 581 | if ( $formField->isList() || $inputType == 'tokens' ) { |
| 582 | $autocompletedatatype = ''; |
| 583 | $autocompletesettings = ''; |
| 584 | $gridParamValues['type'] = 'text'; |
| 585 | } elseif ( !empty( $possibleValues ) |
| 586 | && $autocompletedatatype != 'category' && $autocompletedatatype != 'cargo field' |
| 587 | && $autocompletedatatype != 'concept' && $autocompletedatatype != 'property' ) { |
| 588 | $gridParamValues['values'] = $possibleValues; |
| 589 | if ( $formField->isList() ) { |
| 590 | $gridParamValues['list'] = true; |
| 591 | $gridParamValues['delimiter'] = $formField->getFieldArg( 'delimiter' ); |
| 592 | } |
| 593 | } elseif ( $inputType == 'textarea' ) { |
| 594 | $gridParamValues['type'] = 'textarea'; |
| 595 | } elseif ( $inputType == 'checkbox' ) { |
| 596 | $gridParamValues['type'] = 'checkbox'; |
| 597 | } elseif ( $inputType == 'date' ) { |
| 598 | $gridParamValues['type'] = 'date'; |
| 599 | } elseif ( $inputType == 'datetime' ) { |
| 600 | $gridParamValues['type'] = 'datetime'; |
| 601 | } elseif ( $possibleValues != null ) { |
| 602 | array_unshift( $possibleValues, '' ); |
| 603 | $completePossibleValues = []; |
| 604 | foreach ( $possibleValues as $value ) { |
| 605 | $completePossibleValues[] = [ 'Name' => $value, 'Id' => $value ]; |
| 606 | } |
| 607 | $gridParamValues['type'] = 'select'; |
| 608 | $gridParamValues['items'] = $completePossibleValues; |
| 609 | $gridParamValues['valueField'] = 'Id'; |
| 610 | $gridParamValues['textField'] = 'Name'; |
| 611 | } else { |
| 612 | $gridParamValues['type'] = 'text'; |
| 613 | } |
| 614 | $gridParamValues['autocompletedatatype'] = $autocompletedatatype; |
| 615 | $gridParamValues['autocompletesettings'] = $autocompletesettings; |
| 616 | $gridParamValues['inputType'] = $inputType; |
| 617 | $gridParams[] = $gridParamValues; |
| 618 | } |
| 619 | |
| 620 | $templateName = $tif->getTemplateName(); |
| 621 | $templateDivID = str_replace( ' ', '', $templateName ) . "Grid"; |
| 622 | $templateDivAttrs = [ |
| 623 | 'class' => 'pfSpreadsheet', |
| 624 | 'id' => $templateDivID, |
| 625 | 'data-template-name' => $templateName |
| 626 | ]; |
| 627 | if ( $tif->getHeight() != null ) { |
| 628 | $templateDivAttrs['height'] = $tif->getHeight(); |
| 629 | } |
| 630 | |
| 631 | $loadingImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loading.gif" ] ); |
| 632 | $loadingImageDiv = '<div class="loadingImage">' . $loadingImage . '</div>'; |
| 633 | $text = Html::rawElement( 'div', $templateDivAttrs, $loadingImageDiv ); |
| 634 | |
| 635 | $wgPageFormsGridParams[$templateName] = $gridParams; |
| 636 | $wgPageFormsGridValues[$templateName] = $tif->getGridValues(); |
| 637 | |
| 638 | PFFormUtils::setGlobalVarsForSpreadsheet(); |
| 639 | |
| 640 | return $text; |
| 641 | } |
| 642 | |
| 643 | /** |
| 644 | * Get a string representing the current time, for the time zone |
| 645 | * specified in the wiki. |
| 646 | * @param string $includeTime |
| 647 | * @param string $includeTimezone |
| 648 | * @return string |
| 649 | */ |
| 650 | function getStringForCurrentTime( $includeTime, $includeTimezone ) { |
| 651 | global $wgLocaltimezone, $wgAmericanDates, $wgPageForms24HourTime; |
| 652 | |
| 653 | if ( isset( $wgLocaltimezone ) ) { |
| 654 | $serverTimezone = date_default_timezone_get(); |
| 655 | date_default_timezone_set( $wgLocaltimezone ); |
| 656 | } |
| 657 | $cur_time = time(); |
| 658 | $year = date( "Y", $cur_time ); |
| 659 | $month = date( "n", $cur_time ); |
| 660 | $day = date( "j", $cur_time ); |
| 661 | if ( $wgAmericanDates == true ) { |
| 662 | $month_names = PFFormUtils::getMonthNames(); |
| 663 | $month_name = $month_names[$month - 1]; |
| 664 | $curTimeString = "$month_name $day, $year"; |
| 665 | } else { |
| 666 | $curTimeString = "$year-$month-$day"; |
| 667 | } |
| 668 | if ( isset( $wgLocaltimezone ) ) { |
| 669 | date_default_timezone_set( $serverTimezone ); |
| 670 | } |
| 671 | if ( !$includeTime ) { |
| 672 | return $curTimeString; |
| 673 | } |
| 674 | |
| 675 | if ( $wgPageForms24HourTime ) { |
| 676 | $hour = str_pad( intval( substr( date( "G", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT ); |
| 677 | } else { |
| 678 | $hour = str_pad( intval( substr( date( "g", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT ); |
| 679 | } |
| 680 | $minute = str_pad( intval( substr( date( "i", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT ); |
| 681 | $second = str_pad( intval( substr( date( "s", $cur_time ), 0, 2 ) ), 2, '0', STR_PAD_LEFT ); |
| 682 | if ( $wgPageForms24HourTime ) { |
| 683 | $curTimeString .= " $hour:$minute:$second"; |
| 684 | } else { |
| 685 | $ampm = date( "A", $cur_time ); |
| 686 | $curTimeString .= " $hour:$minute:$second $ampm"; |
| 687 | } |
| 688 | |
| 689 | if ( $includeTimezone ) { |
| 690 | $timezone = date( "T", $cur_time ); |
| 691 | $curTimeString .= " $timezone"; |
| 692 | } |
| 693 | |
| 694 | return $curTimeString; |
| 695 | } |
| 696 | |
| 697 | /** |
| 698 | * If the value passed in for a certain field, when a form is |
| 699 | * submitted, is an array, then it might be from a checkbox |
| 700 | * or date input - in that case, convert it into a string. |
| 701 | * @param array $value |
| 702 | * @param string $delimiter |
| 703 | * @param bool $is_autoedit |
| 704 | * @return string |
| 705 | */ |
| 706 | static function getStringFromPassedInArray( $value, $delimiter, $is_autoedit = false ) { |
| 707 | // If it's just a regular list, concatenate it. |
| 708 | // This is needed due to some strange behavior |
| 709 | // in PF, where, if a preload page is passed in |
| 710 | // in the query string, the form ends up being |
| 711 | // parsed twice. |
| 712 | if ( array_key_exists( 'is_list', $value ) ) { |
| 713 | unset( $value['is_list'] ); |
| 714 | return implode( "$delimiter ", $value ); |
| 715 | } |
| 716 | |
| 717 | // if it has 1 or 2 elements, assume it's a checkbox; if it has |
| 718 | // 3 elements, assume it's a date |
| 719 | // - this handling will have to get more complex if other |
| 720 | // possibilities get added |
| 721 | if ( count( $value ) == 1 ) { |
| 722 | // If this is part of an internal form created to |
| 723 | // do autoedit, treat a blank value as a true null, |
| 724 | // rather than as false. |
| 725 | // @TODO - it's certainly possible that this function |
| 726 | // doesn't need to be called at all, if @is_autoedit |
| 727 | // is true - and the value should simply be blank if |
| 728 | // it's an array. |
| 729 | return $is_autoedit ? '' : PFUtils::getWordForYesOrNo( false ); |
| 730 | } elseif ( count( $value ) == 2 ) { |
| 731 | return PFUtils::getWordForYesOrNo( true ); |
| 732 | // if it's 3 or greater, assume it's a date or datetime |
| 733 | } elseif ( count( $value ) >= 3 ) { |
| 734 | $month = $value['month']; |
| 735 | $day = $value['day']; |
| 736 | if ( $day !== '' ) { |
| 737 | global $wgAmericanDates; |
| 738 | if ( $wgAmericanDates == false ) { |
| 739 | // pad out day to always be two digits |
| 740 | $day = str_pad( $day, 2, "0", STR_PAD_LEFT ); |
| 741 | } |
| 742 | } |
| 743 | $year = $value['year']; |
| 744 | $hour = $minute = $second = $ampm24h = $timezone = null; |
| 745 | if ( isset( $value['hour'] ) ) { |
| 746 | $hour = $value['hour']; |
| 747 | } |
| 748 | if ( isset( $value['minute'] ) ) { |
| 749 | $minute = $value['minute']; |
| 750 | } |
| 751 | if ( isset( $value['second'] ) ) { |
| 752 | $second = $value['second']; |
| 753 | } |
| 754 | if ( isset( $value['ampm24h'] ) ) { |
| 755 | $ampm24h = $value['ampm24h']; |
| 756 | } |
| 757 | if ( isset( $value['timezone'] ) ) { |
| 758 | $timezone = $value['timezone']; |
| 759 | } |
| 760 | // if ( $month !== '' && $day !== '' && $year !== '' ) { |
| 761 | // We can accept either year, or year + month, or year + month + day. |
| 762 | // if ( $month !== '' && $day !== '' && $year !== '' ) { |
| 763 | if ( $year !== '' ) { |
| 764 | // special handling for American dates - otherwise, just |
| 765 | // the standard year/month/day (where month is a number) |
| 766 | global $wgAmericanDates; |
| 767 | |
| 768 | if ( $month == '' ) { |
| 769 | return $year; |
| 770 | } elseif ( $day == '' ) { |
| 771 | if ( !$wgAmericanDates ) { |
| 772 | // The month is a number - we |
| 773 | // need it to be a string, so |
| 774 | // that the date will be parsed |
| 775 | // correctly if strtotime() is |
| 776 | // used. |
| 777 | $monthNames = PFFormUtils::getMonthNames(); |
| 778 | $month = $monthNames[$month - 1]; |
| 779 | } |
| 780 | return "$month $year"; |
| 781 | } else { |
| 782 | if ( $wgAmericanDates == true ) { |
| 783 | $new_value = "$month $day, $year"; |
| 784 | } else { |
| 785 | $new_value = "$year-$month-$day"; |
| 786 | } |
| 787 | // If there's a day, include whatever |
| 788 | // time information we have. |
| 789 | if ( $hour !== null ) { |
| 790 | $new_value .= " " . str_pad( intval( substr( $hour, 0, 2 ) ), 2, '0', STR_PAD_LEFT ) . ":" . str_pad( intval( substr( $minute, 0, 2 ) ), 2, '0', STR_PAD_LEFT ); |
| 791 | } |
| 792 | if ( $second !== null ) { |
| 793 | $new_value .= ":" . str_pad( intval( substr( $second, 0, 2 ) ), 2, '0', STR_PAD_LEFT ); |
| 794 | } |
| 795 | if ( $ampm24h !== null ) { |
| 796 | $new_value .= " $ampm24h"; |
| 797 | } |
| 798 | if ( $timezone !== null ) { |
| 799 | $new_value .= " $timezone"; |
| 800 | } |
| 801 | return $new_value; |
| 802 | } |
| 803 | } |
| 804 | } |
| 805 | return ''; |
| 806 | } |
| 807 | |
| 808 | static function displayLoadingImage() { |
| 809 | global $wgPageFormsScriptPath; |
| 810 | |
| 811 | $text = '<div id="loadingMask"></div>'; |
| 812 | $loadingBGImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loadingbg.png" ] ); |
| 813 | $text .= '<div style="position: fixed; left: 50%; top: 50%;">' . $loadingBGImage . '</div>'; |
| 814 | $loadingImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loading.gif" ] ); |
| 815 | $text .= '<div style="position: fixed; left: 50%; top: 50%; padding: 48px;">' . $loadingImage . '</div>'; |
| 816 | |
| 817 | return Html::rawElement( 'span', [ 'class' => 'loadingImage' ], $text ); |
| 818 | } |
| 819 | |
| 820 | /** |
| 821 | * This function is the real heart of the entire Page Forms |
| 822 | * extension. It handles two main actions: (1) displaying a form on the |
| 823 | * screen, given a form definition and possibly page contents (if an |
| 824 | * existing page is being edited); and (2) creating actual page |
| 825 | * contents, if the form was already submitted by the user. |
| 826 | * |
| 827 | * It also does some related tasks, like figuring out the page name (if |
| 828 | * only a page formula exists). |
| 829 | * @param string $form_def |
| 830 | * @param bool $form_submitted |
| 831 | * @param bool $page_exists |
| 832 | * @param string|null $form_id |
| 833 | * @param string|null $existing_page_content |
| 834 | * @param string|null $page_name |
| 835 | * @param string|null $page_name_formula |
| 836 | * @param int $form_context |
| 837 | * @param array $autocreate_query query parameters from #formredlink |
| 838 | * @param User|null $user |
| 839 | * @return array |
| 840 | * @throws FatalError |
| 841 | * @throws MWException |
| 842 | */ |
| 843 | function formHTML( |
| 844 | $form_def, |
| 845 | $form_submitted, |
| 846 | $page_exists, |
| 847 | $form_id = null, |
| 848 | $existing_page_content = null, |
| 849 | $page_name = null, |
| 850 | $page_name_formula = null, |
| 851 | $form_context = self::CONTEXT_REGULAR, |
| 852 | $autocreate_query = [], |
| 853 | $user = null |
| 854 | ) { |
| 855 | global $wgRequest; |
| 856 | // used to represent the current tab index in the form |
| 857 | global $wgPageFormsTabIndex; |
| 858 | // used for setting various HTML IDs |
| 859 | global $wgPageFormsFieldNum; |
| 860 | global $wgPageFormsShowExpandAllLink; |
| 861 | |
| 862 | // Initialize some variables. |
| 863 | $wiki_page = new PFWikiPage(); |
| 864 | $source_is_page = $page_exists || $existing_page_content != null; |
| 865 | $wgPageFormsTabIndex = 0; |
| 866 | $wgPageFormsFieldNum = 0; |
| 867 | $source_page_matches_this_form = false; |
| 868 | $form_page_title = ''; |
| 869 | $generated_page_name = $page_name_formula ?? ''; |
| 870 | $new_text = ""; |
| 871 | $original_page_content = $existing_page_content; |
| 872 | $is_query = ( $form_context == self::CONTEXT_QUERY || $form_context == self::CONTEXT_EMBEDDED_QUERY ); |
| 873 | $is_embedded = $form_context == self::CONTEXT_EMBEDDED_QUERY; |
| 874 | $is_autoedit = $form_context == self::CONTEXT_AUTOEDIT; |
| 875 | $is_autocreate = $form_context == self::CONTEXT_AUTOCREATE; |
| 876 | |
| 877 | // Disable all form elements if user doesn't have edit |
| 878 | // permission - two different checks are needed, because |
| 879 | // editing permissions can be set in different ways. |
| 880 | // HACK - sometimes we don't know the page name in advance, but |
| 881 | // we still need to set a title here for testing permissions. |
| 882 | if ( $is_embedded ) { |
| 883 | // If this is an embedded form, |
| 884 | // just use the name of the actual page we're on. |
| 885 | global $wgTitle; |
| 886 | $this->mPageTitle = $wgTitle; |
| 887 | } elseif ( $is_query ) { |
| 888 | // We're in Special:RunQuery - just use that as the |
| 889 | // title. |
| 890 | global $wgTitle; |
| 891 | $this->mPageTitle = $wgTitle; |
| 892 | } elseif ( $page_name === '' || $page_name === null ) { |
| 893 | $this->mPageTitle = Title::newFromText( |
| 894 | $wgRequest->getVal( 'namespace' ) . ":Page Forms permissions test" ); |
| 895 | } else { |
| 896 | $this->mPageTitle = Title::newFromText( $page_name ); |
| 897 | } |
| 898 | |
| 899 | if ( $user === null ) { |
| 900 | $user = RequestContext::getMain()->getUser(); |
| 901 | } |
| 902 | |
| 903 | global $wgOut; |
| 904 | // Show previous set of deletions for this page, if it's been |
| 905 | // deleted before. |
| 906 | if ( !$form_submitted && |
| 907 | ( $this->mPageTitle && !$this->mPageTitle->exists() && |
| 908 | $page_name_formula === null ) |
| 909 | ) { |
| 910 | $this->showDeletionLog( $wgOut ); |
| 911 | } |
| 912 | $services = MediaWikiServices::getInstance(); |
| 913 | $hookContainer = $services->getHookContainer(); |
| 914 | // Unfortunately, we can't just call userCan() or its |
| 915 | // equivalent here because it seems to ignore the setting |
| 916 | // "$wgEmailConfirmToEdit = true;". Instead, we'll just get the |
| 917 | // permission errors from the start, and use those to determine |
| 918 | // whether the page is editable. |
| 919 | if ( !$is_query ) { |
| 920 | $permissionManager = $services->getPermissionManager(); |
| 921 | $readOnlyMode = $services->getReadOnlyMode(); |
| 922 | $permissionStatus = $permissionErrors = null; |
| 923 | |
| 924 | if ( method_exists( $permissionManager, 'getPermissionStatus' ) ) { |
| 925 | // MW 1.43+ |
| 926 | $permissionStatus = $permissionManager->getPermissionStatus( 'edit', $user, $this->mPageTitle ); |
| 927 | if ( $readOnlyMode->isReadOnly() ) { |
| 928 | $permissionStatus->error( 'readonlytext', $readOnlyMode->getReason() ); |
| 929 | } |
| 930 | $userCanEditPage = $permissionStatus->isOK(); |
| 931 | } else { |
| 932 | // MW < 1.43 |
| 933 | $permissionErrors = $permissionManager->getPermissionErrors( 'edit', $user, $this->mPageTitle ); |
| 934 | if ( $readOnlyMode->isReadOnly() ) { |
| 935 | $permissionErrors = [ [ 'readonlytext', [ $readOnlyMode->getReason() ] ] ]; |
| 936 | } |
| 937 | $userCanEditPage = count( $permissionErrors ) == 0; |
| 938 | } |
| 939 | |
| 940 | $hookContainer->run( 'PageForms::UserCanEditPage', [ $this->mPageTitle, &$userCanEditPage ] ); |
| 941 | } |
| 942 | |
| 943 | // Start off with a loading spinner - this will be removed by |
| 944 | // the JavaScript once everything has finished loading. |
| 945 | $form_text = self::displayLoadingImage(); |
| 946 | if ( $is_query || $userCanEditPage ) { |
| 947 | $form_is_disabled = false; |
| 948 | // Show "Your IP address will be recorded" warning if |
| 949 | // user is anonymous, and it's not a query. |
| 950 | if ( $user->isAnon() && !$is_query ) { |
| 951 | // Based on code in MediaWiki's EditPage.php. |
| 952 | $anonEditWarning = wfMessage( 'anoneditwarning', |
| 953 | // Log-in link |
| 954 | '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}', |
| 955 | // Sign-up link |
| 956 | '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' )->parse(); |
| 957 | $form_text .= Html::warningBox( $anonEditWarning, 'mw-anon-edit-warning' ); |
| 958 | } |
| 959 | } else { |
| 960 | $form_is_disabled = true; |
| 961 | if ( $wgOut->getTitle() != null ) { |
| 962 | $wgOut->setPageTitle( wfMessage( 'badaccess' )->text() ); |
| 963 | if ( $permissionStatus ) { |
| 964 | $wgOut->addWikiTextAsInterface( $wgOut->formatPermissionStatus( $permissionStatus, 'edit' ) ); |
| 965 | } else { |
| 966 | // MW < 1.43 |
| 967 | $wgOut->addWikiTextAsInterface( |
| 968 | $wgOut->formatPermissionsErrorMessage( $permissionErrors, 'edit' ) |
| 969 | ); |
| 970 | } |
| 971 | $wgOut->addHTML( "\n<hr />\n" ); |
| 972 | } |
| 973 | } |
| 974 | |
| 975 | if ( $wgPageFormsShowExpandAllLink ) { |
| 976 | $form_text .= Html::rawElement( 'p', [ 'id' => 'pf-expand-all' ], |
| 977 | // @TODO - add an i18n message for this. |
| 978 | Html::element( 'a', [ 'href' => '#' ], 'Expand all collapsed parts of the form' ) ) . "\n"; |
| 979 | } |
| 980 | |
| 981 | $parser = $services->getParserFactory()->getInstance(); |
| 982 | if ( !$parser->getOptions() ) { |
| 983 | $parser->setOptions( ParserOptions::newFromUser( $user ) ); |
| 984 | } |
| 985 | $parser->setTitle( $this->mPageTitle ); |
| 986 | // This is needed in order to make sure $parser->mLinkHolders |
| 987 | // is set. |
| 988 | $parser->clearState(); |
| 989 | $parser->setOutputType( Parser::OT_HTML ); |
| 990 | |
| 991 | $form_def = PFFormUtils::getFormDefinition( $parser, $form_def, $form_id ); |
| 992 | |
| 993 | // Turn form definition file into an array of sections, one for |
| 994 | // each template definition (plus the first section). |
| 995 | $form_def_sections = []; |
| 996 | $start_position = 0; |
| 997 | $section_start = 0; |
| 998 | $free_text_was_included = false; |
| 999 | $preloaded_free_text = null; |
| 1000 | // @HACK - replace the 'free text' standard input with a |
| 1001 | // field declaration to get it to be handled as a field. |
| 1002 | $form_def = str_replace( 'standard input|free text', 'field|#freetext#', $form_def ); |
| 1003 | while ( $brackets_loc = strpos( $form_def, "{{{", $start_position ) ) { |
| 1004 | $brackets_end_loc = strpos( $form_def, "}}}", $brackets_loc ); |
| 1005 | $bracketed_string = substr( $form_def, $brackets_loc + 3, $brackets_end_loc - ( $brackets_loc + 3 ) ); |
| 1006 | $tag_components = PFUtils::getFormTagComponents( $bracketed_string ); |
| 1007 | $tag_title = trim( $tag_components[0] ); |
| 1008 | if ( $tag_title == 'for template' || $tag_title == 'end template' ) { |
| 1009 | // Create a section for everything up to here |
| 1010 | $section = substr( $form_def, $section_start, $brackets_loc - $section_start ); |
| 1011 | $form_def_sections[] = $section; |
| 1012 | $section_start = $brackets_loc; |
| 1013 | } |
| 1014 | $start_position = $brackets_loc + 1; |
| 1015 | } |
| 1016 | // end while |
| 1017 | $form_def_sections[] = trim( substr( $form_def, $section_start ) ); |
| 1018 | |
| 1019 | // Cycle through the form definition file, and possibly an |
| 1020 | // existing article as well, finding template and field |
| 1021 | // declarations and replacing them with form elements, either |
| 1022 | // blank or pre-populated, as appropriate. |
| 1023 | $template_name = null; |
| 1024 | $template = null; |
| 1025 | $tif = null; |
| 1026 | // This array will keep track of all the replaced @<name>@ strings |
| 1027 | $placeholderFields = []; |
| 1028 | |
| 1029 | for ( $section_num = 0; $section_num < count( $form_def_sections ); $section_num++ ) { |
| 1030 | $start_position = 0; |
| 1031 | // the append is there to ensure that the original |
| 1032 | // array doesn't get modified; is it necessary? |
| 1033 | $section = " " . $form_def_sections[$section_num]; |
| 1034 | |
| 1035 | while ( $brackets_loc = strpos( $section, '{{{', $start_position ) ) { |
| 1036 | $brackets_end_loc = strpos( $section, "}}}", $brackets_loc ); |
| 1037 | // For cases with more than 3 ending brackets, |
| 1038 | // take the last 3 ones as the tag end. |
| 1039 | while ( isset( $section[$brackets_end_loc + 3] ) && $section[$brackets_end_loc + 3] == "}" ) { |
| 1040 | $brackets_end_loc++; |
| 1041 | } |
| 1042 | $bracketed_string = substr( $section, $brackets_loc + 3, $brackets_end_loc - ( $brackets_loc + 3 ) ); |
| 1043 | $tag_components = PFUtils::getFormTagComponents( $bracketed_string ); |
| 1044 | if ( count( $tag_components ) == 0 ) { |
| 1045 | continue; |
| 1046 | } |
| 1047 | $tag_title = trim( $tag_components[0] ); |
| 1048 | // Checks for forbidden characters |
| 1049 | if ( $tag_title != 'info' ) { |
| 1050 | foreach ( $tag_components as $tag_component ) { |
| 1051 | // Angled brackets could cause a security leak (and should not be necessary). |
| 1052 | // Allow them in "default filename", though. |
| 1053 | $tagParts = explode( '=', $tag_component, 2 ); |
| 1054 | if ( count( $tagParts ) == 2 && $tagParts[0] == 'default filename' ) { |
| 1055 | continue; |
| 1056 | } |
| 1057 | if ( strpos( $tag_component, '<' ) !== false && strpos( $tag_component, '>' ) !== false ) { |
| 1058 | throw new MWException( |
| 1059 | '<div class="error">Error in form definition! The following field tag contains forbidden characters:</div>' . |
| 1060 | "\n<pre>" . htmlspecialchars( $tag_component ) . "</pre>" |
| 1061 | ); |
| 1062 | } |
| 1063 | } |
| 1064 | } |
| 1065 | // ===================================================== |
| 1066 | // for template processing |
| 1067 | // ===================================================== |
| 1068 | if ( $tag_title == 'for template' ) { |
| 1069 | if ( $tif ) { |
| 1070 | $previous_template_name = $tif->getTemplateName(); |
| 1071 | } else { |
| 1072 | $previous_template_name = ''; |
| 1073 | } |
| 1074 | if ( count( $tag_components ) < 2 ) { |
| 1075 | throw new MWException( 'Error: a template name must be specified in each "for template" tag.' ); |
| 1076 | } |
| 1077 | $template_name = str_replace( '_', ' ', self::getParsedValue( $parser, $tag_components[1] ) ); |
| 1078 | $is_new_template = ( $template_name != $previous_template_name ); |
| 1079 | if ( $is_new_template ) { |
| 1080 | $template = PFTemplate::newFromName( $template_name ); |
| 1081 | $tif = PFTemplateInForm::newFromFormTag( $tag_components ); |
| 1082 | } |
| 1083 | // Remove template tag. |
| 1084 | $section = substr_replace( $section, '', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1085 | // If we are editing a page, and this |
| 1086 | // template can be found more than |
| 1087 | // once in that page, and multiple |
| 1088 | // values are allowed, repeat this |
| 1089 | // section. |
| 1090 | if ( $source_is_page ) { |
| 1091 | $tif->setPageRelatedInfo( $existing_page_content ); |
| 1092 | // Get the first instance of |
| 1093 | // this template on the page |
| 1094 | // being edited, even if there |
| 1095 | // are more. |
| 1096 | if ( $tif->pageCallsThisTemplate() ) { |
| 1097 | $tif->setFieldValuesFromPage( $existing_page_content ); |
| 1098 | $existing_template_text = $tif->getFullTextInPage(); |
| 1099 | // Now remove this template from the text being edited. |
| 1100 | $existing_page_content = $this->strReplaceFirst( $existing_template_text, '', $existing_page_content ); |
| 1101 | // If we've found a match in the source |
| 1102 | // page, there's a good chance that this |
| 1103 | // page was created with this form - note |
| 1104 | // that, so we don't send the user a warning. |
| 1105 | $source_page_matches_this_form = true; |
| 1106 | } |
| 1107 | } |
| 1108 | |
| 1109 | // We get values from the request, |
| 1110 | // regardless of whether the source is the |
| 1111 | // page or a form submit, because even if |
| 1112 | // the source is a page, values can still |
| 1113 | // come from a query string. |
| 1114 | // (Unless it's called from #formredlink.) |
| 1115 | if ( !$is_autocreate ) { |
| 1116 | $tif->setFieldValuesFromSubmit(); |
| 1117 | } |
| 1118 | |
| 1119 | $tif->checkIfAllInstancesPrinted( $form_submitted, $source_is_page, $is_autoedit ); |
| 1120 | |
| 1121 | if ( !$tif->allInstancesPrinted() ) { |
| 1122 | $wiki_page->addTemplate( $tif ); |
| 1123 | } |
| 1124 | |
| 1125 | // ===================================================== |
| 1126 | // end template processing |
| 1127 | // ===================================================== |
| 1128 | } elseif ( $tag_title == 'end template' ) { |
| 1129 | if ( count( $tag_components ) > 1 ) { |
| 1130 | throw new MWException( '<div class="error">Error in form definition: \'end template\' tag cannot contain any additional parameters.</div>' ); |
| 1131 | } |
| 1132 | if ( $source_is_page ) { |
| 1133 | // Add any unhandled template fields |
| 1134 | // in the page as hidden variables. |
| 1135 | $form_text .= PFFormUtils::unhandledFieldsHTML( $tif, $is_autoedit ); |
| 1136 | } |
| 1137 | // Remove this tag from the $section variable. |
| 1138 | $section = substr_replace( $section, '', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1139 | $template = null; |
| 1140 | $tif = null; |
| 1141 | // ===================================================== |
| 1142 | // field processing |
| 1143 | // ===================================================== |
| 1144 | } elseif ( $tag_title == 'field' ) { |
| 1145 | // If the template is null, that (hopefully) |
| 1146 | // means we're handling the free text field. |
| 1147 | // Make the template a dummy variable. |
| 1148 | if ( $tif == null ) { |
| 1149 | $template = new PFTemplate( null, [] ); |
| 1150 | // Get free text from the query string, if it was set. |
| 1151 | if ( $wgRequest->getCheck( 'free_text' ) ) { |
| 1152 | $standard_input = $wgRequest->getArray( 'standard_input', [] ); |
| 1153 | $standard_input['#freetext#'] = $wgRequest->getVal( 'free_text' ); |
| 1154 | $wgRequest->setVal( 'standard_input', $standard_input ); |
| 1155 | } |
| 1156 | $tif = PFTemplateInForm::create( 'standard_input', null, null, null, [] ); |
| 1157 | $tif->setFieldValuesFromSubmit(); |
| 1158 | } |
| 1159 | // We get the field name both here |
| 1160 | // and in the PFFormField constructor, |
| 1161 | // because PFFormField isn't equipped |
| 1162 | // to deal with the #freetext# hack, |
| 1163 | // among others. |
| 1164 | $field_name = trim( $tag_components[1] ); |
| 1165 | $form_field = PFFormField::newFromFormFieldTag( $tag_components, $template, $tif, $form_is_disabled, $user ); |
| 1166 | // For special displays, add in the |
| 1167 | // form fields, so we know the data |
| 1168 | // structure. |
| 1169 | if ( ( $tif->getDisplay() == 'table' && ( !$tif->allowsMultiple() || $tif->getInstanceNum() == 0 ) ) || |
| 1170 | ( $tif->getDisplay() == 'spreadsheet' && $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) || ( $tif->getDisplay() == 'calendar' && $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) ) { |
| 1171 | $tif->addField( $form_field ); |
| 1172 | } |
| 1173 | $val_modifier = null; |
| 1174 | if ( $is_autocreate ) { |
| 1175 | $values_from_query = $autocreate_query[$tif->getTemplateName()] ?? []; |
| 1176 | $cur_value = $form_field->getCurrentValue( $values_from_query, $form_submitted, $source_is_page, $tif->allInstancesPrinted(), $val_modifier ); |
| 1177 | } else { |
| 1178 | $cur_value = $form_field->getCurrentValue( $tif->getValuesFromSubmit(), $form_submitted, $source_is_page, $tif->allInstancesPrinted(), $val_modifier, $is_autoedit ); |
| 1179 | } |
| 1180 | $delimiter = $form_field->getFieldArg( 'delimiter' ); |
| 1181 | if ( $form_field->holdsTemplate() ) { |
| 1182 | $placeholderFields[] = self::placeholderFormat( $tif->getTemplateName(), $field_name ); |
| 1183 | } |
| 1184 | |
| 1185 | if ( $val_modifier !== null ) { |
| 1186 | $page_value = $tif->getValuesFromPage()[$field_name]; |
| 1187 | } |
| 1188 | if ( $val_modifier === '+' ) { |
| 1189 | if ( preg_match( "#(,|\^)\s*$cur_value\s*(,|\$)#", $page_value ) === 0 ) { |
| 1190 | if ( trim( $page_value ) !== '' ) { |
| 1191 | // if page_value is empty, simply don't do anything, because then cur_value |
| 1192 | // is already the value it has to be (no delimiter needed). |
| 1193 | $cur_value = $page_value . $delimiter . $cur_value; |
| 1194 | } |
| 1195 | } else { |
| 1196 | $cur_value = $page_value; |
| 1197 | } |
| 1198 | $tif->changeFieldValues( $field_name, $cur_value, $delimiter ); |
| 1199 | } elseif ( $val_modifier === '-' ) { |
| 1200 | // get an array of elements to remove: |
| 1201 | $remove = array_map( 'trim', explode( ",", $cur_value ) ); |
| 1202 | // process the current value: |
| 1203 | $val_array = array_map( 'trim', explode( $delimiter, $page_value ) ); |
| 1204 | // remove element(s) from list |
| 1205 | foreach ( $remove as $rmv ) { |
| 1206 | // go through each element and remove match(es) |
| 1207 | $key = array_search( $rmv, $val_array ); |
| 1208 | if ( $key !== false ) { |
| 1209 | unset( $val_array[$key] ); |
| 1210 | } |
| 1211 | } |
| 1212 | // Convert modified array back to a comma-separated string value and modify |
| 1213 | $cur_value = implode( ",", $val_array ); |
| 1214 | if ( $cur_value === '' ) { |
| 1215 | // HACK: setting an empty string prevents anything from happening at all. |
| 1216 | // set a dummy string that evaluates to an empty string |
| 1217 | $cur_value = '{{subst:lc: }}'; |
| 1218 | } |
| 1219 | $tif->changeFieldValues( $field_name, $cur_value, $delimiter ); |
| 1220 | } |
| 1221 | // If the user is editing a page, and that page contains a call to |
| 1222 | // the template being processed, get the current field's value |
| 1223 | // from the template call. |
| 1224 | // Do the same thing if it's a new page but there's a "preload" - |
| 1225 | // unless a value for this field was already set in the query string. |
| 1226 | if ( ( $page_exists || $cur_value == '' ) && ( $tif->getFullTextInPage() != '' ) && !$form_submitted ) { |
| 1227 | if ( $tif->hasValueFromPageForField( $field_name ) ) { |
| 1228 | // Get value, and remove it, |
| 1229 | // so that at the end we |
| 1230 | // can have a list of all |
| 1231 | // the fields that weren't |
| 1232 | // handled by the form. |
| 1233 | $cur_value = $tif->getAndRemoveValueFromPageForField( $field_name ); |
| 1234 | |
| 1235 | // If the field is a placeholder, the contents of this template |
| 1236 | // parameter should be treated as elements parsed by an another |
| 1237 | // multiple template form. |
| 1238 | // By putting that at the very end of the parsed string, we'll |
| 1239 | // have it processed as a regular multiple template form. |
| 1240 | if ( $form_field->holdsTemplate() ) { |
| 1241 | $existing_page_content .= $cur_value; |
| 1242 | } |
| 1243 | } elseif ( isset( $cur_value ) && !empty( $cur_value ) ) { |
| 1244 | // Do nothing. |
| 1245 | } else { |
| 1246 | $cur_value = ''; |
| 1247 | } |
| 1248 | } |
| 1249 | |
| 1250 | // Handle the free text field. |
| 1251 | if ( $field_name == '#freetext#' ) { |
| 1252 | // If there was no preloading, this will just be blank. |
| 1253 | $preloaded_free_text = $cur_value; |
| 1254 | // Add placeholders for the free text in both the form and |
| 1255 | // the page, using <free_text> tags - once all the free text |
| 1256 | // is known (at the end), it will get substituted in. |
| 1257 | if ( $form_field->isHidden() ) { |
| 1258 | $new_text = Html::hidden( 'pf_free_text', '!free_text!' ); |
| 1259 | } else { |
| 1260 | $wgPageFormsTabIndex++; |
| 1261 | $wgPageFormsFieldNum++; |
| 1262 | if ( $cur_value === '' || $cur_value === null ) { |
| 1263 | $default_value = '!free_text!'; |
| 1264 | } else { |
| 1265 | $default_value = $cur_value; |
| 1266 | } |
| 1267 | $freeTextInput = new PFTextAreaInput( $input_number = null, $default_value, 'pf_free_text', ( $form_is_disabled || $form_field->isRestricted() ), $form_field->getFieldArgs() ); |
| 1268 | $freeTextInput->addJavaScript(); |
| 1269 | $new_text = $freeTextInput->getHtmlText(); |
| 1270 | if ( $form_field->hasFieldArg( 'edittools' ) ) { |
| 1271 | // borrowed from EditPage::showEditTools() |
| 1272 | $edittools_text = self::getParsedValue( $parser, wfMessage( 'edittools', [ 'content' ] )->text() ); |
| 1273 | |
| 1274 | $new_text .= <<<END |
| 1275 | <div class="mw-editTools"> |
| 1276 | $edittools_text |
| 1277 | </div> |
| 1278 | |
| 1279 | END; |
| 1280 | } |
| 1281 | } |
| 1282 | $free_text_was_included = true; |
| 1283 | $wiki_page->addFreeTextSection(); |
| 1284 | } |
| 1285 | |
| 1286 | if ( $tif->getTemplateName() === '' || $field_name == '#freetext#' ) { |
| 1287 | $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1288 | } else { |
| 1289 | if ( is_array( $cur_value ) ) { |
| 1290 | // @TODO - is this code ever called? |
| 1291 | $delimiter = $form_field->getFieldArg( 'is_list' ); |
| 1292 | // first, check if it's a list |
| 1293 | if ( array_key_exists( 'is_list', $cur_value ) && |
| 1294 | $cur_value['is_list'] == true ) { |
| 1295 | $cur_value_in_template = ""; |
| 1296 | foreach ( $cur_value as $key => $val ) { |
| 1297 | if ( $key !== "is_list" ) { |
| 1298 | if ( $cur_value_in_template != "" ) { |
| 1299 | $cur_value_in_template .= $delimiter . " "; |
| 1300 | } |
| 1301 | $cur_value_in_template .= $val; |
| 1302 | } |
| 1303 | } |
| 1304 | } else { |
| 1305 | // If it's not a list, it's probably from a checkbox or date input - |
| 1306 | // convert the values into a string. |
| 1307 | $cur_value_in_template = self::getStringFromPassedInArray( $cur_value, $delimiter ); |
| 1308 | } |
| 1309 | } elseif ( $form_field->holdsTemplate() ) { |
| 1310 | // If this field holds an embedded template, |
| 1311 | // and the value is not an array, it means |
| 1312 | // there are no instances of the template - |
| 1313 | // set the value to null to avoid getting |
| 1314 | // whatever is currently on the page. |
| 1315 | $cur_value_in_template = null; |
| 1316 | } else { |
| 1317 | // value is not an array. |
| 1318 | $cur_value_in_template = $cur_value; |
| 1319 | } |
| 1320 | |
| 1321 | // If we're creating the page name from a formula based on |
| 1322 | // form values, see if the current input is part of that formula, |
| 1323 | // and if so, substitute in the actual value. |
| 1324 | if ( $form_submitted && $generated_page_name !== '' ) { |
| 1325 | // This line appears to be unnecessary. |
| 1326 | // $generated_page_name = str_replace('.', '_', $generated_page_name); |
| 1327 | $generated_page_name = str_replace( ' ', '_', $generated_page_name ); |
| 1328 | $escaped_input_name = str_replace( ' ', '_', $form_field->getInputName() ); |
| 1329 | $generated_page_name = str_ireplace( "<$escaped_input_name>", |
| 1330 | $cur_value_in_template ?? '', $generated_page_name ); |
| 1331 | // Once the substitution is done, replace underlines back |
| 1332 | // with spaces. |
| 1333 | $generated_page_name = str_replace( '_', ' ', $generated_page_name ); |
| 1334 | } |
| 1335 | if ( defined( 'CARGO_VERSION' ) && $form_field->hasFieldArg( 'mapping cargo table' ) && |
| 1336 | $form_field->hasFieldArg( 'mapping cargo field' ) && $form_field->hasFieldArg( 'mapping cargo value field' ) ) { |
| 1337 | $mappingCargoTable = $form_field->getFieldArg( 'mapping cargo table' ); |
| 1338 | $mappingCargoField = $form_field->getFieldArg( 'mapping cargo field' ); |
| 1339 | $mappingCargoValueField = $form_field->getFieldArg( 'mapping cargo value field' ); |
| 1340 | if ( !$form_submitted && $cur_value !== null && $cur_value !== '' ) { |
| 1341 | $cur_value = $this->getCargoBasedMapping( $cur_value, $mappingCargoTable, $mappingCargoField, $mappingCargoValueField, $form_field ); |
| 1342 | } |
| 1343 | if ( $form_submitted && $cur_value_in_template !== null && $cur_value_in_template !== '' ) { |
| 1344 | $cur_value_in_template = $this->getCargoBasedMapping( $cur_value_in_template, $mappingCargoTable, $mappingCargoValueField, $mappingCargoField, $form_field ); |
| 1345 | } |
| 1346 | } |
| 1347 | if ( $cur_value !== '' && |
| 1348 | ( $form_field->hasFieldArg( 'mapping template' ) || |
| 1349 | $form_field->hasFieldArg( 'mapping property' ) || |
| 1350 | ( $form_field->hasFieldArg( 'mapping cargo table' ) && |
| 1351 | $form_field->hasFieldArg( 'mapping cargo field' ) ) || |
| 1352 | $form_field->getUseDisplayTitle() ) ) { |
| 1353 | // If the input type is "tokens', the value is not |
| 1354 | // an array, but the delimiter still needs to be set. |
| 1355 | if ( !is_array( $cur_value ) ) { |
| 1356 | if ( $form_field->isList() ) { |
| 1357 | $delimiter = $form_field->getFieldArg( 'delimiter' ); |
| 1358 | } else { |
| 1359 | $delimiter = null; |
| 1360 | } |
| 1361 | } |
| 1362 | $cur_value = $form_field->valueStringToLabels( $cur_value, $delimiter, $form_submitted ); |
| 1363 | } |
| 1364 | |
| 1365 | // Call hooks - unfortunately this has to be split into two |
| 1366 | // separate calls, because of the different variable names in |
| 1367 | // each case. |
| 1368 | // @TODO - should it be $cur_value for both cases? Or should the |
| 1369 | // hook perhaps modify both variables? |
| 1370 | if ( $form_submitted ) { |
| 1371 | $hookContainer->run( 'PageForms::CreateFormField', [ &$form_field, &$cur_value_in_template, true ] ); |
| 1372 | } else { |
| 1373 | $this->createFormFieldTranslateTag( $template, $tif, $form_field, $cur_value ); |
| 1374 | $hookContainer->run( 'PageForms::CreateFormField', [ &$form_field, &$cur_value, false ] ); |
| 1375 | } |
| 1376 | // if this is not part of a 'multiple' template, increment the |
| 1377 | // global tab index (used for correct tabbing) |
| 1378 | if ( !$form_field->hasFieldArg( 'part_of_multiple' ) ) { |
| 1379 | $wgPageFormsTabIndex++; |
| 1380 | } |
| 1381 | // increment the global field number regardless |
| 1382 | $wgPageFormsFieldNum++; |
| 1383 | if ( $source_is_page && !$tif->allInstancesPrinted() ) { |
| 1384 | // If the source is a page, don't use the default |
| 1385 | // values - except for newly-added instances of a |
| 1386 | // multiple-instance template. |
| 1387 | // If the field is a date field, and its default value was set |
| 1388 | // to 'now', and it has no current value, set $cur_value to be |
| 1389 | // the current date. |
| 1390 | } elseif ( $form_field->getDefaultValue() == 'now' && |
| 1391 | // if the date is hidden, cur_value will already be set |
| 1392 | // to the default value |
| 1393 | ( $cur_value == '' || $cur_value == 'now' ) ) { |
| 1394 | $input_type = $form_field->getInputType(); |
| 1395 | // We don't handle the 'datepicker' and 'datetimepicker' |
| 1396 | // input types here, because they have their own |
| 1397 | // formatting; instead, they handle 'now' themselves. |
| 1398 | if ( $input_type == 'date' || $input_type == 'datetime' || |
| 1399 | $input_type == 'year' || |
| 1400 | ( $input_type == '' && $form_field->getTemplateField()->getPropertyType() == '_dat' ) ) { |
| 1401 | $cur_value_in_template = self::getStringForCurrentTime( $input_type == 'datetime', $form_field->hasFieldArg( 'include timezone' ) ); |
| 1402 | } |
| 1403 | // If the field is a text field, and its default value was set |
| 1404 | // to 'current user', and it has no current value, set $cur_value |
| 1405 | // to be the current user. |
| 1406 | } elseif ( $form_field->getDefaultValue() == 'current user' && |
| 1407 | // if the input is hidden, cur_value will already be set |
| 1408 | // to the default value |
| 1409 | ( $cur_value === '' || $cur_value == 'current user' ) |
| 1410 | ) { |
| 1411 | $cur_value_in_template = $user->isRegistered() ? $user->getName() : ''; |
| 1412 | $cur_value = $cur_value_in_template; |
| 1413 | // UUID is the only default value (so far) that can also be set |
| 1414 | // by the JavaScript, for multiple-instance templates - for the |
| 1415 | // other default values, there's no real need to have a |
| 1416 | // different value for each instance. |
| 1417 | } elseif ( $form_field->getDefaultValue() == 'uuid' && |
| 1418 | ( $cur_value == '' || $cur_value == 'uuid' ) |
| 1419 | ) { |
| 1420 | if ( $tif->allowsMultiple() ) { |
| 1421 | // Will be set by the JS. |
| 1422 | $form_field->setFieldArg( 'class', 'new-uuid' ); |
| 1423 | } else { |
| 1424 | $cur_value = $cur_value_in_template = self::generateUUID(); |
| 1425 | } |
| 1426 | } |
| 1427 | |
| 1428 | // If all instances have been |
| 1429 | // printed, that means we're |
| 1430 | // now printing a "starter" |
| 1431 | // div - set the current value |
| 1432 | // to null, unless it's the |
| 1433 | // default value. |
| 1434 | // (Ideally it wouldn't get |
| 1435 | // set at all, but that seems a |
| 1436 | // little harder.) |
| 1437 | if ( $tif->allInstancesPrinted() && $form_field->getDefaultValue() == null ) { |
| 1438 | $cur_value = null; |
| 1439 | } |
| 1440 | |
| 1441 | $new_text = $this->formFieldHTML( $form_field, $cur_value ); |
| 1442 | $new_text .= $form_field->additionalHTMLForInput( $cur_value, $field_name, $tif->getTemplateName() ); |
| 1443 | |
| 1444 | if ( $new_text ) { |
| 1445 | $wiki_page->addTemplateParam( $template_name, $tif->getInstanceNum(), $field_name, $cur_value_in_template ); |
| 1446 | $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1447 | $start_position = $brackets_loc + strlen( $new_text ); |
| 1448 | } else { |
| 1449 | $start_position = $brackets_end_loc; |
| 1450 | } |
| 1451 | } |
| 1452 | |
| 1453 | if ( $tif->allowsMultiple() && !$tif->allInstancesPrinted() ) { |
| 1454 | $wordForYes = PFUtils::getWordForYesOrNo( true ); |
| 1455 | if ( $form_field->getInputType() == 'checkbox' ) { |
| 1456 | if ( strtolower( $cur_value ) == strtolower( $wordForYes ) || strtolower( $cur_value ) == 'yes' || $cur_value == '1' ) { |
| 1457 | $cur_value = true; |
| 1458 | } else { |
| 1459 | $cur_value = false; |
| 1460 | } |
| 1461 | } |
| 1462 | } |
| 1463 | |
| 1464 | if ( $tif->getDisplay() != null && ( !$tif->allowsMultiple() || !$tif->allInstancesPrinted() ) ) { |
| 1465 | $tif->addGridValue( $field_name, $cur_value ); |
| 1466 | } |
| 1467 | |
| 1468 | // ===================================================== |
| 1469 | // standard input processing |
| 1470 | // ===================================================== |
| 1471 | } elseif ( $tag_title == 'standard input' ) { |
| 1472 | // handle all the possible values |
| 1473 | $input_name = $tag_components[1]; |
| 1474 | $input_label = null; |
| 1475 | $attr = []; |
| 1476 | |
| 1477 | // if it's a query, ignore all standard inputs except run query |
| 1478 | if ( ( $is_query && $input_name != 'run query' ) || ( !$is_query && $input_name == 'run query' ) ) { |
| 1479 | $new_text = ""; |
| 1480 | $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1481 | continue; |
| 1482 | } |
| 1483 | // set a flag so that the standard 'form bottom' won't get displayed |
| 1484 | $this->standardInputsIncluded = true; |
| 1485 | // cycle through the other components |
| 1486 | $is_checked = false; |
| 1487 | for ( $i = 2; $i < count( $tag_components ); $i++ ) { |
| 1488 | $component = $tag_components[$i]; |
| 1489 | $sub_components = array_map( 'trim', explode( '=', $component ) ); |
| 1490 | if ( count( $sub_components ) == 1 ) { |
| 1491 | if ( $sub_components[0] == 'checked' ) { |
| 1492 | $is_checked = true; |
| 1493 | } |
| 1494 | } elseif ( count( $sub_components ) == 2 ) { |
| 1495 | switch ( $sub_components[0] ) { |
| 1496 | case 'label': |
| 1497 | $input_label = self::getParsedValue( $parser, $sub_components[1] ); |
| 1498 | break; |
| 1499 | case 'class': |
| 1500 | $attr['class'] = $sub_components[1]; |
| 1501 | break; |
| 1502 | case 'style': |
| 1503 | $attr['style'] = Sanitizer::checkCSS( $sub_components[1] ); |
| 1504 | break; |
| 1505 | } |
| 1506 | } |
| 1507 | } |
| 1508 | if ( $input_name == 'summary' ) { |
| 1509 | $value = $wgRequest->getVal( 'wpSummary' ); |
| 1510 | $new_text = PFFormUtils::summaryInputHTML( $form_is_disabled, $input_label, $attr, $value ); |
| 1511 | } elseif ( $input_name == 'minor edit' ) { |
| 1512 | $is_checked = $wgRequest->getCheck( 'wpMinoredit' ); |
| 1513 | $new_text = PFFormUtils::minorEditInputHTML( $form_submitted, $form_is_disabled, $is_checked, $input_label, $attr ); |
| 1514 | } elseif ( $input_name == 'watch' ) { |
| 1515 | $is_checked = $wgRequest->getCheck( 'wpWatchthis' ); |
| 1516 | $new_text = PFFormUtils::watchInputHTML( $form_submitted, $form_is_disabled, $is_checked, $input_label, $attr ); |
| 1517 | } elseif ( $input_name == 'save' ) { |
| 1518 | $new_text = PFFormUtils::saveButtonHTML( $form_is_disabled, $input_label, $attr ); |
| 1519 | } elseif ( $input_name == 'save and continue' ) { |
| 1520 | // Remove save and continue button in one-step-process |
| 1521 | if ( $this->mPageTitle == $page_name ) { |
| 1522 | $new_text = PFFormUtils::saveAndContinueButtonHTML( $form_is_disabled, $input_label, $attr ); |
| 1523 | } else { |
| 1524 | $new_text = ''; |
| 1525 | } |
| 1526 | } elseif ( $input_name == 'preview' ) { |
| 1527 | $new_text = PFFormUtils::showPreviewButtonHTML( $form_is_disabled, $input_label, $attr ); |
| 1528 | } elseif ( $input_name == 'changes' ) { |
| 1529 | $new_text = PFFormUtils::showChangesButtonHTML( $form_is_disabled, $input_label, $attr ); |
| 1530 | } elseif ( $input_name == 'cancel' ) { |
| 1531 | $new_text = PFFormUtils::cancelLinkHTML( $form_is_disabled, $input_label, $attr ); |
| 1532 | } elseif ( $input_name == 'run query' ) { |
| 1533 | $new_text = PFFormUtils::runQueryButtonHTML( $form_is_disabled, $input_label, $attr ); |
| 1534 | } |
| 1535 | $section = substr_replace( $section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1536 | // ===================================================== |
| 1537 | // for section processing |
| 1538 | // ===================================================== |
| 1539 | } elseif ( $tag_title == 'section' ) { |
| 1540 | $wgPageFormsFieldNum++; |
| 1541 | $wgPageFormsTabIndex++; |
| 1542 | |
| 1543 | $section_name = trim( $tag_components[1] ); |
| 1544 | $page_section_in_form = PFPageSection::newFromFormTag( $tag_components, $user ); |
| 1545 | $section_text = null; |
| 1546 | |
| 1547 | // Split the existing page contents into the textareas in the form. |
| 1548 | $default_value = ""; |
| 1549 | $section_start_loc = 0; |
| 1550 | if ( $source_is_page && $existing_page_content !== null ) { |
| 1551 | // For the last section of the page, there is no trailing newline in |
| 1552 | // $existing_page_content, but the code below expects it. This code |
| 1553 | // ensures that there is always trailing newline. T72202 |
| 1554 | if ( substr( $existing_page_content, -1 ) !== "\n" ) { |
| 1555 | $existing_page_content .= "\n"; |
| 1556 | } |
| 1557 | |
| 1558 | $equalsSigns = str_repeat( '=', $page_section_in_form->getSectionLevel() ); |
| 1559 | $searchStr = |
| 1560 | '/^' . |
| 1561 | preg_quote( $equalsSigns, '/' ) . |
| 1562 | '[ ]*?' . |
| 1563 | preg_quote( $section_name, '/' ) . |
| 1564 | '[ ]*?' . |
| 1565 | preg_quote( $equalsSigns, '/' ) . |
| 1566 | '$/m'; |
| 1567 | if ( preg_match( $searchStr, $existing_page_content, $matches, PREG_OFFSET_CAPTURE ) ) { |
| 1568 | $section_start_loc = $matches[0][1]; |
| 1569 | $header_text = $matches[0][0]; |
| 1570 | $existing_page_content = str_replace( $header_text, '', $existing_page_content ); |
| 1571 | } else { |
| 1572 | $section_start_loc = 0; |
| 1573 | } |
| 1574 | $section_end_loc = -1; |
| 1575 | |
| 1576 | // get the position of the next template or section defined in the form which is not empty and hidden if empty |
| 1577 | $previous_brackets_end_loc = $brackets_end_loc; |
| 1578 | $next_section_found = false; |
| 1579 | // loop until the next section is found |
| 1580 | while ( !$next_section_found ) { |
| 1581 | $next_bracket_start_loc = strpos( $section, '{{{', $previous_brackets_end_loc ); |
| 1582 | if ( $next_bracket_start_loc == false ) { |
| 1583 | $section_end_loc = strpos( $existing_page_content, '{{', $section_start_loc ); |
| 1584 | $next_section_found = true; |
| 1585 | } else { |
| 1586 | $next_bracket_end_loc = strpos( $section, '}}}', $next_bracket_start_loc ); |
| 1587 | $bracketed_string_next_section = substr( $section, $next_bracket_start_loc + 3, $next_bracket_end_loc - ( $next_bracket_start_loc + 3 ) ); |
| 1588 | $tag_components_next_section = PFUtils::getFormTagComponents( $bracketed_string_next_section ); |
| 1589 | $page_next_section_in_form = PFPageSection::newFromFormTag( $tag_components_next_section, $user ); |
| 1590 | $tag_title_next_section = trim( $tag_components_next_section[0] ); |
| 1591 | if ( $tag_title_next_section == 'section' ) { |
| 1592 | // There is no pattern match for the next section if the section is empty and its hideIfEmpty attribute is set |
| 1593 | if ( preg_match( '/(^={1,6}[ ]*?' . preg_quote( $tag_components_next_section[1], '/' ) . '[ ]*?={1,6}\s*?$)/m', $existing_page_content, $matches, PREG_OFFSET_CAPTURE ) ) { |
| 1594 | $section_end_loc = $matches[0][1]; |
| 1595 | $next_section_found = true; |
| 1596 | // Check for the next section if no pattern match |
| 1597 | } elseif ( $page_next_section_in_form->isHideIfEmpty() ) { |
| 1598 | $previous_brackets_end_loc = $next_bracket_end_loc; |
| 1599 | } else { |
| 1600 | // If none of the above conditions is satisfied, exit the loop. |
| 1601 | break; |
| 1602 | } |
| 1603 | } else { |
| 1604 | $next_section_found = true; |
| 1605 | } |
| 1606 | } |
| 1607 | } |
| 1608 | |
| 1609 | if ( $section_end_loc === -1 || $section_end_loc === null ) { |
| 1610 | $section_text = substr( $existing_page_content, $section_start_loc ); |
| 1611 | $existing_page_content = substr( $existing_page_content, 0, $section_start_loc ); |
| 1612 | } else { |
| 1613 | $section_text = substr( $existing_page_content, $section_start_loc, $section_end_loc - $section_start_loc ); |
| 1614 | $existing_page_content = substr( $existing_page_content, 0, $section_start_loc ) . substr( $existing_page_content, $section_end_loc ); |
| 1615 | } |
| 1616 | } |
| 1617 | |
| 1618 | // If input is from the form. |
| 1619 | if ( ( !$source_is_page ) && $wgRequest ) { |
| 1620 | $text_per_section = $wgRequest->getArray( '_section' ); |
| 1621 | |
| 1622 | if ( is_array( $text_per_section ) && array_key_exists( $section_name, $text_per_section ) ) { |
| 1623 | $section_text = $text_per_section[$section_name]; |
| 1624 | } else { |
| 1625 | $section_text = ''; |
| 1626 | } |
| 1627 | // $section_options will allow to pass additional options in the future without breaking backword compatibility |
| 1628 | $section_options = [ 'hideIfEmpty' => $page_section_in_form->isHideIfEmpty() ]; |
| 1629 | $wiki_page->addSection( $section_name, $page_section_in_form->getSectionLevel(), $section_text, $section_options ); |
| 1630 | } |
| 1631 | |
| 1632 | $section_text = trim( $section_text ); |
| 1633 | |
| 1634 | // Set input name for query string. |
| 1635 | $input_name = '_section' . '[' . $section_name . ']'; |
| 1636 | $other_args = $page_section_in_form->getSectionArgs(); |
| 1637 | $other_args['isSection'] = true; |
| 1638 | if ( $page_section_in_form->isMandatory() ) { |
| 1639 | $other_args['mandatory'] = true; |
| 1640 | } |
| 1641 | |
| 1642 | if ( $page_section_in_form->isHidden() ) { |
| 1643 | $form_section_text = Html::hidden( $input_name, $section_text ); |
| 1644 | } else { |
| 1645 | $sectionInput = new PFTextAreaInput( $wgPageFormsFieldNum, $section_text, $input_name, ( $form_is_disabled || $page_section_in_form->isRestricted() ), $other_args ); |
| 1646 | $sectionInput->addJavaScript(); |
| 1647 | $form_section_text = $sectionInput->getHtmlText(); |
| 1648 | } |
| 1649 | |
| 1650 | $section = substr_replace( $section, $form_section_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1651 | // ===================================================== |
| 1652 | // page info processing |
| 1653 | // ===================================================== |
| 1654 | } elseif ( $tag_title == 'info' ) { |
| 1655 | // TODO: Generate an error message if this is included more than once |
| 1656 | foreach ( array_slice( $tag_components, 1 ) as $component ) { |
| 1657 | $sub_components = array_map( 'trim', explode( '=', $component, 2 ) ); |
| 1658 | // Tag names are case-insensitive |
| 1659 | $tag = strtolower( $sub_components[0] ); |
| 1660 | if ( $tag == 'create title' || $tag == 'add title' ) { |
| 1661 | // Handle this only if |
| 1662 | // we're adding a page. |
| 1663 | if ( !$is_query && !$this->mPageTitle->exists() ) { |
| 1664 | $form_page_title = $sub_components[1]; |
| 1665 | } |
| 1666 | } elseif ( $tag == 'edit title' ) { |
| 1667 | // Handle this only if |
| 1668 | // we're editing a page. |
| 1669 | if ( !$is_query && $this->mPageTitle->exists() ) { |
| 1670 | $form_page_title = $sub_components[1]; |
| 1671 | } |
| 1672 | } elseif ( $tag == 'query title' ) { |
| 1673 | // Handle this only if |
| 1674 | // we're in 'RunQuery'. |
| 1675 | if ( $is_query ) { |
| 1676 | $form_page_title = $sub_components[1]; |
| 1677 | } |
| 1678 | } elseif ( $tag == 'includeonly free text' || $tag == 'onlyinclude free text' ) { |
| 1679 | $wiki_page->makeFreeTextOnlyInclude(); |
| 1680 | } elseif ( $tag == 'query form at top' ) { |
| 1681 | // TODO - this should be made a field of |
| 1682 | // some non-static class that actually |
| 1683 | // prints the form, instead of requiring |
| 1684 | // a global variable. |
| 1685 | global $wgPageFormsRunQueryFormAtTop; |
| 1686 | $wgPageFormsRunQueryFormAtTop = true; |
| 1687 | } |
| 1688 | } |
| 1689 | // Replace the {{{info}}} tag with a hidden span, instead of a blank, to avoid a |
| 1690 | // potential security issue. |
| 1691 | $section = substr_replace( $section, '<span style="visibility: hidden;"></span>', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1692 | // ===================================================== |
| 1693 | // default outer level processing |
| 1694 | // ===================================================== |
| 1695 | } else { |
| 1696 | // Tag is not one of the allowed values - |
| 1697 | // ignore it, other than to HTML-escape it. |
| 1698 | $form_section_text = htmlspecialchars( substr( $section, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ) ); |
| 1699 | $section = substr_replace( $section, $form_section_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc ); |
| 1700 | $start_position = $brackets_end_loc; |
| 1701 | } |
| 1702 | // end if |
| 1703 | } |
| 1704 | // end while |
| 1705 | |
| 1706 | if ( $tif && ( !$tif->allowsMultiple() || $tif->allInstancesPrinted() ) ) { |
| 1707 | $template_text = $wiki_page->createTemplateCallsForTemplateName( $tif->getTemplateName() ); |
| 1708 | // Escape the '$' characters for the preg_replace() call. |
| 1709 | $template_text = str_replace( '$', '\$', $template_text ); |
| 1710 | |
| 1711 | // If there is a placeholder in the text, we |
| 1712 | // know that we are doing a replace. |
| 1713 | if ( $existing_page_content && strpos( $existing_page_content, '{{{insertionpoint}}}', 0 ) !== false ) { |
| 1714 | $existing_page_content = preg_replace( '/\{\{\{insertionpoint\}\}\}(\r?\n?)/', |
| 1715 | preg_replace( '/\}\}/m', '}�', |
| 1716 | preg_replace( '/\{\{/m', '�{', $template_text ) ) . |
| 1717 | "{{{insertionpoint}}}", |
| 1718 | $existing_page_content ); |
| 1719 | } |
| 1720 | } |
| 1721 | |
| 1722 | $multipleTemplateHTML = ''; |
| 1723 | if ( $tif ) { |
| 1724 | if ( $tif->getLabel() != null ) { |
| 1725 | $fieldsetStartHTML = "<fieldset>\n" . Html::element( 'legend', null, $tif->getLabel() ) . "\n"; |
| 1726 | $fieldsetStartHTML .= $tif->getIntro(); |
| 1727 | if ( !$tif->allowsMultiple() ) { |
| 1728 | $form_text .= $fieldsetStartHTML; |
| 1729 | } elseif ( $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) { |
| 1730 | $multipleTemplateHTML .= $fieldsetStartHTML; |
| 1731 | } |
| 1732 | } else { |
| 1733 | if ( !$tif->allowsMultiple() ) { |
| 1734 | $form_text .= $tif->getIntro(); |
| 1735 | } |
| 1736 | if ( $tif->allowsMultiple() && $tif->getInstanceNum() == 0 ) { |
| 1737 | $multipleTemplateHTML .= $tif->getIntro(); |
| 1738 | } |
| 1739 | } |
| 1740 | } |
| 1741 | if ( $tif && $tif->allowsMultiple() ) { |
| 1742 | if ( $tif->getDisplay() == 'spreadsheet' ) { |
| 1743 | if ( $tif->allInstancesPrinted() ) { |
| 1744 | $multipleTemplateHTML .= $this->spreadsheetHTML( $tif ); |
| 1745 | // For spreadsheets, this needs |
| 1746 | // to be specially inserted. |
| 1747 | if ( $tif->getLabel() != null ) { |
| 1748 | $multipleTemplateHTML .= "</fieldset>\n"; |
| 1749 | } |
| 1750 | } |
| 1751 | } elseif ( $tif->getDisplay() == 'calendar' ) { |
| 1752 | if ( $tif->allInstancesPrinted() ) { |
| 1753 | global $wgPageFormsCalendarParams, $wgPageFormsCalendarValues; |
| 1754 | global $wgPageFormsScriptPath; |
| 1755 | $text = ''; |
| 1756 | $params = []; |
| 1757 | foreach ( $tif->getFields() as $formField ) { |
| 1758 | $templateField = $formField->getTemplateField(); |
| 1759 | $inputType = $formField->getInputType(); |
| 1760 | $values = [ 'name' => $templateField->getFieldName() ]; |
| 1761 | if ( $formField->getLabel() !== null ) { |
| 1762 | $values['title'] = $formField->getLabel(); |
| 1763 | } |
| 1764 | $possibleValues = $formField->getPossibleValues(); |
| 1765 | if ( $inputType == 'textarea' ) { |
| 1766 | $values['type'] = 'textarea'; |
| 1767 | } elseif ( $inputType == 'datetime' ) { |
| 1768 | $values['type'] = 'datetime'; |
| 1769 | } elseif ( $inputType == 'checkbox' ) { |
| 1770 | $values['type'] = 'checkbox'; |
| 1771 | } elseif ( $inputType == 'checkboxes' ) { |
| 1772 | $values['type'] = 'checkboxes'; |
| 1773 | } elseif ( $inputType == 'listbox' ) { |
| 1774 | $values['type'] = 'listbox'; |
| 1775 | } elseif ( $inputType == 'date' ) { |
| 1776 | $values['type'] = 'date'; |
| 1777 | } elseif ( $inputType == 'rating' ) { |
| 1778 | $values['type'] = 'rating'; |
| 1779 | } elseif ( $inputType == 'radiobutton' ) { |
| 1780 | $values['type'] = 'radiobutton'; |
| 1781 | } elseif ( $inputType == 'tokens' ) { |
| 1782 | $values['type'] = 'tokens'; |
| 1783 | } elseif ( $possibleValues != null ) { |
| 1784 | array_unshift( $possibleValues, '' ); |
| 1785 | $completePossibleValues = []; |
| 1786 | foreach ( $possibleValues as $value ) { |
| 1787 | $completePossibleValues[] = [ 'Name' => $value, 'Id' => $value ]; |
| 1788 | } |
| 1789 | $values['type'] = 'select'; |
| 1790 | $values['items'] = $completePossibleValues; |
| 1791 | $values['valueField'] = 'Id'; |
| 1792 | $values['textField'] = 'Name'; |
| 1793 | } else { |
| 1794 | $values['type'] = 'text'; |
| 1795 | } |
| 1796 | $params[] = $values; |
| 1797 | } |
| 1798 | $templateName = $tif->getTemplateName(); |
| 1799 | $templateDivID = str_replace( ' ', '_', $templateName ) . "FullCalendar"; |
| 1800 | $templateDivAttrs = [ |
| 1801 | 'class' => 'pfFullCalendarJS', |
| 1802 | 'id' => $templateDivID, |
| 1803 | 'template-name' => $templateName, |
| 1804 | 'title-field' => $tif->getEventTitleField(), |
| 1805 | 'event-date-field' => $tif->getEventDateField(), |
| 1806 | 'event-start-date-field' => $tif->getEventStartDateField(), |
| 1807 | 'event-end-date-field' => $tif->getEventEndDateField() |
| 1808 | ]; |
| 1809 | $loadingImage = Html::element( 'img', [ 'src' => "$wgPageFormsScriptPath/skins/loading.gif" ] ); |
| 1810 | $text = "<div id='fullCalendarLoading1' style='display: none;'>" . $loadingImage . "</div>"; |
| 1811 | $text .= Html::rawElement( 'div', $templateDivAttrs, $text ); |
| 1812 | $wgPageFormsCalendarParams[$templateName] = $params; |
| 1813 | $wgPageFormsCalendarValues[$templateName] = $tif->getGridValues(); |
| 1814 | $fullForm = $this->multipleTemplateInstanceHTML( $tif, $form_is_disabled, $section ); |
| 1815 | $multipleTemplateHTML .= $text; |
| 1816 | $multipleTemplateHTML .= "</fieldset>\n"; |
| 1817 | PFFormUtils::setGlobalVarsForSpreadsheet(); |
| 1818 | } |
| 1819 | } else { |
| 1820 | if ( $tif->getDisplay() == 'table' ) { |
| 1821 | $section = $this->tableHTML( $tif, $tif->getInstanceNum() ); |
| 1822 | } |
| 1823 | if ( $tif->getInstanceNum() == 0 ) { |
| 1824 | $multipleTemplateHTML .= $this->multipleTemplateStartHTML( $tif ); |
| 1825 | } |
| 1826 | if ( !$tif->allInstancesPrinted() ) { |
| 1827 | $multipleTemplateHTML .= $this->multipleTemplateInstanceHTML( $tif, $form_is_disabled, $section ); |
| 1828 | } else { |
| 1829 | $multipleTemplateHTML .= $this->multipleTemplateEndHTML( $tif, $form_is_disabled, $section ); |
| 1830 | } |
| 1831 | } |
| 1832 | $placeholder = $tif->getPlaceholder(); |
| 1833 | if ( $placeholder == null ) { |
| 1834 | // The normal process. |
| 1835 | $form_text .= $multipleTemplateHTML; |
| 1836 | } else { |
| 1837 | // The template text won't be appended |
| 1838 | // at the end of the template like for |
| 1839 | // usual multiple template forms. |
| 1840 | // The HTML text will instead be stored in |
| 1841 | // the $multipleTemplateHTML variable, |
| 1842 | // and then added in the right |
| 1843 | // @insertHTML_".$placeHolderField."@"; position |
| 1844 | // Optimization: actually, instead of |
| 1845 | // separating the processes, the usual |
| 1846 | // multiple template forms could also be |
| 1847 | // handled this way if a fitting |
| 1848 | // placeholder tag was added. |
| 1849 | // We replace the HTML into the current |
| 1850 | // placeholder tag, but also add another |
| 1851 | // placeholder tag, to keep track of it. |
| 1852 | $multipleTemplateHTML .= self::makePlaceholderInFormHTML( $placeholder ); |
| 1853 | $form_text = str_replace( self::makePlaceholderInFormHTML( $placeholder ), $multipleTemplateHTML, $form_text ); |
| 1854 | } |
| 1855 | if ( !$tif->allInstancesPrinted() ) { |
| 1856 | // This will cause the section to be |
| 1857 | // re-parsed on the next go. |
| 1858 | $section_num--; |
| 1859 | $tif->incrementInstanceNum(); |
| 1860 | } |
| 1861 | } elseif ( $tif && $tif->getDisplay() == 'table' ) { |
| 1862 | $form_text .= $this->tableHTML( $tif, 0 ); |
| 1863 | } elseif ( $tif && !$tif->allowsMultiple() && $tif->getLabel() != null ) { |
| 1864 | $form_text .= $section . "\n</fieldset>"; |
| 1865 | } else { |
| 1866 | $form_text .= $section; |
| 1867 | } |
| 1868 | } |
| 1869 | // end for |
| 1870 | |
| 1871 | // Cleanup - everything has been browsed. |
| 1872 | // Remove all the remaining placeholder |
| 1873 | // tags in the HTML and wiki-text. |
| 1874 | foreach ( $placeholderFields as $stringToReplace ) { |
| 1875 | // Remove the @<insertHTML>@ tags from the generated |
| 1876 | // HTML form. |
| 1877 | $form_text = str_replace( self::makePlaceholderInFormHTML( $stringToReplace ), '', $form_text ); |
| 1878 | } |
| 1879 | |
| 1880 | // If it wasn't included in the form definition, add the |
| 1881 | // 'free text' input as a hidden field at the bottom. |
| 1882 | if ( !$free_text_was_included ) { |
| 1883 | $form_text .= Html::hidden( 'pf_free_text', '!free_text!' ); |
| 1884 | } |
| 1885 | // Get free text, and add to page data, as well as retroactively |
| 1886 | // inserting it into the form. |
| 1887 | |
| 1888 | if ( $source_is_page ) { |
| 1889 | // If the page is the source, free_text will just be |
| 1890 | // whatever in the page hasn't already been inserted |
| 1891 | // into the form. |
| 1892 | $free_text = trim( $existing_page_content ); |
| 1893 | // ...or get it from the form submission, if it's not called from #formredlink |
| 1894 | } elseif ( !$is_autocreate && $wgRequest->getCheck( 'pf_free_text' ) ) { |
| 1895 | $free_text = $wgRequest->getVal( 'pf_free_text' ); |
| 1896 | if ( !$free_text_was_included ) { |
| 1897 | $wiki_page->addFreeTextSection(); |
| 1898 | } |
| 1899 | } elseif ( $preloaded_free_text != null ) { |
| 1900 | $free_text = $preloaded_free_text; |
| 1901 | } else { |
| 1902 | $free_text = ''; |
| 1903 | } |
| 1904 | |
| 1905 | if ( $wiki_page->freeTextOnlyInclude() ) { |
| 1906 | $free_text = str_replace( "<onlyinclude>", '', $free_text ); |
| 1907 | $free_text = str_replace( "</onlyinclude>", '', $free_text ); |
| 1908 | $free_text = trim( $free_text ); |
| 1909 | } |
| 1910 | |
| 1911 | $page_text = ''; |
| 1912 | |
| 1913 | $hookContainer->run( 'PageForms::BeforeFreeTextSubst', |
| 1914 | [ &$free_text, $existing_page_content, &$page_text ] ); |
| 1915 | |
| 1916 | // Now that we have the free text, we can create the full page |
| 1917 | // text. |
| 1918 | // The page text needs to be created whether or not the form |
| 1919 | // was submitted, in case this is called from #formredlink. |
| 1920 | $wiki_page->setFreeText( $free_text ); |
| 1921 | $page_text = $wiki_page->createPageText(); |
| 1922 | |
| 1923 | // Also substitute the free text into the form. |
| 1924 | $escaped_free_text = Sanitizer::safeEncodeAttribute( $free_text ); |
| 1925 | $form_text = str_replace( '!free_text!', $escaped_free_text, $form_text ); |
| 1926 | |
| 1927 | // Add a warning in, if we're editing an existing page and that |
| 1928 | // page appears to not have been created with this form. |
| 1929 | if ( !$is_query && $page_name_formula === null && |
| 1930 | $this->mPageTitle->exists() && $existing_page_content !== '' |
| 1931 | && !$source_page_matches_this_form ) { |
| 1932 | // Prepend with a colon in case it's a file or category page. |
| 1933 | $wrongFormText = wfMessage( 'pf_formedit_formwarning', ':' . $page_name )->parse(); |
| 1934 | $form_text = Html::warningBox( $wrongFormText ) . |
| 1935 | "\n<br clear=\"both\" />\n" . $form_text; |
| 1936 | } |
| 1937 | |
| 1938 | // Add form bottom, if no custom "standard inputs" have been defined. |
| 1939 | if ( !$this->standardInputsIncluded ) { |
| 1940 | if ( $is_query ) { |
| 1941 | $form_text .= PFFormUtils::queryFormBottom(); |
| 1942 | } else { |
| 1943 | $form_text .= PFFormUtils::formBottom( $form_submitted, $form_is_disabled ); |
| 1944 | } |
| 1945 | } |
| 1946 | |
| 1947 | if ( !$is_query ) { |
| 1948 | $form_text .= Html::hidden( 'wpStarttime', wfTimestampNow() ); |
| 1949 | // This variable is called $mwWikiPage and not |
| 1950 | // something simpler, to avoid confusion with the |
| 1951 | // variable $wiki_page, which is of type PFWikiPage. |
| 1952 | $mwWikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $this->mPageTitle ); |
| 1953 | $form_text .= Html::hidden( 'wpEdittime', $mwWikiPage->getTimestamp() ); |
| 1954 | $form_text .= Html::hidden( 'editRevId', 0 ); |
| 1955 | $form_text .= Html::hidden( 'wpEditToken', $user->getEditToken() ); |
| 1956 | $form_text .= Html::hidden( 'wpUnicodeCheck', EditPage::UNICODE_CHECK ); |
| 1957 | $form_text .= Html::hidden( 'wpUltimateParam', true ); |
| 1958 | } |
| 1959 | |
| 1960 | $form_text .= "\t</form>\n"; |
| 1961 | $hookContainer->run( 'PageForms::RenderingEnd', [ &$form_text ] ); |
| 1962 | |
| 1963 | // Send the autocomplete values to the browser, along with the |
| 1964 | // mappings of which values should apply to which fields. |
| 1965 | // If doing a replace, the page text is actually the modified |
| 1966 | // original page. |
| 1967 | if ( !$is_embedded ) { |
| 1968 | $form_page_title = self::getParsedValue( $parser, str_replace( "{{!}}", "|", $form_page_title ) ); |
| 1969 | } else { |
| 1970 | $form_page_title = null; |
| 1971 | } |
| 1972 | |
| 1973 | return [ $form_text, $page_text, $form_page_title, $generated_page_name ]; |
| 1974 | } |
| 1975 | |
| 1976 | /** |
| 1977 | * Cargo based mapping compatible with autocompletion |
| 1978 | * @param string $currentValue |
| 1979 | * @param string $mappingCargoTable |
| 1980 | * @param string $mappingCargoField |
| 1981 | * @param string $mappingCargoValueField |
| 1982 | * @param PFFormField $form_field |
| 1983 | * @return string|null $currentValue |
| 1984 | */ |
| 1985 | private function getCargoBasedMapping( $currentValue, $mappingCargoTable, $mappingCargoField, $mappingCargoValueField, $form_field ) { |
| 1986 | $cargoValues = []; |
| 1987 | $delimiter = $form_field->getFieldArg( 'delimiter' ); |
| 1988 | $cur_value = str_replace( '"', '\"', $currentValue ); |
| 1989 | if ( $form_field->isList() ) { |
| 1990 | $cargoValues = explode( $delimiter, $currentValue ); |
| 1991 | } else { |
| 1992 | $cargoValues = [ $currentValue ]; |
| 1993 | } |
| 1994 | foreach ( $cargoValues as $key => $value ) { |
| 1995 | $cargoValue = PFValuesUtils::getValuesForCargoField( $mappingCargoTable, $mappingCargoField, $mappingCargoValueField . '="' . trim( $value ) . '"' ); |
| 1996 | if ( !empty( $cargoValue ) ) { |
| 1997 | $cargoValues[ $key ] = current( $cargoValue ); |
| 1998 | } |
| 1999 | } |
| 2000 | $currentValue = implode( $delimiter, $cargoValues ); |
| 2001 | return $currentValue; |
| 2002 | } |
| 2003 | |
| 2004 | /** |
| 2005 | * Create the HTML to display this field within a form. |
| 2006 | * @param PFFormField $form_field |
| 2007 | * @param string $cur_value |
| 2008 | * @return string |
| 2009 | */ |
| 2010 | function formFieldHTML( $form_field, $cur_value ) { |
| 2011 | global $wgPageFormsFieldNum; |
| 2012 | |
| 2013 | // Also get the actual field, with all the semantic information |
| 2014 | // (type is PFTemplateField, instead of PFFormField) |
| 2015 | $template_field = $form_field->getTemplateField(); |
| 2016 | $class_name = null; |
| 2017 | |
| 2018 | if ( $form_field->isHidden() ) { |
| 2019 | $attribs = []; |
| 2020 | if ( $form_field->hasFieldArg( 'class' ) ) { |
| 2021 | $attribs['class'] = $form_field->getFieldArg( 'class' ); |
| 2022 | } |
| 2023 | $text = Html::hidden( $form_field->getInputName(), $cur_value, $attribs ); |
| 2024 | } elseif ( $form_field->getInputType() !== '' && |
| 2025 | array_key_exists( $form_field->getInputType(), $this->mInputTypeHooks ) && |
| 2026 | $this->mInputTypeHooks[$form_field->getInputType()] != null ) { |
| 2027 | // Last argument to constructor should be a hash, |
| 2028 | // merging the default values for this input type with |
| 2029 | // all other properties set in the form definition, plus |
| 2030 | // some semantic-related arguments. |
| 2031 | $hook_values = $this->mInputTypeHooks[$form_field->getInputType()]; |
| 2032 | $class_name = $hook_values[0]; |
| 2033 | $other_args = $form_field->getArgumentsForInputCall( $hook_values[1] ); |
| 2034 | } else { |
| 2035 | // The input type is not defined in the form. |
| 2036 | $cargo_field_type = $template_field->getFieldType(); |
| 2037 | $property_type = $template_field->getPropertyType(); |
| 2038 | $is_list = ( $form_field->isList() || $template_field->isList() ); |
| 2039 | if ( $cargo_field_type !== '' && |
| 2040 | array_key_exists( $cargo_field_type, $this->mCargoTypeHooks ) && |
| 2041 | isset( $this->mCargoTypeHooks[$cargo_field_type][$is_list] ) ) { |
| 2042 | $hook_values = $this->mCargoTypeHooks[$cargo_field_type][$is_list]; |
| 2043 | $class_name = $hook_values[0]; |
| 2044 | $other_args = $form_field->getArgumentsForInputCall( $hook_values[1] ); |
| 2045 | } elseif ( $property_type !== '' && |
| 2046 | array_key_exists( $property_type, $this->mSemanticTypeHooks ) && |
| 2047 | isset( $this->mSemanticTypeHooks[$property_type][$is_list] ) ) { |
| 2048 | $hook_values = $this->mSemanticTypeHooks[$property_type][$is_list]; |
| 2049 | $class_name = $hook_values[0]; |
| 2050 | $other_args = $form_field->getArgumentsForInputCall( $hook_values[1] ); |
| 2051 | } else { |
| 2052 | // Anything else. |
| 2053 | $class_name = 'PFTextInput'; |
| 2054 | $other_args = $form_field->getArgumentsForInputCall(); |
| 2055 | // Set default size for list inputs. |
| 2056 | if ( $form_field->isList() ) { |
| 2057 | if ( !array_key_exists( 'size', $other_args ) ) { |
| 2058 | $other_args['size'] = 100; |
| 2059 | } |
| 2060 | } |
| 2061 | } |
| 2062 | } |
| 2063 | |
| 2064 | if ( $class_name !== null ) { |
| 2065 | $form_input = new $class_name( $wgPageFormsFieldNum, $cur_value, $form_field->getInputName(), $form_field->isDisabled(), $other_args ); |
| 2066 | |
| 2067 | // If a regex was defined, make this a "regexp" input that wraps |
| 2068 | // around the real one. |
| 2069 | if ( $template_field->getRegex() !== null ) { |
| 2070 | $other_args['regexp'] = $template_field->getRegex(); |
| 2071 | $form_input = PFRegExpInput::newFromInput( $form_input ); |
| 2072 | } |
| 2073 | $form_input->addJavaScript(); |
| 2074 | $text = $form_input->getHtmlText(); |
| 2075 | } |
| 2076 | |
| 2077 | $this->addTranslatableInput( $form_field, $text ); |
| 2078 | return $text; |
| 2079 | } |
| 2080 | |
| 2081 | /** |
| 2082 | * If a field is "translatable", add a hidden input containing the "<!--T:X-->" |
| 2083 | * translate tag. |
| 2084 | * |
| 2085 | * @param PFFormField $form_field |
| 2086 | * @param string &$text |
| 2087 | */ |
| 2088 | private function addTranslatableInput( $form_field, &$text ) { |
| 2089 | if ( !PFUtils::isTranslateEnabled() || !$form_field->hasFieldArg( 'translatable' ) || !$form_field->getFieldArg( 'translatable' ) ) { |
| 2090 | return; |
| 2091 | } |
| 2092 | |
| 2093 | if ( $form_field->hasFieldArg( 'translate_number_tag' ) ) { |
| 2094 | $inputName = $form_field->getInputName(); |
| 2095 | $pattern = '/\[([^\\]\\]]+)\]$/'; |
| 2096 | if ( preg_match( $pattern, $inputName, $matches ) ) { |
| 2097 | $inputName = preg_replace( $pattern, '[${1}_translate_number_tag]', $inputName ); |
| 2098 | } else { |
| 2099 | $inputName .= '_translate_number_tag'; |
| 2100 | } |
| 2101 | $translateTag = $form_field->getFieldArg( 'translate_number_tag' ); |
| 2102 | $text .= "<input type='hidden' name='$inputName' value='$translateTag'/>"; |
| 2103 | } |
| 2104 | } |
| 2105 | |
| 2106 | private function createFormFieldTranslateTag( &$template, &$tif, &$form_field, &$cur_value ) { |
| 2107 | if ( !PFUtils::isTranslateEnabled() || !$form_field->hasFieldArg( 'translatable' ) || !$form_field->getFieldArg( 'translatable' ) ) { |
| 2108 | return; |
| 2109 | } |
| 2110 | |
| 2111 | if ( $cur_value == null ) { |
| 2112 | return; |
| 2113 | } |
| 2114 | |
| 2115 | // If translatable, add translatable tags when saving, or remove them for displaying form. |
| 2116 | if ( preg_match( '#^<translate>(.*)</translate>$#', $cur_value, $matches ) ) { |
| 2117 | $cur_value = $matches[1]; |
| 2118 | } elseif ( substr( $cur_value, 0, strlen( '<translate>' ) ) == '<translate>' |
| 2119 | && substr( $cur_value, -1 * strlen( '</translate>' ) ) == '</translate>' ) { |
| 2120 | // For unknown reasons, the pregmatch regex does not work every time !! :( |
| 2121 | $cur_value = substr( $cur_value, strlen( '<translate>' ), -1 * strlen( '</translate>' ) ); |
| 2122 | } |
| 2123 | |
| 2124 | if ( substr( $cur_value, 0, 6 ) == '<!--T:' ) { |
| 2125 | // hide the tag <!-- T:X --> in another input |
| 2126 | // if field does not use VisualEditor? |
| 2127 | |
| 2128 | if ( preg_match( "/<!-- *T:([a-zA-Z0-9]+) *-->( |\n)/", $cur_value, $matches ) ) { |
| 2129 | // Remove the tag from this input. |
| 2130 | $cur_value = str_replace( $matches[0], '', $cur_value ); |
| 2131 | // Add a field arg, to add a hidden input in form with the tag. |
| 2132 | $form_field->setFieldArg( 'translate_number_tag', $matches[0] ); |
| 2133 | } |
| 2134 | } |
| 2135 | } |
| 2136 | |
| 2137 | private static function generateUUID() { |
| 2138 | // Copied from https://www.php.net/manual/en/function.uniqid.php#94959 |
| 2139 | return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
| 2140 | // 32 bits for "time_low" |
| 2141 | mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), |
| 2142 | // 16 bits for "time_mid" |
| 2143 | mt_rand( 0, 0xffff ), |
| 2144 | // 16 bits for "time_hi_and_version", |
| 2145 | // four most significant bits holds version number 4 |
| 2146 | mt_rand( 0, 0x0fff ) | 0x4000, |
| 2147 | // 16 bits, 8 bits for "clk_seq_hi_res", |
| 2148 | // 8 bits for "clk_seq_low", |
| 2149 | // two most significant bits holds zero and one for variant DCE1.1 |
| 2150 | mt_rand( 0, 0x3fff ) | 0x8000, |
| 2151 | // 48 bits for "node" |
| 2152 | mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) |
| 2153 | ); |
| 2154 | } |
| 2155 | |
| 2156 | /** |
| 2157 | * Cache parsed values as much as possible, to avoid computing- |
| 2158 | * intensive parsing. |
| 2159 | * |
| 2160 | * @param Parser $parser |
| 2161 | * @param string $value |
| 2162 | * @return string |
| 2163 | */ |
| 2164 | public static function getParsedValue( $parser, $value ) { |
| 2165 | if ( !array_key_exists( $value, self::$mParsedValues ) ) { |
| 2166 | self::$mParsedValues[$value] = trim( $parser->recursiveTagParse( $value ) ); |
| 2167 | } |
| 2168 | |
| 2169 | return self::$mParsedValues[$value]; |
| 2170 | } |
| 2171 | |
| 2172 | } |