Tinebase_Filesystem - make it case sensitive
authorPaul Mehrer <p.mehrer@metaways.de>
Wed, 19 Jul 2017 14:02:30 +0000 (16:02 +0200)
committerPaul Mehrer <p.mehrer@metaways.de>
Thu, 20 Jul 2017 12:55:56 +0000 (14:55 +0200)
* recursive search will be set to search case insensitive!

Change-Id: I3ecb1b9e67ffd698eb2ee57058abb6abba739ece
Reviewed-on: http://gerrit.tine20.com/customers/5258
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Paul Mehrer <p.mehrer@metaways.de>
Tested-by: Paul Mehrer <p.mehrer@metaways.de>
13 files changed:
tine20/Filemanager/Controller/Node.php
tine20/Setup/Backend/Abstract.php
tine20/Setup/Backend/Mysql.php
tine20/Setup/Backend/Schema/Field/Xml.php
tine20/Tinebase/Backend/Sql/Command/Interface.php
tine20/Tinebase/Backend/Sql/Command/Mysql.php
tine20/Tinebase/Backend/Sql/Command/Oracle.php
tine20/Tinebase/Backend/Sql/Command/Pgsql.php
tine20/Tinebase/Model/Filter/Text.php
tine20/Tinebase/Model/Tree/Node/Filter.php
tine20/Tinebase/Setup/Update/Release10.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/Tree/Node.php

