Merge #15121 - Fix #15064 Add Export SQL option for a raw query

Pull-request: #15121
Fixes: #15064
Signed-off-by: William Desportes <williamdes@wdes.fr>
This commit is contained in:
William Desportes 2020-01-27 21:22:25 +01:00
commit 3988a8d7b7
No known key found for this signature in database
GPG Key ID: 90A0EF1B8251A889
19 changed files with 414 additions and 37 deletions

View File

@ -168,10 +168,17 @@ final class ExportController extends AbstractController
$unlim_num_rows = 0;
}
$isReturnBackFromRawExport = isset($_POST['export_type']) && $_POST['export_type'] === 'raw';
if (isset($_POST['raw_query']) || $isReturnBackFromRawExport) {
$export_type = 'raw';
} else {
$export_type = 'table';
}
$displayExport = new DisplayExport();
$this->response->addHTML(
$displayExport->getDisplay(
'database',
$export_type,
$db,
$table,
$sql_query,

View File

@ -334,6 +334,10 @@ final class ExportController extends AbstractController
'db' => $db,
'table' => $table,
]);
} elseif ($export_type === 'raw') {
$err_url = Url::getFromRoute('/server/export', [
'sql_query' => $sql_query,
]);
} else {
Core::fatalError(__('Bad parameters!'));
}
@ -409,6 +413,11 @@ final class ExportController extends AbstractController
$mime_type = '';
}
// For raw query export, filename will be export.extension
if ($export_type === 'raw') {
[$filename ] = $this->export->getFinalFilenameAndMimetypeForFilename($export_plugin, $compression, 'export');
}
// Open file on server if needed
if ($save_on_server) {
list($save_filename, $message, $file_handle) = $this->export->openFile(
@ -482,6 +491,10 @@ final class ExportController extends AbstractController
$whatStrucOrData = $GLOBALS[$what . '_structure_or_data'];
if ($export_type === 'raw') {
$whatStrucOrData = 'raw';
}
/**
* Builds the dump
*/
@ -556,6 +569,15 @@ final class ExportController extends AbstractController
$separate_files
);
}
} elseif ($export_type === 'raw') {
Export::exportRaw(
$whatStrucOrData,
$export_plugin,
$crlf,
$err_url,
$sql_query,
$export_type
);
} else {
// We export just one table
// $allrows comes from the form when "Dump all rows" has been selected

View File

@ -552,7 +552,7 @@ class Export
$html .= $this->getHtmlForOptionsSelection($exportType, $multiValues);
$tableObject = new Table($table, $db);
if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge()) {
if (strlen($table) > 0 && empty($numTables) && ! $tableObject->isMerge() && $exportType !== 'raw') {
$html .= $this->getHtmlForOptionsRows($db, $table, $unlimNumRows);
}

View File

@ -4933,6 +4933,12 @@ class Results
$_url_params['single_table'] = 'true';
}
// In case this query doesn't involve any tables,
// implies only raw query is to be exported
if (! $analyzed_sql_results['select_tables']) {
$_url_params['raw_query'] = 'true';
}
if (! $header_shown) {
$results_operations_html .= $header;
$header_shown = true;

View File

@ -279,6 +279,52 @@ class Export
return $memory_limit;
}
/**
* Returns the filename and MIME type for a compression and an export plugin
*
* @param ExportPlugin $exportPlugin the export plugin
* @param string $compression compression asked
* @param string $filename the filename
*
* @return string[] the filename and mime type
*/
public function getFinalFilenameAndMimetypeForFilename(
ExportPlugin $exportPlugin,
string $compression,
string $filename
): array {
// Grab basic dump extension and mime type
// Check if the user already added extension;
// get the substring where the extension would be if it was included
$extensionStartPos = mb_strlen($filename) - mb_strlen(
$exportPlugin->getProperties()->getExtension()
) - 1;
$userExtension = mb_substr(
$filename,
$extensionStartPos,
mb_strlen($filename)
);
$requiredExtension = '.' . $exportPlugin->getProperties()->getExtension();
if (mb_strtolower($userExtension) != $requiredExtension) {
$filename .= $requiredExtension;
}
$mime_type = $exportPlugin->getProperties()->getMimeType();
// If dump is going to be compressed, set correct mime_type and add
// compression to extension
if ($compression === 'gzip') {
$filename .= '.gz';
$mime_type = 'application/x-gzip';
} elseif ($compression === 'zip') {
$filename .= '.zip';
$mime_type = 'application/zip';
}
return [
$filename,
$mime_type,
];
}
/**
* Return the filename and MIME type for export file
*
@ -313,6 +359,14 @@ class Export
$filename_template
);
}
} elseif ($export_type == 'raw') {
if (! empty($remember_template)) {
$GLOBALS['PMA_Config']->setUserValue(
'pma_raw_filename_template',
'Export/file_template_raw',
$filename_template
);
}
} else {
if (! empty($remember_template)) {
$GLOBALS['PMA_Config']->setUserValue(
@ -325,38 +379,13 @@ class Export
$filename = Util::expandUserString($filename_template);
// remove dots in filename (coming from either the template or already
// part of the filename) to avoid a remote code execution vulnerability
$filename = Sanitize::sanitizeFilename($filename, $replaceDots = true);
$filename = Sanitize::sanitizeFilename($filename, true);
// Grab basic dump extension and mime type
// Check if the user already added extension;
// get the substring where the extension would be if it was included
$extension_start_pos = mb_strlen($filename) - mb_strlen(
$export_plugin->getProperties()->getExtension()
) - 1;
$user_extension = mb_substr(
$filename,
$extension_start_pos,
mb_strlen($filename)
return $this->getFinalFilenameAndMimetypeForFilename(
$export_plugin,
$compression,
$filename
);
$required_extension = '.' . $export_plugin->getProperties()->getExtension();
if (mb_strtolower($user_extension) != $required_extension) {
$filename .= $required_extension;
}
$mime_type = $export_plugin->getProperties()->getMimeType();
// If dump is going to be compressed, set correct mime_type and add
// compression to extension
if ($compression == 'gzip') {
$filename .= '.gz';
$mime_type = 'application/x-gzip';
} elseif ($compression == 'zip') {
$filename .= '.zip';
$mime_type = 'application/zip';
}
return [
$filename,
$mime_type,
];
}
/**
@ -890,6 +919,41 @@ class Export
}
}
/**
* Export raw query
*
* @param string $whatStrucOrData whether to export structure for each table or raw
* @param ExportPlugin $export_plugin the selected export plugin
* @param string $crlf end of line character(s)
* @param string $err_url the URL in case of error
* @param string $sql_query the query to be executed
* @param string $export_type the export type
*
* @return void
*/
public static function exportRaw(
string $whatStrucOrData,
ExportPlugin $export_plugin,
string $crlf,
string $err_url,
string $sql_query,
string $export_type
): void {
// In case the we need to dump just the raw query
if ($whatStrucOrData === 'raw') {
if (! $export_plugin->exportRawQuery(
$err_url,
$sql_query,
$crlf
)) {
$GLOBALS['message'] = Message::error(
__('Exporting a raw query is not supported for this export method.')
);
return;
}
}
}
/**
* Export at the table level
*

View File

@ -338,4 +338,18 @@ class ExportCsv extends ExportPlugin
return true;
}
/**
* Outputs result of raw query in CSV format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
}

View File

@ -287,4 +287,74 @@ class ExportJson extends ExportPlugin
return true;
}
/**
* Outputs result raw query in JSON format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
$buffer = $this->encode(
[
'type' => 'raw',
'data' => '@@DATA@@',
]
);
list($header, $footer) = explode('"@@DATA@@"', $buffer);
if (! $this->export->outputHandler($header . $crlf . '[' . $crlf)) {
return false;
}
$result = $GLOBALS['dbi']->query(
$sql_query,
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_UNBUFFERED
);
$columns_cnt = $GLOBALS['dbi']->numFields($result);
$columns = [];
for ($i = 0; $i < $columns_cnt; $i++) {
$col_as = $GLOBALS['dbi']->fieldName($result, $i);
$columns[$i] = stripslashes($col_as);
}
$record_cnt = 0;
while ($record = $GLOBALS['dbi']->fetchRow($result)) {
$record_cnt++;
if ($record_cnt > 1) {
if (! $this->export->outputHandler(',' . $crlf)) {
return false;
}
}
$data = [];
for ($i = 0; $i < $columns_cnt; $i++) {
$data[$columns[$i]] = $record[$i];
}
$encodedData = $this->encode($data);
if (! $encodedData) {
return false;
}
if (! $this->export->outputHandler($encodedData)) {
return false;
}
}
if (! $this->export->outputHandler($crlf . ']' . $crlf . $footer . $crlf)) {
return false;
}
$GLOBALS['dbi']->freeResult($result);
return true;
}
}

View File

@ -439,6 +439,20 @@ class ExportLatex extends ExportPlugin
return true;
} // end getTableLaTeX
/**
* Outputs result raw query
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the seperator for a file
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
/**
* Outputs table's structure
*

View File

@ -289,7 +289,9 @@ class ExportMediawiki extends ExportPlugin
// Print data comment
$output = $this->_exportComment(
'Table data for ' . Util::backquote($table_alias)
$table_alias != ''
? 'Table data for ' . Util::backquote($table_alias)
: 'Query results'
);
// Begin the table construction
@ -348,6 +350,20 @@ class ExportMediawiki extends ExportPlugin
return $this->export->outputHandler($output);
}
/**
* Outputs result raw query in MediaWiki format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
/**
* Outputs comments containing info about the exported tables
*

View File

@ -337,4 +337,18 @@ class ExportOds extends ExportPlugin
return true;
}
/**
* Outputs result raw query in ODS format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
}

View File

@ -260,9 +260,12 @@ class ExportOdt extends ExportPlugin
$GLOBALS['odt_buffer']
.= '<text:h text:outline-level="2" text:style-name="Heading_2"'
. ' text:is-list-header="true">'
. __('Dumping data for table') . ' ' . htmlspecialchars($table_alias)
. '</text:h>'
. ' text:is-list-header="true">';
$table_alias != ''
? $GLOBALS['odt_buffer'] .= __('Dumping data for table') . ' ' . htmlspecialchars($table_alias)
: $GLOBALS['odt_buffer'] .= __('Dumping data for query result');
$GLOBALS['odt_buffer']
.= '</text:h>'
. '<table:table'
. ' table:name="' . htmlspecialchars($table_alias) . '_structure">'
. '<table:table-column'
@ -340,6 +343,20 @@ class ExportOdt extends ExportPlugin
return true;
}
/**
* Outputs result raw query in ODT format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
/**
* Returns a stand-in CREATE definition to resolve view dependencies
*

View File

@ -232,6 +232,29 @@ class ExportPdf extends ExportPlugin
return true;
} // end of the 'PMA_exportData()' function
/**
* Outputs result of raw query in PDF format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
$pdf = $this->_getPdf();
$attr = [
'dbAlias' => '----',
'tableAlias' => '----',
'purpose' => __('Query result data'),
];
$pdf->setAttributes($attr);
$pdf->mysqlReport($sql_query);
return true;
}
/**
* Outputs table structure
*

View File

@ -249,4 +249,18 @@ class ExportPhparray extends ExportPlugin
return true;
}
/**
* Outputs result of raw query as PHP array
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
}

View File

@ -90,6 +90,13 @@ class ExportSql extends ExportPlugin
$hide_sql = true;
}
// In case we have `raw_query` parameter set,
// we initialize SQL option
if (isset($_REQUEST['raw_query'])) {
$hide_structure = false;
$hide_sql = false;
}
if (! $hide_sql) {
$exportPluginProperties = new ExportPluginProperties();
$exportPluginProperties->setText('SQL');
@ -2006,6 +2013,20 @@ class ExportSql extends ExportPlugin
return $schema_create;
} // end of the '_getTableComments()' function
/**
* Outputs raw query
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the seperator for a file
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->export->outputHandler($sql_query);
}
/**
* Outputs table's structure
*

View File

@ -186,7 +186,9 @@ class ExportTexytext extends ExportPlugin
$this->initAlias($aliases, $db_alias, $table_alias);
if (! $this->export->outputHandler(
'== ' . __('Dumping data for table') . ' ' . $table_alias . "\n\n"
$table_alias != ''
? '== ' . __('Dumping data for table') . ' ' . $table_alias . "\n\n"
: '==' . __('Dumping data for query result') . "\n\n"
)
) {
return false;
@ -245,6 +247,20 @@ class ExportTexytext extends ExportPlugin
return true;
}
/**
* Outputs result raw query in TexyText format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
/**
* Returns a stand-in CREATE definition to resolve view dependencies
*

View File

@ -219,4 +219,18 @@ class ExportYaml extends ExportPlugin
return true;
} // end getTableYAML
/**
* Outputs result raw query in YAML format
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the end of line sequence
*
* @return bool if succeeded
*/
public function exportRawQuery(string $err_url, string $sql_query, string $crlf): bool
{
return $this->exportData('', '', $crlf, $err_url, $sql_query);
}
}

