Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
15.79% |
42 / 266 |
|
0.00% |
0 / 22 |
CRAP | |
0.00% |
0 / 1 |
MessageWebImporter | |
15.79% |
42 / 266 |
|
0.00% |
0 / 22 |
2829.32 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroup | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setGroup | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getCode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setCode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAction | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doHeader | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
doFooter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
allowProcess | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getActions | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
execute | |
0.00% |
0 / 124 |
|
0.00% |
0 / 1 |
650 | |||
doAction | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
110 | |||
checkProcessTime | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doImport | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
doFuzzy | |
87.50% |
42 / 48 |
|
0.00% |
0 / 1 |
8.12 | |||
makeTranslationTitle | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
makeSectionElement | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
escapeNameForPHP | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\Synchronization; |
5 | |
6 | use ContentHandler; |
7 | use DifferenceEngine; |
8 | use InvalidArgumentException; |
9 | use Language; |
10 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
11 | use MediaWiki\Extension\Translate\MessageLoading\MessageHandle; |
12 | use MediaWiki\Extension\Translate\SystemUsers\FuzzyBot; |
13 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
14 | use MediaWiki\Html\Html; |
15 | use MediaWiki\MediaWikiServices; |
16 | use MediaWiki\Revision\SlotRecord; |
17 | use MediaWiki\Title\Title; |
18 | use MessageGroup; |
19 | use RequestContext; |
20 | use RuntimeException; |
21 | use Sanitizer; |
22 | use User; |
23 | use Xml; |
24 | |
25 | /** |
26 | * Class which encapsulates message importing. It scans for changes (new, changed, deleted), |
27 | * displays them in pretty way with diffs and finally executes the actions the user choices. |
28 | * |
29 | * @author Niklas Laxström |
30 | * @author Siebrand Mazeland |
31 | * @copyright Copyright © 2009-2013, Niklas Laxström, Siebrand Mazeland |
32 | * @license GPL-2.0-or-later |
33 | */ |
34 | class MessageWebImporter { |
35 | private Title $title; |
36 | private User $user; |
37 | private MessageGroup $group; |
38 | private string $code; |
39 | private $time; |
40 | /** Maximum processing time in seconds. */ |
41 | private const MAX_PROCESSING_TIME = 43; |
42 | |
43 | /** |
44 | * @param Title $title |
45 | * @param User $user |
46 | * @param MessageGroup|string|null $group |
47 | * @param string $code |
48 | */ |
49 | public function __construct( Title $title, User $user, $group = null, string $code = 'en' ) { |
50 | $this->setTitle( $title ); |
51 | $this->setUser( $user ); |
52 | $this->setGroup( $group ); |
53 | $this->setCode( $code ); |
54 | } |
55 | |
56 | /** Wrapper for consistency with SpecialPage */ |
57 | public function getTitle(): Title { |
58 | return $this->title; |
59 | } |
60 | |
61 | public function setTitle( Title $title ): void { |
62 | $this->title = $title; |
63 | } |
64 | |
65 | public function getUser(): User { |
66 | return $this->user; |
67 | } |
68 | |
69 | public function setUser( User $user ): void { |
70 | $this->user = $user; |
71 | } |
72 | |
73 | public function getGroup(): MessageGroup { |
74 | return $this->group; |
75 | } |
76 | |
77 | /** @param MessageGroup|string $group MessageGroup object or group ID */ |
78 | public function setGroup( $group ): void { |
79 | if ( $group instanceof MessageGroup ) { |
80 | $this->group = $group; |
81 | } else { |
82 | $this->group = MessageGroups::getGroup( $group ); |
83 | } |
84 | } |
85 | |
86 | public function getCode(): string { |
87 | return $this->code; |
88 | } |
89 | |
90 | public function setCode( string $code = 'en' ): void { |
91 | $this->code = $code; |
92 | } |
93 | |
94 | protected function getAction(): string { |
95 | return $this->getTitle()->getLocalURL(); |
96 | } |
97 | |
98 | protected function doHeader(): string { |
99 | $formParams = [ |
100 | 'method' => 'post', |
101 | 'action' => $this->getAction(), |
102 | 'class' => 'mw-translate-manage' |
103 | ]; |
104 | |
105 | $csrfTokenSet = RequestContext::getMain()->getCsrfTokenSet(); |
106 | return Xml::openElement( 'form', $formParams ) . |
107 | Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . |
108 | Html::hidden( 'token', $csrfTokenSet->getToken() ) . |
109 | Html::hidden( 'process', 1 ); |
110 | } |
111 | |
112 | protected function doFooter(): string { |
113 | return '</form>'; |
114 | } |
115 | |
116 | protected function allowProcess(): bool { |
117 | $context = RequestContext::getMain(); |
118 | $request = $context->getRequest(); |
119 | $csrfTokenSet = $context->getCsrfTokenSet(); |
120 | |
121 | return $request->wasPosted() |
122 | && $request->getBool( 'process' ) |
123 | && $csrfTokenSet->matchTokenField( 'token' ); |
124 | } |
125 | |
126 | protected function getActions(): array { |
127 | return [ |
128 | 'import', |
129 | $this->code === 'en' ? 'fuzzy' : 'conflict', |
130 | 'ignore', |
131 | ]; |
132 | } |
133 | |
134 | public function execute( array $messages ): bool { |
135 | $context = RequestContext::getMain(); |
136 | $output = $context->getOutput(); |
137 | |
138 | // Set up diff engine |
139 | $diff = new DifferenceEngine(); |
140 | $diff->showDiffStyle(); |
141 | $diff->setReducedLineNumbers(); |
142 | |
143 | // Check whether we do processing |
144 | $process = $this->allowProcess(); |
145 | |
146 | // Initialise collection |
147 | $group = $this->getGroup(); |
148 | $code = $this->getCode(); |
149 | $collection = $group->initCollection( $code ); |
150 | $collection->loadTranslations(); |
151 | |
152 | $output->addHTML( $this->doHeader() ); |
153 | |
154 | // Initialise variable to keep track whether all changes were imported |
155 | // or not. If we're allowed to process, initially assume they were. |
156 | $allDone = $process; |
157 | |
158 | // Determine changes for each message. |
159 | $changed = []; |
160 | |
161 | foreach ( $messages as $key => $value ) { |
162 | $old = null; |
163 | $isExistingMessageFuzzy = false; |
164 | |
165 | if ( isset( $collection[$key] ) ) { |
166 | // This returns null if no existing translation is found |
167 | $old = $collection[$key]->translation(); |
168 | $isExistingMessageFuzzy = $collection[$key]->hasTag( 'fuzzy' ); |
169 | } |
170 | |
171 | if ( $old === null ) { |
172 | // We found a new translation for this message of the |
173 | // current group: import it. |
174 | if ( $process ) { |
175 | $action = 'import'; |
176 | $this->doAction( |
177 | $action, |
178 | $group, |
179 | $key, |
180 | $value |
181 | ); |
182 | } |
183 | // Show the user that we imported the new translation |
184 | $para = '<code class="mw-tmi-new">' . htmlspecialchars( $key ) . '</code>'; |
185 | $name = $context->msg( 'translate-manage-import-new' )->rawParams( $para ) |
186 | ->escaped(); |
187 | $text = Utilities::convertWhiteSpaceToHTML( $value ); |
188 | $changed[] = self::makeSectionElement( $name, 'new', $text ); |
189 | } else { |
190 | // No changes at all, ignore |
191 | if ( $old === (string)$value ) { |
192 | continue; |
193 | } |
194 | |
195 | // Check if the message is already fuzzy in the system, and then determine if there are changes |
196 | $oldTextForDiff = $old; |
197 | if ( $isExistingMessageFuzzy ) { |
198 | if ( MessageHandle::makeFuzzyString( $old ) === (string)$value ) { |
199 | continue; |
200 | } |
201 | |
202 | // Normalize the display of FUZZY message diffs so that if an old message has |
203 | // a fuzzy tag, then that is added to the text used in the diff. |
204 | $oldTextForDiff = MessageHandle::makeFuzzyString( $old ); |
205 | } |
206 | |
207 | $oldContent = ContentHandler::makeContent( $oldTextForDiff, $diff->getTitle() ); |
208 | $newContent = ContentHandler::makeContent( $value, $diff->getTitle() ); |
209 | $diff->setContent( $oldContent, $newContent ); |
210 | $text = $diff->getDiff( '', '' ); |
211 | |
212 | // This is a changed translation. Note it for the next steps. |
213 | $type = 'changed'; |
214 | |
215 | // Get the user instructions for the current message, |
216 | // submitted together with the form |
217 | $action = $context->getRequest() |
218 | ->getVal( self::escapeNameForPHP( "action-$type-$key" ) ); |
219 | |
220 | if ( $process ) { |
221 | if ( $changed === [] ) { |
222 | // Initialise the HTML list showing the changes performed |
223 | $changed[] = '<ul>'; |
224 | } |
225 | |
226 | if ( $action === null ) { |
227 | // We have been told to process the messages, but not |
228 | // what to do with this one. Tell the user. |
229 | $message = $context->msg( |
230 | 'translate-manage-inconsistent', |
231 | wfEscapeWikiText( "action-$type-$key" ) |
232 | )->parse(); |
233 | $changed[] = "<li>$message</li></ul>"; |
234 | |
235 | // Also stop any further processing for the other messages. |
236 | $process = false; |
237 | } else { |
238 | // Check processing time |
239 | if ( !isset( $this->time ) ) { |
240 | $this->time = (int)wfTimestamp(); |
241 | } |
242 | |
243 | // We have all the necessary information on this changed |
244 | // translation: actually process the message |
245 | $messageKeyAndParams = $this->doAction( |
246 | $action, |
247 | $group, |
248 | $key, |
249 | $value |
250 | ); |
251 | |
252 | // Show what we just did, adding to the list of changes |
253 | $msgKey = array_shift( $messageKeyAndParams ); |
254 | $params = $messageKeyAndParams; |
255 | $message = $context->msg( $msgKey, $params )->parse(); |
256 | $changed[] = "<li>$message</li>"; |
257 | |
258 | // Stop processing further messages if too much time |
259 | // has been spent. |
260 | if ( $this->checkProcessTime() ) { |
261 | $process = false; |
262 | $message = $context->msg( 'translate-manage-toolong' ) |
263 | ->numParams( self::MAX_PROCESSING_TIME )->parse(); |
264 | $changed[] = "<li>$message</li></ul>"; |
265 | } |
266 | |
267 | continue; |
268 | } |
269 | } |
270 | |
271 | // We are not processing messages, or no longer, or this was an |
272 | // un-actionable translation. We will eventually return false |
273 | $allDone = false; |
274 | |
275 | // Prepare to ask the user what to do with this message |
276 | $actions = $this->getActions(); |
277 | $defaultAction = $action ?: 'import'; |
278 | |
279 | $act = []; |
280 | |
281 | // Give grep a chance to find the usages: |
282 | // translate-manage-action-import, translate-manage-action-conflict, |
283 | // translate-manage-action-ignore, translate-manage-action-fuzzy |
284 | foreach ( $actions as $action ) { |
285 | $label = $context->msg( "translate-manage-action-$action" )->text(); |
286 | $name = self::escapeNameForPHP( "action-$type-$key" ); |
287 | $id = Sanitizer::escapeIdForAttribute( "action-$key-$action" ); |
288 | $act[] = Xml::radioLabel( $label, $name, $action, $id, $action === $defaultAction ); |
289 | } |
290 | |
291 | $param = '<code class="mw-tmi-diff">' . htmlspecialchars( $key ) . '</code>'; |
292 | $name = $context->msg( 'translate-manage-import-diff' ) |
293 | ->rawParams( $param, implode( ' ', $act ) ) |
294 | ->escaped(); |
295 | |
296 | $changed[] = self::makeSectionElement( $name, $type, $text ); |
297 | } |
298 | } |
299 | |
300 | if ( !$process ) { |
301 | $collection->filter( 'hastranslation', false ); |
302 | $keys = $collection->getMessageKeys(); |
303 | |
304 | $diff = array_diff( $keys, array_keys( $messages ) ); |
305 | |
306 | foreach ( $diff as $s ) { |
307 | $para = '<code class="mw-tmi-deleted">' . htmlspecialchars( $s ) . '</code>'; |
308 | $name = $context->msg( 'translate-manage-import-deleted' )->rawParams( $para )->escaped(); |
309 | $text = Utilities::convertWhiteSpaceToHTML( $collection[$s]->translation() ); |
310 | $changed[] = self::makeSectionElement( $name, 'deleted', $text ); |
311 | } |
312 | } |
313 | |
314 | if ( $process || ( $changed === [] && $code !== 'en' ) ) { |
315 | if ( $changed === [] ) { |
316 | $output->addWikiMsg( 'translate-manage-nochanges-other' ); |
317 | } |
318 | |
319 | if ( $changed === [] || !str_starts_with( end( $changed ), '<li>' ) ) { |
320 | $changed[] = '<ul>'; |
321 | } |
322 | |
323 | $changed[] = '</ul>'; |
324 | |
325 | $languageName = Utilities::getLanguageName( $code, $context->getLanguage()->getCode() ); |
326 | $message = $context |
327 | ->msg( 'translate-manage-import-done', $group->getId(), $group->getLabel(), $languageName ) |
328 | ->parse(); |
329 | $changed[] = Html::successBox( $message ); |
330 | $output->addHTML( implode( "\n", $changed ) ); |
331 | } else { |
332 | // END |
333 | if ( $changed !== [] ) { |
334 | if ( $code === 'en' ) { |
335 | $output->addWikiMsg( 'translate-manage-intro-en' ); |
336 | } else { |
337 | $lang = Utilities::getLanguageName( |
338 | $code, |
339 | $context->getLanguage()->getCode() |
340 | ); |
341 | $output->addWikiMsg( 'translate-manage-intro-other', $lang ); |
342 | } |
343 | $output->addHTML( Html::hidden( 'language', $code ) ); |
344 | $output->addHTML( implode( "\n", $changed ) ); |
345 | $output->addHTML( Xml::submitButton( $context->msg( 'translate-manage-submit' )->text() ) ); |
346 | } else { |
347 | $output->addWikiMsg( 'translate-manage-nochanges' ); |
348 | } |
349 | } |
350 | |
351 | $output->addHTML( $this->doFooter() ); |
352 | |
353 | return $allDone; |
354 | } |
355 | |
356 | /** |
357 | * Perform an action on a given group/key/code |
358 | * |
359 | * @param string $action Options: 'import', 'conflict' or 'ignore' |
360 | * @param MessageGroup $group |
361 | * @param string $key Message key |
362 | * @param string $message Contents for the $key/code combination |
363 | * @return array Action result |
364 | */ |
365 | private function doAction( |
366 | string $action, |
367 | MessageGroup $group, |
368 | string $key, |
369 | string $message |
370 | ): array { |
371 | global $wgTranslateDocumentationLanguageCode; |
372 | |
373 | $comment = ''; |
374 | $code = $this->getCode(); |
375 | $title = $this->makeTranslationTitle( $group, $key, $code ); |
376 | |
377 | if ( $action === 'import' || $action === 'conflict' ) { |
378 | if ( $action === 'import' ) { |
379 | $comment = wfMessage( 'translate-manage-import-summary' )->inContentLanguage()->plain(); |
380 | } else { |
381 | $comment = wfMessage( 'translate-manage-conflict-summary' )->inContentLanguage()->plain(); |
382 | $message = MessageHandle::makeFuzzyString( $message ); |
383 | } |
384 | |
385 | return self::doImport( $title, $message, $comment, $this->getUser() ); |
386 | } elseif ( $action === 'ignore' ) { |
387 | return [ 'translate-manage-import-ignore', $key ]; |
388 | } elseif ( $action === 'fuzzy' && $code !== 'en' && |
389 | $code !== $wgTranslateDocumentationLanguageCode |
390 | ) { |
391 | $message = MessageHandle::makeFuzzyString( $message ); |
392 | |
393 | return self::doImport( $title, $message, $comment, $this->getUser() ); |
394 | } elseif ( $action === 'fuzzy' && $code === 'en' ) { |
395 | return self::doFuzzy( $title, $message, $comment, $this->getUser() ); |
396 | } else { |
397 | throw new InvalidArgumentException( "Unhandled action $action" ); |
398 | } |
399 | } |
400 | |
401 | protected function checkProcessTime() { |
402 | return (int)wfTimestamp() - $this->time >= self::MAX_PROCESSING_TIME; |
403 | } |
404 | |
405 | /** @return string[] */ |
406 | private static function doImport( |
407 | Title $title, |
408 | string $message, |
409 | string $summary, |
410 | User $user |
411 | ): array { |
412 | $wikiPage = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title ); |
413 | $content = ContentHandler::makeContent( $message, $title ); |
414 | $status = $wikiPage->doUserEditContent( |
415 | $content, |
416 | $user, |
417 | $summary |
418 | ); |
419 | $success = $status->isOK(); |
420 | |
421 | if ( $success ) { |
422 | return [ 'translate-manage-import-ok', |
423 | wfEscapeWikiText( $title->getPrefixedText() ) |
424 | ]; |
425 | } |
426 | |
427 | $text = "Failed to import new version of page {$title->getPrefixedText()}\n"; |
428 | $text .= $status->getWikiText(); |
429 | throw new RuntimeException( $text ); |
430 | } |
431 | |
432 | /** @return string[] */ |
433 | public static function doFuzzy( |
434 | Title $title, |
435 | string $message, |
436 | string $comment, |
437 | ?User $user |
438 | ): array { |
439 | $context = RequestContext::getMain(); |
440 | $services = MediaWikiServices::getInstance(); |
441 | |
442 | if ( !$context->getUser()->isAllowed( 'translate-manage' ) ) { |
443 | return [ 'badaccess-group0' ]; |
444 | } |
445 | |
446 | // Edit with fuzzybot if there is no user. |
447 | if ( !$user ) { |
448 | $user = FuzzyBot::getUser(); |
449 | } |
450 | |
451 | // Work on all subpages of base title. |
452 | $handle = new MessageHandle( $title ); |
453 | $titleText = $handle->getKey(); |
454 | |
455 | $revStore = $services->getRevisionStore(); |
456 | $queryInfo = $revStore->getQueryInfo( [ 'page' ] ); |
457 | $dbw = $services->getDBLoadBalancer()->getConnection( DB_PRIMARY ); |
458 | // TODO Migrate to RevisionStore::newSelectQueryBuilder once we support >= 1.41 |
459 | $rows = $dbw->newSelectQueryBuilder() |
460 | ->tables( $queryInfo['tables'] ) |
461 | ->fields( $queryInfo['fields'] ) |
462 | ->where( [ |
463 | 'page_namespace' => $title->getNamespace(), |
464 | 'page_latest=rev_id', |
465 | 'page_title' . $dbw->buildLike( "$titleText/", $dbw->anyString() ), |
466 | ] ) |
467 | ->joinConds( $queryInfo['joins'] ) |
468 | ->caller( __METHOD__ ) |
469 | ->fetchResultSet(); |
470 | |
471 | $changed = []; |
472 | $slots = $revStore->getContentBlobsForBatch( $rows, [ SlotRecord::MAIN ] )->getValue(); |
473 | |
474 | foreach ( $rows as $row ) { |
475 | global $wgTranslateDocumentationLanguageCode; |
476 | |
477 | $translationTitle = Title::makeTitle( (int)$row->page_namespace, $row->page_title ); |
478 | |
479 | // No fuzzy for English original or documentation language code. |
480 | if ( $translationTitle->getSubpageText() === 'en' || |
481 | $translationTitle->getSubpageText() === $wgTranslateDocumentationLanguageCode |
482 | ) { |
483 | // Use imported text, not database text. |
484 | $text = $message; |
485 | } elseif ( isset( $slots[$row->rev_id] ) ) { |
486 | $slot = $slots[$row->rev_id][SlotRecord::MAIN]; |
487 | $text = MessageHandle::makeFuzzyString( $slot->blob_data ); |
488 | } else { |
489 | $text = MessageHandle::makeFuzzyString( |
490 | Utilities::getTextFromTextContent( |
491 | $revStore->newRevisionFromRow( $row )->getContent( SlotRecord::MAIN ) |
492 | ) |
493 | ); |
494 | } |
495 | |
496 | // Do actual import |
497 | $changed[] = self::doImport( |
498 | $translationTitle, |
499 | $text, |
500 | $comment, |
501 | $user |
502 | ); |
503 | } |
504 | |
505 | // Format return text |
506 | $text = ''; |
507 | foreach ( $changed as $c ) { |
508 | $key = array_shift( $c ); |
509 | $text .= '* ' . $context->msg( $key, $c )->plain() . "\n"; |
510 | } |
511 | |
512 | return [ 'translate-manage-import-fuzzy', "\n" . $text ]; |
513 | } |
514 | |
515 | /** |
516 | * Given a group, message key and language code, creates a title for the |
517 | * translation page. |
518 | * |
519 | * @param MessageGroup $group |
520 | * @param string $key Message key |
521 | * @param string $code Language code |
522 | * @return Title |
523 | */ |
524 | private function makeTranslationTitle( MessageGroup $group, string $key, string $code ): Title { |
525 | $ns = $group->getNamespace(); |
526 | |
527 | return Title::makeTitleSafe( $ns, "$key/$code" ); |
528 | } |
529 | |
530 | /** |
531 | * Make section elements. |
532 | * |
533 | * @param string $legend Legend as raw html. |
534 | * @param string $type Contents of type class. |
535 | * @param string $content Contents as raw html. |
536 | * @param Language|null $lang The language in which the text is written. |
537 | * @return string Section element as html. |
538 | */ |
539 | public static function makeSectionElement( |
540 | string $legend, |
541 | string $type, |
542 | string $content, |
543 | Language $lang = null |
544 | ): string { |
545 | $containerParams = [ 'class' => "mw-tpt-sp-section mw-tpt-sp-section-type-{$type}" ]; |
546 | $legendParams = [ 'class' => 'mw-tpt-sp-legend' ]; |
547 | $contentParams = [ 'class' => 'mw-tpt-sp-content' ]; |
548 | if ( $lang ) { |
549 | $contentParams['dir'] = $lang->getDir(); |
550 | $contentParams['lang'] = $lang->getCode(); |
551 | } |
552 | |
553 | return Html::rawElement( 'div', $containerParams, |
554 | Html::rawElement( 'div', $legendParams, $legend ) . |
555 | Html::rawElement( 'div', $contentParams, $content ) |
556 | ); |
557 | } |
558 | |
559 | /** |
560 | * Escape name such that it validates as name and id parameter in html, and |
561 | * so that we can get it back with WebRequest::getVal(). Especially dot and |
562 | * spaces are difficult for the latter. |
563 | */ |
564 | private static function escapeNameForPHP( string $name ): string { |
565 | $replacements = [ |
566 | '(' => '(OP)', |
567 | ' ' => '(SP)', |
568 | "\t" => '(TAB)', |
569 | '.' => '(DOT)', |
570 | "'" => '(SQ)', |
571 | "\"" => '(DQ)', |
572 | '%' => '(PC)', |
573 | '&' => '(AMP)', |
574 | ]; |
575 | |
576 | return strtr( $name, $replacements ); |
577 | } |
578 | } |