0013190: new customfield type (multiple) "records"
authorStefanie Stamer <s.stamer@metaways.de>
Thu, 8 Jun 2017 13:32:04 +0000 (15:32 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Mon, 19 Jun 2017 15:23:01 +0000 (17:23 +0200)
https://forge.tine20.org/view.php?id=13190

Change-Id: Ibef74eb95274bbd8fbcc5215b77188f9635a8d52
Reviewed-on: http://gerrit.tine20.com/customers/4845
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tinebase/CustomFieldTest.php
tine20/Admin/js/customfield/EditDialog.js
tine20/Tinebase/CustomField.php
tine20/Tinebase/js/widgets/customfields/Field.js
tine20/Tinebase/js/widgets/grid/PickerGridLayerCombo.js

index 956e771..f8b1ced 100644 (file)
@@ -4,7 +4,7 @@
  *
  * @package     Tinebase
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2009-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
@@ -16,7 +16,7 @@ require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php'
 /**
  * Test class for Tinebase_CustomField
  */
-class Tinebase_CustomFieldTest extends PHPUnit_Framework_TestCase
+class Tinebase_CustomFieldTest extends TestCase
 {
     /**
      * unit under test (UIT)
@@ -25,13 +25,6 @@ class Tinebase_CustomFieldTest extends PHPUnit_Framework_TestCase
     protected $_instance;
 
     /**
-     * transaction id if test is wrapped in an transaction
-     */
-    protected $_transactionId = NULL;
-    
-    protected $_user = NULL;
-    
-    /**
      * Sets up the fixture.
      * This method is called before a test is executed.
      *
@@ -39,7 +32,7 @@ class Tinebase_CustomFieldTest extends PHPUnit_Framework_TestCase
      */
     protected function setUp()
     {
-        $this->_transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
+        parent::setUp();
         $this->_instance = Tinebase_CustomField::getInstance();
         
         Sales_Controller_Contract::getInstance()->setNumberPrefix();
@@ -47,23 +40,6 @@ class Tinebase_CustomFieldTest extends PHPUnit_Framework_TestCase
     }
 
     /**
-     * Tears down the fixture
-     * This method is called after a test is executed.
-     *
-     * @access protected
-     */
-    protected function tearDown()
-    {
-        if ($this->_transactionId) {
-            Tinebase_TransactionManager::getInstance()->rollBack();
-        }
-        
-        if ($this->_user) {
-            Tinebase_Core::set(Tinebase_Core::USER, $this->_user);
-        }
-    }
-    
-    /**
      * test add customfield to the same record
      * #7330: https://forge.tine20.org/mantisbt/view.php?id=7330
      */
@@ -225,7 +201,6 @@ class Tinebase_CustomFieldTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(2, count($contact->customfields));
         
         // change user and check cfs
-        $this->_user = Tinebase_Core::getUser();
         $sclever = Tinebase_User::getInstance()->getFullUserByLoginName('sclever');
         Tinebase_Core::set(Tinebase_Core::USER, $sclever);
         $contact = Addressbook_Controller_Contact::getInstance()->get($contact->getId());
@@ -233,6 +208,40 @@ class Tinebase_CustomFieldTest extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * testMultiRecordCustomField
+     */
+    public function testMultiRecordCustomField()
+    {
+        $createdCustomField = $this->_instance->addCustomField(self::getCustomField(array(
+            'name'              => 'test',
+            'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Addressbook')->getId(),
+            'model'             => 'Addressbook_Model_Contact',
+            'definition' => array('type' => 'recordList', "recordListConfig" => array("value" => array("records" => "Tine.Addressbook.Model.Contact")))
+        )));
+
+        //Customfield record 1
+        $contact1 = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'org_name'     => 'contact 1'
+        )));
+        //Customfield record 2
+        $contact2 = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'org_name'     => 'contact 2'
+        )));
+
+        $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family'     => 'contact'
+        )));
+
+        $cfValue = array($createdCustomField->name => array($contact1, $contact2));
+        $contact->customfields = $cfValue;
+        $contact = Addressbook_Controller_Contact::getInstance()->update($contact);
+
+        self::assertTrue(is_array($contact->customfields['test']),
+            'cf not saved: ' . print_r($contact->toArray(), TRUE));
+        self::assertEquals(2, count($contact->customfields['test']));
+        self::assertTrue(in_array($contact->customfields['test'][0]['org_name'], array('contact 1', 'contact 2')));
+    }
+    /**
      * @see 0012222: customfields with space in name are not shown
      */
     public function testAddCustomFieldWithSpace()
