Merge PR #16500 Add paging to Routines page

Closes #16500

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2025-04-16 20:30:29 -03:00
commit 32db9bf052
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
7 changed files with 103 additions and 13 deletions

View File

@ -1664,6 +1664,14 @@ Generic settings
The maximum number of table names to be displayed in the main panel's
list (except on the Export page).
.. config:option:: $cfg['MaxRoutineList']
:type: integer
:default: 250
The maximum number of routine names to be displayed in the main panel's
routine list.
.. config:option:: $cfg['ShowHint']
:type: boolean

View File

@ -1122,7 +1122,7 @@ parameters:
-
message: '#^Cannot cast mixed to int\.$#'
identifier: cast.int
count: 35
count: 36
path: src/Config/Settings.php
-
@ -2028,6 +2028,12 @@ parameters:
count: 4
path: src/Controllers/Database/RoutinesController.php
-
message: '#^Cannot cast mixed to int\.$#'
identifier: cast.int
count: 1
path: src/Controllers/Database/RoutinesController.php
-
message: '#^Cannot use \+\+ on mixed\.$#'
identifier: postInc.type

View File

@ -38,6 +38,10 @@
</div>
</div>
{%- if has_any_routines -%}
{{ list_navigator_html|raw }}
{%- endif -%}
<form id="rteListForm" class="ajax" action="{{ url('/database/routines') }}">
{{ get_hidden_inputs(db, table) }}
@ -63,6 +67,10 @@
</table>
</form>
{%- if has_any_routines -%}
{{ list_navigator_html|raw }}
{%- endif -%}
<div class="modal fade" id="routinesEditorModal" tabindex="-1" aria-labelledby="routinesEditorModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">

View File

@ -785,6 +785,7 @@ class ConfigFile
'MaxSizeForInputField' => 'validatePositiveNumber',
'MinSizeForInputField' => 'validateNonNegativeNumber',
'MaxTableList' => 'validatePositiveNumber',
'MaxRoutineList' => 'validatePositiveNumber',
'MemoryLimit' => [['validateByRegex', '/^(-1|(\d+(?:[kmg])?))$/i']],
'NavigationTreeDisplayItemFilterMinimum' => 'validatePositiveNumber',
'NavigationTreeTableLevel' => 'validatePositiveNumber',
@ -806,6 +807,7 @@ class ConfigFile
'_userValidators' => [
'MaxDbList' => [['validateUpperBound', 'value:MaxDbList']],
'MaxTableList' => [['validateUpperBound', 'value:MaxTableList']],
'MaxRoutineList' => [['validateUpperBound', 'value:MaxRoutineList']],
'QueryHistoryMax' => [['validateUpperBound', 'value:QueryHistoryMax']],
],
];

View File

