1<?php
2/* vim: set expandtab sw=4 ts=4 sts=4: */
3/**
4 * Common functions for generating lists of Routines, Triggers and Events.
5 *
6 * @package PhpMyAdmin
7 */
8namespace PhpMyAdmin\Rte;
9
10use PhpMyAdmin\Response;
11use PhpMyAdmin\Rte\Words;
12use PhpMyAdmin\SqlParser\Parser;
13use PhpMyAdmin\SqlParser\Statements\CreateStatement;
14use PhpMyAdmin\SqlParser\Utils\Routine;
15use PhpMyAdmin\Template;
16use PhpMyAdmin\Url;
17use PhpMyAdmin\Util;
18
19/**
20 * PhpMyAdmin\Rte\RteList class
21 *
22 * @package PhpMyAdmin
23 */
24class RteList
25{
26    /**
27     * Creates a list of items containing the relevant
28     * information and some action links.
29     *
30     * @param string $type  One of ['routine'|'trigger'|'event']
31     * @param array  $items An array of items
32     *
33     * @return string HTML code of the list of items
34     */
35    public static function get($type, array $items)
36    {
37        global $table;
38
39        /**
40         * Conditional classes switch the list on or off
41         */
42        $class1 = 'hide';
43        $class2 = '';
44        if (! $items) {
45            $class1 = '';
46            $class2 = ' hide';
47        }
48        /**
49         * Generate output
50         */
51        $retval  = "<!-- LIST OF " . Words::get('docu') . " START -->\n";
52        $retval .= '<form id="rteListForm" class="ajax" action="';
53        switch ($type) {
54        case 'routine':
55            $retval .= 'db_routines.php';
56            break;
57        case 'trigger':
58            if (! empty($table)) {
59                $retval .= 'tbl_triggers.php';
60            } else {
61                $retval .= 'db_triggers.php';
62            }
63            break;
64        case 'event':
65            $retval .= 'db_events.php';
66            break;
67        default:
68            break;
69        }
70        $retval .= '">';
71        $retval .= Url::getHiddenInputs($GLOBALS['db'], $GLOBALS['table']);
72        $retval .= "<fieldset>\n";
73        $retval .= "    <legend>\n";
74        $retval .= "        " . Words::get('title') . "\n";
75        $retval .= "        "
76            . Util::showMySQLDocu(Words::get('docu')) . "\n";
77        $retval .= "    </legend>\n";
78        $retval .= "    <div class='$class1' id='nothing2display'>\n";
79        $retval .= "      " . Words::get('nothing') . "\n";
80        $retval .= "    </div>\n";
81        $retval .= "    <table class='data$class2'>\n";
82        $retval .= "        <!-- TABLE HEADERS -->\n";
83        $retval .= "        <tr>\n";
84        // th cells with a colspan need corresponding td cells, according to W3C
85        switch ($type) {
86        case 'routine':
87            $retval .= "            <th></th>\n";
88            $retval .= "            <th>" . __('Name') . "</th>\n";
89            $retval .= "            <th colspan='4'>" . __('Action') . "</th>\n";
90            $retval .= "            <th>" . __('Type') . "</th>\n";
91            $retval .= "            <th>" . __('Returns') . "</th>\n";
92            $retval .= "        </tr>\n";
93            $retval .= "        <tr class='hide'>\n"; // see comment above
94            for ($i = 0; $i < 7; $i++) {
95                $retval .= "            <td></td>\n";
96            }
97            break;
98        case 'trigger':
99            $retval .= "            <th></th>\n";
100            $retval .= "            <th>" . __('Name') . "</th>\n";
101            if (empty($table)) {
102                $retval .= "            <th>" . __('Table') . "</th>\n";
103            }
104            $retval .= "            <th colspan='3'>" . __('Action') . "</th>\n";
105            $retval .= "            <th>" . __('Time') . "</th>\n";
106            $retval .= "            <th>" . __('Event') . "</th>\n";
107            $retval .= "        </tr>\n";
108            $retval .= "        <tr class='hide'>\n"; // see comment above
109            for ($i = 0; $i < (empty($table) ? 7 : 6); $i++) {
110                $retval .= "            <td></td>\n";
111            }
112            break;
113        case 'event':
114            $retval .= "            <th></th>\n";
115            $retval .= "            <th>" . __('Name') . "</th>\n";
116            $retval .= "            <th>" . __('Status') . "</th>\n";
117            $retval .= "            <th colspan='3'>" . __('Action') . "</th>\n";
118            $retval .= "            <th>" . __('Type') . "</th>\n";
119            $retval .= "        </tr>\n";
120            $retval .= "        <tr class='hide'>\n"; // see comment above
121            for ($i = 0; $i < 6; $i++) {
122                $retval .= "            <td></td>\n";
123            }
124            break;
125        default:
126            break;
127        }
128        $retval .= "        </tr>\n";
129        $retval .= "        <!-- TABLE DATA -->\n";
130        $count = 0;
131        $response = Response::getInstance();
132        foreach ($items as $item) {
133            if ($response->isAjax() && empty($_REQUEST['ajax_page_request'])) {
134                $rowclass = 'ajaxInsert hide';
135            } else {
136                $rowclass = '';
137            }
138            // Get each row from the correct function
139            switch ($type) {
140            case 'routine':
141                $retval .= self::getRoutineRow($item, $rowclass);
142                break;
143            case 'trigger':
144                $retval .= self::getTriggerRow($item, $rowclass);
145                break;
146            case 'event':
147                $retval .= self::getEventRow($item, $rowclass);
148                break;
149            default:
150                break;
151            }
152            $count++;
153        }
154        $retval .= "    </table>\n";
155
156        if (count($items)) {
157            $retval .= '<div class="withSelected">';
158            $retval .= Template::get('select_all')
159                ->render(
160                    array(
161                        'pma_theme_image' => $GLOBALS['pmaThemeImage'],
162                        'text_dir'        => $GLOBALS['text_dir'],
163                        'form_name'       => 'rteListForm',
164                    )
165                );
166            $retval .= Util::getButtonOrImage(
167                'submit_mult', 'mult_submit',
168                __('Export'), 'b_export', 'export'
169            );
170            $retval .= Util::getButtonOrImage(
171                'submit_mult', 'mult_submit',
172                __('Drop'), 'b_drop', 'drop'
173            );
174            $retval .= '</div>';
175        }
176
177        $retval .= "</fieldset>\n";
178        $retval .= "</form>\n";
179        $retval .= "<!-- LIST OF " . Words::get('docu') . " END -->\n";
180
181        return $retval;
182    } // end self::get()
183
184    /**
185     * Creates the contents for a row in the list of routines
186     *
187     * @param array  $routine  An array of routine data
188     * @param string $rowclass Additional class
189     *
190     * @return string HTML code of a row for the list of routines
191     */
192    public static function getRoutineRow(array $routine, $rowclass = '')
193    {
194        global $url_query, $db, $titles;
195
196        $sql_drop = sprintf(
197            'DROP %s IF EXISTS %s',
198            $routine['type'],
199            Util::backquote($routine['name'])
200        );
201        $type_link = "item_type={$routine['type']}";
202
203        $retval  = "        <tr class='$rowclass'>\n";
204        $retval .= "            <td>\n";
205        $retval .= '                <input type="checkbox"'
206            . ' class="checkall" name="item_name[]"'
207            . ' value="' . htmlspecialchars($routine['name']) . '" />';
208        $retval .= "            </td>\n";
209        $retval .= "            <td>\n";
210        $retval .= "                <span class='drop_sql hide'>"
211            . htmlspecialchars($sql_drop) . "</span>\n";
212        $retval .= "                <strong>\n";
213        $retval .= "                    "
214            . htmlspecialchars($routine['name']) . "\n";
215        $retval .= "                </strong>\n";
216        $retval .= "            </td>\n";
217        $retval .= "            <td>\n";
218
219        // this is for our purpose to decide whether to
220        // show the edit link or not, so we need the DEFINER for the routine
221        $where = "ROUTINE_SCHEMA " . Util::getCollateForIS() . "="
222            . "'" . $GLOBALS['dbi']->escapeString($db) . "' "
223            . "AND SPECIFIC_NAME='" . $GLOBALS['dbi']->escapeString($routine['name']) . "'"
224            . "AND ROUTINE_TYPE='" . $GLOBALS['dbi']->escapeString($routine['type']) . "'";
225        $query = "SELECT `DEFINER` FROM INFORMATION_SCHEMA.ROUTINES WHERE $where;";
226        $routine_definer = $GLOBALS['dbi']->fetchValue($query);
227
228        $curr_user = $GLOBALS['dbi']->getCurrentUser();
229
230        // Since editing a procedure involved dropping and recreating, check also for
231        // CREATE ROUTINE privilege to avoid lost procedures.
232        if ((Util::currentUserHasPrivilege('CREATE ROUTINE', $db)
233            && $curr_user == $routine_definer)
234            || $GLOBALS['dbi']->isSuperuser()
235        ) {
236            $retval .= '                <a class="ajax edit_anchor"'
237                                             . ' href="db_routines.php'
238                                             . $url_query
239                                             . '&amp;edit_item=1'
240                                             . '&amp;item_name='
241                                             . urlencode($routine['name'])
242                                             . '&amp;' . $type_link
243                                             . '">' . $titles['Edit'] . "</a>\n";
244        } else {
245            $retval .= "                {$titles['NoEdit']}\n";
246        }
247        $retval .= "            </td>\n";
248        $retval .= "            <td>\n";
249
250        // There is a problem with Util::currentUserHasPrivilege():
251        // it does not detect all kinds of privileges, for example
252        // a direct privilege on a specific routine. So, at this point,
253        // we show the Execute link, hoping that the user has the correct rights.
254        // Also, information_schema might be hiding the ROUTINE_DEFINITION
255        // but a routine with no input parameters can be nonetheless executed.
256
257        // Check if the routine has any input parameters. If it does,
258        // we will show a dialog to get values for these parameters,
259        // otherwise we can execute it directly.
260
261        $definition = $GLOBALS['dbi']->getDefinition(
262            $db, $routine['type'], $routine['name']
263        );
264        if ($definition !== false) {
265            $parser = new Parser($definition);
266
267            /**
268             * @var CreateStatement $stmt
269             */
270            $stmt = $parser->statements[0];
271
272            $params = Routine::getParameters($stmt);
273
274            if (Util::currentUserHasPrivilege('EXECUTE', $db)) {
275                $execute_action = 'execute_routine';
276                for ($i = 0; $i < $params['num']; $i++) {
277                    if ($routine['type'] == 'PROCEDURE'
278                        && $params['dir'][$i] == 'OUT'
279                    ) {
280                        continue;
281                    }
282                    $execute_action = 'execute_dialog';
283                    break;
284                }
285                $query_part = $execute_action . '=1&amp;item_name='
286                    . urlencode($routine['name']) . '&amp;' . $type_link;
287                $retval .= '                <a class="ajax exec_anchor"'
288                                                 . ' href="db_routines.php'
289                                                 . $url_query
290                                                 . ($execute_action == 'execute_routine'
291                                                     ? '" data-post="' . $query_part
292                                                     : '&amp;' . $query_part)
293                                                 . '">' . $titles['Execute'] . "</a>\n";
294            } else {
295                $retval .= "                {$titles['NoExecute']}\n";
296            }
297        }
298
299        $retval .= "            </td>\n";
300        $retval .= "            <td>\n";
301        if ((Util::currentUserHasPrivilege('CREATE ROUTINE', $db)
302            && $curr_user == $routine_definer)
303            || $GLOBALS['dbi']->isSuperuser()
304        ) {
305            $retval .= '                <a class="ajax export_anchor"'
306                                             . ' href="db_routines.php'
307                                             . $url_query
308                                             . '&amp;export_item=1'
309                                             . '&amp;item_name='
310                                             . urlencode($routine['name'])
311                                             . '&amp;' . $type_link
312                                             . '">' . $titles['Export'] . "</a>\n";
313        } else {
314            $retval .= "                {$titles['NoExport']}\n";
315        }
316        $retval .= "            </td>\n";
317        $retval .= "            <td>\n";
318        $retval .= Util::linkOrButton(
319            'sql.php' . $url_query . '&amp;sql_query=' . urlencode($sql_drop) . '&amp;goto=db_routines.php' . urlencode("?db={$db}"),
320            $titles['Drop'],
321            ['class' => 'ajax drop_anchor']
322        );
323        $retval .= "            </td>\n";
324        $retval .= "            <td>\n";
325        $retval .= "                 {$routine['type']}\n";
326        $retval .= "            </td>\n";
327        $retval .= "            <td dir=\"ltr\">\n";
328        $retval .= "                "
329            . htmlspecialchars($routine['returns']) . "\n";
330        $retval .= "            </td>\n";
331        $retval .= "        </tr>\n";
332
333        return $retval;
334    } // end self::getRoutineRow()
335
336    /**
337     * Creates the contents for a row in the list of triggers
338     *
339     * @param array  $trigger  An array of routine data
340     * @param string $rowclass Additional class
341     *
342     * @return string HTML code of a cell for the list of triggers
343     */
344    public static function getTriggerRow(array $trigger, $rowclass = '')
345    {
346        global $url_query, $db, $table, $titles;
347
348        $retval  = "        <tr class='$rowclass'>\n";
349        $retval .= "            <td>\n";
350        $retval .= '                <input type="checkbox"'
351            . ' class="checkall" name="item_name[]"'
352            . ' value="' . htmlspecialchars($trigger['name']) . '" />';
353        $retval .= "            </td>\n";
354        $retval .= "            <td>\n";
355        $retval .= "                <span class='drop_sql hide'>"
356            . htmlspecialchars($trigger['drop']) . "</span>\n";
357        $retval .= "                <strong>\n";
358        $retval .= "                    " . htmlspecialchars($trigger['name']) . "\n";
359        $retval .= "                </strong>\n";
360        $retval .= "            </td>\n";
361        if (empty($table)) {
362            $retval .= "            <td>\n";
363            $retval .= "<a href='db_triggers.php{$url_query}"
364                . "&amp;table=" . urlencode($trigger['table']) . "'>"
365                . htmlspecialchars($trigger['table']) . "</a>";
366            $retval .= "            </td>\n";
367        }
368        $retval .= "            <td>\n";
369        if (Util::currentUserHasPrivilege('TRIGGER', $db, $table)) {
370            $retval .= '                <a class="ajax edit_anchor"'
371                                             . ' href="db_triggers.php'
372                                             . $url_query
373                                             . '&amp;edit_item=1'
374                                             . '&amp;item_name='
375                                             . urlencode($trigger['name'])
376                                             . '">' . $titles['Edit'] . "</a>\n";
377        } else {
378            $retval .= "                {$titles['NoEdit']}\n";
379        }
380        $retval .= "            </td>\n";
381        $retval .= "            <td>\n";
382        $retval .= '                    <a class="ajax export_anchor"'
383                                             . ' href="db_triggers.php'
384                                             . $url_query
385                                             . '&amp;export_item=1'
386                                             . '&amp;item_name='
387                                             . urlencode($trigger['name'])
388                                             . '">' . $titles['Export'] . "</a>\n";
389        $retval .= "            </td>\n";
390        $retval .= "            <td>\n";
391        if (Util::currentUserHasPrivilege('TRIGGER', $db)) {
392            $retval .= Util::linkOrButton(
393                'sql.php' . $url_query . '&amp;sql_query=' . urlencode($trigger['drop']) . '&amp;goto=db_triggers.php' . urlencode("?db={$db}"),
394                $titles['Drop'],
395                ['class' => 'ajax drop_anchor']
396            );
397        } else {
398            $retval .= "                {$titles['NoDrop']}\n";
399        }
400        $retval .= "            </td>\n";
401        $retval .= "            <td>\n";
402        $retval .= "                 {$trigger['action_timing']}\n";
403        $retval .= "            </td>\n";
404        $retval .= "            <td>\n";
405        $retval .= "                 {$trigger['event_manipulation']}\n";
406        $retval .= "            </td>\n";
407        $retval .= "        </tr>\n";
408
409        return $retval;
410    } // end self::getTriggerRow()
411
412    /**
413     * Creates the contents for a row in the list of events
414     *
415     * @param array  $event    An array of routine data
416     * @param string $rowclass Additional class
417     *
418     * @return string HTML code of a cell for the list of events
419     */
420    public static function getEventRow(array $event, $rowclass = '')
421    {
422        global $url_query, $db, $titles;
423
424        $sql_drop = sprintf(
425            'DROP EVENT IF EXISTS %s',
426            Util::backquote($event['name'])
427        );
428
429        $retval  = "        <tr class='$rowclass'>\n";
430        $retval .= "            <td>\n";
431        $retval .= '                <input type="checkbox"'
432            . ' class="checkall" name="item_name[]"'
433            . ' value="' . htmlspecialchars($event['name']) . '" />';
434        $retval .= "            </td>\n";
435        $retval .= "            <td>\n";
436        $retval .= "                <span class='drop_sql hide'>"
437            . htmlspecialchars($sql_drop) . "</span>\n";
438        $retval .= "                <strong>\n";
439        $retval .= "                    "
440            . htmlspecialchars($event['name']) . "\n";
441        $retval .= "                </strong>\n";
442        $retval .= "            </td>\n";
443        $retval .= "            <td>\n";
444        $retval .= "                 {$event['status']}\n";
445        $retval .= "            </td>\n";
446        $retval .= "            <td>\n";
447        if (Util::currentUserHasPrivilege('EVENT', $db)) {
448            $retval .= '                <a class="ajax edit_anchor"'
449                                             . ' href="db_events.php'
450                                             . $url_query
451                                             . '&amp;edit_item=1'
452                                             . '&amp;item_name='
453                                             . urlencode($event['name'])
454                                             . '">' . $titles['Edit'] . "</a>\n";
455        } else {
456            $retval .= "                {$titles['NoEdit']}\n";
457        }
458        $retval .= "            </td>\n";
459        $retval .= "            <td>\n";
460        $retval .= '                <a class="ajax export_anchor"'
461                                         . ' href="db_events.php'
462                                         . $url_query
463                                         . '&amp;export_item=1'
464                                         . '&amp;item_name='
465                                         . urlencode($event['name'])
466                                         . '">' . $titles['Export'] . "</a>\n";
467        $retval .= "            </td>\n";
468        $retval .= "            <td>\n";
469        if (Util::currentUserHasPrivilege('EVENT', $db)) {
470            $retval .= Util::linkOrButton(
471                'sql.php' . $url_query . '&amp;sql_query=' . urlencode($sql_drop) . '&amp;goto=db_events.php' . urlencode("?db={$db}"),
472                $titles['Drop'],
473                ['class' => 'ajax drop_anchor']
474            );
475        } else {
476            $retval .= "                {$titles['NoDrop']}\n";
477        }
478        $retval .= "            </td>\n";
479        $retval .= "            <td>\n";
480        $retval .= "                 {$event['type']}\n";
481        $retval .= "            </td>\n";
482        $retval .= "        </tr>\n";
483
484        return $retval;
485    } // end self::getEventRow()
486}
487