index d1619b0..285c68d 100644 (file)
@@ -55,7 +55,7 @@ Tine.Admin.CustomfieldEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
      * type of field with stores
      * @type {Array}
      */
-    fieldsWithStore: ['record', 'keyField'],
+    fieldsWithStore: ['record', 'keyField',  'recordList'],
     
     /**
      * currently selected field type
@@ -162,7 +162,7 @@ Tine.Admin.CustomfieldEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
      */ 
     onStoreWindowOK: function () {
         if (this[this.fieldType + 'Store'].isValid()) {
-            if (this.fieldType == 'record') {
+            if (this.fieldType == 'record' || this.fieldType == 'recordList') {
                 this[this.fieldType + 'Config'] = {
                     value: {
                         records: this[this.fieldType + 'Store'].getValue()
@@ -292,6 +292,10 @@ Tine.Admin.CustomfieldEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
         
         return this[this.fieldType + 'Store'];
     },
+
+    initRecordListStore: function () {
+        return this.initRecordStore();
+    },
     
     /**
      * Show window for configuring field store
@@ -436,8 +440,8 @@ Tine.Admin.CustomfieldEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                             ['boolean', this.app.i18n._('Boolean')],
                             ['searchcombo', this.app.i18n._('Search Combo')],
                             ['keyField', this.app.i18n._('Key Field')],
-                            ['record', this.app.i18n._('Record')]
-                            
+                            ['record', this.app.i18n._('Record')],
+                            ['recordList', this.app.i18n._('Record List')]
                         ],
                         name: 'type',
                         fieldLabel: this.app.i18n._('Type'),
index dc0d157..00d3771 100644 (file)
@@ -395,21 +395,10 @@ class Tinebase_CustomField implements Tinebase_Controller_SearchInterface
                 $filtered = $existingCustomFields->filter('customfield_id', $customField->id);
                 
                 // we need to resolve the modelName and the record value if array is given (e.g. on updating customfield)
-                if (strtolower($customField->definition['type']) == 'record') {
-                    $modelParts = explode('.', $customField->definition['recordConfig']['value']['records']); // get model parts from saved record class e.g. Tine.Admin.Model.Group
-                    $modelName  = $modelParts[1] . '_Model_' . $modelParts[3];
-                    
-                    if (is_array($value)) {
-                        /** @var Tinebase_Record_Interface $model */
-                        $model = new $modelName(array(), TRUE);
-                        $value = $value[$model->getIdProperty()];
-                    }
-                    // check if customfield value is the record itself
-                    if (get_class($_record) == $modelName && $_record->getId() == $value) {
-                        throw new Tinebase_Exception_Record_Validation('It is not allowed to add the same record as customfield record!');
-                    }
+                if (strtolower($customField->definition['type']) == 'record' || strtolower($customField->definition['type']) == 'recordlist') {
+                    $value = $this->_getValueForRecordOrListCf($_record, $customField, $value);
                 }
-                
+
                 switch (count($filtered)) {
                     case 1:
                         $cf = $filtered->getFirstRecord();
@@ -439,6 +428,53 @@ class Tinebase_CustomField implements Tinebase_Controller_SearchInterface
             }
         }
     }
+
+    /**
+     * @param $_record
+     * @param $_customField
+     * @param $_value
+     * @return string
+     * @throws Tinebase_Exception_Record_Validation
+     */
+    protected function _getValueForRecordOrListCf($_record, $_customField, $_value)
+    {
+        // get model parts from saved record class e.g. Tine.Admin.Model.Group
+        $modelParts = explode('.', $_customField->definition[$_customField->definition['type'] . 'Config']['value']['records']);
+        $modelName  = $modelParts[1] . '_Model_' . $modelParts[3];
+        $model = new $modelName(array(), true);
+        $idProperty = $model->getIdProperty();
+        if (is_array($_value)) {
+            if (strtolower($_customField->definition['type']) == 'record') {
+                /** @var Tinebase_Record_Interface $model */
+                $value = $_value[$idProperty];
+
+            } else {
+                // recordlist
+                $values = array();
+                foreach ($_value as $record) {
+                    if (is_array($record) || $record instanceof Tinebase_Record_Abstract) {
+                        $values[] = $record[$idProperty];
+                    } else {
+                        $values[] = $record;
+                    }
+                }
+
+                // remove own record if in list
+                sort($values);
+                Tinebase_Helper::array_remove_by_value($_record->getId(), $values);
+                $value = json_encode($values);
+            }
+        } else {
+            $value = $_value;
+        }
+
+        // check if customfield value is the record itself
+        if (get_class($_record) == $modelName && strpos($value, $_record->getId()) !== false) {
+            throw new Tinebase_Exception_Record_Validation('It is not allowed to add the same record as customfield record!');
+        }
+
+        return $value;
+    }
     
     /**
      * get custom fields and add them to $_record->customfields array
@@ -480,8 +516,8 @@ class Tinebase_CustomField implements Tinebase_Controller_SearchInterface
         $idx = $configs->getIndexById($customField->customfield_id);
         if ($idx !== FALSE) {
             $config = $configs[$idx];
-            if (strtolower($config->definition['type']) == 'record') {
-                $value = $this->_getRecordTypeCfValue($config, $customField->value);
+            if (strtolower($config->definition->type) == 'record' || strtolower($config->definition->type) == 'recordlist') {
+                $value = $this->_getRecordTypeCfValue($config, $customField->value, strtolower($config->definition['type']));
             } else {
                 $value = $customField->value;
             }
@@ -503,15 +539,21 @@ class Tinebase_CustomField implements Tinebase_Controller_SearchInterface
      * @param string $value
      * @return string
      */
-    protected function _getRecordTypeCfValue($config, $value)
+    protected function _getRecordTypeCfValue($config, $value, $type = 'record')
     {
         try {
-            $model = $config->definition['recordConfig']['value']['records'];
+            $recordConfigIndex = $type === 'record' ? 'recordConfig' : 'recordListConfig';
+            $model = $config->definition[$recordConfigIndex]['value']['records'];
             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
-                . ' Fetching record customfield of type ' . $model);
+                . ' Fetching ' . $type . ' customfield of type ' . $model);
             
             $controller = Tinebase_Core::getApplicationInstance($model);
-            $result = $controller->get($value)->toArray();
+            // TODO why do we already convert to array here? should be done in converter!
+            if ($type === 'record') {
+                $result = $controller->get($value)->toArray();
+            } else {
+                $result = $controller->getMultiple(Tinebase_Helper::jsonDecode($value))->toArray();
+            }
         } catch (Exception $e) {
             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
                 . ' Error resolving custom field record: ' . $e->getMessage());
@@ -520,7 +562,7 @@ class Tinebase_CustomField implements Tinebase_Controller_SearchInterface
         
         return $result;
     }
