"Fossies" - the Fresh Open Source Software Archive

Member "phpMyAdmin-5.1.0-all-languages/libraries/classes/Controllers/Table/RelationController.php" (24 Feb 2021, 12084 Bytes) of package /linux/www/phpMyAdmin-5.1.0-all-languages.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "RelationController.php": 5.0.4-english_vs_5.1.0-english.

    1 <?php
    2 
    3 declare(strict_types=1);
    4 
    5 namespace PhpMyAdmin\Controllers\Table;
    6 
    7 use PhpMyAdmin\Core;
    8 use PhpMyAdmin\DatabaseInterface;
    9 use PhpMyAdmin\Html\Generator;
   10 use PhpMyAdmin\Index;
   11 use PhpMyAdmin\Relation;
   12 use PhpMyAdmin\Response;
   13 use PhpMyAdmin\Table;
   14 use PhpMyAdmin\Template;
   15 use PhpMyAdmin\Util;
   16 use function array_key_exists;
   17 use function array_keys;
   18 use function array_values;
   19 use function htmlspecialchars;
   20 use function mb_strtoupper;
   21 use function md5;
   22 use function strtoupper;
   23 use function uksort;
   24 use function usort;
   25 
   26 /**
   27  * Display table relations for viewing and editing.
   28  *
   29  * Includes phpMyAdmin relations and InnoDB relations.
   30  */
   31 final class RelationController extends AbstractController
   32 {
   33     /** @var Relation */
   34     private $relation;
   35 
   36     /** @var DatabaseInterface */
   37     private $dbi;
   38 
   39     /**
   40      * @param Response          $response
   41      * @param string            $db       Database name
   42      * @param string            $table    Table name
   43      * @param DatabaseInterface $dbi
   44      */
   45     public function __construct(
   46         $response,
   47         Template $template,
   48         $db,
   49         $table,
   50         Relation $relation,
   51         $dbi
   52     ) {
   53         parent::__construct($response, $template, $db, $table);
   54         $this->relation = $relation;
   55         $this->dbi = $dbi;
   56     }
   57 
   58     /**
   59      * Index
   60      */
   61     public function index(): void
   62     {
   63         global $route;
   64 
   65         $options = [
   66             'CASCADE' => 'CASCADE',
   67             'SET_NULL' => 'SET NULL',
   68             'NO_ACTION' => 'NO ACTION',
   69             'RESTRICT' => 'RESTRICT',
   70         ];
   71 
   72         $table = $this->dbi->getTable($this->db, $this->table);
   73         $storageEngine = mb_strtoupper((string) $table->getStatusInfo('Engine'));
   74 
   75         $cfgRelation = $this->relation->getRelationsParam();
   76 
   77         $relations = [];
   78         if ($cfgRelation['relwork']) {
   79             $relations = $this->relation->getForeigners(
   80                 $this->db,
   81                 $this->table,
   82                 '',
   83                 'internal'
   84             );
   85         }
   86 
   87         $relationsForeign = [];
   88         if (Util::isForeignKeySupported($storageEngine)) {
   89             $relationsForeign = $this->relation->getForeigners(
   90                 $this->db,
   91                 $this->table,
   92                 '',
   93                 'foreign'
   94             );
   95         }
   96 
   97         // Send table of column names to populate corresponding dropdowns depending
   98         // on the current selection
   99         if (isset($_POST['getDropdownValues'])
  100             && $_POST['getDropdownValues'] === 'true'
  101         ) {
  102             // if both db and table are selected
  103             if (isset($_POST['foreignTable'])) {
  104                 $this->getDropdownValueForTable();
  105             } else { // if only the db is selected
  106                 $this->getDropdownValueForDatabase($storageEngine);
  107             }
  108 
  109             return;
  110         }
  111 
  112         $this->addScriptFiles(['table/relation.js', 'indexes.js']);
  113 
  114         // Set the database
  115         $this->dbi->selectDb($this->db);
  116 
  117         // updates for Internal relations
  118         if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
  119             $this->updateForInternalRelation($table, $cfgRelation, $relations);
  120         }
  121 
  122         // updates for foreign keys
  123         $this->updateForForeignKeys($table, $options, $relationsForeign);
  124 
  125         // Updates for display field
  126         if ($cfgRelation['displaywork'] && isset($_POST['display_field'])) {
  127             $this->updateForDisplayField($table, $cfgRelation);
  128         }
  129 
  130         // If we did an update, refresh our data
  131         if (isset($_POST['destination_db']) && $cfgRelation['relwork']) {
  132             $relations = $this->relation->getForeigners(
  133                 $this->db,
  134                 $this->table,
  135                 '',
  136                 'internal'
  137             );
  138         }
  139         if (isset($_POST['destination_foreign_db'])
  140             && Util::isForeignKeySupported($storageEngine)
  141         ) {
  142             $relationsForeign = $this->relation->getForeigners(
  143                 $this->db,
  144                 $this->table,
  145                 '',
  146                 'foreign'
  147             );
  148         }
  149 
  150         /**
  151          * Dialog
  152          */
  153         // Now find out the columns of our $table
  154         // need to use DatabaseInterface::QUERY_STORE with $this->dbi->numRows()
  155         // in mysqli
  156         $columns = $this->dbi->getColumns($this->db, $this->table);
  157 
  158         $column_array = [];
  159         $column_hash_array = [];
  160         $column_array[''] = '';
  161         foreach ($columns as $column) {
  162             if (strtoupper($storageEngine) !== 'INNODB'
  163                 && empty($column['Key'])
  164             ) {
  165                 continue;
  166             }
  167 
  168             $column_array[$column['Field']] = $column['Field'];
  169             $column_hash_array[$column['Field']] = md5($column['Field']);
  170         }
  171         if ($GLOBALS['cfg']['NaturalOrder']) {
  172             uksort($column_array, 'strnatcasecmp');
  173         }
  174 
  175         // common form
  176         $engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine();
  177         $this->render('table/relation/common_form', [
  178             'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
  179             'db' => $this->db,
  180             'table' => $this->table,
  181             'cfg_relation' => $cfgRelation,
  182             'tbl_storage_engine' => $storageEngine,
  183             'existrel' => $relations,
  184             'existrel_foreign' => array_key_exists('foreign_keys_data', $relationsForeign)
  185                 ? $relationsForeign['foreign_keys_data']
  186                 : [],
  187             'options_array' => $options,
  188             'column_array' => $column_array,
  189             'column_hash_array' => $column_hash_array,
  190             'save_row' => array_values($columns),
  191             'url_params' => $GLOBALS['url_params'],
  192             'databases' => $GLOBALS['dblist']->databases,
  193             'dbi' => $this->dbi,
  194             'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
  195             'route' => $route,
  196         ]);
  197     }
  198 
  199     /**
  200      * Update for display field
  201      *
  202      * @param Table $table       table
  203      * @param array $cfgRelation relation parameters
  204      */
  205     private function updateForDisplayField(Table $table, array $cfgRelation): void
  206     {
  207         if (! $table->updateDisplayField(
  208             $_POST['display_field'],
  209             $cfgRelation
  210         )) {
  211             return;
  212         }
  213 
  214         $this->response->addHTML(
  215             Generator::getMessage(
  216                 __('Display column was successfully updated.'),
  217                 '',
  218                 'success'
  219             )
  220         );
  221     }
  222 
  223     /**
  224      * Update for FK
  225      *
  226      * @param Table $table            Table
  227      * @param array $options          Options
  228      * @param array $relationsForeign External relations
  229      */
  230     private function updateForForeignKeys(Table $table, array $options, array $relationsForeign): void
  231     {
  232         $multi_edit_columns_name = $_POST['foreign_key_fields_name'] ?? null;
  233         $preview_sql_data = '';
  234         $seen_error = false;
  235 
  236         // (for now, one index name only; we keep the definitions if the
  237         // foreign db is not the same)
  238         if (isset($_POST['destination_foreign_db'], $_POST['destination_foreign_table'])
  239             && isset($_POST['destination_foreign_column'])) {
  240             [
  241                 $html,
  242                 $preview_sql_data,
  243                 $display_query,
  244                 $seen_error,
  245             ] = $table->updateForeignKeys(
  246                 $_POST['destination_foreign_db'],
  247                 $multi_edit_columns_name,
  248                 $_POST['destination_foreign_table'],
  249                 $_POST['destination_foreign_column'],
  250                 $options,
  251                 $this->table,
  252                 array_key_exists('foreign_keys_data', $relationsForeign)
  253                     ? $relationsForeign['foreign_keys_data']
  254                     : []
  255             );
  256             $this->response->addHTML($html);
  257         }
  258 
  259         // If there is a request for SQL previewing.
  260         if (isset($_POST['preview_sql'])) {
  261             Core::previewSQL($preview_sql_data);
  262 
  263             exit;
  264         }
  265 
  266         if (empty($display_query) || $seen_error) {
  267             return;
  268         }
  269 
  270         $GLOBALS['display_query'] = $display_query;
  271         $this->response->addHTML(
  272             Generator::getMessage(
  273                 __('Your SQL query has been executed successfully.'),
  274                 null,
  275                 'success'
  276             )
  277         );
  278     }
  279 
  280     /**
  281      * Update for internal relation
  282      *
  283      * @param Table $table       Table
  284      * @param array $cfgRelation Relation parameters
  285      * @param array $relations   Relations
  286      */
  287     private function updateForInternalRelation(Table $table, array $cfgRelation, array $relations): void
  288     {
  289         $multi_edit_columns_name = $_POST['fields_name'] ?? null;
  290 
  291         if (! $table->updateInternalRelations(
  292             $multi_edit_columns_name,
  293             $_POST['destination_db'],
  294             $_POST['destination_table'],
  295             $_POST['destination_column'],
  296             $cfgRelation,
  297             $relations
  298         )) {
  299             return;
  300         }
  301 
  302         $this->response->addHTML(
  303             Generator::getMessage(
  304                 __('Internal relationships were successfully updated.'),
  305                 '',
  306                 'success'
  307             )
  308         );
  309     }
  310 
  311     /**
  312      * Send table columns for foreign table dropdown
  313      */
  314     public function getDropdownValueForTable(): void
  315     {
  316         $foreignTable = $_POST['foreignTable'];
  317         $table_obj = $this->dbi->getTable($_POST['foreignDb'], $foreignTable);
  318         // Since views do not have keys defined on them provide the full list of
  319         // columns
  320         if ($table_obj->isView()) {
  321             $columnList = $table_obj->getColumns(false, false);
  322         } else {
  323             $columnList = $table_obj->getIndexedColumns(false, false);
  324         }
  325         $columns = [];
  326         foreach ($columnList as $column) {
  327             $columns[] = htmlspecialchars($column);
  328         }
  329         if ($GLOBALS['cfg']['NaturalOrder']) {
  330             usort($columns, 'strnatcasecmp');
  331         }
  332         $this->response->addJSON('columns', $columns);
  333 
  334         // @todo should be: $server->db($db)->table($table)->primary()
  335         $primary = Index::getPrimary($foreignTable, $_POST['foreignDb']);
  336         if ($primary === false) {
  337             return;
  338         }
  339 
  340         $this->response->addJSON('primary', array_keys($primary->getColumns()));
  341     }
  342 
  343     /**
  344      * Send database selection values for dropdown
  345      *
  346      * @param string $storageEngine Storage engine.
  347      */
  348     public function getDropdownValueForDatabase(string $storageEngine): void
  349     {
  350         $tables = [];
  351         $foreign = isset($_POST['foreign']) && $_POST['foreign'] === 'true';
  352 
  353         if ($foreign) {
  354             $query = 'SHOW TABLE STATUS FROM '
  355                 . Util::backquote($_POST['foreignDb']);
  356             $tables_rs = $this->dbi->query(
  357                 $query,
  358                 DatabaseInterface::CONNECT_USER,
  359                 DatabaseInterface::QUERY_STORE
  360             );
  361 
  362             while ($row = $this->dbi->fetchArray($tables_rs)) {
  363                 if (! isset($row['Engine'])
  364                     || mb_strtoupper($row['Engine']) != $storageEngine
  365                 ) {
  366                     continue;
  367                 }
  368 
  369                 $tables[] = htmlspecialchars($row['Name']);
  370             }
  371         } else {
  372             $query = 'SHOW TABLES FROM '
  373                 . Util::backquote($_POST['foreignDb']);
  374             $tables_rs = $this->dbi->query(
  375                 $query,
  376                 DatabaseInterface::CONNECT_USER,
  377                 DatabaseInterface::QUERY_STORE
  378             );
  379             while ($row = $this->dbi->fetchArray($tables_rs)) {
  380                 $tables[] = htmlspecialchars($row[0]);
  381             }
  382         }
  383         if ($GLOBALS['cfg']['NaturalOrder']) {
  384             usort($tables, 'strnatcasecmp');
  385         }
  386         $this->response->addJSON('tables', $tables);
  387     }
  388 }