diff --git a/.github/workflows/test-selenium.yml b/.github/workflows/test-selenium.yml index e54c265efb..2b22347fd9 100644 --- a/.github/workflows/test-selenium.yml +++ b/.github/workflows/test-selenium.yml @@ -66,7 +66,6 @@ jobs: - "Database/Operations" - "Database/Procedures" - "Database/Structure" - - "Database/Triggers" - "Export" - "Import" - "Login" @@ -79,6 +78,7 @@ jobs: - "Table/Operations" - "Table/Structure" - "Tracking" + - "Triggers" - "Xss" steps: - name: Checkout code diff --git a/js/src/database/triggers.ts b/js/src/triggers.ts similarity index 98% rename from js/src/database/triggers.ts rename to js/src/triggers.ts index 3f3db42ce2..f6f7e29e8b 100644 --- a/js/src/database/triggers.ts +++ b/js/src/triggers.ts @@ -1,11 +1,11 @@ import $ from 'jquery'; -import { AJAX } from '../modules/ajax.ts'; -import { Functions } from '../modules/functions.ts'; -import { Navigation } from '../modules/navigation.ts'; -import { ajaxRemoveMessage, ajaxShowMessage } from '../modules/ajax-message.ts'; -import getJsConfirmCommonParam from '../modules/functions/getJsConfirmCommonParam.ts'; +import { AJAX } from './modules/ajax.ts'; +import { Functions } from './modules/functions.ts'; +import { Navigation } from './modules/navigation.ts'; +import { ajaxRemoveMessage, ajaxShowMessage } from './modules/ajax-message.ts'; +import getJsConfirmCommonParam from './modules/functions/getJsConfirmCommonParam.ts'; -AJAX.registerTeardown('database/triggers.js', function () { +AJAX.registerTeardown('triggers.js', function () { $(document).off('click', 'a.ajax.add_anchor, a.ajax.edit_anchor'); $(document).off('click', 'a.ajax.export_anchor'); $(document).off('click', '#bulkActionExportButton'); @@ -559,7 +559,7 @@ const DatabaseTriggers = { } }; -AJAX.registerOnload('database/triggers.js', function () { +AJAX.registerOnload('triggers.js', function () { /** * Attach Ajax event handlers for the Add/Edit functionality. */ diff --git a/libraries/classes/Controllers/Table/TriggersController.php b/libraries/classes/Controllers/Table/TriggersController.php deleted file mode 100644 index 17257afba4..0000000000 --- a/libraries/classes/Controllers/Table/TriggersController.php +++ /dev/null @@ -1,78 +0,0 @@ -addScriptFiles(['database/triggers.js']); - - if (! $this->response->isAjax()) { - /** - * Displays the header and tabs - */ - if (! empty($GLOBALS['table']) && in_array($GLOBALS['table'], $this->dbi->getTables($GLOBALS['db']))) { - $this->checkParameters(['db', 'table']); - - $GLOBALS['urlParams'] = ['db' => $GLOBALS['db'], 'table' => $GLOBALS['table']]; - $GLOBALS['errorUrl'] = Util::getScriptNameForOption($GLOBALS['cfg']['DefaultTabTable'], 'table'); - $GLOBALS['errorUrl'] .= Url::getCommon($GLOBALS['urlParams'], '&'); - - DbTableExists::check($GLOBALS['db'], $GLOBALS['table']); - } else { - $GLOBALS['table'] = ''; - - $this->checkParameters(['db']); - - $GLOBALS['errorUrl'] = Util::getScriptNameForOption($GLOBALS['cfg']['DefaultTabDatabase'], 'database'); - $GLOBALS['errorUrl'] .= Url::getCommon(['db' => $GLOBALS['db']], '&'); - - if (! $this->hasDatabase()) { - return; - } - } - } elseif (strlen($GLOBALS['db']) > 0) { - $this->dbi->selectDb($GLOBALS['db']); - } - - /** - * Keep a list of errors that occurred while - * processing an 'Add' or 'Edit' operation. - */ - $GLOBALS['errors'] = []; - - $this->triggers->main(); - } -} diff --git a/libraries/classes/Controllers/Database/TriggersController.php b/libraries/classes/Controllers/Triggers/IndexController.php similarity index 72% rename from libraries/classes/Controllers/Database/TriggersController.php rename to libraries/classes/Controllers/Triggers/IndexController.php index 8aabcff32f..44bcf70140 100644 --- a/libraries/classes/Controllers/Database/TriggersController.php +++ b/libraries/classes/Controllers/Triggers/IndexController.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace PhpMyAdmin\Controllers\Database; +namespace PhpMyAdmin\Controllers\Triggers; use PhpMyAdmin\Controllers\AbstractController; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\DbTableExists; use PhpMyAdmin\Http\ServerRequest; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Template; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Url; use PhpMyAdmin\Util; @@ -20,7 +20,7 @@ use function strlen; /** * Triggers management. */ -class TriggersController extends AbstractController +final class IndexController extends AbstractController { public function __construct( ResponseRenderer $response, @@ -37,7 +37,7 @@ class TriggersController extends AbstractController $GLOBALS['urlParams'] ??= null; $GLOBALS['errorUrl'] ??= null; - $this->addScriptFiles(['database/triggers.js']); + $this->addScriptFiles(['triggers.js']); if (! $this->response->isAjax()) { /** @@ -73,6 +73,19 @@ class TriggersController extends AbstractController */ $GLOBALS['errors'] = []; - $this->triggers->main(); + $this->triggers->handleEditor(); + $this->triggers->export(); + + $triggers = Triggers::getDetails($this->dbi, $GLOBALS['db'], $GLOBALS['table']); + $hasTriggerPrivilege = Util::currentUserHasPrivilege('TRIGGER', $GLOBALS['db'], $GLOBALS['table']); + $isAjax = $this->response->isAjax() && empty($_REQUEST['ajax_page_request']); + + $this->render('triggers/list', [ + 'db' => $GLOBALS['db'], + 'table' => $GLOBALS['table'], + 'triggers' => $triggers, + 'has_privilege' => $hasTriggerPrivilege, + 'is_ajax' => $isAjax, + ]); } } diff --git a/libraries/classes/Menu.php b/libraries/classes/Menu.php index 73c8b43720..4f7a7b4b4e 100644 --- a/libraries/classes/Menu.php +++ b/libraries/classes/Menu.php @@ -303,10 +303,10 @@ class Menu } if (! $isSystemSchema && Util::currentUserHasPrivilege('TRIGGER', $this->db, $this->table) && ! $tableIsView) { - $tabs['triggers']['route'] = '/table/triggers'; + $tabs['triggers']['route'] = '/triggers'; $tabs['triggers']['text'] = __('Triggers'); $tabs['triggers']['icon'] = 'b_triggers'; - $tabs['triggers']['active'] = $route === '/table/triggers'; + $tabs['triggers']['active'] = $route === '/triggers'; } return $tabs; @@ -396,10 +396,10 @@ class Menu } if (Util::currentUserHasPrivilege('TRIGGER', $this->db)) { - $tabs['triggers']['route'] = '/database/triggers'; + $tabs['triggers']['route'] = '/triggers'; $tabs['triggers']['text'] = __('Triggers'); $tabs['triggers']['icon'] = 'b_triggers'; - $tabs['triggers']['active'] = $route === '/database/triggers'; + $tabs['triggers']['active'] = $route === '/triggers'; } } diff --git a/libraries/classes/Navigation/Nodes/NodeTrigger.php b/libraries/classes/Navigation/Nodes/NodeTrigger.php index e62dbf7572..26823c46be 100644 --- a/libraries/classes/Navigation/Nodes/NodeTrigger.php +++ b/libraries/classes/Navigation/Nodes/NodeTrigger.php @@ -29,11 +29,11 @@ class NodeTrigger extends Node $this->icon = ['image' => 'b_triggers', 'title' => __('Trigger')]; $this->links = [ 'text' => [ - 'route' => '/database/triggers', + 'route' => '/triggers', 'params' => ['edit_item' => 1, 'db' => null, 'item_name' => null], ], 'icon' => [ - 'route' => '/database/triggers', + 'route' => '/triggers', 'params' => ['export_item' => 1, 'db' => null, 'item_name' => null], ], ]; diff --git a/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php b/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php index 0e9f82a88b..1172aa92cc 100644 --- a/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php +++ b/libraries/classes/Navigation/Nodes/NodeTriggerContainer.php @@ -26,8 +26,8 @@ class NodeTriggerContainer extends Node $this->icon = ['image' => 'b_triggers', 'title' => __('Triggers')]; $this->links = [ - 'text' => ['route' => '/database/triggers', 'params' => ['db' => null, 'table' => null]], - 'icon' => ['route' => '/database/triggers', 'params' => ['db' => null, 'table' => null]], + 'text' => ['route' => '/triggers', 'params' => ['db' => null, 'table' => null]], + 'icon' => ['route' => '/triggers', 'params' => ['db' => null, 'table' => null]], ]; $this->realName = 'triggers'; @@ -35,8 +35,8 @@ class NodeTriggerContainer extends Node $new = NodeFactory::getInstanceForNewNode($newLabel, 'new_trigger italics'); $new->icon = ['image' => 'b_trigger_add', 'title' => $newLabel]; $new->links = [ - 'text' => ['route' => '/database/triggers', 'params' => ['add_item' => 1, 'db' => null]], - 'icon' => ['route' => '/database/triggers', 'params' => ['add_item' => 1, 'db' => null]], + 'text' => ['route' => '/triggers', 'params' => ['add_item' => 1, 'db' => null]], + 'icon' => ['route' => '/triggers', 'params' => ['add_item' => 1, 'db' => null]], ]; $this->addChild($new); } diff --git a/libraries/classes/Operations.php b/libraries/classes/Operations.php index 62ad4a2c57..fffde44bcc 100644 --- a/libraries/classes/Operations.php +++ b/libraries/classes/Operations.php @@ -7,11 +7,11 @@ namespace PhpMyAdmin; use PhpMyAdmin\ConfigStorage\Relation; use PhpMyAdmin\Database\Events; use PhpMyAdmin\Database\Routines; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\Dbal\DatabaseName; use PhpMyAdmin\Engines\Innodb; use PhpMyAdmin\Partitioning\Partition; use PhpMyAdmin\Plugins\Export\ExportSql; +use PhpMyAdmin\Triggers\Triggers; use function __; use function array_merge; diff --git a/libraries/classes/Plugins/Export/ExportHtmlword.php b/libraries/classes/Plugins/Export/ExportHtmlword.php index 27ef29f524..22d9a7d0e7 100644 --- a/libraries/classes/Plugins/Export/ExportHtmlword.php +++ b/libraries/classes/Plugins/Export/ExportHtmlword.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins\Export; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\Dbal\ResultInterface; @@ -18,6 +17,7 @@ use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem; use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem; use PhpMyAdmin\Properties\Options\Items\TextPropertyItem; use PhpMyAdmin\Properties\Plugins\ExportPluginProperties; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Util; use function __; diff --git a/libraries/classes/Plugins/Export/ExportOdt.php b/libraries/classes/Plugins/Export/ExportOdt.php index 4a0174c648..92654200b0 100644 --- a/libraries/classes/Plugins/Export/ExportOdt.php +++ b/libraries/classes/Plugins/Export/ExportOdt.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins\Export; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\FieldMetadata; @@ -19,6 +18,7 @@ use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem; use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem; use PhpMyAdmin\Properties\Options\Items\TextPropertyItem; use PhpMyAdmin\Properties\Plugins\ExportPluginProperties; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Util; use function __; diff --git a/libraries/classes/Plugins/Export/ExportSql.php b/libraries/classes/Plugins/Export/ExportSql.php index c3661c9c57..bc5788f7ed 100644 --- a/libraries/classes/Plugins/Export/ExportSql.php +++ b/libraries/classes/Plugins/Export/ExportSql.php @@ -10,7 +10,6 @@ namespace PhpMyAdmin\Plugins\Export; use PhpMyAdmin\Charsets; use PhpMyAdmin\Database\Events; use PhpMyAdmin\Database\Routines; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\FieldMetadata; @@ -30,6 +29,7 @@ use PhpMyAdmin\SqlParser\Context; use PhpMyAdmin\SqlParser\Parser; use PhpMyAdmin\SqlParser\Statements\CreateStatement; use PhpMyAdmin\SqlParser\Token; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Util; use PhpMyAdmin\Version; diff --git a/libraries/classes/Plugins/Export/ExportTexytext.php b/libraries/classes/Plugins/Export/ExportTexytext.php index a2f8b69e1e..11722847fd 100644 --- a/libraries/classes/Plugins/Export/ExportTexytext.php +++ b/libraries/classes/Plugins/Export/ExportTexytext.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins\Export; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\Dbal\ResultInterface; @@ -18,6 +17,7 @@ use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem; use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem; use PhpMyAdmin\Properties\Options\Items\TextPropertyItem; use PhpMyAdmin\Properties\Plugins\ExportPluginProperties; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Util; use function __; diff --git a/libraries/classes/Plugins/Export/ExportXml.php b/libraries/classes/Plugins/Export/ExportXml.php index e14d205737..6ea4eaa6df 100644 --- a/libraries/classes/Plugins/Export/ExportXml.php +++ b/libraries/classes/Plugins/Export/ExportXml.php @@ -6,7 +6,6 @@ namespace PhpMyAdmin\Plugins\Export; use PhpMyAdmin\Database\Events; use PhpMyAdmin\Database\Routines; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\Plugins\ExportPlugin; @@ -15,6 +14,7 @@ use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup; use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem; use PhpMyAdmin\Properties\Options\Items\HiddenPropertyItem; use PhpMyAdmin\Properties\Plugins\ExportPluginProperties; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Util; use PhpMyAdmin\Version; diff --git a/libraries/classes/Plugins/Export/Helpers/Pdf.php b/libraries/classes/Plugins/Export/Helpers/Pdf.php index d12b2a7e2d..4beeceb7fc 100644 --- a/libraries/classes/Plugins/Export/Helpers/Pdf.php +++ b/libraries/classes/Plugins/Export/Helpers/Pdf.php @@ -8,13 +8,13 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins\Export\Helpers; use PhpMyAdmin\ConfigStorage\Relation; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\Dbal\ResultInterface; use PhpMyAdmin\FieldMetadata; use PhpMyAdmin\Pdf as PdfLib; use PhpMyAdmin\Transformations; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\Util; use TCPDF_STATIC; diff --git a/libraries/classes/Query/Generator.php b/libraries/classes/Query/Generator.php index 53425c7bcb..c1fad373fe 100644 --- a/libraries/classes/Query/Generator.php +++ b/libraries/classes/Query/Generator.php @@ -215,11 +215,10 @@ class Generator . ', EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT' . ', EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER' . ' FROM information_schema.TRIGGERS' - . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '=' - . ' \'' . $escapedDb . '\''; + . ' WHERE EVENT_OBJECT_SCHEMA ' . Util::getCollateForIS() . '= ' . $escapedDb; if ($escapedTable !== null) { - $query .= ' AND EVENT_OBJECT_TABLE ' . Util::getCollateForIS() . " = '" . $escapedTable . "';"; + $query .= ' AND EVENT_OBJECT_TABLE ' . Util::getCollateForIS() . ' = ' . $escapedTable . ';'; } return $query; diff --git a/libraries/classes/Table.php b/libraries/classes/Table.php index 2e4c5b6d6f..6b9ecec88c 100644 --- a/libraries/classes/Table.php +++ b/libraries/classes/Table.php @@ -8,7 +8,6 @@ use PhpMyAdmin\ConfigStorage\Features\DisplayFeature; use PhpMyAdmin\ConfigStorage\Features\RelationFeature; use PhpMyAdmin\ConfigStorage\Features\UiPreferencesFeature; use PhpMyAdmin\ConfigStorage\Relation; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\Html\Generator; use PhpMyAdmin\Html\MySQLDocumentation; @@ -23,6 +22,7 @@ use PhpMyAdmin\SqlParser\Statements\AlterStatement; use PhpMyAdmin\SqlParser\Statements\CreateStatement; use PhpMyAdmin\SqlParser\Statements\DropStatement; use PhpMyAdmin\SqlParser\Utils\Table as TableUtils; +use PhpMyAdmin\Triggers\Triggers; use Stringable; use function __; diff --git a/libraries/classes/Triggers/Trigger.php b/libraries/classes/Triggers/Trigger.php new file mode 100644 index 0000000000..b3814d1557 --- /dev/null +++ b/libraries/classes/Triggers/Trigger.php @@ -0,0 +1,44 @@ +handleEditor(); - $this->export(); - - $items = self::getDetails($this->dbi, $GLOBALS['db'], $GLOBALS['table']); - $hasTriggerPrivilege = Util::currentUserHasPrivilege('TRIGGER', $GLOBALS['db'], $GLOBALS['table']); - $isAjax = $this->response->isAjax() && empty($_REQUEST['ajax_page_request']); - - $rows = ''; - foreach ($items as $item) { - $rows .= $this->template->render('database/triggers/row', [ - 'db' => $GLOBALS['db'], - 'table' => $GLOBALS['table'], - 'trigger' => $item, - 'has_drop_privilege' => $hasTriggerPrivilege, - 'has_edit_privilege' => $hasTriggerPrivilege, - 'row_class' => $isAjax ? 'ajaxInsert hide' : '', - ]); - } - - echo $this->template->render('database/triggers/list', [ - 'db' => $GLOBALS['db'], - 'table' => $GLOBALS['table'], - 'items' => $items, - 'rows' => $rows, - 'has_privilege' => $hasTriggerPrivilege, - ]); - } - /** * Handles editor requests for adding or editing an item */ @@ -197,7 +162,7 @@ class Triggers ); $this->response->addJSON( 'new_row', - $this->template->render('database/triggers/row', [ + $this->template->render('triggers/row', [ 'db' => $GLOBALS['db'], 'table' => $GLOBALS['table'], 'trigger' => $trigger, @@ -335,12 +300,9 @@ class Triggers */ public function getEditorForm(string $db, string $table, string $mode, array $item): string { - $query = 'SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES` '; - $query .= 'WHERE `TABLE_SCHEMA`=\'' . $this->dbi->escapeString($db) . '\' '; - $query .= 'AND `TABLE_TYPE` IN (\'BASE TABLE\', \'SYSTEM VERSIONED\')'; - $tables = $this->dbi->fetchResult($query); + $tables = $this->getTables($db); - return $this->template->render('database/triggers/editor_form', [ + return $this->template->render('triggers/editor_form', [ 'db' => $db, 'table' => $table, 'is_edit' => $mode === 'edit', @@ -469,7 +431,7 @@ class Triggers echo $message->getDisplay(); } - private function export(): void + public function export(): void { if (empty($_GET['export_item']) || empty($_GET['item_name'])) { return; @@ -496,7 +458,7 @@ class Triggers exit; } - $this->response->addHTML($this->template->render('database/triggers/export', [ + $this->response->addHTML($this->template->render('triggers/export', [ 'data' => $exportData, 'item_name' => $itemName, ])); @@ -539,48 +501,46 @@ class Triggers $result = []; if (! $GLOBALS['cfg']['Server']['DisableIS']) { $query = QueryGenerator::getInformationSchemaTriggersRequest( - $dbi->escapeString($db), - $table === '' ? null : $dbi->escapeString($table), + $dbi->quoteString($db), + $table === '' ? null : $dbi->quoteString($table), ); } else { $query = 'SHOW TRIGGERS FROM ' . Util::backquote($db); if ($table !== '') { - $query .= " LIKE '" . $dbi->escapeString($table) . "';"; + $query .= ' LIKE ' . $dbi->quoteString($table) . ';'; } } + /** @var mixed[][] $triggers */ $triggers = $dbi->fetchResult($query); foreach ($triggers as $trigger) { - if ($GLOBALS['cfg']['Server']['DisableIS']) { - $trigger['TRIGGER_NAME'] = $trigger['Trigger']; - $trigger['ACTION_TIMING'] = $trigger['Timing']; - $trigger['EVENT_MANIPULATION'] = $trigger['Event']; - $trigger['EVENT_OBJECT_TABLE'] = $trigger['Table']; - $trigger['ACTION_STATEMENT'] = $trigger['Statement']; - $trigger['DEFINER'] = $trigger['Definer']; + $newTrigger = Trigger::tryFromArray($trigger); + if ($newTrigger === null) { + continue; } $oneResult = []; - $oneResult['name'] = $trigger['TRIGGER_NAME']; - $oneResult['table'] = $trigger['EVENT_OBJECT_TABLE']; - $oneResult['action_timing'] = $trigger['ACTION_TIMING']; - $oneResult['event_manipulation'] = $trigger['EVENT_MANIPULATION']; - $oneResult['definition'] = $trigger['ACTION_STATEMENT']; - $oneResult['definer'] = $trigger['DEFINER']; + $oneResult['name'] = $newTrigger->name; + $oneResult['table'] = $newTrigger->table; + $oneResult['action_timing'] = $newTrigger->timing; + $oneResult['event_manipulation'] = $newTrigger->event; + $oneResult['definition'] = $newTrigger->statement; + $oneResult['definer'] = $newTrigger->definer; // do not prepend the schema name; this way, importing the // definition into another schema will work - $oneResult['full_trigger_name'] = Util::backquote($trigger['TRIGGER_NAME']); - $oneResult['drop'] = 'DROP TRIGGER IF EXISTS ' - . $oneResult['full_trigger_name']; - $oneResult['create'] = 'CREATE TRIGGER ' - . $oneResult['full_trigger_name'] . ' ' - . $trigger['ACTION_TIMING'] . ' ' - . $trigger['EVENT_MANIPULATION'] - . ' ON ' . Util::backquote($trigger['EVENT_OBJECT_TABLE']) - . "\n" . ' FOR EACH ROW ' - . $trigger['ACTION_STATEMENT'] . "\n" . $delimiter . "\n"; + $oneResult['full_trigger_name'] = Util::backquote($newTrigger->name); + $oneResult['drop'] = 'DROP TRIGGER IF EXISTS ' . $oneResult['full_trigger_name']; + $oneResult['create'] = sprintf( + "CREATE TRIGGER %s %s %s ON %s\n FOR EACH ROW %s\n%s\n", + $oneResult['full_trigger_name'], + $newTrigger->timing, + $newTrigger->event, + Util::backquote($newTrigger->table), + $newTrigger->statement, + $delimiter, + ); $result[] = $oneResult; } @@ -591,4 +551,19 @@ class Triggers return $result; } + + /** @return list */ + private function getTables(string $db): array + { + $query = sprintf( + 'SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=%s' + . " AND `TABLE_TYPE` IN ('BASE TABLE', 'SYSTEM VERSIONED')", + $this->dbi->quoteString($db), + ); + $tables = $this->dbi->fetchResult($query); + Assert::allStringNotEmpty($tables); + Assert::isList($tables); + + return $tables; + } } diff --git a/libraries/routes.php b/libraries/routes.php index f384ec45ae..18d8e1d2ca 100644 --- a/libraries/routes.php +++ b/libraries/routes.php @@ -35,6 +35,7 @@ use PhpMyAdmin\Controllers\TableController; use PhpMyAdmin\Controllers\ThemesController; use PhpMyAdmin\Controllers\ThemeSetController; use PhpMyAdmin\Controllers\Transformation; +use PhpMyAdmin\Controllers\Triggers; use PhpMyAdmin\Controllers\UserPasswordController; use PhpMyAdmin\Controllers\VersionCheckController; use PhpMyAdmin\Controllers\View; @@ -108,7 +109,6 @@ return static function (RouteCollector $routes): void { $routes->post('/show-create', Database\Structure\ShowCreateController::class); }); $routes->addRoute(['GET', 'POST'], '/tracking', Database\TrackingController::class); - $routes->addRoute(['GET', 'POST'], '/triggers', Database\TriggersController::class); }); $routes->post('/databases', DatabaseController::class); $routes->addRoute(['GET', 'POST'], '/error-report', ErrorReportController::class); @@ -290,7 +290,6 @@ return static function (RouteCollector $routes): void { $routes->post('/unique', Table\Structure\UniqueController::class); }); $routes->addRoute(['GET', 'POST'], '/tracking', Table\TrackingController::class); - $routes->addRoute(['GET', 'POST'], '/triggers', Table\TriggersController::class); $routes->addRoute(['GET', 'POST'], '/zoom-search', Table\ZoomSearchController::class); }); $routes->post('/tables', TableController::class); @@ -302,6 +301,7 @@ return static function (RouteCollector $routes): void { $routes->addRoute(['GET', 'POST'], '/overview', Transformation\OverviewController::class); $routes->addRoute(['GET', 'POST'], '/wrapper', Transformation\WrapperController::class); }); + $routes->addRoute(['GET', 'POST'], '/triggers', Triggers\IndexController::class); $routes->addRoute(['GET', 'POST'], '/user-password', UserPasswordController::class); $routes->addRoute(['GET', 'POST'], '/version-check', VersionCheckController::class); $routes->addGroup('/view', static function (RouteCollector $routes): void { diff --git a/libraries/services.php b/libraries/services.php index dfae78d440..fe28313e42 100644 --- a/libraries/services.php +++ b/libraries/services.php @@ -14,7 +14,6 @@ use PhpMyAdmin\Database\Designer; use PhpMyAdmin\Database\Designer\Common; use PhpMyAdmin\Database\Events; use PhpMyAdmin\Database\Routines; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\ErrorHandler; use PhpMyAdmin\ErrorReport; @@ -50,6 +49,7 @@ use PhpMyAdmin\Theme\ThemeManager; use PhpMyAdmin\Tracking\Tracking; use PhpMyAdmin\Tracking\TrackingChecker; use PhpMyAdmin\Transformations; +use PhpMyAdmin\Triggers\Triggers; use PhpMyAdmin\UserPassword; use PhpMyAdmin\UserPreferences; use PhpMyAdmin\Utils\HttpRequest; diff --git a/libraries/services_controllers.php b/libraries/services_controllers.php index 394869c3f8..d48c183cdf 100644 --- a/libraries/services_controllers.php +++ b/libraries/services_controllers.php @@ -34,6 +34,7 @@ use PhpMyAdmin\Controllers\TableController; use PhpMyAdmin\Controllers\ThemesController; use PhpMyAdmin\Controllers\ThemeSetController; use PhpMyAdmin\Controllers\Transformation; +use PhpMyAdmin\Controllers\Triggers; use PhpMyAdmin\Controllers\UserPasswordController; use PhpMyAdmin\Controllers\VersionCheckController; use PhpMyAdmin\Controllers\View; @@ -349,15 +350,6 @@ return [ '$dbi' => '@dbi', ], ], - Database\TriggersController::class => [ - 'class' => Database\TriggersController::class, - 'arguments' => [ - '$response' => '@response', - '$template' => '@template', - '$dbi' => '@dbi', - '$triggers' => '@triggers', - ], - ], DatabaseController::class => [ 'class' => DatabaseController::class, 'arguments' => ['$response' => '@response', '$template' => '@template'], @@ -1417,8 +1409,8 @@ return [ '$trackingChecker' => '@tracking_checker', ], ], - Table\TriggersController::class => [ - 'class' => Table\TriggersController::class, + Triggers\IndexController::class => [ + 'class' => Triggers\IndexController::class, 'arguments' => [ '$response' => '@response', '$template' => '@template', diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 930597dbd3..9aafd09bea 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3135,136 +3135,6 @@ parameters: count: 1 path: libraries/classes/Database/Search.php - - - message: "#^Cannot access offset 'ACTION_STATEMENT' on mixed\\.$#" - count: 3 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'ACTION_TIMING' on mixed\\.$#" - count: 3 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'DEFINER' on mixed\\.$#" - count: 2 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'Definer' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'EVENT_MANIPULATION' on mixed\\.$#" - count: 3 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'EVENT_OBJECT_TABLE' on mixed\\.$#" - count: 3 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'Event' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'Statement' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'TRIGGER_NAME' on mixed\\.$#" - count: 3 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'Table' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'Timing' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'Trigger' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'action_timing' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'create' on mixed\\.$#" - count: 2 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'definer' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'definition' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'drop' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'event_manipulation' on mixed\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'name' on mixed\\.$#" - count: 4 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Cannot access offset 'table' on mixed\\.$#" - count: 2 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Offset 'create' does not exist on array\\|null\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Offset 'drop' does not exist on array\\|null\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Parameter \\#1 \\$createStatement of method PhpMyAdmin\\\\Database\\\\Triggers\\:\\:checkResult\\(\\) expects string, mixed given\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Parameter \\#1 \\$identifier of static method PhpMyAdmin\\\\Util\\:\\:backquote\\(\\) expects string\\|Stringable\\|null, mixed given\\.$#" - count: 4 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Parameter \\#1 \\$query of method PhpMyAdmin\\\\DatabaseInterface\\:\\:tryQuery\\(\\) expects string, mixed given\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - - - message: "#^Parameter \\#1 \\$string of function trim expects string, mixed given\\.$#" - count: 1 - path: libraries/classes/Database/Triggers.php - - message: "#^Binary operation \"\\+\" between string\\|null and string\\|null results in an error\\.$#" count: 1 @@ -5741,7 +5611,7 @@ parameters: path: libraries/classes/Plugins/Export/ExportXml.php - - message: "#^Parameter \\#3 \\$table of static method PhpMyAdmin\\\\Database\\\\Triggers\\:\\:getDetails\\(\\) expects string, mixed given\\.$#" + message: "#^Parameter \\#3 \\$table of static method PhpMyAdmin\\\\Triggers\\\\Triggers\\:\\:getDetails\\(\\) expects string, mixed given\\.$#" count: 1 path: libraries/classes/Plugins/Export/ExportXml.php @@ -8500,6 +8370,76 @@ parameters: count: 1 path: libraries/classes/Transformations.php + - + message: "#^Cannot access offset 'action_timing' on mixed\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'create' on mixed\\.$#" + count: 2 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'definer' on mixed\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'definition' on mixed\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'drop' on mixed\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'event_manipulation' on mixed\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'name' on mixed\\.$#" + count: 4 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Cannot access offset 'table' on mixed\\.$#" + count: 2 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Offset 'create' does not exist on array\\|null\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Offset 'drop' does not exist on array\\|null\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Parameter \\#1 \\$createStatement of method PhpMyAdmin\\\\Triggers\\\\Triggers\\:\\:checkResult\\(\\) expects string, mixed given\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Parameter \\#1 \\$identifier of static method PhpMyAdmin\\\\Util\\:\\:backquote\\(\\) expects string\\|Stringable\\|null, mixed given\\.$#" + count: 2 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Parameter \\#1 \\$query of method PhpMyAdmin\\\\DatabaseInterface\\:\\:tryQuery\\(\\) expects string, mixed given\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + + - + message: "#^Parameter \\#1 \\$string of function trim expects string, mixed given\\.$#" + count: 1 + path: libraries/classes/Triggers/Triggers.php + - message: "#^Call to an undefined method object\\:\\:configure\\(\\)\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index ecbed742f6..5d39590c72 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1681,22 +1681,6 @@ __construct - - - - - - - - - - - __construct - - - $request - - $request @@ -4216,19 +4200,6 @@ - - - - - - - - - - - $request - - @@ -4332,6 +4303,19 @@ __construct + + + + + + + + + + + $request + + @@ -5505,159 +5489,6 @@ searchTypeDescription]]> - - - escapeString - escapeString - escapeString - escapeString - - - - - - - - - - - $createItem - $exportData - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $createItem - $exportData - $item - - - - - - - - - - - - - - - - $string - $temp - $trigger - $trigger - $trigger - - - - - - - $value - $value - - - $string - - - - - - - - - - - - - - - - $itemName - $itemName - - - - - - - - - - - - - - - - - $createItem - - - - - - - - - - isSuccess - - escapeString @@ -13037,6 +12868,111 @@ bool + + + + + + + + + + + $createItem + $exportData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $createItem + $exportData + + + + + + + + + + $string + $temp + $trigger + $trigger + $value + $value + + + $string + + + + + + + + + + + + + $itemName + $itemName + + + + + + + + + + + + + + + + + $createItem + + + + + + + + + + isSuccess + + diff --git a/templates/database/triggers/editor_form.twig b/templates/triggers/editor_form.twig similarity index 97% rename from templates/database/triggers/editor_form.twig rename to templates/triggers/editor_form.twig index adcc6d2613..f80f9994c5 100644 --- a/templates/database/triggers/editor_form.twig +++ b/templates/triggers/editor_form.twig @@ -1,4 +1,4 @@ -
+ {{ get_hidden_inputs(db, table) }} {% if is_edit %} diff --git a/templates/database/triggers/export.twig b/templates/triggers/export.twig similarity index 100% rename from templates/database/triggers/export.twig rename to templates/triggers/export.twig diff --git a/templates/database/triggers/list.twig b/templates/triggers/list.twig similarity index 69% rename from templates/database/triggers/list.twig rename to templates/triggers/list.twig index 192324a06f..2e193e866e 100644 --- a/templates/database/triggers/list.twig +++ b/templates/triggers/list.twig @@ -23,20 +23,20 @@ - + {{ get_hidden_inputs(db, table) }} -
+
{% trans 'There are no triggers to display.' %}
- +
@@ -52,7 +52,20 @@ {% for i in 0..(table is empty ? 7 : 6) %}{% endfor %} - {{ rows|raw }} + {% for trigger in triggers %} + {{ include( + 'triggers/row.twig', + { + 'db': db, + 'table': table, + 'trigger': trigger, + 'has_drop_privilege': has_privilege, + 'has_edit_privilege': has_privilege, + 'row_class': is_ajax ? 'ajaxInsert hide' : '', + }, + with_context = false + ) }} + {% endfor %}
diff --git a/templates/database/triggers/row.twig b/templates/triggers/row.twig similarity index 81% rename from templates/database/triggers/row.twig rename to templates/triggers/row.twig index 1aab7e97a9..9820dc4045 100644 --- a/templates/database/triggers/row.twig +++ b/templates/triggers/row.twig @@ -8,7 +8,7 @@ {% if table is empty %} - {{ trigger.table }} + {{ trigger.table }} {% endif %} @@ -19,7 +19,7 @@ {% if has_edit_privilege %} - render('database/triggers/row', [ + $expected = $template->render('triggers/list', [ 'db' => $GLOBALS['db'], 'table' => $GLOBALS['table'], - 'trigger' => $items[0], - 'has_drop_privilege' => true, - 'has_edit_privilege' => true, - 'row_class' => '', + 'triggers' => $triggers, + 'has_privilege' => true, + 'is_ajax' => false, ]); - $this->expectOutputString($template->render('database/triggers/list', [ - 'db' => $GLOBALS['db'], - 'table' => $GLOBALS['table'], - 'items' => $items, - 'rows' => $rows, - 'has_privilege' => true, - ])); + $this->assertSame($expected, $response->getHTMLResult()); } } diff --git a/test/classes/Navigation/Nodes/NodeTriggerContainerTest.php b/test/classes/Navigation/Nodes/NodeTriggerContainerTest.php index a7a6d511cf..901141828e 100644 --- a/test/classes/Navigation/Nodes/NodeTriggerContainerTest.php +++ b/test/classes/Navigation/Nodes/NodeTriggerContainerTest.php @@ -29,8 +29,8 @@ class NodeTriggerContainerTest extends AbstractTestCase $parent = new NodeTriggerContainer(); $this->assertEquals( [ - 'text' => ['route' => '/database/triggers', 'params' => ['db' => null, 'table' => null]], - 'icon' => ['route' => '/database/triggers', 'params' => ['db' => null, 'table' => null]], + 'text' => ['route' => '/triggers', 'params' => ['db' => null, 'table' => null]], + 'icon' => ['route' => '/triggers', 'params' => ['db' => null, 'table' => null]], ], $parent->links, ); diff --git a/test/classes/Navigation/Nodes/NodeTriggerTest.php b/test/classes/Navigation/Nodes/NodeTriggerTest.php index 01193bae3a..8018c7c328 100644 --- a/test/classes/Navigation/Nodes/NodeTriggerTest.php +++ b/test/classes/Navigation/Nodes/NodeTriggerTest.php @@ -30,11 +30,11 @@ class NodeTriggerTest extends AbstractTestCase $this->assertEquals( [ 'text' => [ - 'route' => '/database/triggers', + 'route' => '/triggers', 'params' => ['edit_item' => 1, 'db' => null, 'item_name' => null], ], 'icon' => [ - 'route' => '/database/triggers', + 'route' => '/triggers', 'params' => ['export_item' => 1, 'db' => null, 'item_name' => null], ], ], diff --git a/test/classes/Triggers/TriggerTest.php b/test/classes/Triggers/TriggerTest.php new file mode 100644 index 0000000000..4f30c90c66 --- /dev/null +++ b/test/classes/Triggers/TriggerTest.php @@ -0,0 +1,96 @@ +assertNull(Trigger::tryFromArray([])); + } + + /** + * @param mixed[] $trigger + * + * @dataProvider arrayWithValidValuesProvider + */ + public function testTryFromArrayWithValidValues(array $trigger): void + { + $actual = Trigger::tryFromArray($trigger); + $this->assertNotNull($actual); + $this->assertSame('trigger_name', $actual->name); + $this->assertSame('BEFORE', $actual->timing); + $this->assertSame('UPDATE', $actual->event); + $this->assertSame('test_table', $actual->table); + $this->assertSame('BEGIN END', $actual->statement); + $this->assertSame('definer@localhost', $actual->definer); + } + + /** @return iterable */ + public static function arrayWithValidValuesProvider(): iterable + { + yield [ + [ + 'Trigger' => 'trigger_name', + 'Timing' => 'BEFORE', + 'Event' => 'UPDATE', + 'Table' => 'test_table', + 'Statement' => 'BEGIN END', + 'Definer' => 'definer@localhost', + ], + ]; + + yield [ + [ + 'TRIGGER_NAME' => 'trigger_name', + 'ACTION_TIMING' => 'BEFORE', + 'EVENT_MANIPULATION' => 'UPDATE', + 'EVENT_OBJECT_TABLE' => 'test_table', + 'ACTION_STATEMENT' => 'BEGIN END', + 'DEFINER' => 'definer@localhost', + ], + ]; + } + + /** + * @param mixed[] $trigger + * + * @dataProvider arrayWithInvalidValuesProvider + */ + public function testTryFromArrayWithInvalidValues(array $trigger): void + { + $this->assertNull(Trigger::tryFromArray($trigger)); + } + + /** @return iterable */ + public static function arrayWithInvalidValuesProvider(): iterable + { + yield [ + [ + 'Trigger' => null, + 'Timing' => null, + 'Event' => null, + 'Table' => null, + 'Statement' => null, + 'Definer' => null, + ], + ]; + + yield [ + [ + 'TRIGGER_NAME' => null, + 'ACTION_TIMING' => null, + 'EVENT_MANIPULATION' => null, + 'EVENT_OBJECT_TABLE' => null, + 'ACTION_STATEMENT' => null, + 'DEFINER' => null, + ], + ]; + } +} diff --git a/test/classes/Database/TriggersTest.php b/test/classes/Triggers/TriggersTest.php similarity index 59% rename from test/classes/Database/TriggersTest.php rename to test/classes/Triggers/TriggersTest.php index 4f05374f5c..61b6c06f5f 100644 --- a/test/classes/Database/TriggersTest.php +++ b/test/classes/Triggers/TriggersTest.php @@ -2,14 +2,17 @@ declare(strict_types=1); -namespace PhpMyAdmin\Tests\Database; +namespace PhpMyAdmin\Tests\Triggers; -use PhpMyAdmin\Database\Triggers; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Template; use PhpMyAdmin\Tests\AbstractTestCase; +use PhpMyAdmin\Triggers\Triggers; -/** @covers \PhpMyAdmin\Database\Triggers */ +/** + * @covers \PhpMyAdmin\Triggers\Trigger + * @covers \PhpMyAdmin\Triggers\Triggers + */ class TriggersTest extends AbstractTestCase { private Triggers $triggers; @@ -324,4 +327,132 @@ class TriggersTest extends AbstractTestCase ], ]; } + + public function testGetDetails(): void + { + $GLOBALS['cfg']['Server']['DisableIS'] = true; + $dbiDummy = $this->createDbiDummy(); + $dbiDummy->addResult( + 'SHOW TRIGGERS FROM `test_db`', + [ + ['test_trigger', 'INSERT', 'test_table', 'BEGIN END', 'AFTER', 'definer@localhost'], + ['a_trigger', 'UPDATE', 'test_table2', 'BEGIN END', 'BEFORE', 'definer2@localhost'], + ], + ['Trigger', 'Event', 'Table', 'Statement', 'Timing', 'Definer'], + ); + + $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db'); + $expected = [ + [ + 'name' => 'a_trigger', + 'table' => 'test_table2', + 'action_timing' => 'BEFORE', + 'event_manipulation' => 'UPDATE', + 'definition' => 'BEGIN END', + 'definer' => 'definer2@localhost', + 'full_trigger_name' => '`a_trigger`', + 'drop' => 'DROP TRIGGER IF EXISTS `a_trigger`', + 'create' => "CREATE TRIGGER `a_trigger` BEFORE UPDATE ON `test_table2`\n FOR EACH ROW BEGIN END\n//\n", + ], + [ + 'name' => 'test_trigger', + 'table' => 'test_table', + 'action_timing' => 'AFTER', + 'event_manipulation' => 'INSERT', + 'definition' => 'BEGIN END', + 'definer' => 'definer@localhost', + 'full_trigger_name' => '`test_trigger`', + 'drop' => 'DROP TRIGGER IF EXISTS `test_trigger`', + 'create' => "CREATE TRIGGER `test_trigger` AFTER INSERT ON `test_table`\n FOR EACH ROW BEGIN END\n//\n", + ], + ]; + $this->assertSame($expected, $triggers); + } + + public function testGetDetails2(): void + { + $GLOBALS['cfg']['Server']['DisableIS'] = true; + $dbiDummy = $this->createDbiDummy(); + $dbiDummy->addResult( + "SHOW TRIGGERS FROM `test_db` LIKE 'test_table2';", + [['a_trigger', 'UPDATE', 'test_table2', 'BEGIN END', 'BEFORE', 'definer2@localhost']], + ['Trigger', 'Event', 'Table', 'Statement', 'Timing', 'Definer'], + ); + + $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db', 'test_table2', '$$'); + $expected = [ + [ + 'name' => 'a_trigger', + 'table' => 'test_table2', + 'action_timing' => 'BEFORE', + 'event_manipulation' => 'UPDATE', + 'definition' => 'BEGIN END', + 'definer' => 'definer2@localhost', + 'full_trigger_name' => '`a_trigger`', + 'drop' => 'DROP TRIGGER IF EXISTS `a_trigger`', + 'create' => "CREATE TRIGGER `a_trigger` BEFORE UPDATE ON `test_table2`\n FOR EACH ROW BEGIN END\n$$\n", + ], + ]; + $this->assertSame($expected, $triggers); + } + + /** @covers \PhpMyAdmin\Query\Generator */ + public function testGetDetails3(): void + { + $GLOBALS['cfg']['Server']['DisableIS'] = false; + $dbiDummy = $this->createDbiDummy(); + // phpcs:disable Generic.Files.LineLength.TooLong + $dbiDummy->addResult( + "SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT, EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_SCHEMA COLLATE utf8_bin= 'test_db'", + [['test_db', 'test_trigger', 'DELETE', 'test_table', 'AFTER', 'BEGIN END', 'test_db', 'test_table', 'definer@localhost']], + ['TRIGGER_SCHEMA', 'TRIGGER_NAME', 'EVENT_MANIPULATION', 'EVENT_OBJECT_TABLE', 'ACTION_TIMING', 'ACTION_STATEMENT', 'EVENT_OBJECT_SCHEMA', 'EVENT_OBJECT_TABLE', 'DEFINER'], + ); + // phpcs:enable + + $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db'); + $expected = [ + [ + 'name' => 'test_trigger', + 'table' => 'test_table', + 'action_timing' => 'AFTER', + 'event_manipulation' => 'DELETE', + 'definition' => 'BEGIN END', + 'definer' => 'definer@localhost', + 'full_trigger_name' => '`test_trigger`', + 'drop' => 'DROP TRIGGER IF EXISTS `test_trigger`', + 'create' => "CREATE TRIGGER `test_trigger` AFTER DELETE ON `test_table`\n FOR EACH ROW BEGIN END\n//\n", + ], + ]; + $this->assertSame($expected, $triggers); + } + + /** @covers \PhpMyAdmin\Query\Generator */ + public function testGetDetails4(): void + { + $GLOBALS['cfg']['Server']['DisableIS'] = false; + $dbiDummy = $this->createDbiDummy(); + // phpcs:disable Generic.Files.LineLength.TooLong + $dbiDummy->addResult( + "SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT, EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, DEFINER FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_SCHEMA COLLATE utf8_bin= 'test_db' AND EVENT_OBJECT_TABLE COLLATE utf8_bin = 'test_table';", + [['test_db', 'test_trigger', 'DELETE', 'test_table', 'AFTER', 'BEGIN END', 'test_db', 'test_table', 'definer@localhost']], + ['TRIGGER_SCHEMA', 'TRIGGER_NAME', 'EVENT_MANIPULATION', 'EVENT_OBJECT_TABLE', 'ACTION_TIMING', 'ACTION_STATEMENT', 'EVENT_OBJECT_SCHEMA', 'EVENT_OBJECT_TABLE', 'DEFINER'], + ); + // phpcs:enable + + $triggers = Triggers::getDetails($this->createDatabaseInterface($dbiDummy), 'test_db', 'test_table'); + $expected = [ + [ + 'name' => 'test_trigger', + 'table' => 'test_table', + 'action_timing' => 'AFTER', + 'event_manipulation' => 'DELETE', + 'definition' => 'BEGIN END', + 'definer' => 'definer@localhost', + 'full_trigger_name' => '`test_trigger`', + 'drop' => 'DROP TRIGGER IF EXISTS `test_trigger`', + 'create' => "CREATE TRIGGER `test_trigger` AFTER DELETE ON `test_table`\n FOR EACH ROW BEGIN END\n//\n", + ], + ]; + $this->assertSame($expected, $triggers); + } } diff --git a/test/selenium/Database/TriggersTest.php b/test/selenium/TriggersTest.php similarity index 98% rename from test/selenium/Database/TriggersTest.php rename to test/selenium/TriggersTest.php index afea588550..dbaea9d442 100644 --- a/test/selenium/Database/TriggersTest.php +++ b/test/selenium/TriggersTest.php @@ -2,9 +2,7 @@ declare(strict_types=1); -namespace PhpMyAdmin\Tests\Selenium\Database; - -use PhpMyAdmin\Tests\Selenium\TestBase; +namespace PhpMyAdmin\Tests\Selenium; /** @coversNothing */ class TriggersTest extends TestBase diff --git a/webpack.config.cjs b/webpack.config.cjs index 936aa33ebe..b95f815805 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -31,7 +31,6 @@ module.exports = [ 'database/search': rootPath + '/js/src/database/search.ts', 'database/structure': rootPath + '/js/src/database/structure.ts', 'database/tracking': rootPath + '/js/src/database/tracking.ts', - 'database/triggers': rootPath + '/js/src/database/triggers.ts', 'designer/init': rootPath + '/js/src/designer/init.ts', 'drag_drop_import': rootPath + '/js/src/drag_drop_import.ts', 'error_report': rootPath + '/js/src/error_report.ts', @@ -77,6 +76,7 @@ module.exports = [ 'transformations/sql_editor': rootPath + '/js/src/transformations/sql_editor.ts', 'transformations/xml': rootPath + '/js/src/transformations/xml.ts', 'transformations/xml_editor': rootPath + '/js/src/transformations/xml_editor.ts', + 'triggers': rootPath + '/js/src/triggers.ts', 'u2f': rootPath + '/js/src/u2f.ts', 'validator-messages': rootPath + '/js/src/validator-messages.ts', 'webauthn': rootPath + '/js/src/webauthn.ts',