@ -59,6 +59,7 @@ use const VERSION_CHECK_DEFAULT;
* ProxyPass: string,
* MaxDbList: int<1, max>,
* MaxTableList: int<1, max>,
* MaxRoutineList: int<1, max>,
* ShowHint: bool,
* MaxCharactersInDisplayedSQL: int<1, max>,
* PersistentConnections: bool,
@ -479,6 +480,19 @@ final class Settings
*/
public int $MaxTableList;
/**
* maximum number of routines displayed in routine list
*
* ```php
* $cfg['MaxRoutineList'] = 250;
* ```
*
* @link https://docs.phpmyadmin.net/en/latest/config.html#cfg_MaxRoutineList
*
* @psalm-var positive-int
*/
public int $MaxRoutineList;
/**
* whether to show hint or not
*
@ -2630,6 +2644,7 @@ final class Settings
$this->ProxyPass = $this->setProxyPass($settings);
$this->MaxDbList = $this->setMaxDbList($settings);
$this->MaxTableList = $this->setMaxTableList($settings);
$this->MaxRoutineList = $this->setMaxRoutineList($settings);
$this->ShowHint = $this->setShowHint($settings);
$this->MaxCharactersInDisplayedSQL = $this->setMaxCharactersInDisplayedSQL($settings);
$this->PersistentConnections = $this->setPersistentConnections($settings);
@ -2827,6 +2842,7 @@ final class Settings
'ProxyPass' => $this->ProxyPass,
'MaxDbList' => $this->MaxDbList,
'MaxTableList' => $this->MaxTableList,
'MaxRoutineList' => $this->MaxRoutineList,
'ShowHint' => $this->ShowHint,
'MaxCharactersInDisplayedSQL' => $this->MaxCharactersInDisplayedSQL,
'PersistentConnections' => $this->PersistentConnections,
@ -3250,6 +3266,22 @@ final class Settings
return $maxTableList >= 1 ? $maxTableList : 250;
}
/**
* @param array<int|string, mixed> $settings
*
* @psalm-return positive-int
*/
private function setMaxRoutineList(array $settings): int
{
if (! isset($settings['MaxRoutineList'])) {
return 250;
}
$maxRoutineList = (int) $settings['MaxRoutineList'];
return $maxRoutineList >= 1 ? $maxRoutineList : 250;
}
/** @param array<int|string, mixed> $settings */
private function setShowHint(array $settings): bool
{

View File

@ -11,6 +11,7 @@ use PhpMyAdmin\Current;
use PhpMyAdmin\Database\Routines;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Http\Response;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Identifiers\DatabaseName;
@ -18,14 +19,18 @@ use PhpMyAdmin\Identifiers\TableName;
use PhpMyAdmin\Message;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\Template;
use PhpMyAdmin\Url;
use PhpMyAdmin\UrlParams;
use PhpMyAdmin\UserPrivilegesFactory;
use PhpMyAdmin\Util;
use function __;
use function array_slice;
use function count;
use function htmlentities;
use function htmlspecialchars;
use function in_array;
use function max;
use function mb_strtoupper;
use function sprintf;
use function trim;
@ -453,6 +458,31 @@ final class RoutinesController implements InvocableController
}
$items = Routines::getDetails($this->dbi, Current::$database, $type);
$totalNumRoutines = count($items);
$pageSize = $config->settings['MaxRoutineList'];
$pos = (int) $request->getParam('pos');
// Checks if there are any routines to be shown on current page.
// If there are no routines, the user is redirected to the last page
// having any.
if ($totalNumRoutines > 0 && $pos >= $totalNumRoutines) {
$redirParams = [
'db' => Current::$database,
'pos' => max(0, $totalNumRoutines - $pageSize),
'reload' => 1,
];
if ($this->response->isAjax()) {
$redirParams['ajax_request'] = 'true';
$redirParams['ajax_page_request'] = 'true';
}
$this->response->redirectToRoute('/database/routines', $redirParams);
return $this->response->response();
}
$items = array_slice($items, $pos, $pageSize);
$isAjax = $request->isAjax() && empty($_REQUEST['ajax_page_request']);
$rows = '';
@ -463,12 +493,24 @@ final class RoutinesController implements InvocableController
);
}
$urlParams = ['pos' => $pos, 'db' => Current::$database];
$listNavigator = Generator::getListNavigator(
$totalNumRoutines,
$pos,
$urlParams,
Url::getFromRoute('/database/routines'),
'frame_content',
$pageSize,
);
$this->response->render('database/routines/index', [
'db' => Current::$database,
'table' => Current::$table,
'has_any_routines' => $items !== [],
'rows' => $rows,
'has_privilege' => Util::currentUserHasPrivilege('CREATE ROUTINE', Current::$database, Current::$table),
'list_navigator_html' => $listNavigator,
]);
return $this->response->response();

View File

@ -149,9 +149,7 @@ final class RoutinesControllerTest extends AbstractTestCase
<span class="text-nowrap"><img src="themes/dot.gif" title="Create new routine" alt="Create new routine" class="icon ic_b_routine_add">&nbsp;Create new routine</span>
</a>
</div>
</div>
<form id="rteListForm" class="ajax" action="index.php?route=/database/routines&server=2&lang=en">
</div><form id="rteListForm" class="ajax" action="index.php?route=/database/routines&server=2&lang=en">
<input type="hidden" name="db" value="test_db"><input type="hidden" name="server" value="2"><input type="hidden" name="lang" value="en"><input type="hidden" name="token" value="token">
<div id="nothing2display" class="hide">
@ -233,9 +231,7 @@ final class RoutinesControllerTest extends AbstractTestCase
</tbody>
</table>
</form>
<div class="modal fade" id="routinesEditorModal" tabindex="-1" aria-labelledby="routinesEditorModalLabel" aria-hidden="true">
</form><div class="modal fade" id="routinesEditorModal" tabindex="-1" aria-labelledby="routinesEditorModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
@ -354,9 +350,7 @@ HTML;
<span class="text-nowrap"><img src="themes/dot.gif" title="Create new routine" alt="Create new routine" class="icon ic_b_routine_add">&nbsp;Create new routine</span>
</a>
</div>
</div>
<form id="rteListForm" class="ajax" action="index.php?route=/database/routines&server=2&lang=en">
</div><form id="rteListForm" class="ajax" action="index.php?route=/database/routines&server=2&lang=en">
<input type="hidden" name="db" value="test_db"><input type="hidden" name="server" value="2"><input type="hidden" name="lang" value="en"><input type="hidden" name="token" value="token">
<div id="nothing2display">
@ -380,9 +374,7 @@ HTML;
<tr class="hide"><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
</tbody>
</table>
</form>
<div class="modal fade" id="routinesEditorModal" tabindex="-1" aria-labelledby="routinesEditorModalLabel" aria-hidden="true">
</form><div class="modal fade" id="routinesEditorModal" tabindex="-1" aria-labelledby="routinesEditorModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">