From 85390a71693d33c2efabf1857b4f2120c104b735 Mon Sep 17 00:00:00 2001 From: Chirayu Chiripal Date: Fri, 16 May 2014 19:40:13 +0530 Subject: [PATCH] rfe-982: Support for editing binary fields in hexadecimal Signed-off-by: Chirayu Chiripal --- ChangeLog | 1 + doc/config.rst | 8 - js/makegrid.js | 30 +- js/messages.php | 1 + js/tbl_change.js | 14 +- libraries/DisplayResults.class.php | 366 ++++++-------------- libraries/Header.class.php | 4 + libraries/Util.class.php | 5 - libraries/config.default.php | 7 - libraries/config/messages.inc.php | 2 - libraries/config/user_preferences.forms.php | 1 - libraries/insert_edit.lib.php | 50 ++- libraries/sql.lib.php | 4 + test/bootstrap-dist.php | 1 + test/classes/PMA_DisplayResults_test.php | 324 ++++++++++++++++- test/libraries/PMA_insert_edit_test.php | 41 +-- 16 files changed, 514 insertions(+), 345 deletions(-) diff --git a/ChangeLog b/ChangeLog index 815f823350..2ddadd8a74 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,7 @@ phpMyAdmin - ChangeLog - rfe #1448 Allow clicking an approximate row count to get a correct one - rfe #1487 "Browse foreign values" should be a modal dialog - rfe #1523 Better visual clue for table structure primary key column +- rfe #982 Support for editing binary fields in hexadecimal 4.2.3.0 (not yet released) diff --git a/doc/config.rst b/doc/config.rst index fa47953560..c47a917d74 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1643,14 +1643,6 @@ Browse mode descending order for columns of type TIME, DATE, DATETIME and TIMESTAMP, ascending order else- by default. -.. config:option:: $cfg['DisplayBinaryAsHex'] - - :type: boolean - :default: true - - Defines whether the "Show binary contents as HEX" browse option is - ticked by default. - .. config:option:: $cfg['GridEditing'] :type: string diff --git a/js/makegrid.js b/js/makegrid.js index bbb57d6677..b95eb2a0a6 100644 --- a/js/makegrid.js +++ b/js/makegrid.js @@ -641,12 +641,16 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi if (($this_field.attr('data-decimals') > 0) && ( $this_field.attr('data-type').indexOf('time') != -1)){ new_html = new_html.substring(0, new_html.length - (6 - $this_field.attr('data-decimals'))); } - if ($this_field.is('.truncated')) { - if (new_html.length > g.maxTruncatedLen) { - new_html = new_html.substring(0, g.maxTruncatedLen) + '...'; - } + $this_field.removeClass('truncated'); + if (PMA_commonParams.get('pftext') === 'P' && new_html.length > g.maxTruncatedLen) { + $this_field.addClass('truncated'); + new_html = new_html.substring(0, g.maxTruncatedLen) + '...'; } - $this_field.find('span').text(new_html); + var selector = 'span'; + if ($this_field.hasClass('hex') && $this_field.find('a').length) { + selector = 'a'; + } + $this_field.find(selector).text(new_html); } if ($this_field.is('.bit')) { $this_field.find('span').text($this_field.data('value')); @@ -954,11 +958,6 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi g.lastXHR = null; $editArea.removeClass('edit_area_loading'); if (data.success === true) { - if ($td.is('.truncated')) { - // get the truncated data length - g.maxTruncatedLen = $(g.currentEditCell).text().length - 3; - } - $td.data('original_data', data.value); $(g.cEdit).find('.edit_box').val(data.value); $editArea.append(''); @@ -1150,6 +1149,8 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi } else { if ($this_field.is('.bit')) { fields_type.push('bit'); + } else if ($this_field.hasClass('hex')) { + fields_type.push('hex'); } fields_null.push(''); fields.push($this_field.data('value')); @@ -1366,6 +1367,14 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi // because selected value from drop-down, new window or multiple // selection list will always be updated to the edit box this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else if ($this_field.hasClass('hex')) { + if ($(g.cEdit).find('.edit_box').val().match(/^[a-f0-9]*$/i) !== null) { + this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); + } else { + var hexError = '
' + PMA_messages.strEnterValidHex + '
'; + PMA_ajaxShowMessage(hexError, false); + this_field_params[field_name] = PMA_getCellValue(g.currentEditCell); + } } else { this_field_params[field_name] = $(g.cEdit).find('.edit_box').val(); } @@ -1660,6 +1669,7 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi // initialize cell editing configuration g.saveCellsAtOnce = $('#save_cells_at_once').val(); + g.maxTruncatedLen = PMA_commonParams.get('LimitChars'); // register events $(t).find('td.data.click1') diff --git a/js/messages.php b/js/messages.php index 3ce320981c..927a554bc5 100644 --- a/js/messages.php +++ b/js/messages.php @@ -352,6 +352,7 @@ $js_messages['strColVisibHint'] = __( ); $js_messages['strShowAllCol'] = __('Show all'); $js_messages['strAlertNonUnique'] = __('This table does not contain a unique column. Features related to the grid edit, checkbox, Edit, Copy and Delete links may not work after saving.'); +$js_messages['strEnterValidHex'] = __('Please enter valid hexadecimal string. Valid characters are 0-9, A-F.'); // this approach does not work when the parameter is changed via user prefs switch ($GLOBALS['cfg']['GridEditing']) { diff --git a/js/tbl_change.js b/js/tbl_change.js index a05d93369d..b18a0ced3f 100644 --- a/js/tbl_change.js +++ b/js/tbl_change.js @@ -173,6 +173,10 @@ function verificationsAfterFieldChange(urlField, multi_edit, theType) // Unchecks the Ignore checkbox for the current row $("input[name='insert_ignore_" + multi_edit + "']").prop('checked', false); var $this_input = $("input[name='fields[multi_edit][" + multi_edit + "][" + urlField + "]']"); + // check if it is textarea rather than input + if ($this_input.length === 0) { + $this_input = $("textarea[name='fields[multi_edit][" + multi_edit + "][" + urlField + "]']"); + } // Does this field come from datepicker? if ($this_input.data('comes_from') == 'datepicker') { @@ -228,9 +232,17 @@ function verificationsAfterFieldChange(urlField, multi_edit, theType) return false; } } + // validate binary & blob types + if (theType.indexOf('blob') > -1 || theType.indexOf('binary') > -1) { + $this_input.removeClass("invalid_value"); + if ($this_input.val().match(/^[a-f0-9]*$/i) === null) { + $this_input.addClass("invalid_value"); + return false; + } + } } } - /* End of datetime validation*/ + /* End of fields validation*/ /** diff --git a/libraries/DisplayResults.class.php b/libraries/DisplayResults.class.php index cb19c8b721..bf425b3b0b 100644 --- a/libraries/DisplayResults.class.php +++ b/libraries/DisplayResults.class.php @@ -1661,11 +1661,6 @@ class PMA_DisplayResults 'display_blob', __('Show BLOB contents'), ! empty($_SESSION['tmpval']['display_blob']), false ) - . '
' - . PMA_Util::getCheckbox( - 'display_binary_as_hex', __('Show binary contents as HEX'), - ! empty($_SESSION['tmpval']['display_binary_as_hex']), false - ) . ''; // I would have preferred to name this "display_transformation". @@ -2413,8 +2408,8 @@ class PMA_DisplayResults * * @access private * - * @see _getDataCellForBlobColumns(), _getDataCellForGeometryColumns(), - * _getDataCellForNonNumericAndNonBlobColumns() + * @see _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns() */ private function _buildValueDisplay($class, $condition_field, $value) { @@ -2435,9 +2430,9 @@ class PMA_DisplayResults * * @access private * - * @see _getDataCellForNumericColumns(), _getDataCellForBlobColumns(), + * @see _getDataCellForNumericColumns(), * _getDataCellForGeometryColumns(), - * _getDataCellForNonNumericAndNonBlobColumns() + * _getDataCellForNonNumericColumns() */ private function _buildNullDisplay($class, $condition_field, $meta, $align = '') { @@ -2463,9 +2458,9 @@ class PMA_DisplayResults * * @access private * - * @see _getDataCellForNumericColumns(), _getDataCellForBlobColumns(), + * @see _getDataCellForNumericColumns(), * _getDataCellForGeometryColumns(), - * _getDataCellForNonNumericAndNonBlobColumns() + * _getDataCellForNonNumericColumns() */ private function _buildEmptyDisplay($class, $condition_field, $meta, $align = '') { @@ -2517,6 +2512,11 @@ class PMA_DisplayResults $bit_class = ' bit'; } + $hex_class = ''; + if (strpos($meta->flags, 'binary') !== false) { + $hex_class = ' hex'; + } + $mime_type_class = ''; if (isset($meta->mimetype)) { $mime_type_class = ' ' . preg_replace('/\//', '_', $meta->mimetype); @@ -2525,7 +2525,7 @@ class PMA_DisplayResults return $class . ($condition_field ? ' condition' : '') . $nowrap . ' ' . ($is_field_truncated ? ' truncated' : '') . ($transformation_plugin != $default_function ? ' transformed' : '') - . $enum_class . $set_class . $bit_class . $mime_type_class; + . $enum_class . $set_class . $bit_class . $hex_class . $mime_type_class; } // end of the '_addClass()' function @@ -2962,20 +2962,6 @@ class PMA_DisplayResults $transform_options ); - } elseif (stristr($meta->type, self::BLOB_FIELD)) { - // b l o b - - // PMA_mysql_fetch_fields returns BLOB in place of - // TEXT fields type so we have to ensure it's really a BLOB - $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $i); - - $vertical_display['data'][$row_no][$i] - = $this->_getDataCellForBlobColumns( - $row[$i], $class, $meta, $_url_params, $field_flags, - $transformation_plugin, $default_function, - $transform_options, $condition_field, $is_field_truncated - ); - } elseif ($meta->type == self::GEOMETRY_FIELD) { // g e o m e t r y @@ -2992,10 +2978,10 @@ class PMA_DisplayResults ); } else { - // n o t n u m e r i c a n d n o t B L O B + // n o t n u m e r i c $vertical_display['data'][$row_no][$i] - = $this->_getDataCellForNonNumericAndNonBlobColumns( + = $this->_getDataCellForNonNumericColumns( $row[$i], $class, $meta, $map, $_url_params, $condition_field, $transformation_plugin, $default_function, $transform_options, @@ -3717,105 +3703,6 @@ class PMA_DisplayResults } // end of the '_getDataCellForNumericColumns()' function - /** - * Get data cell for blob type fields - * - * @param string $column the relevant column in data row - * @param string $class the html class for column - * @param object $meta the meta-information about this - * field - * @param array $_url_params the parameters for generate url - * @param string $field_flags field flags for column(blob, - * primary etc) - * @param string $transformation_plugin the name of transformation function - * @param string $default_function the default transformation function - * @param string $transform_options the transformation parameters - * @param boolean $condition_field the column should highlighted - * or not - * @param boolean $is_field_truncated the condition for blob data - * replacements - * - * @return string $cell the prepared cell, html content - * - * @access private - * - * @see _getTableBody() - */ - private function _getDataCellForBlobColumns( - $column, $class, $meta, $_url_params, $field_flags, $transformation_plugin, - $default_function, $transform_options, $condition_field, $is_field_truncated - ) { - - if (stristr($field_flags, self::BINARY_FIELD)) { - - // remove 'grid_edit' from $class as we can't edit binary data. - $class = str_replace('grid_edit', '', $class); - - if (! isset($column) || is_null($column)) { - - $cell = $this->_buildNullDisplay($class, $condition_field, $meta); - - } else { - - $blobtext = $this->_handleNonPrintableContents( - self::BLOB_FIELD, (isset($column) ? $column : ''), - $transformation_plugin, $transform_options, - $default_function, $meta, $_url_params - ); - - $cell = $this->_buildValueDisplay( - $class, $condition_field, $blobtext - ); - unset($blobtext); - } - } else { - // not binary: - - if (! isset($column) || is_null($column)) { - - $cell = $this->_buildNullDisplay($class, $condition_field, $meta); - - } elseif ($column != '') { - - // if a transform function for blob is set, none of these - // replacements will be made - $limitChars = $GLOBALS['cfg']['LimitChars']; - if (($GLOBALS['PMA_String']->strlen($column) > $limitChars) - && ($_SESSION['tmpval']['pftext'] == self::DISPLAY_PARTIAL_TEXT) - && empty($this->transformation_info[strtolower($this->__get('db'))][strtolower($this->__get('table'))][strtolower(strtolower($meta->name))]) - ) { - $column = $GLOBALS['PMA_String']->substr( - $column, 0, $GLOBALS['cfg']['LimitChars'] - ) . '...'; - $is_field_truncated = true; - } - - // displays all space characters, 4 space - // characters for tabulations and / - $column = ($default_function != $transformation_plugin) - ? $transformation_plugin->applyTransformation( - $column, - $transform_options, - $meta - ) - : $default_function($column, array(), $meta); - - if ($is_field_truncated) { - $class .= ' truncated'; - } - - $cell = $this->_buildValueDisplay($class, $condition_field, $column); - - } else { - $cell = $this->_buildEmptyDisplay($class, $condition_field, $meta); - } - } - - return $cell; - - } // end of the '_getDataCellForBlobColumns()' function - - /** * Get data cell for geometry type fields * @@ -3843,9 +3730,6 @@ class PMA_DisplayResults $is_field_truncated, $analyzed_sql ) { - $pftext = $_SESSION['tmpval']['pftext']; - $limitChars = $GLOBALS['cfg']['LimitChars']; - if (! isset($column) || is_null($column)) { $cell = $this->_buildNullDisplay($class, $condition_field, $meta); @@ -3872,15 +3756,7 @@ class PMA_DisplayResults // Convert to WKT format $wktval = PMA_Util::asWKT($column); - - if (($GLOBALS['PMA_String']->strlen($wktval) > $limitChars) - && ($pftext == self::DISPLAY_PARTIAL_TEXT) - ) { - $wktval = $GLOBALS['PMA_String']->substr( - $wktval, 0, $limitChars - ) . '...'; - $is_field_truncated = true; - } + $is_field_truncated = $this->_getPartialText($wktval); $cell = $this->_getRowData( $class, $condition_field, $analyzed_sql, $meta, $map, @@ -3896,16 +3772,8 @@ class PMA_DisplayResults $where_comparison = ' = ' . $column; - $wkbval = $this->_displayBinaryAsPrintable($column, 'binary', 8); - - if (($GLOBALS['PMA_String']->strlen($wkbval) > $limitChars) - && ($pftext == self::DISPLAY_PARTIAL_TEXT) - ) { - $wkbval = $GLOBALS['PMA_String']->substr( - $wkbval, 0, $GLOBALS['cfg']['LimitChars'] - ) . '...'; - $is_field_truncated = true; - } + $wkbval = $GLOBALS['PMA_String']->substr(bin2hex($column), 8); + $is_field_truncated = $this->_getPartialText($wkbval); $cell = $this->_getRowData( $class, $condition_field, @@ -3937,7 +3805,7 @@ class PMA_DisplayResults /** - * Get data cell for non numeric and non blob type fields + * Get data cell for non numeric type fields * * @param string $column the relevant column in data row * @param string $class the html class for column @@ -3949,11 +3817,10 @@ class PMA_DisplayResults * @param string $transformation_plugin the name of transformation function * @param string $default_function the default transformation function * @param string $transform_options the transformation parameters - * @param boolean $is_field_truncated the condition for blob data - * replacements + * @param boolean $is_field_truncated is data truncated due to LimitChars * @param array $analyzed_sql the analyzed query * @param integer &$dt_result the link id associated to the query - * which results have to be displayed + * which results have to be displayed * @param integer $col_index the column index * * @return string $cell the prepared data cell, html content @@ -3962,18 +3829,26 @@ class PMA_DisplayResults * * @see _getTableBody() */ - private function _getDataCellForNonNumericAndNonBlobColumns( + private function _getDataCellForNonNumericColumns( $column, $class, $meta, $map, $_url_params, $condition_field, $transformation_plugin, $default_function, $transform_options, $is_field_truncated, $analyzed_sql, &$dt_result, $col_index ) { - $limitChars = $GLOBALS['cfg']['LimitChars']; $is_analyse = $this->__get('is_analyse'); $field_flags = $GLOBALS['dbi']->fieldFlags($dt_result, $col_index); - if (stristr($field_flags, self::BINARY_FIELD) - && ($GLOBALS['cfg']['ProtectBinary'] == 'all' - || $GLOBALS['cfg']['ProtectBinary'] == 'noblob') + // disable inline grid editing + // if binary fields are protected + // or transformation plugin is of non text type + // such as image + if ((stristr($field_flags, self::BINARY_FIELD) + && ($GLOBALS['cfg']['ProtectBinary'] === 'all' + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' + && !stristr($meta->type, self::BLOB_FIELD)) + || ($GLOBALS['cfg']['ProtectBinary'] === 'blob' + && stristr($meta->type, self::BLOB_FIELD)))) + || (gettype($transformation_plugin) === 'object' + && strpos($transformation_plugin->getMIMEtype(), 'Text') === false) ) { $class = str_replace('grid_edit', '', $class); } @@ -3985,16 +3860,12 @@ class PMA_DisplayResults } elseif ($column != '') { // Cut all fields to $GLOBALS['cfg']['LimitChars'] - // (unless it's a link-type transformation) - if ($GLOBALS['PMA_String']->strlen($column) > $limitChars - && ($_SESSION['tmpval']['pftext'] == self::DISPLAY_PARTIAL_TEXT) - && ! (gettype($transformation_plugin) == "object" + // (unless it's a link-type transformation or binary) + if (!(gettype($transformation_plugin) === "object" && strpos($transformation_plugin->getName(), 'Link') !== false) + && !stristr($field_flags, self::BINARY_FIELD) ) { - $column = $GLOBALS['PMA_String']->substr( - $column, 0, $GLOBALS['cfg']['LimitChars'] - ) . '...'; - $is_field_truncated = true; + $is_field_truncated = $this->_getPartialText($column); } $formatted = false; @@ -4008,26 +3879,31 @@ class PMA_DisplayResults // being BINARY but they are quite readable, // so don't treat them as BINARY } elseif (stristr($field_flags, self::BINARY_FIELD) - && ($meta->type == self::STRING_FIELD) && !(isset($is_analyse) && $is_analyse) ) { - - if ($_SESSION['tmpval']['display_binary']) { - - // user asked to see the real contents of BINARY - // fields - $column = $this->_displayBinaryAsPrintable($column, 'binary'); - - } else { - // we show the BINARY message and field's size - // (or maybe use a transformation) - $column = $this->_handleNonPrintableContents( - self::BINARY_FIELD, $column, $transformation_plugin, - $transform_options, $default_function, - $meta, $_url_params - ); - $formatted = true; + // we show the BINARY or BLOB message and field's size + // (or maybe use a transformation) + $binary_or_blob = self::BLOB_FIELD; + if ($meta->type === self::STRING_FIELD) { + $binary_or_blob = self::BINARY_FIELD; } + $column = $this->_handleNonPrintableContents( + $binary_or_blob, $column, $transformation_plugin, + $transform_options, $default_function, + $meta, $_url_params, $is_field_truncated + ); + $class = $this->_addClass( + $class, $condition_field, $meta, '', + $is_field_truncated, $transformation_plugin, $default_function + ); + $result = strip_tags($column); + // disable inline grid editing + // if binary or blob data is not shown + if (stristr($result, $binary_or_blob)) { + $class = str_replace('grid_edit', '', $class); + } + $formatted = true; + } elseif (((substr($meta->type, 0, 9) == self::TIMESTAMP_FIELD) || ($meta->type == self::DATETIME_FIELD) || ($meta->type == self::TIME_FIELD) @@ -4076,7 +3952,7 @@ class PMA_DisplayResults return $cell; - } // end of the '_getDataCellForNonNumericAndNonBlobColumns()' function + } // end of the '_getDataCellForNonNumericColumns()' function /** @@ -4443,23 +4319,6 @@ class PMA_DisplayResults $query['display_binary'] = true; } - if (isset($_REQUEST['display_binary_as_hex'])) { - $query['display_binary_as_hex'] = true; - unset($_REQUEST['display_binary_as_hex']); - } elseif (isset($_REQUEST['display_options_form'])) { - // we know that the checkbox was unchecked - unset($query['display_binary_as_hex']); - } elseif (isset($_REQUEST['full_text_button'])) { - // do nothing to keep the value that is there in the session - } else { - // display_binary_as_hex config option - if (isset($GLOBALS['cfg']['DisplayBinaryAsHex']) - && ($GLOBALS['cfg']['DisplayBinaryAsHex'] === true) - ) { - $query['display_binary_as_hex'] = true; - } - } - if (isset($_REQUEST['display_blob'])) { $query['display_blob'] = true; unset($_REQUEST['display_blob']); @@ -4498,9 +4357,6 @@ class PMA_DisplayResults $_SESSION['tmpval']['display_binary'] = isset( $query['display_binary'] ); - $_SESSION['tmpval']['display_binary_as_hex'] = isset( - $query['display_binary_as_hex'] - ); $_SESSION['tmpval']['display_blob'] = isset( $query['display_blob'] ); @@ -5501,31 +5357,32 @@ class PMA_DisplayResults * Verifies what to do with non-printable contents (binary or BLOB) * in Browse mode. * - * @param string $category BLOB|BINARY|GEOMETRY - * @param string $content the binary content - * @param string $transformation_plugin transformation plugin. - * Can also be the default function: - * PMA_mimeDefaultFunction - * @param string $transform_options transformation parameters - * @param string $default_function default transformation function - * @param object $meta the meta-information about the field - * @param array $url_params parameters that should go to the - * download link + * @param string $category BLOB|BINARY|GEOMETRY + * @param string $content the binary content + * @param string $transformation_plugin transformation plugin. + * Can also be the default function: + * PMA_mimeDefaultFunction + * @param string $transform_options transformation parameters + * @param string $default_function default transformation function + * @param object $meta the meta-information about the field + * @param array $url_params parameters that should go to the + * download link + * @param boolean &$is_truncated the result is truncated or not * * @return mixed string or float * * @access private * - * @see _getDataCellForBlobColumns(), - * _getDataCellForGeometryColumns(), - * _getDataCellForNonNumericAndNonBlobColumns(), + * @see _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns(), * _getSortedColumnMessage() */ private function _handleNonPrintableContents( $category, $content, $transformation_plugin, $transform_options, - $default_function, $meta, $url_params = array() + $default_function, $meta, $url_params = array(), &$is_truncated = null ) { + $is_truncated = false; $result = '[' . $category; if (isset($content)) { @@ -5544,11 +5401,13 @@ class PMA_DisplayResults $result .= ']'; // if we want to use a text transformation on a BLOB column - if (gettype($transformation_plugin) == "object" + if (gettype($transformation_plugin) === "object" && (strpos($transformation_plugin->getMIMESubtype(), 'Octetstream') || strpos($transformation_plugin->getMIMEtype(), 'Text') !== false) ) { - $result = $content; + // Applying Transformations on hex string of binary data + // seems more appropriate + $result = bin2hex($content); } if ($size > 0) { @@ -5562,11 +5421,14 @@ class PMA_DisplayResults } else { $result = $default_function($result, array(), $meta); - if (stristr($meta->type, self::BLOB_FIELD) - && $_SESSION['tmpval']['display_blob'] + if (($_SESSION['tmpval']['display_binary'] + && $meta->type === self::STRING_FIELD) + || ($_SESSION['tmpval']['display_blob'] + && stristr($meta->type, self::BLOB_FIELD)) ) { // in this case, restart from the original $content - $result = $this->_displayBinaryAsPrintable($content, 'blob'); + $result = bin2hex($content); + $is_truncated = $this->_getPartialText($result); } /* Create link to download */ @@ -5610,7 +5472,7 @@ class PMA_DisplayResults * @access private * * @see _getDataCellForNumericColumns(), _getDataCellForGeometryColumns(), - * _getDataCellForNonNumericAndNonBlobColumns(), + * _getDataCellForNonNumericColumns(), * */ private function _getRowData( @@ -6060,50 +5922,34 @@ class PMA_DisplayResults } // end of the '_getCheckboxAndLinks()' function + /** - * Display binary columns as hex string if requested - * otherwise escape the contents using the best possible way + * Truncates given string based on LimitChars configuration + * and Session pftext variable + * (string is truncated only if necessary) * - * @param string $content String to parse - * @param string $binary_or_blob binary' or 'blob' - * @param int $hexlength optional, get substring + * @param string &$str string to be truncated * - * @return String Displayable version of the binary string + * @return boolean true if truncated, otherwise false * - * @access private + * @access private * - * @see _getDataCellForGeometryColumns - * _getDataCellForNonNumericAndNonBlobColumns - * _handleNonPrintableContents + * @see _handleNonPrintableContents(), _getDataCellForGeometryColumns(), + * _getDataCellForNonNumericColumns */ - private function _displayBinaryAsPrintable( - $content, $binary_or_blob, $hexlength = null - ) { - if ($binary_or_blob === 'binary' - && $_SESSION['tmpval']['display_binary_as_hex'] + private function _getPartialText(&$str) + { + if ($GLOBALS['PMA_String']->strlen($str) > $GLOBALS['cfg']['LimitChars'] + && $_SESSION['tmpval']['pftext'] === self::DISPLAY_PARTIAL_TEXT ) { - $content = bin2hex($content); - if ($hexlength !== null) { - $content = $GLOBALS['PMA_String']->substr($content, $hexlength); - } - } elseif (PMA_Util::containsNonPrintableAscii($content)) { - if (PMA_PHP_INT_VERSION < 50400) { - $content = htmlspecialchars( - PMA_Util::replaceBinaryContents( - $content - ) - ); - } else { - // The ENT_SUBSTITUTE option is available for PHP >= 5.4.0 - $content = htmlspecialchars( - PMA_Util::replaceBinaryContents( - $content - ), - ENT_SUBSTITUTE - ); - } + $str = $GLOBALS['PMA_String']->substr( + $str, 0, $GLOBALS['cfg']['LimitChars'] + ) . '...'; + + return true; } - return $content; + + return false; } } diff --git a/libraries/Header.class.php b/libraries/Header.class.php index dcb3f39bc1..3a237dccd2 100644 --- a/libraries/Header.class.php +++ b/libraries/Header.class.php @@ -206,6 +206,8 @@ class PMA_Header { $db = ! empty($GLOBALS['db']) ? $GLOBALS['db'] : ''; $table = ! empty($GLOBALS['table']) ? $GLOBALS['table'] : ''; + $pftext = ! empty($_SESSION['tmpval']['pftext']) + ? $_SESSION['tmpval']['pftext'] : ''; return array( 'common_query' => PMA_URL_getCommon('', '', '&'), 'opendb_url' => $GLOBALS['cfg']['DefaultTabDatabase'], @@ -226,6 +228,8 @@ class PMA_Header 'pma_text_left_default_tab' => PMA_Util::getTitleForTarget( $GLOBALS['cfg']['NavigationTreeDefaultTabTable'] ), + 'LimitChars' => $GLOBALS['cfg']['LimitChars'], + 'pftext' => $pftext, 'confirm' => $GLOBALS['cfg']['Confirm'] ); } diff --git a/libraries/Util.class.php b/libraries/Util.class.php index af390813f7..e62f7d9586 100644 --- a/libraries/Util.class.php +++ b/libraries/Util.class.php @@ -3683,11 +3683,6 @@ class PMA_Util $default_function = $cfg['DefaultFunctions']['FUNC_UUID']; } - // this is set only when appropriate and is always true - if (isset($field['display_binary_as_hex'])) { - $default_function = 'UNHEX'; - } - return $default_function; } diff --git a/libraries/config.default.php b/libraries/config.default.php index 6cc3d1ad16..d6e36c7251 100644 --- a/libraries/config.default.php +++ b/libraries/config.default.php @@ -1090,13 +1090,6 @@ $cfg['MaxRows'] = 25; */ $cfg['Order'] = 'SMART'; -/** - * default for 'Show binary contents as HEX' - * - * @global string $cfg['DisplayBinaryAsHex'] - */ -$cfg['DisplayBinaryAsHex'] = true; - /** * grid editing: save edited cell(s) in browse-mode at once * diff --git a/libraries/config/messages.inc.php b/libraries/config/messages.inc.php index bc19c99840..f41680622d 100644 --- a/libraries/config/messages.inc.php +++ b/libraries/config/messages.inc.php @@ -89,8 +89,6 @@ $strConfigDefaultTabTable_name = __('Default table tab'); $strConfigHideStructureActions_desc = __('Whether the table structure actions should be hidden.'); $strConfigHideStructureActions_name = __('Hide table structure actions'); -$strConfigDisplayBinaryAsHex_desc = __('Show binary contents as HEX by default.'); -$strConfigDisplayBinaryAsHex_name = __('Show binary contents as HEX'); $strConfigDisplayServersList_desc = __('Show server listing as a list instead of a drop down.'); $strConfigDisplayServersList_name = __('Display servers as a list'); diff --git a/libraries/config/user_preferences.forms.php b/libraries/config/user_preferences.forms.php index 51b481fa76..604a35b0c8 100644 --- a/libraries/config/user_preferences.forms.php +++ b/libraries/config/user_preferences.forms.php @@ -112,7 +112,6 @@ $forms['Main_panel']['Browse'] = array( 'ShowAll', 'MaxRows', 'Order', - 'DisplayBinaryAsHex', 'BrowsePointerEnable', 'BrowseMarkerEnable', 'GridEditing', diff --git a/libraries/insert_edit.lib.php b/libraries/insert_edit.lib.php index f23561e2dd..6538bd610e 100644 --- a/libraries/insert_edit.lib.php +++ b/libraries/insert_edit.lib.php @@ -454,9 +454,12 @@ function PMA_getFunctionColumn($column, $is_upload, $column_name_appendix, $tabindex, $idindex, $insert_mode ) { $html_output = ''; - if (($GLOBALS['cfg']['ProtectBinary'] && $column['is_blob'] && ! $is_upload) - || ($GLOBALS['cfg']['ProtectBinary'] === 'all' && $column['is_binary']) - || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && ! $column['is_blob']) + if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' + && $column['is_blob'] && !$is_upload) + || ($GLOBALS['cfg']['ProtectBinary'] === 'all' + && $column['is_binary']) + || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' + && $column['is_binary']) ) { $html_output .= '' . __('Binary') . '' . "\n"; } elseif (strstr($column['True_Type'], 'enum') @@ -1095,6 +1098,11 @@ function PMA_getBinaryAndBlobColumn( $vkey, $is_upload ) { $html_output = ''; + // Add field type : Protected or Hexadecimal + $fields_type_html = ''; + // Default value : hex + $fields_type_val = 'hex'; if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' && $column['is_blob']) || ($GLOBALS['cfg']['ProtectBinary'] === 'all') || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && !$column['is_blob']) @@ -1107,9 +1115,8 @@ function PMA_getBinaryAndBlobColumn( $html_output .= ' (' . $data_size[0] . ' ' . $data_size[1] . ')'; unset($data_size); } - $html_output .= '' - . ''; } elseif ($column['is_blob'] || ($column['len'] > $GLOBALS['cfg']['LimitChars']) @@ -1127,6 +1134,7 @@ function PMA_getBinaryAndBlobColumn( $unnullify_trigger, $tabindex, $tabindex_for_value, $idindex ); } + $html_output .= sprintf($fields_type_html, $fields_type_val); if ($is_upload && $column['is_blob']) { $html_output .= '
' @@ -1680,21 +1688,11 @@ function PMA_getSpecialCharsAndBackupFieldForExistingRow( } else { // special binary "characters" if ($column['is_binary'] - || ($column['is_blob'] && ! $GLOBALS['cfg']['ProtectBinary']) + || ($column['is_blob'] && $GLOBALS['cfg']['ProtectBinary'] !== 'all') ) { - if ($_SESSION['tmpval']['display_binary_as_hex'] - && $GLOBALS['cfg']['ShowFunctionFields'] - ) { - $current_row[$column['Field']] = bin2hex( - $current_row[$column['Field']] - ); - $column['display_binary_as_hex'] = true; - } else { + $current_row[$column['Field']] = bin2hex( $current_row[$column['Field']] - = PMA_Util::replaceBinaryContents( - $current_row[$column['Field']] - ); - } + ); } // end if $special_chars = htmlspecialchars($current_row[$column['Field']]); @@ -1766,15 +1764,6 @@ function PMA_getSpecialCharsAndBackupFieldForInsertingMode( } $backup_field = ''; $special_chars_encoded = PMA_Util::duplicateFirstNewline($special_chars); - // this will select the UNHEX function while inserting - if (($column['is_binary'] - || ($column['is_blob'] && ! $GLOBALS['cfg']['ProtectBinary'])) - && (isset($_SESSION['tmpval']['display_binary_as_hex']) - && $_SESSION['tmpval']['display_binary_as_hex']) - && $GLOBALS['cfg']['ShowFunctionFields'] - ) { - $column['display_binary_as_hex'] = true; - } return array( $real_null_value, $data, $special_chars, $backup_field, $special_chars_encoded @@ -2279,7 +2268,8 @@ function PMA_getQueryValuesForInsertAndUpdateInMultipleEdit($multi_edit_columns_ . ' = ' . $current_value_as_an_array; } elseif (empty($multi_edit_funcs[$key]) && isset($multi_edit_columns_prev[$key]) - && ("'" . PMA_Util::sqlAddSlashes($multi_edit_columns_prev[$key]) . "'" == $current_value) + && (("'" . PMA_Util::sqlAddSlashes($multi_edit_columns_prev[$key]) . "'" === $current_value) + || ('0x' . $multi_edit_columns_prev[$key] === $current_value)) ) { // No change for this column and no MySQL function is used -> next column } elseif (! empty($current_value)) { @@ -2376,6 +2366,8 @@ function PMA_getCurrentValueForDifferentTypes($possibly_uploaded_val, $key, } else { $current_value = ''; } + } elseif ($type === 'hex') { + $current_value = '0x' . $current_value; } elseif ($type == 'bit') { $current_value = preg_replace('/[^01]/', '0', $current_value); $current_value = "b'" . PMA_Util::sqlAddSlashes($current_value) . "'"; diff --git a/libraries/sql.lib.php b/libraries/sql.lib.php index 2033e2a102..6614143b2e 100644 --- a/libraries/sql.lib.php +++ b/libraries/sql.lib.php @@ -1755,6 +1755,10 @@ function PMA_sendQueryResponseForNoResultsReturned($analyzed_sql_results, $db, function PMA_sendResponseForGridEdit($result) { $row = $GLOBALS['dbi']->fetchRow($result); + $field_flags = $GLOBALS['dbi']->fieldFlags($result, 0); + if (stristr($field_flags, PMA_DisplayResults::BINARY_FIELD)) { + $row[0] = bin2hex($row[0]); + } $response = PMA_Response::getInstance(); $response->addJSON('value', $row[0]); exit; diff --git a/test/bootstrap-dist.php b/test/bootstrap-dist.php index fd1e078c59..dde8f7f9e9 100644 --- a/test/bootstrap-dist.php +++ b/test/bootstrap-dist.php @@ -55,6 +55,7 @@ session_start(); // Standard environment for tests $_SESSION[' PMA_token '] = 'token'; +$_SESSION['tmpval']['pftext'] = 'F'; $GLOBALS['lang'] = 'en'; $GLOBALS['is_ajax_request'] = false; diff --git a/test/classes/PMA_DisplayResults_test.php b/test/classes/PMA_DisplayResults_test.php index c4d60335d5..5f0e6dd8a9 100644 --- a/test/classes/PMA_DisplayResults_test.php +++ b/test/classes/PMA_DisplayResults_test.php @@ -16,6 +16,9 @@ require_once 'libraries/js_escape.lib.php'; require_once 'libraries/core.lib.php'; require_once 'libraries/Config.class.php'; require_once 'libraries/relation.lib.php'; +require_once 'libraries/String.class.php'; +require_once 'libraries/plugins/transformations/Text_Plain_Link.class.php'; +require_once 'libraries/DatabaseInterface.class.php'; /** * Test cases for displaying results. @@ -38,12 +41,22 @@ class PMA_DisplayResults_Test extends PHPUnit_Framework_TestCase */ protected function setUp() { + $GLOBALS['server'] = 0; $this->object = new PMA_DisplayResults('as', '', '', ''); $GLOBALS['PMA_Config'] = new PMA_Config(); $GLOBALS['PMA_Config']->enableBc(); - $GLOBALS['server'] = 0; $GLOBALS['text_dir'] = 'ltr'; + $GLOBALS['PMA_String'] = new PMA_String(); include_once 'libraries/Response.class.php'; + + $dbi = $this->getMockBuilder('PMA_DatabaseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $dbi->expects($this->any())->method('fieldFlags') + ->will($this->returnArgument(1)); + + $GLOBALS['dbi'] = $dbi; } /** @@ -736,7 +749,10 @@ class PMA_DisplayResults_Test extends PHPUnit_Framework_TestCase array( '`a_sales`.`customer_id` ASC, `b_sales`.`customer_id` DESC', array( - array('`a_sales`.`customer_id` ASC', '`b_sales`.`customer_id` DESC'), + array( + '`a_sales`.`customer_id` ASC', + '`b_sales`.`customer_id` DESC' + ), array('`a_sales`.`customer_id`', '`b_sales`.`customer_id`'), array('ASC', 'DESC') ) @@ -1704,4 +1720,308 @@ class PMA_DisplayResults_Test extends PHPUnit_Framework_TestCase $this->object->__get('highlight_columns') ); } + + + /** + * Data provider for testGetPartialText + * + * @return array parameters and output + */ + public function dataProviderForTestGetPartialText() + { + return array( + array('P', 10, 'foo', false), + array('P', 1, 'foo', true), + array('F', 10, 'foo', false), + array('F', 1, 'foo', false) + ); + } + + + /** + * Test _getPartialText + * + * @param string $pftext Partial or Full text + * @param integer $limitChars Partial or Full text + * @param string $str the string to be tested + * @param boolean $output return value of _getPartialText + * + * @return void + * + * @dataProvider dataProviderForTestGetPartialText + */ + public function testGetPartialText($pftext, $limitChars, $str, $output) + { + $_SESSION['tmpval']['pftext'] = $pftext; + $GLOBALS['cfg']['LimitChars'] = $limitChars; + $this->assertEquals( + $output, + $this->_callPrivateFunction( + '_getPartialText', + array(&$str) + ) + ); + } + + + /** + * Data provider for testHandleNonPrintableContents + * + * @return array parameters and output + */ + public function dataProviderForTestHandleNonPrintableContents() + { + $transformation_plugin = new Text_Plain_Link(null); + $meta = new StdClass(); + $meta->type = 'BLOB'; + $url_params = array('db' => 'foo'); + + return array( + array( + true, + true, + 'BLOB', + '1001', + 'PMA_mimeDefaultFunction', + '', + 'PMA_mimeDefaultFunction', + $meta, + $url_params, + null, + '31303031' + ), + array( + true, + false, + 'BLOB', + '1001', + 'PMA_mimeDefaultFunction', + '', + 'PMA_mimeDefaultFunction', + $meta, + $url_params, + null, + '[BLOB - 4 B]' + ), + array( + false, + false, + 'BINARY', + '1001', + $transformation_plugin, + '', + 'PMA_mimeDefaultFunction', + $meta, + $url_params, + null, + '31303031' + ), + array( + false, + true, + 'GEOMETRY', + null, + '', + '', + 'PMA_mimeDefaultFunction', + $meta, + $url_params, + null, + '[GEOMETRY - NULL]' + ) + ); + } + + + /** + * Test _handleNonPrintableContents + * + * @param boolean $display_binary show binary contents? + * @param boolean $display_blob show blob contents? + * @param string $category BLOB|BINARY|GEOMETRY + * @param string $content the binary content + * @param string $transformation_plugin transformation plugin. + * Can also be the default function: + * PMA_mimeDefaultFunction + * @param string $transform_options transformation parameters + * @param string $default_function default transformation function + * @param object $meta the meta-information about the field + * @param array $url_params parameters that should go to the + * download link + * @param boolean $is_truncated the result is truncated or not + * @param string $output the output of this function + * + * @return void + * + * @dataProvider dataProviderForTestHandleNonPrintableContents + */ + public function testHandleNonPrintableContents( + $display_binary, $display_blob, $category, $content, + $transformation_plugin, $transform_options, $default_function, + $meta, $url_params, $is_truncated, $output + ) { + $_SESSION['tmpval']['display_binary'] = $display_binary; + $_SESSION['tmpval']['display_blob'] = $display_blob; + $GLOBALS['cfg']['LimitChars'] = 50; + $this->assertEquals( + $output, + $this->_callPrivateFunction( + '_handleNonPrintableContents', + array( + $category, $content, $transformation_plugin, + $transform_options, $default_function, + $meta, $url_params, &$is_truncated + ) + ) + ); + } + + + /** + * Data provider for testGetDataCellForNonNumericColumns + * + * @return array parameters and output + */ + public function dataProviderForTestGetDataCellForNonNumericColumns() + { + $transformation_plugin = new Text_Plain_Link(null); + $meta = new StdClass(); + $meta->type = 'BLOB'; + $meta->flags = 'blob binary'; + + $meta2 = new StdClass(); + $meta2->type = 'string'; + $meta2->flags = ''; + $meta2->decimals = 0; + $meta2->name = 'varchar'; + $url_params = array('db' => 'foo'); + + return array( + array( + 'all', + '1001', + 'grid_edit', + $meta, + array(), + $url_params, + false, + 'PMA_mimeDefaultFunction', + 'PMA_mimeDefaultFunction', + array('http://www.github.com/'), + false, + array(), + 0, + 'binary', + '[BLOB - 4 B]' + ), + array( + 'noblob', + '1001', + 'grid_edit', + $meta, + array(), + $url_params, + false, + $transformation_plugin, + 'PMA_mimeDefaultFunction', + '', + false, + array(), + 0, + 'binary', + '' + . '31303031' + ), + array( + 'noblob', + null, + 'grid_edit', + $meta2, + array(), + $url_params, + false, + $transformation_plugin, + 'PMA_mimeDefaultFunction', + '', + false, + array(), + 0, + 0, + 'NULL' + ), + array( + 'all', + 'foo bar baz', + 'grid_edit', + $meta2, + array(), + $url_params, + false, + 'PMA_mimeDefaultFunction', + 'PMA_mimeDefaultFunction', + '', + false, + array(), + 0, + 0, + 'foo bar baz' . "\n" + ) + ); + } + + + /** + * Test _getDataCellForNonNumericColumns + * + * @param boolean $protectBinary all|blob|noblob|no + * @param string $column the relevant column in data row + * @param string $class the html class for column + * @param object $meta the meta-information about the field + * @param array $map the list of relations + * @param array $_url_params the parameters for generate url + * @param boolean $condition_field the column should highlighted + * or not + * @param string $transformation_plugin the name of transformation function + * @param string $default_function the default transformation function + * @param string $transform_options the transformation parameters + * @param boolean $is_field_truncated is data truncated due to LimitChars + * @param array $analyzed_sql the analyzed query + * @param integer $dt_result the link id associated to the query + * which results have to be displayed + * @param integer $col_index the column index + * @param string $output the output of this function + * + * @return void + * + * @dataProvider dataProviderForTestGetDataCellForNonNumericColumns + */ + public function testGetDataCellForNonNumericColumns( + $protectBinary, $column, $class, $meta, $map, + $_url_params, $condition_field, $transformation_plugin, + $default_function, $transform_options, $is_field_truncated, + $analyzed_sql, $dt_result, $col_index, $output + ) { + $_SESSION['tmpval']['display_binary'] = true; + $_SESSION['tmpval']['display_blob'] = false; + $_SESSION['tmpval']['relational_display'] = false; + $GLOBALS['cfg']['LimitChars'] = 50; + $GLOBALS['cfg']['ProtectBinary'] = $protectBinary; + $this->assertEquals( + $output, + $this->_callPrivateFunction( + '_getDataCellForNonNumericColumns', + array( + $column, $class, $meta, $map, $_url_params, $condition_field, + $transformation_plugin, $default_function, $transform_options, + $is_field_truncated, $analyzed_sql, &$dt_result, $col_index + ) + ) + ); + } } diff --git a/test/libraries/PMA_insert_edit_test.php b/test/libraries/PMA_insert_edit_test.php index 6fc836fb6e..217ecc9b35 100644 --- a/test/libraries/PMA_insert_edit_test.php +++ b/test/libraries/PMA_insert_edit_test.php @@ -569,7 +569,7 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase */ public function testGetFunctionColumn() { - $GLOBALS['cfg']['ProtectBinary'] = true; + $GLOBALS['cfg']['ProtectBinary'] = 'blob'; $column['is_blob'] = true; $this->assertTag( PMA_getTagArray('', array('content' => 'Binary')), @@ -1141,8 +1141,9 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase ); $this->assertEquals( - 'Binary - do not edit (5 B)' + 'Binary - do not edit (5 B)' . '
 (Max: 64KiB)' . "\n", $result @@ -1158,9 +1159,9 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase ); $this->assertEquals( - 'Binary - do not edit (4 B)', + 'Binary - do not edit (4 B)', $result ); @@ -1174,9 +1175,9 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase ); $this->assertEquals( - 'Binary - do not edit (4 B)', + 'Binary - do not edit (4 B)', $result ); @@ -1200,7 +1201,8 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase "\na\n" . '

 (Max: 64KiB)' . "\n", $result @@ -1224,7 +1226,8 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase $this->assertEquals( "\na\n" . '', + . 'id="field_1_3" c tabindex="3">', $result ); @@ -1248,7 +1251,8 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase $this->assertEquals( "\na\n" . '', + . '"textfield" c tabindex="3" id="field_1_3" />' + . '', $result ); } @@ -1732,7 +1736,6 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase $current_row['f'] = "11001"; $extracted_columnspec['spec_in_brackets'] = 20; $column['True_Type'] = 'char'; - $_SESSION['tmpval']['display_binary_as_hex'] = true; $GLOBALS['cfg']['ShowFunctionFields'] = true; $result = PMA_getSpecialCharsAndBackupFieldForExistingRow( @@ -1750,9 +1753,8 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase $result ); - // Case 5 (false display_binary_as_hex) + // Case 5 $current_row['f'] = "11001\x00"; - $_SESSION['tmpval']['display_binary_as_hex'] = false; $result = PMA_getSpecialCharsAndBackupFieldForExistingRow( $current_row, $column, $extracted_columnspec, false, array('int'), 'a' @@ -1761,10 +1763,10 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase $this->assertEquals( array( false, - '11001\0', - '11001\0', - '11001\0', - '' + "313130303100", + "313130303100", + "313130303100", + '' ), $result ); @@ -1781,7 +1783,6 @@ class PMA_InsertEditTest extends PHPUnit_Framework_TestCase $column['Default'] = b'101'; $column['is_binary'] = true; $GLOBALS['cfg']['ProtectBinary'] = false; - $_SESSION['tmpval']['display_binary_as_hex'] = true; $GLOBALS['cfg']['ShowFunctionFields'] = true; $result = PMA_getSpecialCharsAndBackupFieldForInsertingMode($column, false);