View File

@ -139,6 +139,23 @@ abstract class ExportPlugin
return true;
}
/**
* Outputs for raw query
*
* @param string $err_url the url to go back in case of error
* @param string $sql_query the rawquery to output
* @param string $crlf the seperator for a file
*
* @return bool if succeeded
*/
public function exportRawQuery(
string $err_url,
string $sql_query,
string $crlf
): bool {
return false;
}
/**
* Outputs table's structure
*

View File

@ -5,6 +5,8 @@
{% trans 'Exporting databases from the current server' %}
{% elseif export_type == 'database' %}
{{ 'Exporting tables from "%s" database'|trans|format(db) }}
{% elseif export_type == 'raw' %}
{% trans 'Exporting raw query' %}
{% else %}
{{ 'Exporting rows from "%s" table'|trans|format(table) }}
{% endif %}

View File

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests;
use PhpMyAdmin\Export;
use PhpMyAdmin\Plugins\Export\ExportPhparray;
use PHPUnit\Framework\TestCase;
/**
@ -103,4 +104,29 @@ class ExportTest extends TestCase
$actual = $this->export->mergeAliases($aliases1, $aliases2);
$this->assertEquals($expected, $actual);
}
/**
* Test for getFinalFilenameAndMimetypeForFilename
*
* @return void
*/
public function testGetFinalFilenameAndMimetypeForFilename()
{
$exportPlugin = new ExportPhparray();
$finalFileName = $this->export->getFinalFilenameAndMimetypeForFilename($exportPlugin, 'zip', 'myfilename');
$this->assertSame([
'myfilename.php.zip',
'application/zip',
], $finalFileName);
$finalFileName = $this->export->getFinalFilenameAndMimetypeForFilename($exportPlugin, 'gzip', 'myfilename');
$this->assertSame([
'myfilename.php.gz',
'application/x-gzip',
], $finalFileName);
$finalFileName = $this->export->getFinalFilenameAndMimetypeForFilename($exportPlugin, 'gzip', 'export.db1.table1.file');
$this->assertSame([
'export.db1.table1.file.php.gz',
'application/x-gzip',
], $finalFileName);
}
}