index 677a0c4..69c69d3 100644 (file)
@@ -346,14 +346,15 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
     
     protected function _searchNodesRecursive($_filter, $_pagination)
     {
-        $_filter->removeFilter('path');
-        $_filter->removeFilter('recursive');
-        $_filter->removeFilter('type');
-        $_filter->addFilter($_filter->createFilter('type', 'equals', Tinebase_Model_Tree_FileObject::TYPE_FILE));
+        $filter = clone $_filter;
+        $filter->removeFilter('path');
+        $filter->removeFilter('recursive');
+        $filter->removeFilter('type');
+        $filter->addFilter($_filter->createFilter('type', 'equals', Tinebase_Model_Tree_FileObject::TYPE_FILE));
 
-        $result = $this->_backend->searchNodes($_filter, $_pagination);
+        $filter = new Tinebase_Model_Tree_Node_Filter($filter->toArray(), '', array('nameCaseInSensitive' => true));
 
-        $_filter->addFilter($_filter->createFilter('recursive', 'equals', 'true'));
+        $result = $this->_backend->searchNodes($filter, $_pagination);
 
         // resolve path
         $parents = array();
@@ -556,9 +557,13 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
     public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
     {
         if ($_filter->getFilter('recursive')) {
-            $_filter->removeFilter('recursive');
-            $result = $this->_backend->searchNodesCount($_filter);
-            $_filter->addFilter($_filter->createFilter('recursive', 'equals', 'true'));
+            $filter = clone $_filter;
+            $filter->removeFilter('path');
+            $filter->removeFilter('recursive');
+            $filter->removeFilter('type');
+            $filter->addFilter($filter->createFilter('type', 'equals', Tinebase_Model_Tree_FileObject::TYPE_FILE));
+            $filter = new Tinebase_Model_Tree_Node_Filter($filter->toArray(), '', array('nameCaseInSensitive' => true));
+            $result = $this->_backend->searchNodesCount($filter);
         } else {
             $path = $this->_checkFilterACL($_filter, $_action);
             if ($path->containerType === Tinebase_Model_Tree_Node_Path::TYPE_ROOT) {
index 648f670..0ca7bc5 100644 (file)
@@ -546,6 +546,7 @@ abstract class Setup_Backend_Abstract implements Setup_Backend_Interface
 
         $buffer = $this->_addDeclarationFieldType($buffer, $_field, $_tableName);
         $buffer = $this->_addDeclarationUnsigned($buffer, $_field);
+        $buffer = $this->_addDeclarationCollation($buffer, $_field);
         $buffer = $this->_addDeclarationDefaultValue($buffer, $_field);
         $buffer = $this->_addDeclarationNotNull($buffer, $_field);
         $buffer = $this->_addDeclarationAutoincrement($buffer, $_field);
@@ -553,7 +554,12 @@ abstract class Setup_Backend_Abstract implements Setup_Backend_Interface
         
         return $buffer;
     }
-    
+
+    protected function _addDeclarationCollation(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
+    {
+        return $_buffer;
+    }
+
     protected function _addDeclarationFieldType(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field, $_tableName = '')
     {
         $typeMapping = $this->getTypeMapping($_field->type);
index ff97044..631978b 100644 (file)
@@ -505,4 +505,12 @@ EOT;
         }
         return false;
     }
+
+    protected function _addDeclarationCollation(array $_buffer, Setup_Backend_Schema_Field_Abstract $_field)
+    {
+        if (isset($_field->collation)) {
+            $_buffer[] = 'COLLATE ' . $_field->collation;
+        }
+        return $_buffer;
+    }
 }
index 74bd37f..4a2729b 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Setup
  * @license     http://www.gnu.org/licenses/agpl.html AGPL3
- * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Matthias Greiling <m.greiling@metaways.de>
  */
 
@@ -62,6 +62,12 @@ class Setup_Backend_Schema_Field_Xml extends Setup_Backend_Schema_Field_Abstract
             $this->notnull = false;
         }
 
+        if(isset($_declaration->collation)) {
+            $this->collation = $_declaration->collation;
+        } else {
+            $this->collation = NULL;
+        }
+
         switch ($this->type) {
             case 'enum':
                 if (isset($_declaration->value[0])) {
index 6b50103..4b2b122 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -82,6 +82,13 @@ interface Tinebase_Backend_Sql_Command_Interface
      * @return string
      */
     public function getLike();
+
+    /**
+     * get case sensitive like keyword
+     *
+     * @return string
+     */
+    public function getCsLike();
     
     /**
      * prepare value for case insensitive search
index 56d1472..b524e71 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -159,6 +159,16 @@ class Tinebase_Backend_Sql_Command_Mysql implements Tinebase_Backend_Sql_Command
     {
         return 'LIKE';
     }
+
+    /**
+     * get case sensitive like keyword
+     *
+     * @return string
+     */
+    public function getCsLike()
+    {
+        return 'LIKE';
+    }
     
     /**
      * prepare value for case insensitive search
index 6a9fb94..45b9f7a 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -135,6 +135,16 @@ class Tinebase_Backend_Sql_Command_Oracle implements Tinebase_Backend_Sql_Comman
     {
         return 'LIKE';
     }
+
+    /**
+     * get case sensitive like keyword
+     *
+     * @return string
+     */
+    public function getCsLike()
+    {
+        return 'LIKE';
+    }
     
     /**
      * prepare value for case insensitive search
index d538f49..71d25a7 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -161,6 +161,16 @@ class Tinebase_Backend_Sql_Command_Pgsql implements Tinebase_Backend_Sql_Command
     {
         return 'iLIKE';
     }
+
+    /**
+     * get case sensitive like keyword
+     *
+     * @return string
+     */
+    public function getCsLike()
+    {
+        return 'LIKE';
+    }
     
     /**
      * prepare value for case insensitive search
index d7c038d..0080a14 100644 (file)
@@ -46,6 +46,10 @@ class Tinebase_Model_Filter_Text extends Tinebase_Model_Filter_Abstract
      */
     protected $_opSqlMap = array();
 
+    protected $_caseSensitive = false;
+
+    protected $_binary = false;
+
     /**
      * get a new single filter action
      *
@@ -58,6 +62,15 @@ class Tinebase_Model_Filter_Text extends Tinebase_Model_Filter_Abstract
      */
     public function __construct($_fieldOrData, $_operator = NULL, $_value = NULL, array $_options = array())
     {
+        if (isset($_options['caseSensitive']) && $_options['caseSensitive']) {
+            $this->_caseSensitive = true;
+        }
+        if (isset($_options['binary']) && $_options['binary']) {
+            $this->_binary = true;
+            if (!isset($_options['caseSensitive'])) {
+                $this->_caseSensitive = true;
+            }
+        }
         $this->_setOpSqlMap();
         parent::__construct($_fieldOrData, $_operator, $_value, $_options);
     }
@@ -69,20 +82,38 @@ class Tinebase_Model_Filter_Text extends Tinebase_Model_Filter_Abstract
     {
         $db = Tinebase_Core::getDb();
         $sqlCommand = Tinebase_Backend_Sql_Command::factory($db);
-        $this->_opSqlMap = array(
-            'equals'            => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '?'  ),
-            'contains'          => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '%?%'),
-            'notcontains'       => array('sqlop' => ' NOT ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),      'wildcards' => '%?%'),
-            'startswith'        => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '?%' ),
-            'endswith'          => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '%?' ),
-            'not'               => array('sqlop' => ' NOT ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),      'wildcards' => '?'  ),
-            'in'                => array('sqlop' => ' IN (?)',          'wildcards' => '?'  ),
-            'notin'             => array('sqlop' => ' NOT IN (?)',      'wildcards' => '?'  ),
-            'isnull'            => array('sqlop' => ' IS NULL',         'wildcards' => '?'  ),
-            'notnull'           => array('sqlop' => ' IS NOT NULL',     'wildcards' => '?'  ),
-            'group'             => array('sqlop' => ' NOT ' . $sqlCommand->getLike() . "  ''",    'wildcards' => '?'  ),
-            'equalsspecial'     => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '?'  ),
-        );
+
+        if ($this->_caseSensitive) {
+            $this->_opSqlMap = array(
+                'equals'            => array('sqlop' => ' ' .$sqlCommand->getCsLike() .' (?)',          'wildcards' => '?'  ),
+                'contains'          => array('sqlop' => ' ' .$sqlCommand->getCsLike() .' (?)',          'wildcards' => '%?%'),
+                'notcontains'       => array('sqlop' => ' NOT ' .$sqlCommand->getCsLike() .' (?)',      'wildcards' => '%?%'),
+                'startswith'        => array('sqlop' => ' ' .$sqlCommand->getCsLike() .' (?)',          'wildcards' => '?%' ),
+                'endswith'          => array('sqlop' => ' ' .$sqlCommand->getCsLike() .' (?)',          'wildcards' => '%?' ),
+                'not'               => array('sqlop' => ' NOT ' .$sqlCommand->getCsLike() .' (?)',      'wildcards' => '?'  ),
+                'in'                => array('sqlop' => ' IN (?)',          'wildcards' => '?'  ),
+                'notin'             => array('sqlop' => ' NOT IN (?)',      'wildcards' => '?'  ),
+                'isnull'            => array('sqlop' => ' IS NULL',         'wildcards' => '?'  ),
+                'notnull'           => array('sqlop' => ' IS NOT NULL',     'wildcards' => '?'  ),
+                'group'             => array('sqlop' => ' NOT ' . $sqlCommand->getCsLike() . "  ''",    'wildcards' => '?'  ),
+                'equalsspecial'     => array('sqlop' => ' ' .$sqlCommand->getCsLike() .' (?)',          'wildcards' => '?'  ),
+            );
+        } else {
+            $this->_opSqlMap = array(
+                'equals'            => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '?'  ),
+                'contains'          => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '%?%'),
+                'notcontains'       => array('sqlop' => ' NOT ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),      'wildcards' => '%?%'),
+                'startswith'        => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '?%' ),
+                'endswith'          => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '%?' ),
+                'not'               => array('sqlop' => ' NOT ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),      'wildcards' => '?'  ),
+                'in'                => array('sqlop' => ' IN (?)',          'wildcards' => '?'  ),
+                'notin'             => array('sqlop' => ' NOT IN (?)',      'wildcards' => '?'  ),
+                'isnull'            => array('sqlop' => ' IS NULL',         'wildcards' => '?'  ),
+                'notnull'           => array('sqlop' => ' IS NOT NULL',     'wildcards' => '?'  ),
+                'group'             => array('sqlop' => ' NOT ' . $sqlCommand->getLike() . "  ''",    'wildcards' => '?'  ),
+                'equalsspecial'     => array('sqlop' => ' ' .$sqlCommand->getLike() .' ' . $sqlCommand->prepareForILike('(?)'),          'wildcards' => '?'  ),
+            );
+        }
     }
 
     /**
@@ -96,6 +127,11 @@ class Tinebase_Model_Filter_Text extends Tinebase_Model_Filter_Abstract
     {
         // quote field identifier, set action and replace wildcards
         $field = $this->_getQuotedFieldName($_backend);
+        if (!$this->_binary && $this->_caseSensitive && Tinebase_Core::getDb() instanceof Zend_Db_Adapter_Pdo_Mysql) {
+            $field = 'BINARY ' . $field;
+        } elseif ($this->_binary && !$this->_caseSensitive && Tinebase_Core::getDb() instanceof Zend_Db_Adapter_Pdo_Mysql) {
+            $field .= ' COLLATE utf8_general_ci';
+        }
         
         if (! (isset($this->_opSqlMap[$this->_operator]) || array_key_exists($this->_operator, $this->_opSqlMap))) {
             throw new Tinebase_Exception_InvalidArgument('Operator "' . $this->_operator . '" not defined in sql map of ' . get_class($this));
@@ -141,7 +177,11 @@ class Tinebase_Model_Filter_Text extends Tinebase_Model_Filter_Abstract
         }
         
         if (! in_array($this->_operator, array('in', 'notin'))) {
-            $where = Tinebase_Core::getDb()->quoteInto(Tinebase_Backend_Sql_Command::factory($db)->prepareForILike($field) . ' ' . $action['sqlop'], $value);
+            if ($this->_caseSensitive) {
+                $where = Tinebase_Core::getDb()->quoteInto($field . ' ' . $action['sqlop'], $value);
+            } else {
+                $where = Tinebase_Core::getDb()->quoteInto(Tinebase_Backend_Sql_Command::factory($db)->prepareForILike($field) . ' ' . $action['sqlop'], $value);
+            }
         } else {
             $where = Tinebase_Core::getDb()->quoteInto($field . $action['sqlop'], $value);
         }
index 7e250b7..a1f8f1d 100644 (file)
@@ -55,7 +55,10 @@ class Tinebase_Model_Tree_Node_Filter extends Tinebase_Model_Filter_GrantsFilter
         'id'                    => array('filter' => 'Tinebase_Model_Filter_Id'),
         'path'                  => array('filter' => 'Tinebase_Model_Tree_Node_PathFilter'),
         'parent_id'             => array('filter' => 'Tinebase_Model_Filter_Text'),
-        'name'                  => array('filter' => 'Tinebase_Model_Filter_Text'),
+        'name'                  => array(
+            'filter' => 'Tinebase_Model_Filter_Text',
+            'options' => array('binary' => true)
+        ),
         'object_id'             => array('filter' => 'Tinebase_Model_Filter_Text'),
         'acl_node'              => array('filter' => 'Tinebase_Model_Filter_Text'),
     // tree_fileobjects table
@@ -120,6 +123,19 @@ class Tinebase_Model_Tree_Node_Filter extends Tinebase_Model_Filter_GrantsFilter
     );
 
     /**
+     * set options
+     *
+     * @param array $_options
+     */
+    protected function _setOptions(array $_options)
+    {
+        if (isset($_options['nameCaseInSensitive']) && $_options['nameCaseInSensitive']) {
+            $this->_filterModel['name']['options']['caseSensitive'] = false;
+        }
+        parent::_setOptions($_options);
+    }
+
+    /**
      * append grants acl filter
      *
      * @param Zend_Db_Select $select
index 638f219..00696f8 100644 (file)
@@ -1974,7 +1974,7 @@ class Tinebase_Setup_Update_Release10 extends Setup_Update_Abstract
     /**
      * update to 10.39
      *
-     * tree_nodes add
+     * tree_nodes add pin_protected
      */
     public function update_38()
     {
@@ -1989,4 +1989,26 @@ class Tinebase_Setup_Update_Release10 extends Setup_Update_Abstract
         }
         $this->setApplicationVersion('Tinebase', '10.39');
     }