-    
+
     /**
      * get all customfields of all given records
      * 
index 7cde7f7..ac10b57 100644 (file)
@@ -96,6 +96,19 @@ Tine.widgets.customfields.Field = Ext.extend(Ext.Panel, {
                             editDialog: editDialog
                         });
                         break;
+                    case 'recordlist':
+                        var options = def.options ? def.options : {},
+                            recordListConfig = def.recordListConfig ? def.recordListConfig : null;
+
+                        Ext.apply(fieldDef, {
+                            xtype: 'tinepickergridlayercombo',
+                            app: options.app ? options.app : app,
+                            resizable: true,
+                            gridRecordClass: eval(recordListConfig.value.records),
+                            allowLinkingItself: false,
+                            editDialog: editDialog
+                        });
+                        break;
                     case 'integer':
                     case 'int':
                         fieldDef.xtype = 'numberfield';
index cab51de..4275775 100644 (file)
@@ -74,7 +74,11 @@ Tine.widgets.grid.PickerGridLayerCombo = Ext.extend(Ext.ux.form.LayerCombo, {
      * sets values to innerForm (grid)
      */
     setFormValue: function (value) {
-        this.setStoreFromArray(listRoles);
+        if (!value) {
+            value = [];
+        }
+
+        this.setStoreFromArray(value);
     },
 
     /**
@@ -95,13 +99,20 @@ Tine.widgets.grid.PickerGridLayerCombo = Ext.extend(Ext.ux.form.LayerCombo, {
      */
     setStoreFromArray: function(data) {
         //this.pickerGrid.getStore().clearData();
+        var rawData = [];
         this.pickerGrid.getStore().removeAll();
 
         for (var i = data.length-1; i >=0; --i) {
-            var recordData = data[i];
+            var recordData = data[i],
+                newRecord = new this.gridRecordClass(recordData);
+            this.pickerGrid.getStore().insert(0, newRecord);
+            rawData.push(newRecord.getTitle());
+        }
 
-            this.pickerGrid.getStore().insert(0, new this.gridRecordClass(recordData));
+        if (rawData) {
+            this.setRawValue(rawData.join(', '));
         }
+
     },
 
     /**
@@ -120,3 +131,5 @@ Tine.widgets.grid.PickerGridLayerCombo = Ext.extend(Ext.ux.form.LayerCombo, {
         return result;
     }
 });
+
+Ext.reg('tinepickergridlayercombo', Tine.widgets.grid.PickerGridLayerCombo);