Translated using Weblate (Portuguese)
[phpmyadmin.git] / tests / unit / InsertEditTest.php
blob4fb2f3984d89f0292488eec80515c75b7d0ec1e5
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin\Tests;
7 use PhpMyAdmin\Column;
8 use PhpMyAdmin\Config;
9 use PhpMyAdmin\ConfigStorage\Relation;
10 use PhpMyAdmin\Core;
11 use PhpMyAdmin\Current;
12 use PhpMyAdmin\Dbal\DatabaseInterface;
13 use PhpMyAdmin\Dbal\Warning;
14 use PhpMyAdmin\EditField;
15 use PhpMyAdmin\FileListing;
16 use PhpMyAdmin\InsertEdit;
17 use PhpMyAdmin\InsertEditColumn;
18 use PhpMyAdmin\ResponseRenderer;
19 use PhpMyAdmin\Table\Table;
20 use PhpMyAdmin\Template;
21 use PhpMyAdmin\Tests\Stubs\DbiDummy;
22 use PhpMyAdmin\Tests\Stubs\DummyResult;
23 use PhpMyAdmin\Transformations;
24 use PhpMyAdmin\TypeClass;
25 use PhpMyAdmin\Url;
26 use PhpMyAdmin\UrlParams;
27 use PHPUnit\Framework\Attributes\CoversClass;
28 use PHPUnit\Framework\Attributes\DataProvider;
29 use PHPUnit\Framework\Attributes\Medium;
30 use ReflectionProperty;
32 use function hash;
33 use function is_object;
34 use function is_scalar;
35 use function is_string;
36 use function mb_substr;
37 use function md5;
38 use function password_verify;
39 use function sprintf;
41 use const MYSQLI_PRI_KEY_FLAG;
42 use const MYSQLI_TYPE_DECIMAL;
43 use const MYSQLI_TYPE_TIMESTAMP;
44 use const MYSQLI_TYPE_TINY;
46 #[CoversClass(InsertEdit::class)]
47 #[CoversClass(EditField::class)]
48 #[CoversClass(InsertEditColumn::class)]
49 #[Medium]
50 class InsertEditTest extends AbstractTestCase
52 protected DatabaseInterface $dbi;
54 protected DbiDummy $dummyDbi;
56 private InsertEdit $insertEdit;
58 /**
59 * Setup for test cases
61 protected function setUp(): void
63 parent::setUp();
65 $this->setLanguage();
67 $this->setGlobalConfig();
69 $this->dummyDbi = $this->createDbiDummy();
70 $this->dbi = $this->createDatabaseInterface($this->dummyDbi);
71 DatabaseInterface::$instance = $this->dbi;
72 $config = Config::getInstance();
73 $config->settings['ServerDefault'] = 1;
74 Current::$database = 'db';
75 Current::$table = 'table';
76 $config->settings['LimitChars'] = 50;
77 $config->settings['LongtextDoubleTextarea'] = false;
78 $config->settings['ShowFieldTypesInDataEditView'] = true;
79 $config->settings['ShowFunctionFields'] = true;
80 $config->settings['ProtectBinary'] = 'blob';
81 $config->settings['MaxSizeForInputField'] = 10;
82 $config->settings['MinSizeForInputField'] = 2;
83 $config->settings['TextareaRows'] = 5;
84 $config->settings['TextareaCols'] = 4;
85 $config->settings['CharTextareaRows'] = 5;
86 $config->settings['CharTextareaCols'] = 6;
87 $config->settings['AllowThirdPartyFraming'] = false;
88 $config->set('SendErrorReports', 'ask');
89 $config->settings['DefaultTabDatabase'] = '/database/structure';
90 $config->settings['ShowDatabasesNavigationAsTree'] = true;
91 $config->settings['DefaultTabTable'] = '/sql';
92 $config->settings['NavigationTreeDefaultTabTable'] = '/table/structure';
93 $config->settings['NavigationTreeDefaultTabTable2'] = '';
94 $config->settings['Confirm'] = true;
95 $config->settings['LoginCookieValidity'] = 1440;
96 $config->settings['enable_drag_drop_import'] = true;
97 $this->insertEdit = new InsertEdit(
98 $this->dbi,
99 new Relation($this->dbi),
100 new Transformations(),
101 new FileListing(),
102 new Template(),
103 $config,
106 $this->dbi->setVersion([
107 '@@version' => '10.9.3-MariaDB-1:10.9.3+maria~ubu2204',
108 '@@version_comment' => 'mariadb.org binary distribution',
113 * Teardown all objects
115 protected function tearDown(): void
117 parent::tearDown();
119 $response = new ReflectionProperty(ResponseRenderer::class, 'instance');
120 $response->setValue(null, null);
121 DatabaseInterface::$instance = null;
125 * Test for getFormParametersForInsertForm
127 public function testGetFormParametersForInsertForm(): void
129 $whereClause = ['foo' => 'bar ', '1' => ' test'];
130 $_POST['clause_is_unique'] = false;
131 $_POST['sql_query'] = 'SELECT a';
132 UrlParams::$goto = 'index.php';
134 $result = $this->insertEdit->getFormParametersForInsertForm('dbname', 'tablename', $whereClause, 'localhost');
136 self::assertSame(
138 'db' => 'dbname',
139 'table' => 'tablename',
140 'goto' => 'index.php',
141 'err_url' => 'localhost',
142 'sql_query' => 'SELECT a',
143 'where_clause[foo]' => 'bar',
144 'where_clause[1]' => 'test',
145 'clause_is_unique' => false,
147 $result,
152 * Test for getFormParametersForInsertForm
154 public function testGetFormParametersForInsertFormGet(): void
156 $whereClause = ['foo' => 'bar ', '1' => ' test'];
157 $_GET['clause_is_unique'] = false;
158 $_GET['sql_query'] = 'SELECT a';
159 $_GET['sql_signature'] = Core::signSqlQuery($_GET['sql_query']);
160 UrlParams::$goto = 'index.php';
162 $result = $this->insertEdit->getFormParametersForInsertForm('dbname', 'tablename', $whereClause, 'localhost');
164 self::assertSame(
166 'db' => 'dbname',
167 'table' => 'tablename',
168 'goto' => 'index.php',
169 'err_url' => 'localhost',
170 'sql_query' => 'SELECT a',
171 'where_clause[foo]' => 'bar',
172 'where_clause[1]' => 'test',
173 'clause_is_unique' => false,
175 $result,
180 * Test for analyzeWhereClauses
182 public function testAnalyzeWhereClause(): void
184 $clauses = ['a=1', 'b="fo\o"'];
186 $resultStub1 = self::createMock(DummyResult::class);
187 $resultStub2 = self::createMock(DummyResult::class);
189 $dbi = $this->getMockBuilder(DatabaseInterface::class)
190 ->disableOriginalConstructor()
191 ->getMock();
193 $dbi->expects(self::exactly(2))
194 ->method('query')
195 ->willReturn($resultStub1, $resultStub2);
197 $resultStub1->expects(self::once())
198 ->method('fetchAssoc')
199 ->willReturn(['assoc1']);
201 $resultStub2->expects(self::once())
202 ->method('fetchAssoc')
203 ->willReturn(['assoc2']);
205 $dbi->expects(self::exactly(2))
206 ->method('getFieldsMeta')
207 ->willReturn([], []);
209 DatabaseInterface::$instance = $dbi;
210 $this->insertEdit = new InsertEdit(
211 $dbi,
212 new Relation($dbi),
213 new Transformations(),
214 new FileListing(),
215 new Template(),
216 Config::getInstance(),
218 $result = $this->callFunction(
219 $this->insertEdit,
220 InsertEdit::class,
221 'analyzeWhereClauses',
222 [$clauses, 'table', 'db'],
225 self::assertSame(
226 [[$resultStub1, $resultStub2], [['assoc1'], ['assoc2']], false],
227 $result,
232 * Test for hasUniqueCondition
234 public function testHasUniqueCondition(): void
236 $meta = FieldHelper::fromArray([
237 'type' => MYSQLI_TYPE_DECIMAL,
238 'flags' => MYSQLI_PRI_KEY_FLAG,
239 'table' => 'table',
240 'orgname' => 'orgname',
243 $resultStub = self::createMock(DummyResult::class);
245 $dbi = $this->getMockBuilder(DatabaseInterface::class)
246 ->disableOriginalConstructor()
247 ->getMock();
249 $dbi->expects(self::once())
250 ->method('getFieldsMeta')
251 ->with($resultStub)
252 ->willReturn([$meta]);
254 DatabaseInterface::$instance = $dbi;
255 $this->insertEdit = new InsertEdit(
256 $dbi,
257 new Relation($dbi),
258 new Transformations(),
259 new FileListing(),
260 new Template(),
261 Config::getInstance(),
264 $result = $this->callFunction(
265 $this->insertEdit,
266 InsertEdit::class,
267 'hasUniqueCondition',
268 [['1' => 1], $resultStub],
271 self::assertTrue($result);
273 // TODO: Add test for false case
276 public function testLoadFirstRow(): void
278 $resultStub = self::createMock(DummyResult::class);
280 $dbi = $this->getMockBuilder(DatabaseInterface::class)
281 ->disableOriginalConstructor()
282 ->getMock();
284 $dbi->expects(self::once())
285 ->method('query')
286 ->with('SELECT * FROM `db`.`table` LIMIT 1;')
287 ->willReturn($resultStub);
289 DatabaseInterface::$instance = $dbi;
290 $this->insertEdit = new InsertEdit(
291 $dbi,
292 new Relation($dbi),
293 new Transformations(),
294 new FileListing(),
295 new Template(),
296 Config::getInstance(),
299 $result = $this->callFunction(
300 $this->insertEdit,
301 InsertEdit::class,
302 'loadFirstRow',
303 ['table', 'db'],
306 self::assertSame($resultStub, $result);
309 /** @return list<array{int, array<array<never>>}> */
310 public static function dataProviderConfigValueInsertRows(): array
312 return [[2, [[], []]], [3, [[], [], []]]];
316 * Test for loadFirstRow
318 * @param array<array<never>> $rowsValue
320 #[DataProvider('dataProviderConfigValueInsertRows')]
321 public function testGetInsertRows(int $configValue, array $rowsValue): void
323 Config::getInstance()->settings['InsertRows'] = $configValue;
325 $result = $this->callFunction(
326 $this->insertEdit,
327 InsertEdit::class,
328 'getInsertRows',
332 self::assertSame($rowsValue, $result);
336 * Test for showTypeOrFunction
338 public function testShowTypeOrFunction(): void
340 $config = Config::getInstance();
341 $config->settings['ShowFieldTypesInDataEditView'] = true;
342 $config->settings['ServerDefault'] = 1;
343 $urlParams = ['ShowFunctionFields' => 2];
345 $result = $this->insertEdit->showTypeOrFunction('function', $urlParams, false);
347 self::assertStringContainsString('index.php?route=/table/change', $result);
348 self::assertStringContainsString(
349 'ShowFunctionFields=1&ShowFieldTypesInDataEditView=1&goto=index.php%3Froute%3D%2Fsql',
350 $result,
352 self::assertStringContainsString('Function', $result);
354 // case 2
355 $result = $this->insertEdit->showTypeOrFunction('function', $urlParams, true);
357 self::assertStringContainsString('index.php?route=/table/change', $result);
358 self::assertStringContainsString(
359 'ShowFunctionFields=0&ShowFieldTypesInDataEditView=1&goto=index.php%3Froute%3D%2Fsql',
360 $result,
362 self::assertStringContainsString('Function', $result);
364 // case 3
365 $result = $this->insertEdit->showTypeOrFunction('type', $urlParams, false);
367 self::assertStringContainsString('index.php?route=/table/change', $result);
368 self::assertStringContainsString(
369 'ShowFunctionFields=1&ShowFieldTypesInDataEditView=1&goto=index.php%3Froute%3D%2Fsql',
370 $result,
372 self::assertStringContainsString('Type', $result);
374 // case 4
375 $result = $this->insertEdit->showTypeOrFunction('type', $urlParams, true);
377 self::assertStringContainsString('index.php?route=/table/change', $result);
378 self::assertStringContainsString(
379 'ShowFunctionFields=1&ShowFieldTypesInDataEditView=0&goto=index.php%3Froute%3D%2Fsql',
380 $result,
382 self::assertStringContainsString('Type', $result);
386 * Test for getColumnTitle
388 public function testGetColumnTitle(): void
390 $fieldName = 'f1<';
392 self::assertSame(
393 $this->callFunction(
394 $this->insertEdit,
395 InsertEdit::class,
396 'getColumnTitle',
397 [$fieldName, []],
399 'f1&lt;',
402 $comments = [];
403 $comments['f1<'] = 'comment>';
405 $result = $this->callFunction(
406 $this->insertEdit,
407 InsertEdit::class,
408 'getColumnTitle',
409 [$fieldName, $comments],
412 $result = $this->parseString($result);
414 self::assertStringContainsString('title="comment&gt;"', $result);
416 self::assertStringContainsString('f1&lt;', $result);
420 * Test for isColumn
422 public function testIsColumn(): void
424 $types = ['binary', 'varbinary'];
426 $columnType = 'binaryfoo';
427 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
429 $columnType = 'Binaryfoo';
430 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
432 $columnType = 'varbinaryfoo';
433 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
435 $columnType = 'barbinaryfoo';
436 self::assertFalse($this->insertEdit->isColumn($columnType, $types));
438 $types = ['char', 'varchar'];
440 $columnType = 'char(10)';
441 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
443 $columnType = 'VarChar(20)';
444 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
446 $columnType = 'foochar';
447 self::assertFalse($this->insertEdit->isColumn($columnType, $types));
449 $types = ['blob', 'tinyblob', 'mediumblob', 'longblob'];
451 $columnType = 'blob';
452 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
454 $columnType = 'bloB';
455 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
457 $columnType = 'mediumBloB';
458 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
460 $columnType = 'tinyblobabc';
461 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
463 $columnType = 'longblob';
464 self::assertTrue($this->insertEdit->isColumn($columnType, $types));
466 $columnType = 'foolongblobbar';
467 self::assertFalse($this->insertEdit->isColumn($columnType, $types));
471 * Test for getNullifyCodeForNullColumn
473 public function testGetNullifyCodeForNullColumn(): void
475 $foreigners = ['foreign_keys_data' => []];
476 $column = new InsertEditColumn(
477 'f',
478 'enum(ababababababababababa)',
479 false,
481 null,
484 false,
485 false,
486 false,
487 false,
489 self::assertSame(
490 '1',
491 $this->callFunction(
492 $this->insertEdit,
493 InsertEdit::class,
494 'getNullifyCodeForNullColumn',
495 [$column, $foreigners, false],
499 $column = new InsertEditColumn(
500 'f',
501 'enum(abababababab20)',
502 false,
504 null,
507 false,
508 false,
509 false,
510 false,
512 self::assertSame(
513 '2',
514 $this->callFunction(
515 $this->insertEdit,
516 InsertEdit::class,
517 'getNullifyCodeForNullColumn',
518 [$column, $foreigners, false],
522 $column = new InsertEditColumn('f', 'set', false, '', null, '', -1, false, false, false, false);
523 self::assertSame(
524 '3',
525 $this->callFunction(
526 $this->insertEdit,
527 InsertEdit::class,
528 'getNullifyCodeForNullColumn',
529 [$column, $foreigners, false],
533 $column = new InsertEditColumn('f', '', false, '', null, '', -1, false, false, false, false);
534 $foreigners['f'] = ['something'/* What should the mocked value actually be? */];
535 self::assertSame(
536 '4',
537 $this->callFunction(
538 $this->insertEdit,
539 InsertEdit::class,
540 'getNullifyCodeForNullColumn',
541 [$column, $foreigners, false],
547 * Test for getTextarea
549 public function testGetTextarea(): void
551 $config = Config::getInstance();
552 $config->settings['TextareaRows'] = 20;
553 $config->settings['TextareaCols'] = 10;
554 $config->settings['CharTextareaRows'] = 7;
555 $config->settings['CharTextareaCols'] = 1;
556 $config->settings['LimitChars'] = 20;
558 $column = new InsertEditColumn(
559 'f',
560 'char(10)',
561 false,
563 null,
564 'auto_increment',
566 false,
567 false,
568 true,
569 false,
571 (new ReflectionProperty(InsertEdit::class, 'fieldIndex'))->setValue($this->insertEdit, 2);
572 $result = $this->callFunction(
573 $this->insertEdit,
574 InsertEdit::class,
575 'getTextarea',
576 [$column, 'a', 'b', '', 'foobar', TypeClass::Char],
579 $result = $this->parseString($result);
581 self::assertStringContainsString(
582 '<textarea name="fieldsb" class="charField" '
583 . 'data-maxlength="10" rows="7" cols="1" dir="ltr" '
584 . 'id="field_2_3" tabindex="2" data-type="CHAR">',
585 $result,
590 * Test for getHtmlInput
592 public function testGetHTMLinput(): void
594 Config::getInstance()->settings['ShowFunctionFields'] = true;
595 $column = new InsertEditColumn('f', 'date', false, 'PRI', null, '', -1, false, false, false, false);
596 (new ReflectionProperty(InsertEdit::class, 'fieldIndex'))->setValue($this->insertEdit, 23);
597 $result = $this->callFunction(
598 $this->insertEdit,
599 InsertEdit::class,
600 'getHtmlInput',
601 [$column, 'a', 'b', 30, 'c', 'DATE'],
604 self::assertSame(
605 '<input type="text" name="fieldsa" value="b" size="30" data-type="DATE"'
606 . ' class="textfield datefield" onchange="c" tabindex="23" id="field_23_3">',
607 $result,
610 // case 2 datetime
611 $column = new InsertEditColumn('f', 'datetime', false, 'PRI', null, '', -1, false, false, false, false);
612 $result = $this->callFunction(
613 $this->insertEdit,
614 InsertEdit::class,
615 'getHtmlInput',
616 [$column, 'a', 'b', 30, 'c', 'DATE'],
618 self::assertSame(
619 '<input type="text" name="fieldsa" value="b" size="30" data-type="DATE"'
620 . ' class="textfield datetimefield" onchange="c" tabindex="23" id="field_23_3">',
621 $result,
624 // case 3 timestamp
625 $column = new InsertEditColumn('f', 'timestamp', false, 'PRI', null, '', -1, false, false, false, false);
626 $result = $this->callFunction(
627 $this->insertEdit,
628 InsertEdit::class,
629 'getHtmlInput',
630 [$column, 'a', 'b', 30, 'c', 'DATE'],
632 self::assertSame(
633 '<input type="text" name="fieldsa" value="b" size="30" data-type="DATE"'
634 . ' class="textfield datetimefield" onchange="c" tabindex="23" id="field_23_3">',
635 $result,
638 // case 4 int
639 $column = new InsertEditColumn('f', 'int(11)', false, 'PRI', null, '', -1, false, false, false, false);
640 $result = $this->callFunction(
641 $this->insertEdit,
642 InsertEdit::class,
643 'getHtmlInput',
644 [$column, 'a', 'b', 11, 'c', 'INT'],
646 self::assertSame(
647 '<input type="text" name="fieldsa" value="b" size="11" min="-2147483648" max="2147483647" data-type="INT"'
648 . ' class="textfield" onchange="c" tabindex="23" inputmode="numeric" id="field_23_3">',
649 $result,
654 * Test for getMaxUploadSize
656 public function testGetMaxUploadSize(): void
658 $type = 'tinyblob';
659 $result = $this->callFunction(
660 $this->insertEdit,
661 InsertEdit::class,
662 'getMaxUploadSize',
663 [$type],
666 self::assertSame("(Max: 256B)\n", $result);
668 // case 2
669 // this should stub Util::getUploadSizeInBytes() but it's not possible
670 $type = 'blob';
671 $result = $this->callFunction(
672 $this->insertEdit,
673 InsertEdit::class,
674 'getMaxUploadSize',
675 [$type],
678 self::assertSame("(Max: 64KiB)\n", $result);
682 * Test for getValueColumnForOtherDatatypes
684 public function testGetValueColumnForOtherDatatypes(): void
686 $column = new InsertEditColumn('f', 'char(25)', false, '', null, '', 20, false, false, true, false);
687 $config = Config::getInstance();
688 $config->settings['CharEditing'] = '';
689 $config->settings['MaxSizeForInputField'] = 30;
690 $config->settings['MinSizeForInputField'] = 10;
691 $config->settings['TextareaRows'] = 20;
692 $config->settings['TextareaCols'] = 10;
693 $config->settings['CharTextareaRows'] = 7;
694 $config->settings['CharTextareaCols'] = 1;
695 $config->settings['LimitChars'] = 50;
696 $config->settings['ShowFunctionFields'] = true;
698 $extractedColumnSpec = '25';
699 (new ReflectionProperty(InsertEdit::class, 'fieldIndex'))->setValue($this->insertEdit, 22);
700 $result = $this->callFunction(
701 $this->insertEdit,
702 InsertEdit::class,
703 'getValueColumnForOtherDatatypes',
705 $column,
706 'defchar',
707 'a',
708 'b',
709 'c',
710 '&lt;',
711 '&lt;',
712 "foo\nbar",
713 $extractedColumnSpec,
717 self::assertSame(
718 "a\na\n"
719 . '<textarea name="fieldsb" class="charField" '
720 . 'data-maxlength="25" rows="7" cols="1" dir="ltr" '
721 . 'id="field_22_3" onchange="c" tabindex="22" data-type="CHAR">'
722 . '&lt;</textarea>',
723 $result,
726 // case 2: (else)
727 $column = new InsertEditColumn(
728 'f',
729 'timestamp',
730 false,
732 null,
733 'auto_increment',
735 false,
736 false,
737 false,
738 false,
740 $result = $this->callFunction(
741 $this->insertEdit,
742 InsertEdit::class,
743 'getValueColumnForOtherDatatypes',
745 $column,
746 'defchar',
747 'a',
748 'b',
749 'c',
750 '&lt;',
751 '&lt;',
752 "foo\nbar",
753 $extractedColumnSpec,
757 self::assertSame(
758 "a\n"
759 . '<input type="text" name="fieldsb" value="&lt;" size="20" data-type="'
760 . 'DATE" class="textfield datetimefield" onchange="c" tabindex="22" id="field_22_3"'
761 . '><input type="hidden" name="auto_incrementb" value="1">'
762 . '<input type="hidden" name="fields_typeb" value="timestamp">',
763 $result,
766 // case 3: (else -> datetime)
767 $column = new InsertEditColumn(
768 'f',
769 'datetime',
770 false,
772 null,
773 'auto_increment',
775 false,
776 false,
777 false,
778 false,
780 $result = $this->callFunction(
781 $this->insertEdit,
782 InsertEdit::class,
783 'getValueColumnForOtherDatatypes',
785 $column,
786 'defchar',
787 'a',
788 'b',
789 'c',
790 '&lt;',
791 '&lt;',
792 "foo\nbar",
793 $extractedColumnSpec,
797 $result = $this->parseString($result);
799 self::assertStringContainsString('<input type="hidden" name="fields_typeb" value="datetime">', $result);
801 // case 4: (else -> date)
802 $column = new InsertEditColumn('f', 'date', false, '', null, 'auto_increment', 20, false, false, false, false);
803 $result = $this->callFunction(
804 $this->insertEdit,
805 InsertEdit::class,
806 'getValueColumnForOtherDatatypes',
808 $column,
809 'defchar',
810 'a',
811 'b',
812 'c',
813 '&lt;',
814 '&lt;',
815 "foo\nbar",
816 $extractedColumnSpec,
820 $result = $this->parseString($result);
822 self::assertStringContainsString('<input type="hidden" name="fields_typeb" value="date">', $result);
824 // case 5: (else -> bit)
825 $column = new InsertEditColumn('f', 'bit', false, '', null, 'auto_increment', 20, false, false, false, false);
826 $result = $this->callFunction(
827 $this->insertEdit,
828 InsertEdit::class,
829 'getValueColumnForOtherDatatypes',
831 $column,
832 'defchar',
833 'a',
834 'b',
835 'c',
836 '&lt;',
837 '&lt;',
838 "foo\nbar",
839 $extractedColumnSpec,
843 $result = $this->parseString($result);
845 self::assertStringContainsString('<input type="hidden" name="fields_typeb" value="bit">', $result);
847 // case 6: (else -> uuid)
848 $column = new InsertEditColumn('f', 'uuid', false, '', null, 'auto_increment', 20, false, false, false, false);
849 $result = $this->callFunction(
850 $this->insertEdit,
851 InsertEdit::class,
852 'getValueColumnForOtherDatatypes',
854 $column,
855 'defchar',
856 'a',
857 'b',
858 'c',
859 '&lt;',
860 '&lt;',
861 "foo\nbar",
862 $extractedColumnSpec,
866 $result = $this->parseString($result);
868 self::assertStringContainsString('<input type="hidden" name="fields_typeb" value="uuid">', $result);
872 * Test for getColumnSize
874 public function testGetColumnSize(): void
876 $column = new InsertEditColumn(
877 'f',
878 'char(10)',
879 false,
881 null,
882 'auto_increment',
884 false,
885 false,
886 true,
887 false,
889 $specInBrackets = '45';
890 $config = Config::getInstance();
891 $config->settings['MinSizeForInputField'] = 30;
892 $config->settings['MaxSizeForInputField'] = 40;
894 self::assertSame(
896 $this->callFunction(
897 $this->insertEdit,
898 InsertEdit::class,
899 'getColumnSize',
900 [$column, $specInBrackets],
904 self::assertSame('textarea', $config->settings['CharEditing']);
906 // case 2
907 $column = new InsertEditColumn(
908 'f',
909 'char(10)',
910 false,
912 null,
913 'auto_increment',
915 false,
916 false,
917 false,
918 false,
920 self::assertSame(
922 $this->callFunction(
923 $this->insertEdit,
924 InsertEdit::class,
925 'getColumnSize',
926 [$column, $specInBrackets],
932 * Test for getContinueInsertionForm
934 public function testGetContinueInsertionForm(): void
936 $whereClauseArray = ['a<b'];
937 $config = Config::getInstance();
938 $config->settings['InsertRows'] = 1;
939 $config->settings['ServerDefault'] = 1;
940 UrlParams::$goto = 'index.php';
941 $_POST['where_clause'] = true;
942 $_POST['sql_query'] = 'SELECT 1';
944 $result = $this->insertEdit->getContinueInsertionForm('tbl', 'db', $whereClauseArray, 'localhost');
946 self::assertStringContainsString(
947 '<form id="continueForm" method="post" action="' . Url::getFromRoute('/table/change')
948 . '" name="continueForm" class="row g-3">',
949 $result,
952 self::assertStringContainsString('<input type="hidden" name="db" value="db">', $result);
954 self::assertStringContainsString('<input type="hidden" name="table" value="tbl">', $result);
956 self::assertStringContainsString('<input type="hidden" name="goto" value="index.php">', $result);
958 self::assertStringContainsString('<input type="hidden" name="err_url" value="localhost">', $result);
960 self::assertStringContainsString('<input type="hidden" name="sql_query" value="SELECT 1">', $result);
962 self::assertStringContainsString('<input type="hidden" name="where_clause[0]" value="a&lt;b">', $result);
965 public function testIsWhereClauseNumeric(): void
967 self::assertFalse(InsertEdit::isWhereClauseNumeric(null));
968 self::assertFalse(InsertEdit::isWhereClauseNumeric(''));
969 self::assertFalse(InsertEdit::isWhereClauseNumeric([]));
970 self::assertTrue(InsertEdit::isWhereClauseNumeric('`actor`.`actor_id` = 1'));
971 self::assertTrue(InsertEdit::isWhereClauseNumeric(['`actor`.`actor_id` = 1']));
972 self::assertFalse(InsertEdit::isWhereClauseNumeric('`actor`.`first_name` = \'value\''));
973 self::assertFalse(InsertEdit::isWhereClauseNumeric(['`actor`.`first_name` = \'value\'']));
977 * Test for getHeadAndFootOfInsertRowTable
979 public function testGetHeadAndFootOfInsertRowTable(): void
981 $config = Config::getInstance();
982 $config->settings['ShowFieldTypesInDataEditView'] = true;
983 $config->settings['ShowFunctionFields'] = true;
984 $config->settings['ServerDefault'] = 1;
985 $urlParams = ['ShowFunctionFields' => 2];
987 $result = $this->callFunction(
988 $this->insertEdit,
989 InsertEdit::class,
990 'getHeadAndFootOfInsertRowTable',
991 [$urlParams],
994 $result = $this->parseString($result);
996 self::assertStringContainsString('index.php?route=/table/change', $result);
998 self::assertStringContainsString('ShowFunctionFields=1&ShowFieldTypesInDataEditView=0', $result);
1000 self::assertStringContainsString('ShowFunctionFields=0&ShowFieldTypesInDataEditView=1', $result);
1004 * Test for getSpecialCharsAndBackupFieldForExistingRow
1006 public function testGetSpecialCharsAndBackupFieldForExistingRow(): void
1008 $currentRow = [];
1009 $currentRow['f'] = null;
1010 $_POST['default_action'] = 'insert';
1011 $column = new InsertEditColumn(
1012 'f',
1013 'char(10)',
1014 false,
1015 'PRI',
1016 null,
1017 'fooauto_increment',
1019 false,
1020 false,
1021 true,
1022 false,
1025 $result = $this->callFunction(
1026 $this->insertEdit,
1027 InsertEdit::class,
1028 'getSpecialCharsAndBackupFieldForExistingRow',
1029 [$currentRow, $column, '', 'a', false],
1032 self::assertEquals(
1033 [true, null, null, null, '<input type="hidden" name="fields_preva" value="">'],
1034 $result,
1037 // Case 2 (bit)
1038 unset($_POST['default_action']);
1040 $currentRow['f'] = '123';
1041 $extractedColumnSpec = '20';
1042 $column = new InsertEditColumn(
1043 'f',
1044 'bit',
1045 false,
1046 'PRI',
1047 null,
1048 'fooauto_increment',
1050 false,
1051 false,
1052 true,
1053 false,
1056 $result = $this->callFunction(
1057 $this->insertEdit,
1058 InsertEdit::class,
1059 'getSpecialCharsAndBackupFieldForExistingRow',
1060 [$currentRow, $column, $extractedColumnSpec, 'a', false],
1063 self::assertEquals(
1064 [false, '', '00000000000001111011', null, '<input type="hidden" name="fields_preva" value="123">'],
1065 $result,
1068 $currentRow['f'] = 'abcd';
1069 $result = $this->callFunction(
1070 $this->insertEdit,
1071 InsertEdit::class,
1072 'getSpecialCharsAndBackupFieldForExistingRow',
1073 [$currentRow, $column, $extractedColumnSpec, 'a', true],
1076 self::assertEquals(
1077 [false, '', 'abcd', null, '<input type="hidden" name="fields_preva" value="abcd">'],
1078 $result,
1081 // Case 3 (bit)
1082 $dbi = $this->getMockBuilder(DatabaseInterface::class)
1083 ->disableOriginalConstructor()
1084 ->getMock();
1086 DatabaseInterface::$instance = $dbi;
1087 $this->insertEdit = new InsertEdit(
1088 $dbi,
1089 new Relation($dbi),
1090 new Transformations(),
1091 new FileListing(),
1092 new Template(),
1093 Config::getInstance(),
1096 $currentRow['f'] = '123';
1097 $extractedColumnSpec = '20';
1098 $column = new InsertEditColumn(
1099 'f',
1100 'geometry',
1101 false,
1102 'PRI',
1103 null,
1104 'fooauto_increment',
1106 false,
1107 false,
1108 true,
1109 false,
1112 $result = $this->callFunction(
1113 $this->insertEdit,
1114 InsertEdit::class,
1115 'getSpecialCharsAndBackupFieldForExistingRow',
1116 [$currentRow, $column, $extractedColumnSpec, 'a', false],
1119 self::assertEquals(
1120 [false, '', "'',", null, '<input type="hidden" name="fields_preva" value="\'\',">'],
1121 $result,
1124 // Case 4 (else)
1125 $column = new InsertEditColumn(
1126 'f',
1127 'char',
1128 false,
1129 'PRI',
1130 null,
1131 'fooauto_increment',
1133 false,
1134 true,
1135 true,
1136 false,
1138 $config = Config::getInstance();
1139 $config->settings['ProtectBinary'] = false;
1140 $currentRow['f'] = '11001';
1141 $extractedColumnSpec = '20';
1142 $config->settings['ShowFunctionFields'] = true;
1144 $result = $this->callFunction(
1145 $this->insertEdit,
1146 InsertEdit::class,
1147 'getSpecialCharsAndBackupFieldForExistingRow',
1148 [$currentRow, $column, $extractedColumnSpec, 'a', false],
1151 self::assertSame(
1153 false,
1154 '3131303031',
1155 '3131303031',
1156 '3131303031',
1157 '<input type="hidden" name="fields_preva" value="3131303031">',
1159 $result,
1162 // Case 5
1163 $currentRow['f'] = "11001\x00";
1165 $result = $this->callFunction(
1166 $this->insertEdit,
1167 InsertEdit::class,
1168 'getSpecialCharsAndBackupFieldForExistingRow',
1169 [$currentRow, $column, $extractedColumnSpec, 'a', false],
1172 self::assertSame(
1174 false,
1175 '313130303100',
1176 '313130303100',
1177 '313130303100',
1178 '<input type="hidden" name="fields_preva" value="313130303100">',
1180 $result,
1185 * Test for getDefaultValue
1187 #[DataProvider('providerForTestGetSpecialCharsForInsertingMode')]
1188 public function testGetDefaultValue(
1189 string|null $defaultValue,
1190 string $trueType,
1191 string $expected,
1192 ): void {
1193 $config = Config::getInstance();
1194 $config->settings['ProtectBinary'] = false;
1195 $config->settings['ShowFunctionFields'] = true;
1197 /** @var string $result */
1198 $result = $this->callFunction(
1199 $this->insertEdit,
1200 InsertEdit::class,
1201 'getDefaultValue',
1202 [$defaultValue, $trueType],
1205 self::assertSame($expected, $result);
1209 * Data provider for test getDefaultValue()
1211 * @return array<string, array{string|null, string, string}>
1213 public static function providerForTestGetSpecialCharsForInsertingMode(): array
1215 return [
1216 'bit' => [
1217 'b\'101\'',
1218 'bit',
1219 '101',
1221 'char' => [
1222 null,
1223 'char',
1226 'time with CURRENT_TIMESTAMP value' => [
1227 'CURRENT_TIMESTAMP',
1228 'time',
1229 'CURRENT_TIMESTAMP',
1231 'time with current_timestamp() value' => [
1232 'current_timestamp()',
1233 'time',
1234 'current_timestamp()',
1236 'time with no dot value' => [
1237 '10',
1238 'time',
1239 '10.000000',
1241 'time with dot value' => [
1242 '10.08',
1243 'time',
1244 '10.080000',
1246 'any text with escape text default' => [
1247 '"lorem\"ipsem"',
1248 'text',
1249 '"lorem\"ipsem"',
1251 'varchar with html special chars' => [
1252 'hello world<br><b>lorem</b> ipsem',
1253 'varchar',
1254 'hello world<br><b>lorem</b> ipsem',
1256 'text with html special chars' => [
1257 '\'</textarea><script>alert(1)</script>\'',
1258 'text',
1259 '\'</textarea><script>alert(1)</script>\'',
1265 * Test for setSessionForEditNext
1267 public function testSetSessionForEditNext(): void
1269 $meta = FieldHelper::fromArray([
1270 'type' => MYSQLI_TYPE_DECIMAL,
1271 'flags' => MYSQLI_PRI_KEY_FLAG,
1272 'orgname' => 'orgname',
1273 'table' => 'table',
1274 'orgtable' => 'table',
1277 $row = ['1' => 1];
1279 $resultStub = self::createMock(DummyResult::class);
1281 $dbi = $this->getMockBuilder(DatabaseInterface::class)
1282 ->disableOriginalConstructor()
1283 ->getMock();
1285 $dbi->expects(self::once())
1286 ->method('query')
1287 ->with('SELECT * FROM `db`.`table` WHERE `a` > 2 LIMIT 1;')
1288 ->willReturn($resultStub);
1290 $resultStub->expects(self::once())
1291 ->method('fetchRow')
1292 ->willReturn($row);
1294 $dbi->expects(self::once())
1295 ->method('getFieldsMeta')
1296 ->with($resultStub)
1297 ->willReturn([$meta]);
1299 DatabaseInterface::$instance = $dbi;
1300 Current::$database = 'db';
1301 Current::$table = 'table';
1302 $this->insertEdit = new InsertEdit(
1303 $dbi,
1304 new Relation($dbi),
1305 new Transformations(),
1306 new FileListing(),
1307 new Template(),
1308 Config::getInstance(),
1310 $this->insertEdit->setSessionForEditNext('`a` = 2');
1312 self::assertSame('CONCAT(`table`.`orgname`) IS NULL', $_SESSION['edit_next']);
1316 * Test for getGotoInclude
1318 public function testGetGotoInclude(): void
1320 UrlParams::$goto = '123.php';
1321 Current::$table = '';
1323 self::assertSame(
1324 '/database/sql',
1325 $this->insertEdit->getGotoInclude('index'),
1328 Current::$table = 'tbl';
1329 self::assertSame(
1330 '/table/sql',
1331 $this->insertEdit->getGotoInclude('index'),
1334 UrlParams::$goto = 'index.php?route=/database/sql';
1336 self::assertSame(
1337 '/database/sql',
1338 $this->insertEdit->getGotoInclude('index'),
1341 self::assertSame('', Current::$table);
1343 UrlParams::$goto = 'index.php?route=/sql&server=2';
1345 self::assertSame(
1346 '/sql',
1347 $this->insertEdit->getGotoInclude('index'),
1350 $_POST['after_insert'] = 'new_insert';
1351 self::assertSame(
1352 '/table/change',
1353 $this->insertEdit->getGotoInclude('index'),
1358 * Test for getErrorUrl
1360 public function testGetErrorUrl(): void
1362 Config::getInstance()->settings['ServerDefault'] = 1;
1363 self::assertSame(
1364 'index.php?route=/table/change&lang=en',
1365 $this->insertEdit->getErrorUrl([]),
1368 $_POST['err_url'] = 'localhost';
1369 self::assertSame(
1370 'localhost',
1371 $this->insertEdit->getErrorUrl([]),
1376 * Test for executeSqlQuery
1378 public function testExecuteSqlQuery(): void
1380 $query = ['SELECT * FROM `test_db`.`test_table`;', 'SELECT * FROM `test_db`.`test_table_yaml`;'];
1381 Config::getInstance()->settings['IgnoreMultiSubmitErrors'] = false;
1382 $_POST['submit_type'] = '';
1384 $dbi = DatabaseInterface::getInstance();
1385 $this->insertEdit = new InsertEdit(
1386 $dbi,
1387 new Relation($dbi),
1388 new Transformations(),
1389 new FileListing(),
1390 new Template(),
1391 Config::getInstance(),
1393 $result = $this->insertEdit->executeSqlQuery($query);
1395 self::assertSame([], $result[3]);
1399 * Test for executeSqlQuery
1401 public function testExecuteSqlQueryWithTryQuery(): void
1403 $query = ['SELECT * FROM `test_db`.`test_table`;', 'SELECT * FROM `test_db`.`test_table_yaml`;'];
1404 Config::getInstance()->settings['IgnoreMultiSubmitErrors'] = true;
1405 $_POST['submit_type'] = '';
1407 $dbi = DatabaseInterface::getInstance();
1408 $this->insertEdit = new InsertEdit(
1409 $dbi,
1410 new Relation($dbi),
1411 new Transformations(),
1412 new FileListing(),
1413 new Template(),
1414 Config::getInstance(),
1416 $result = $this->insertEdit->executeSqlQuery($query);
1418 self::assertSame([], $result[3]);
1422 * Test for getWarningMessages
1424 public function testGetWarningMessages(): void
1426 $warnings = [
1427 Warning::fromArray(['Level' => 'Error', 'Code' => '1001', 'Message' => 'Message 1']),
1428 Warning::fromArray(['Level' => 'Warning', 'Code' => '1002', 'Message' => 'Message 2']),
1431 $dbi = $this->getMockBuilder(DatabaseInterface::class)
1432 ->disableOriginalConstructor()
1433 ->getMock();
1435 $dbi->expects(self::once())
1436 ->method('getWarnings')
1437 ->willReturn($warnings);
1439 DatabaseInterface::$instance = $dbi;
1440 $this->insertEdit = new InsertEdit(
1441 $dbi,
1442 new Relation($dbi),
1443 new Transformations(),
1444 new FileListing(),
1445 new Template(),
1446 Config::getInstance(),
1449 $result = (array) $this->callFunction(
1450 $this->insertEdit,
1451 InsertEdit::class,
1452 'getWarningMessages',
1456 self::assertSame(['Error: #1001 Message 1', 'Warning: #1002 Message 2'], $result);
1460 * Test for getDisplayValueForForeignTableColumn
1462 public function testGetDisplayValueForForeignTableColumn(): void
1464 $map = [];
1465 $map['f']['foreign_db'] = 'information_schema';
1466 $map['f']['foreign_table'] = 'TABLES';
1467 $map['f']['foreign_field'] = 'f';
1469 $resultStub = self::createMock(DummyResult::class);
1471 $dbi = $this->getMockBuilder(DatabaseInterface::class)
1472 ->disableOriginalConstructor()
1473 ->getMock();
1475 $dbi->expects(self::once())
1476 ->method('tryQuery')
1477 ->with('SELECT `TABLE_COMMENT` FROM `information_schema`.`TABLES` WHERE `f`=1')
1478 ->willReturn($resultStub);
1480 $resultStub->expects(self::once())
1481 ->method('numRows')
1482 ->willReturn(2);
1484 $resultStub->expects(self::once())
1485 ->method('fetchValue')
1486 ->with(0)
1487 ->willReturn('2');
1489 DatabaseInterface::$instance = $dbi;
1490 $this->insertEdit = new InsertEdit(
1491 $dbi,
1492 new Relation($dbi),
1493 new Transformations(),
1494 new FileListing(),
1495 new Template(),
1496 Config::getInstance(),
1499 $result = $this->insertEdit->getDisplayValueForForeignTableColumn('=1', $map, 'f');
1501 self::assertSame('2', $result);
1505 * Test for getLinkForRelationalDisplayField
1507 public function testGetLinkForRelationalDisplayField(): void
1509 Config::getInstance()->settings['ServerDefault'] = 1;
1510 $_SESSION['tmpval']['relational_display'] = 'K';
1511 $map = [];
1512 $map['f']['foreign_db'] = 'information_schema';
1513 $map['f']['foreign_table'] = 'TABLES';
1514 $map['f']['foreign_field'] = 'f';
1516 $result = $this->insertEdit->getLinkForRelationalDisplayField($map, 'f', '=1', 'a>', 'b<');
1518 $sqlSignature = Core::signSqlQuery('SELECT * FROM `information_schema`.`TABLES` WHERE `f`=1');
1520 self::assertSame(
1521 '<a href="index.php?route=/sql&db=information_schema&table=TABLES&pos=0&'
1522 . 'sql_signature=' . $sqlSignature . '&'
1523 . 'sql_query=SELECT+%2A+FROM+%60information_schema%60.%60TABLES%60+WHERE'
1524 . '+%60f%60%3D1&lang=en" title="a&gt;">b&lt;</a>',
1525 $result,
1528 $_SESSION['tmpval']['relational_display'] = 'D';
1529 $result = $this->insertEdit->getLinkForRelationalDisplayField($map, 'f', '=1', 'a>', 'b<');
1531 self::assertSame(
1532 '<a href="index.php?route=/sql&db=information_schema&table=TABLES&pos=0&'
1533 . 'sql_signature=' . $sqlSignature . '&'
1534 . 'sql_query=SELECT+%2A+FROM+%60information_schema%60.%60TABLES%60+WHERE'
1535 . '+%60f%60%3D1&lang=en" title="b&lt;">a&gt;</a>',
1536 $result,
1541 * Test for transformEditedValues
1543 public function testTransformEditedValues(): void
1545 $_SESSION[' HMAC_secret '] = hash('sha1', 'test');
1546 $editedValues = [['c' => 'cname']];
1547 $config = Config::getInstance();
1548 $config->settings['DefaultTransformations']['PreApPend'] = ['', ''];
1549 $config->settings['ServerDefault'] = 1;
1550 $_POST['where_clause'] = '1';
1551 $_POST['where_clause_sign'] = Core::signSqlQuery($_POST['where_clause']);
1552 $result = $this->insertEdit->transformEditedValues(
1553 'db',
1554 'table',
1555 "'','option ,, quoted',abd",
1556 $editedValues,
1557 'Text_Plain_PreApPend.php',
1558 'c',
1559 [['a' => 'b']],
1562 self::assertSame(
1563 [['a' => 'b'], 'transformations' => ['cnameoption ,, quoted']],
1564 $result,
1569 * Test for getQueryValuesForInsert
1571 public function testGetQueryValuesForInsert(): void
1573 // Simple insert
1574 $result = $this->insertEdit->getQueryValueForInsert(
1575 new EditField(
1576 'fld',
1577 'foo',
1579 false,
1580 false,
1581 false,
1583 null,
1584 null,
1585 false,
1587 false,
1590 self::assertSame("'foo'", $result);
1592 // Test for file upload
1593 $result = $this->insertEdit->getQueryValueForInsert(
1594 new EditField(
1596 '0x123',
1598 false,
1599 false,
1600 false,
1602 null,
1603 null,
1604 true,
1606 false,
1610 self::assertSame('0x123', $result);
1612 // Test functions
1613 $this->dummyDbi->addResult(
1614 'SELECT UUID()',
1616 ['uuid1234'],// Minimal working setup for 2FA
1620 // case 1
1621 $result = $this->insertEdit->getQueryValueForInsert(
1622 new EditField(
1626 false,
1627 false,
1628 false,
1629 'UUID',
1630 null,
1631 null,
1632 false,
1634 false,
1638 self::assertSame("'uuid1234'", $result);
1640 // case 2
1641 $result = $this->insertEdit->getQueryValueForInsert(
1642 new EditField(
1644 "'",
1646 false,
1647 false,
1648 false,
1649 'AES_ENCRYPT',
1651 null,
1652 false,
1654 false,
1657 self::assertSame("AES_ENCRYPT('\\'','')", $result);
1659 // case 3
1660 $result = $this->insertEdit->getQueryValueForInsert(
1661 new EditField(
1663 "'",
1665 false,
1666 false,
1667 false,
1668 'ABS',
1669 null,
1670 null,
1671 false,
1673 false,
1676 self::assertSame("ABS('\\'')", $result);
1678 // case 4
1679 $result = $this->insertEdit->getQueryValueForInsert(
1680 new EditField(
1684 false,
1685 false,
1686 false,
1687 'RAND',
1688 null,
1689 null,
1690 false,
1692 false,
1695 self::assertSame('RAND()', $result);
1697 // case 5
1698 $result = $this->insertEdit->getQueryValueForInsert(
1699 new EditField(
1701 "a'c",
1703 false,
1704 false,
1705 false,
1706 'PHP_PASSWORD_HASH',
1707 null,
1708 null,
1709 false,
1711 false,
1714 self::assertTrue(password_verify("a'c", mb_substr($result, 1, -1)));
1716 // case 7
1717 $result = $this->insertEdit->getQueryValueForInsert(
1718 new EditField('', "'POINT(3 4)',4326", '', true, false, false, 'ST_GeomFromText', null, null, false),
1719 false,
1722 self::assertSame('ST_GeomFromText(\'POINT(3 4)\',4326)', $result);
1724 // case 8
1725 $result = $this->insertEdit->getQueryValueForInsert(
1726 new EditField('', 'POINT(3 4),4326', '', true, false, false, 'ST_GeomFromText', null, null, false),
1727 false,
1730 self::assertSame('ST_GeomFromText(\'POINT(3 4)\',4326)', $result);
1732 // case 9
1733 $result = $this->insertEdit->getQueryValueForInsert(
1734 new EditField('', "'POINT(3 4)'", '', true, false, false, 'ST_GeomFromText', null, null, false),
1735 false,
1738 self::assertSame('ST_GeomFromText(\'POINT(3 4)\')', $result);
1740 // case 10
1741 $result = $this->insertEdit->getQueryValueForInsert(
1742 new EditField('', 'POINT(3 4)', '', true, false, false, 'ST_GeomFromText', null, null, false),
1743 false,
1746 self::assertSame('ST_GeomFromText(\'POINT(3 4)\')', $result);
1748 // Test different data types
1750 // Datatype: protected copied from the databse
1751 Current::$table = 'test_table';
1752 $result = $this->insertEdit->getQueryValueForInsert(
1753 new EditField(
1754 'name',
1756 'protected',
1757 false,
1758 false,
1759 false,
1761 null,
1762 null,
1763 false,
1765 true,
1766 '`id` = 4',
1768 self::assertSame('0x313031', $result);
1770 // An empty value for auto increment column should be converted to NULL
1771 $result = $this->insertEdit->getQueryValueForInsert(
1772 new EditField(
1774 '', // empty for null
1776 true,
1777 false,
1778 false,
1780 null,
1781 null,
1782 false,
1784 false,
1787 self::assertSame('NULL', $result);
1789 // Simple empty value
1790 $result = $this->insertEdit->getQueryValueForInsert(
1791 new EditField(
1795 false,
1796 false,
1797 false,
1799 null,
1800 null,
1801 false,
1803 false,
1806 self::assertSame("''", $result);
1808 // Datatype: set
1809 $result = $this->insertEdit->getQueryValueForInsert(
1810 new EditField(
1812 '', // doesn't matter what the value is
1813 'set',
1814 false,
1815 false,
1816 false,
1818 null,
1819 null,
1820 false,
1822 false,
1825 self::assertSame("''", $result);
1827 // Datatype: protected with no value should produce an empty string
1828 $result = $this->insertEdit->getQueryValueForInsert(
1829 new EditField(
1832 'protected',
1833 false,
1834 false,
1835 false,
1837 null,
1838 null,
1839 false,
1841 false,
1844 self::assertSame('', $result);
1846 // Datatype: protected with null flag set
1847 $result = $this->insertEdit->getQueryValueForInsert(
1848 new EditField(
1851 'protected',
1852 false,
1853 true,
1854 false,
1856 null,
1857 null,
1858 false,
1860 false,
1863 self::assertSame('NULL', $result);
1865 // Datatype: bit
1866 $result = $this->insertEdit->getQueryValueForInsert(
1867 new EditField(
1869 '20\'12',
1870 'bit',
1871 false,
1872 false,
1873 false,
1875 null,
1876 null,
1877 false,
1879 false,
1882 self::assertSame("b'00010'", $result);
1884 // Datatype: date
1885 $result = $this->insertEdit->getQueryValueForInsert(
1886 new EditField(
1888 '20\'12',
1889 'date',
1890 false,
1891 false,
1892 false,
1894 null,
1895 null,
1896 false,
1898 false,
1901 self::assertSame("'20\\'12'", $result);
1903 // A NULL checkbox
1904 $result = $this->insertEdit->getQueryValueForInsert(
1905 new EditField(
1908 'set',
1909 false,
1910 true,
1911 false,
1913 null,
1914 null,
1915 false,
1917 false,
1920 self::assertSame('NULL', $result);
1922 // Datatype: protected but NULL checkbox was unchecked without uploading a file
1923 $result = $this->insertEdit->getQueryValueForInsert(
1924 new EditField(
1927 'protected',
1928 false,
1929 false,
1930 true, // was previously NULL
1932 null,
1933 null,
1934 false, // no upload
1936 false,
1939 self::assertSame("''", $result);
1941 // Datatype: date with default value
1942 $result = $this->insertEdit->getQueryValueForInsert(
1943 new EditField(
1945 'current_timestamp()',
1946 'date',
1947 false,
1948 false,
1949 true, // NULL should be ignored
1951 null,
1952 null,
1953 false,
1955 false,
1958 self::assertSame('current_timestamp()', $result);
1960 // Datatype: hex without 0x
1961 $result = $this->insertEdit->getQueryValueForInsert(
1962 new EditField(
1964 '222aaafff',
1965 'hex',
1966 false,
1967 false,
1968 false,
1970 null,
1971 null,
1972 false,
1974 false,
1977 self::assertSame('0x222aaafff', $result);
1979 // Datatype: hex with 0x
1980 $result = $this->insertEdit->getQueryValueForInsert(
1981 new EditField(
1983 '0x222aaafff',
1984 'hex',
1985 false,
1986 false,
1987 false,
1989 null,
1990 null,
1991 false,
1993 false,
1996 self::assertSame('0x222aaafff', $result);
2000 * Test for getQueryValuesForUpdate
2002 public function testGetQueryValuesForUpdate(): void
2004 // Simple update
2005 $result = $this->insertEdit->getQueryValueForUpdate(
2006 new EditField(
2007 'fld',
2008 'foo',
2010 false,
2011 false,
2012 false,
2014 null,
2015 null,
2016 false,
2019 self::assertSame("`fld` = 'foo'", $result);
2021 // Update of null when it was null previously
2022 $result = $this->insertEdit->getQueryValueForUpdate(
2023 new EditField(
2024 'fld',
2025 '', // null fields will have no value
2027 false,
2028 true,
2029 true,
2031 null,
2032 null,
2033 false,
2036 self::assertSame('', $result);
2038 // Update of null when it was NOT null previously
2039 $result = $this->insertEdit->getQueryValueForUpdate(
2040 new EditField(
2041 'fld',
2042 '', // null fields will have no value
2044 false,
2045 true,
2046 false,
2048 null,
2049 '', // in edit mode the previous value will be empty string
2050 false,
2053 self::assertSame('`fld` = NULL', $result);
2055 // Update to NOT null when it was null previously
2056 $result = $this->insertEdit->getQueryValueForUpdate(
2057 new EditField(
2058 'fld',
2059 "ab'c",
2061 false,
2062 false,
2063 true,
2065 null,
2066 null,
2067 false,
2070 self::assertSame("`fld` = 'ab\'c'", $result);
2072 // Test to see if a zero-string is not ignored
2073 $result = $this->insertEdit->getQueryValueForUpdate(
2074 new EditField(
2075 'fld',
2076 '0', // zero-string provided as value
2078 false,
2079 false,
2080 false,
2082 null,
2083 null,
2084 false,
2087 self::assertSame("`fld` = '0'", $result);
2089 // Test to check if blob field that was left unchanged during edit will be ignored
2090 $result = $this->insertEdit->getQueryValueForUpdate(
2091 new EditField(
2092 'fld',
2093 '', // no value
2094 'protected',
2095 false,
2096 false,
2097 false,
2099 null,
2100 null,
2101 false,
2104 self::assertSame('', $result);
2106 // Test to see if a field will be ignored if it the value is unchanged
2107 $result = $this->insertEdit->getQueryValueForUpdate(
2108 new EditField(
2109 'fld',
2110 "a'b",
2112 false,
2113 false,
2114 false,
2116 null,
2117 "a'b",
2118 false,
2122 self::assertSame('', $result);
2124 // Test that an empty value uses the uuid function to generate a value
2125 $result = $this->insertEdit->getQueryValueForUpdate(
2126 new EditField(
2127 'fld',
2128 "''",
2129 'uuid',
2130 false,
2131 false,
2132 false,
2134 null,
2136 false,
2140 self::assertSame('`fld` = uuid()', $result);
2142 // Test that the uuid function as a value uses the uuid function to generate a value
2143 $result = $this->insertEdit->getQueryValueForUpdate(
2144 new EditField(
2145 'fld',
2146 "'uuid()'",
2147 'uuid',
2148 false,
2149 false,
2150 false,
2152 null,
2154 false,
2158 self::assertSame('`fld` = uuid()', $result);
2160 // Test that the uuid function as a value uses the uuid function to generate a value
2161 $result = $this->insertEdit->getQueryValueForUpdate(
2162 new EditField(
2163 'fld',
2164 'uuid()',
2165 'uuid',
2166 false,
2167 false,
2168 false,
2170 null,
2172 false,
2176 self::assertSame('`fld` = uuid()', $result);
2178 // Test that the uuid type does not have a default value other than null when it is nullable
2179 $result = $this->insertEdit->getQueryValueForUpdate(
2180 new EditField(
2181 'fld',
2183 'uuid',
2184 false,
2185 true,
2186 false,
2188 null,
2190 false,
2194 self::assertSame('`fld` = NULL', $result);
2198 * Test for verifyWhetherValueCanBeTruncatedAndAppendExtraData
2200 public function testVerifyWhetherValueCanBeTruncatedAndAppendExtraData(): void
2202 $extraData = ['isNeedToRecheck' => true];
2204 $_POST['where_clause'] = [];
2205 $_POST['where_clause'][0] = 1;
2207 $dbi = $this->getMockBuilder(DatabaseInterface::class)
2208 ->disableOriginalConstructor()
2209 ->getMock();
2211 $resultStub = self::createMock(DummyResult::class);
2213 $dbi->expects(self::exactly(3))
2214 ->method('tryQuery')
2215 ->with('SELECT `table`.`a` FROM `db`.`table` WHERE 1')
2216 ->willReturn($resultStub);
2218 $meta1 = FieldHelper::fromArray(['type' => MYSQLI_TYPE_TINY]);
2219 $meta2 = FieldHelper::fromArray(['type' => MYSQLI_TYPE_TINY]);
2220 $meta3 = FieldHelper::fromArray(['type' => MYSQLI_TYPE_TIMESTAMP]);
2221 $dbi->expects(self::exactly(3))
2222 ->method('getFieldsMeta')
2223 ->willReturn([$meta1], [$meta2], [$meta3]);
2225 $resultStub->expects(self::exactly(3))
2226 ->method('fetchValue')
2227 ->willReturn(false, '123', '2013-08-28 06:34:14');
2229 DatabaseInterface::$instance = $dbi;
2230 $this->insertEdit = new InsertEdit(
2231 $dbi,
2232 new Relation($dbi),
2233 new Transformations(),
2234 new FileListing(),
2235 new Template(),
2236 Config::getInstance(),
2239 $this->insertEdit->verifyWhetherValueCanBeTruncatedAndAppendExtraData('db', 'table', 'a', $extraData);
2241 self::assertFalse($extraData['isNeedToRecheck']);
2243 $this->insertEdit->verifyWhetherValueCanBeTruncatedAndAppendExtraData('db', 'table', 'a', $extraData);
2245 self::assertSame('123', $extraData['truncatableFieldValue']);
2246 self::assertTrue($extraData['isNeedToRecheck']);
2248 $this->insertEdit->verifyWhetherValueCanBeTruncatedAndAppendExtraData('db', 'table', 'a', $extraData);
2250 self::assertSame('2013-08-28 06:34:14.000000', $extraData['truncatableFieldValue']);
2251 self::assertTrue($extraData['isNeedToRecheck']);
2255 * Test for getTableColumns
2257 public function testGetTableColumns(): void
2259 $dbi = $this->getMockBuilder(DatabaseInterface::class)
2260 ->disableOriginalConstructor()
2261 ->getMock();
2263 $dbi->expects(self::once())
2264 ->method('selectDb')
2265 ->with('db');
2267 $columns = [
2268 new Column('b', 'd', null, false, '', null, '', '', ''),
2269 new Column('f', 'h', null, true, '', null, '', '', ''),
2272 $dbi->expects(self::once())
2273 ->method('getColumns')
2274 ->with('db', 'table')
2275 ->willReturn($columns);
2277 DatabaseInterface::$instance = $dbi;
2278 $this->insertEdit = new InsertEdit(
2279 $dbi,
2280 new Relation($dbi),
2281 new Transformations(),
2282 new FileListing(),
2283 new Template(),
2284 Config::getInstance(),
2287 $result = $this->insertEdit->getTableColumns('db', 'table');
2289 self::assertEquals(
2291 new Column('b', 'd', null, false, '', null, '', '', ''),
2292 new Column('f', 'h', null, true, '', null, '', '', ''),
2294 $result,
2299 * Test for determineInsertOrEdit
2301 public function testDetermineInsertOrEdit(): void
2303 $dbi = $this->getMockBuilder(DatabaseInterface::class)
2304 ->disableOriginalConstructor()
2305 ->getMock();
2307 $resultStub = self::createMock(DummyResult::class);
2309 $dbi->expects(self::exactly(2))
2310 ->method('query')
2311 ->willReturn($resultStub);
2313 DatabaseInterface::$instance = $dbi;
2314 $_POST['where_clause'] = '1';
2315 $_SESSION['edit_next'] = '1';
2316 $_POST['ShowFunctionFields'] = true;
2317 $_POST['ShowFieldTypesInDataEditView'] = true;
2318 $_POST['after_insert'] = 'edit_next';
2319 $config = Config::getInstance();
2320 $config->settings['InsertRows'] = 2;
2321 $config->settings['ShowSQL'] = false;
2322 $_POST['default_action'] = 'insert';
2324 $responseMock = $this->getMockBuilder(ResponseRenderer::class)
2325 ->disableOriginalConstructor()
2326 ->onlyMethods(['addHtml'])
2327 ->getMock();
2329 $restoreInstance = ResponseRenderer::getInstance();
2330 $response = new ReflectionProperty(ResponseRenderer::class, 'instance');
2331 $response->setValue(null, $responseMock);
2333 $this->insertEdit = new InsertEdit(
2334 $dbi,
2335 new Relation($dbi),
2336 new Transformations(),
2337 new FileListing(),
2338 new Template(),
2339 Config::getInstance(),
2342 $result = $this->insertEdit->determineInsertOrEdit('1', 'db', 'table');
2344 self::assertEquals(
2345 [false, null, [$resultStub], [[]], false, 'edit_next'],
2346 $result,
2349 // case 2
2350 unset($_POST['where_clause']);
2351 unset($_SESSION['edit_next']);
2352 $_POST['default_action'] = '';
2354 $result = $this->insertEdit->determineInsertOrEdit(null, 'db', 'table');
2356 $response->setValue(null, $restoreInstance);
2358 self::assertSame(
2359 [true, null, $resultStub, [[], []], false, 'edit_next'],
2360 $result,
2365 * Test for getCommentsMap
2367 public function testGetCommentsMap(): void
2369 $config = Config::getInstance();
2370 $config->settings['ShowPropertyComments'] = false;
2372 $dbi = $this->getMockBuilder(DatabaseInterface::class)
2373 ->disableOriginalConstructor()
2374 ->getMock();
2376 $dbi->expects(self::once())
2377 ->method('getColumns')
2378 ->with('db', 'table')
2379 ->willReturn([new Column('d', 'd', null, false, '', null, '', '', 'b')]);
2381 $dbi->expects(self::any())
2382 ->method('getTable')
2383 ->willReturn(new Table('table', 'db', $dbi));
2385 DatabaseInterface::$instance = $dbi;
2386 $this->insertEdit = new InsertEdit(
2387 $dbi,
2388 new Relation($dbi),
2389 new Transformations(),
2390 new FileListing(),
2391 new Template(),
2392 Config::getInstance(),
2395 self::assertSame(
2397 $this->insertEdit->getCommentsMap('db', 'table'),
2400 $config->settings['ShowPropertyComments'] = true;
2402 self::assertSame(
2403 ['d' => 'b'],
2404 $this->insertEdit->getCommentsMap('db', 'table'),
2409 * Test for getHtmlForIgnoreOption
2411 public function testGetHtmlForIgnoreOption(): void
2413 $expected = '<input type="checkbox" %sname="insert_ignore_1"'
2414 . ' id="insert_ignore_1"><label for="insert_ignore_1">'
2415 . 'Ignore</label><br>' . "\n";
2416 $checked = 'checked ';
2417 self::assertSame(
2418 sprintf($expected, $checked),
2419 $this->insertEdit->getHtmlForIgnoreOption(1),
2422 self::assertSame(
2423 sprintf($expected, ''),
2424 $this->insertEdit->getHtmlForIgnoreOption(1, false),
2429 * Test for getHtmlForInsertEditFormColumn
2431 public function testGetHtmlForInsertEditFormColumn(): void
2433 $_SESSION[' HMAC_secret '] = hash('sha1', 'test');
2434 InsertEdit::$pluginScripts = [];
2435 $foreigners = ['foreign_keys_data' => []];
2436 $tableColumn = new Column('col', 'varchar(20)', null, true, '', null, '', 'insert,update,select', '');
2437 $repopulate = [md5('col') => 'val'];
2438 $columnMime = [
2439 'input_transformation' => 'Input/Image_JPEG_Upload.php',
2440 'input_transformation_options' => '150',
2443 // Test w/ input transformation
2444 $actual = $this->callFunction(
2445 $this->insertEdit,
2446 InsertEdit::class,
2447 'getHtmlForInsertEditFormColumn',
2449 $tableColumn,
2453 false,
2456 false,
2457 $foreigners,
2458 'table',
2459 'db',
2462 $repopulate,
2463 $columnMime,
2468 $actual = $this->parseString($actual);
2470 self::assertStringContainsString('col', $actual);
2471 self::assertStringContainsString('<option>AES_ENCRYPT</option>', $actual);
2472 self::assertStringContainsString('<span class="column_type" dir="ltr">varchar(20)</span>', $actual);
2473 self::assertStringContainsString('<tr class="noclick">', $actual);
2474 self::assertStringContainsString('<span class="default_value hide">', $actual);
2475 self::assertStringContainsString('<img src="" width="150" height="100" alt="Image preview here">', $actual);
2476 self::assertStringContainsString(
2477 '<input type="file" '
2478 . 'name="fields_upload[multi_edit][0][d89e2ddb530bb8953b290ab0793aecb0]" '
2479 . 'accept="image/*" '
2480 . 'class="image-upload"'
2481 . '>',
2482 $actual,
2485 // Test w/o input_transformation
2486 $tableColumn = new Column('qwerty', 'datetime', null, true, '', null, '', 'insert,update,select', '');
2487 $repopulate = [md5('qwerty') => '12-10-14'];
2488 $actual = $this->callFunction(
2489 $this->insertEdit,
2490 InsertEdit::class,
2491 'getHtmlForInsertEditFormColumn',
2493 $tableColumn,
2497 true,
2500 false,
2501 $foreigners,
2502 'table',
2503 'db',
2506 $repopulate,
2512 $actual = $this->parseString($actual);
2514 self::assertStringContainsString('qwerty', $actual);
2515 self::assertStringContainsString('<option>UUID</option>', $actual);
2516 self::assertStringContainsString('<span class="column_type" dir="ltr">datetime</span>', $actual);
2517 self::assertStringContainsString(
2518 '<input type="text" name="fields[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]" value="12-10-14.000000"',
2519 $actual,
2522 self::assertStringContainsString(
2523 '<select name="funcs[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]"'
2524 . ' onchange="return verificationsAfterFieldChange(\'d8578edf8458ce06fbc5bb76a58c5ca4\','
2525 . ' \'0\', \'datetime\')" id="field_1_1">',
2526 $actual,
2528 self::assertStringContainsString('<option>DATE</option>', $actual);
2530 self::assertStringContainsString(
2531 '<input type="hidden" name="fields_null_prev[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]">',
2532 $actual,
2535 self::assertStringContainsString(
2536 '<input type="checkbox" class="checkbox_null"'
2537 . ' name="fields_null[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]" id="field_1_2"'
2538 . ' aria-label="Use the NULL value for this column.">',
2539 $actual,
2542 self::assertStringContainsString(
2543 '<input type="hidden" class="nullify_code"'
2544 . ' name="nullify_code[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]" value="5"',
2545 $actual,
2548 self::assertStringContainsString(
2549 '<input type="hidden" class="hashed_field"'
2550 . ' name="hashed_field[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]" '
2551 . 'value="d8578edf8458ce06fbc5bb76a58c5ca4">',
2552 $actual,
2555 self::assertStringContainsString(
2556 '<input type="hidden" class="multi_edit"'
2557 . ' name="multi_edit[multi_edit][0][d8578edf8458ce06fbc5bb76a58c5ca4]" value="[multi_edit][0]"',
2558 $actual,
2563 * Test for getHtmlForInsertEditRow
2565 public function testGetHtmlForInsertEditRow(): void
2567 InsertEdit::$pluginScripts = [];
2568 $config = Config::getInstance();
2569 $config->settings['LongtextDoubleTextarea'] = true;
2570 $config->settings['CharEditing'] = 'input';
2571 $config->settings['TextareaRows'] = 10;
2572 $config->settings['TextareaCols'] = 11;
2573 $foreigners = ['foreign_keys_data' => []];
2574 $tableColumns = [
2575 new Column('test', 'longtext', null, true, '', null, '', 'select,insert,update,references', ''),
2578 $actual = $this->insertEdit->getHtmlForInsertEditRow(
2580 $tableColumns,
2582 [FieldHelper::fromArray(['type' => 0, 'length' => -1])],
2583 false,
2585 false,
2586 $foreigners,
2587 'table',
2588 'db',
2591 ['wc'],
2593 self::assertStringContainsString('test', $actual);
2594 self::assertStringContainsString('<th>Column</th>', $actual);
2595 self::assertStringContainsString('<a', $actual);
2596 self::assertStringContainsString('<th class="w-50">Value</th>', $actual);
2597 self::assertStringContainsString('<span class="column_type" dir="ltr">longtext</span>', $actual);
2598 self::assertStringContainsString(
2599 '<textarea name="fields[multi_edit][0][098f6bcd4621d373cade4e832627b4f6]" id="field_1_3"'
2600 . ' data-type="CHAR" dir="ltr" rows="20" cols="22"',
2601 $actual,
2606 * Test for getHtmlForInsertEditRow based on the column privilges
2608 public function testGetHtmlForInsertEditRowBasedOnColumnPrivileges(): void
2610 InsertEdit::$pluginScripts = [];
2611 $config = Config::getInstance();
2612 $config->settings['LongtextDoubleTextarea'] = true;
2613 $config->settings['CharEditing'] = 'input';
2614 $foreigners = ['foreign_keys_data' => []];
2616 // edit
2617 $tableColumns = [
2618 new Column('foo', 'longtext', null, true, '', null, '', 'select,insert,update,references', ''),
2619 new Column('bar', 'longtext', null, true, '', null, '', 'select,insert,references', ''),
2622 $fieldMetadata = [
2623 FieldHelper::fromArray(['type' => 0, 'length' => -1]),
2624 FieldHelper::fromArray(['type' => 0, 'length' => -1]),
2625 FieldHelper::fromArray(['type' => 0, 'length' => -1]),
2628 $actual = $this->insertEdit->getHtmlForInsertEditRow(
2630 $tableColumns,
2632 $fieldMetadata,
2633 false,
2635 false,
2636 $foreigners,
2637 'table',
2638 'db',
2641 ['wc'],
2643 self::assertStringContainsString('foo', $actual);
2644 self::assertStringContainsString('bar', $actual);
2646 // insert
2647 $tableColumns = [
2648 new Column('foo', 'longtext', null, true, '', null, '', 'select,insert,update,references', ''),
2649 new Column('bar', 'longtext', null, true, '', null, '', 'select,update,references', ''),
2650 new Column('point', 'point', null, false, '', null, '', 'select,update,references', ''),
2652 $actual = $this->insertEdit->getHtmlForInsertEditRow(
2654 $tableColumns,
2656 $fieldMetadata,
2657 true,
2659 false,
2660 $foreigners,
2661 'table',
2662 'db',
2665 ['wc'],
2667 self::assertStringContainsString('foo', $actual);
2668 self::assertStringContainsString(
2669 '<textarea name="fields[multi_edit][0][37b51d194a7513e45b56f6524f2d51f2]"',
2670 $actual,
2672 self::assertStringContainsString(
2673 '<span class="text-nowrap"><img src="themes/dot.gif" title="Edit/Insert"' .
2674 ' alt="Edit/Insert" class="icon ic_b_edit">&nbsp;Edit/Insert</span>',
2675 $actual,
2680 * Convert mixed type value to string
2682 private function parseString(mixed $value): string
2684 if (is_string($value)) {
2685 return $value;
2688 if (is_object($value) || is_scalar($value)) {
2689 return (string) $value;
2692 return '';