+
+    /**
+     * update to 10.40
+     *
+     * tree_nodes make name column case sensitive
+     */
+    public function update_39()
+    {
+        if ($this->getTableVersion('tree_nodes') < 7) {
+            if (Tinebase_Core::getDb() instanceof Zend_Db_Adapter_Pdo_Mysql) {
+                $this->_backend->alterCol('tree_nodes', new Setup_Backend_Schema_Field_Xml('<field>
+                        <name>name</name>
+                        <type>text</type>
+                        <length>255</length>
+                        <notnull>true</notnull>
+                        <collation>utf8_bin</collation>
+                    </field>'));
+            }
+            $this->setTableVersion('tree_nodes', 7);
+        }
+        $this->setApplicationVersion('Tinebase', '10.40');
+    }
 }
index 292eb27..1c7ab12 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Tinebase</name>
-    <version>10.39</version>
+    <version>10.40</version>
     <tables>
         <table>
             <name>applications</name>
 
         <table>
             <name>tree_nodes</name>
-            <version>6</version>
+            <version>7</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <type>text</type>
                     <length>255</length>
                     <notnull>true</notnull>
+                    <collation>utf8_bin</collation>
                 </field>
                 <field>
                     <name>islink</name>
index 3f942bf..ad5ecb4 100644 (file)
@@ -340,7 +340,7 @@ class Tinebase_Tree_Node extends Tinebase_Backend_Sql_Abstract
         }
         $child = $this->search($searchFilter)->getFirstRecord();
         
-        if (!$child || $childName !== $child->name) {
+        if (!$child) {
             if (true === $throw) {
                 throw new Tinebase_Exception_NotFound('child: ' . $childName . ' not found!');
             }