Added support for both aliased and non-aliased column names.
authorMattias Michaux <mattias.michaux@gmail.com>
Thu, 12 May 2016 07:42:08 +0000 (09:42 +0200)
committerMattias Michaux <mattias.michaux@gmail.com>
Thu, 12 May 2016 07:42:08 +0000 (09:42 +0200)
CRM/Core/Page/AJAX.php
CRM/Utils/Rule.php
CRM/Utils/Sort.php
CRM/Utils/Type.php
tests/phpunit/CRM/Utils/TypeTest.php

index 84185152f1767ade3fe74edfb0a573b2c6636067..5830a624cf5fab569794ff52d33402fcd12902ca 100644 (file)
@@ -222,7 +222,7 @@ class CRM_Core_Page_AJAX {
     $sortMapper = array();
     if (isset($_GET['columns'])) {
       foreach ($_GET['columns'] as $key => $value) {
-        $sortMapper[$key] = CRM_Utils_Type::validate($value['data'], 'MysqlColumnName');
+        $sortMapper[$key] = CRM_Utils_Type::validate($value['data'], 'MysqlColumnNameOrAlias');
       };
     }
 
index efb64eb8589eb38fb4b626dcca37bafaabaafffe..3e6dbd0c4eea71227fa0313d07ae6485afd4643a 100644 (file)
@@ -88,24 +88,22 @@ class CRM_Utils_Rule {
   }
 
   /**
-   * Validate an acceptable column name for sorting results.
+   * Validate that a string is a valid MySQL column name or alias.
    *
    * @param $str
    *
    * @return bool
    */
-  public static function mysqlColumnName($str) {
+  public static function mysqlColumnNameOrAlias($str) {
     // Check not empty.
     if (empty($str)) {
       return FALSE;
     }
 
-    // Ensure it only contains valid characters (alphanumeric, hyphens and
-    // underscores).
-    //
-    // MySQL permits column names that don't match this (eg containing spaces),
-    // but CiviCRM won't create those ...
-    if (!preg_match('/^[\w-]{1,64}(\.[\w-]{1,64})?$/i', $str)) {
+    // Ensure the string contains only valid characters:
+    // For column names: alphanumeric and underscores
+    // For aliases: backticks, alphanumeric hyphens and underscores.
+    if (!preg_match('^((`[\w-]{1,64}`|\w{1,64})\.)?(`[\w-]{1,64}`|\w{1,64})$/i', $str)) {
       return FALSE;
     }
 
@@ -139,7 +137,7 @@ class CRM_Utils_Rule {
     // at all, so we split and loop over.
     $parts = explode(',', $str);
     foreach ($parts as $part) {
-      if (!preg_match('/^(([\w-]{1,64})((\.)([\w-]{1,64}))?( (asc|desc))?)$/i', trim($part))) {
+      if (!preg_match('/^((`[\w-]{1,64}`|\w{1,64})\.)?(`[\w-]{1,64}`|\w{1,64})( (asc|desc))?$/i', trim($part))) {
         return FALSE;
       }
     }
index 2b5765f23598eac92f0a8d6f4c53435fc312ab54..fec2d400e32d85250d0184a6f6cdb72f984bdc4b 100644 (file)
@@ -121,7 +121,7 @@ class CRM_Utils_Sort {
 
     foreach ($vars as $weight => $value) {
       $this->_vars[$weight] = array(
-        'name' => CRM_Utils_Type::validate($value['sort'], 'MysqlColumnName'),
+        'name' => CRM_Utils_Type::validate($value['sort'], 'MysqlColumnNameOrAlias'),
         'direction' => CRM_Utils_Array::value('direction', $value),
         'title' => $value['name'],
       );
index 44e8f17a07e8eed7e3184ae3ddb3e77cf18bb2a7..43fa00a5b29d47248a74dd447c13139be023da10 100644 (file)
@@ -268,8 +268,9 @@ class CRM_Utils_Type {
         }
         break;
 
-      case 'MysqlColumnName':
-        if (CRM_Utils_Rule::mysqlColumnName($data)) {
+      case 'mysqlColumnNameOrAlias':
+        if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
+          $data = str_replace('`', '', $data);
           $parts = explode('.', $data);
           $data = '`' . implode('`.`', $parts) . '`';
 
@@ -287,7 +288,7 @@ class CRM_Utils_Type {
         if (CRM_Utils_Rule::mysqlOrderBy($data)) {
           $parts = explode(',', $data);
           foreach ($parts as &$part) {
-            $part = preg_replace_callback('/(?:([\w]+)(?:(?:\.)([\w]+))?(?: (asc|desc))?)/i', array('CRM_Utils_Type', 'mysqlOrderByCallback'), trim($part));
+            $part = preg_replace_callback('/^(?:(?:((?:`[\w-]{1,64}`|\w{1,64}))(?:\.))?(`[\w-]{1,64}`|\w{1,64})(?: (asc|desc))?)$/i', array('CRM_Utils_Type', 'mysqlOrderByCallback'), trim($part));
           }
           return implode(', ', $parts);
         }
@@ -396,8 +397,8 @@ class CRM_Utils_Type {
         }
         break;
 
-      case 'MysqlColumnName':
-        if (CRM_Utils_Rule::mysqlColumnName($data)) {
+      case 'mysqlColumnNameOrAlias':
+        if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) {
           return $data;
         }
         break;
@@ -432,15 +433,16 @@ class CRM_Utils_Type {
    */
   public static function mysqlOrderByCallback($matches) {
     $output = '';
+    $matches = str_replace('`', '', $matches);
 
-    // Column or table name.
+    // Table name.
     if (isset($matches[1])) {
-      $output .= '`' . $matches[1] . '`';
+      $output .= '`' . $matches[1] . '`.';
     }
 
-    // Column name in case there is a table.
+    // Column name.
     if (isset($matches[2]) && $matches[2]) {
-      $output .= '.`' . $matches[2] . '`';
+      $output .= '`' . $matches[2] . '`';
     }
 
     // Sort order.
index 367f08e299e293b958eac165b1b6d34eb1106b5c..781b2d16ab66ae681c776036b3617bd264bb5ecb 100644 (file)
@@ -37,21 +37,26 @@ class CRM_Utils_TypeTest extends CiviUnitTestCase {
       array(-10, 'Positive', NULL),
       array('-10', 'Positive', NULL),
       array('-10foo', 'Positive', NULL),
-      array('civicrm_column_name', 'MysqlColumnName', 'civicrm_column_name'),
-      array('table.civicrm_column_name', 'MysqlColumnName', 'table.civicrm_column_name'),
-      array('table.civicrm_column_name.toomanydots', 'MysqlColumnName', NULL),
-      array('Home-street_address', 'MysqlColumnName', 'Home-street_address'),
-      array('column_name, sleep(5)', 'MysqlColumnName', NULL),
-      array(str_repeat('a', 64), 'MysqlColumnName', str_repeat('a', 64)),
-      array(str_repeat('a', 65), 'MysqlColumnName', NULL),
-      array(str_repeat('a', 64) . '.' . str_repeat('a', 64), 'MysqlColumnName', str_repeat('a', 64) . '.' . str_repeat('a', 64)),
-      array(str_repeat('a', 64) . '.' . str_repeat('a', 65), 'MysqlColumnName', NULL),
-      array(str_repeat('a', 65) . '.' . str_repeat('a', 64), 'MysqlColumnName', NULL),
+      array('civicrm_column_name', 'mysqlColumnNameOrAlias', 'civicrm_column_name'),
+      array('table.civicrm_column_name', 'mysqlColumnNameOrAlias', 'table.civicrm_column_name'),
+      array('table.civicrm_column_name.toomanydots', 'mysqlColumnNameOrAlias', NULL),
+      array('Home-street_address', 'mysqlColumnNameOrAlias', NULL),
+      array('`Home-street_address`', 'mysqlColumnNameOrAlias', '`Home-street_address`'),
+      array('table.`Home-street_address`', 'mysqlColumnNameOrAlias', 'table.`Home-street_address`'),
+      array('`table-alias`.`Home-street_address`', 'mysqlColumnNameOrAlias', '`table-alias`.`Home-street_address`'),
+      array('`table-alias`.column', 'mysqlColumnNameOrAlias', '`table-alias`.column'),
+      array('column_name, sleep(5)', 'mysqlColumnNameOrAlias', NULL),
+      array(str_repeat('a', 64), 'mysqlColumnNameOrAlias', str_repeat('a', 64)),
+      array(str_repeat('a', 65), 'mysqlColumnNameOrAlias', NULL),
+      array(str_repeat('a', 64) . '.' . str_repeat('a', 64), 'mysqlColumnNameOrAlias', str_repeat('a', 64) . '.' . str_repeat('a', 64)),
+      array(str_repeat('a', 64) . '.' . str_repeat('a', 65), 'mysqlColumnNameOrAlias', NULL),
+      array(str_repeat('a', 65) . '.' . str_repeat('a', 64), 'mysqlColumnNameOrAlias', NULL),
       array('asc', 'MysqlOrderByDirection', 'asc'),
       array('DESC', 'MysqlOrderByDirection', 'desc'),
       array('DESCc', 'MysqlOrderByDirection', NULL),
       array('table.civicrm_column_name desc', 'MysqlOrderBy', 'table.civicrm_column_name desc'),
       array('table.civicrm_column_name desc,other_column, another_column desc', 'MysqlOrderBy', 'table.civicrm_column_name desc,other_column, another_column desc'),
+      array('table.`Home-street_address` asc, `table-alias`.`Home-street_address` desc,`table-alias`.column', 'MysqlOrderBy', 'table.`Home-street_address` asc, `table-alias`.`Home-street_address` desc,`table-alias`.column'),
     );
   }
 
@@ -100,6 +105,7 @@ class CRM_Utils_TypeTest extends CiviUnitTestCase {
       array('DESCc', 'MysqlOrderByDirection', NULL),
       array('table.civicrm_column_name desc', 'MysqlOrderBy', '`table`.`civicrm_column_name` desc'),
       array('table.civicrm_column_name desc,other_column,another_column desc', 'MysqlOrderBy', '`table`.`civicrm_column_name` desc, `other_column`, `another_column` desc'),
+      array('table.`Home-street_address` asc, `table-alias`.`Home-street_address` desc,`table-alias`.column', 'MysqlOrderBy', '`table`.`Home-street_address` asc, `table-alias`.`Home-street_address` desc, `table-alias`.`column`'),
     );
   }