$this->assertTrue(array_reduce($children, function($result, $container){
return $result || $container instanceof Tasks_Frontend_WebDAV_Container;
}, FALSE), 'tasks container is missing');
-
+
return $children;
}
+
+ public function testGetUserDirectory()
+ {
+ $grants = Tinebase_Model_Grants::getPersonalGrants($this->_personas['sclever']->getId());
+ $grants->merge(new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(array(
+ 'account_id' => Tinebase_Core::getUser()->getId(),
+ 'account_type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
+ Tinebase_Model_Grants::GRANT_READ => true,
+ Tinebase_Model_Grants::GRANT_EXPORT => true,
+ Tinebase_Model_Grants::GRANT_SYNC => true,
+ ))));
+ $scleverTestCal = $this->_getCalendarTestContainer(Tinebase_Model_Container::TYPE_PERSONAL, $grants);
+
+ $_SERVER['HTTP_USER_AGENT'] = 'xxx';
+ $collection = new Calendar_Frontend_WebDAV(\Sabre\CalDAV\Plugin::CALENDAR_ROOT . '/' . $this->_personas['sclever']->contact_id, true);
+ $children = $collection->getChildren();
+
+ $containerIds = [];
+ foreach ($children as $child) {
+ $containerIds[] = $child->getName();
+ }
+ self::assertTrue(in_array($scleverTestCal->getId(), $containerIds));
+ }
/**
* test getChild
*
* @return Tinebase_Model_Container
*/
- protected function _getCalendarTestContainer($type = Tinebase_Model_Container::TYPE_PERSONAL)
+ protected function _getCalendarTestContainer($type = Tinebase_Model_Container::TYPE_PERSONAL, $grants = null)
{
$container = Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
'name' => Tinebase_Record_Abstract::generateUID(),
'type' => $type,
'backend' => 'Sql',
'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
- )));
+ )), $grants);
return $container;
}
$node = $this->_getWebDAVTree()->getNodeForPath('/webdav/Calendar/records/Calendar_Model_Event/' . $savedEvent->getId() . '/' . $tempFile->name);
$this->assertEquals('text/plain', $node->getContentType());
$this->assertEquals(17, $node->getSize());
-
+ $handle = $node->get();
+ self::assertTrue(is_resource($handle));
+ fclose($handle);
+
return $savedEvent;
}
$this->assertEquals('text/plain', $node->getContentType());
$this->assertEquals(17, $node->getSize());
}
-
+
/**
*
* @return \Sabre\DAV\ObjectTree
/**
* Test class for Tinebase_PreferenceTest
*/
-class Tinebase_PreferenceTest extends PHPUnit_Framework_TestCase
+class Tinebase_PreferenceTest extends TestCase
{
/**
* unit under test (UIT)
}
}
+ /**
+ * @see 0013214: allow to set fixed calendars as user preference
+ */
+ public function testSetFixedCalendarsPreference()
+ {
+ $personalContainer = $this->_getPersonalContainer('Calendar');
+ $data =
+ array (
+ 'Calendar' =>
+ array (
+ 'fixedCalendars' =>
+ array (
+ 'value' =>
+ array (
+ 0 =>
+ $personalContainer->toArray()
+ ),
+ ),
+ ),
+ );
+
+ $frontend = new Tinebase_Frontend_Json();
+ $result = $frontend->savePreferences($data, /* adminMode */ false);
+ self::assertEquals('success', $result['status']);
+
+ $preferences = $frontend->searchPreferencesForApplication('Calendar', array());
+ $fixedCalendarsPref = false;
+ foreach ($preferences['results'] as $preference) {
+ if ($preference['name'] == 'fixedCalendars') {
+ $fixedCalendarsPref = $preference;
+ }
+ }
+ self::assertTrue(is_array($fixedCalendarsPref), print_r($preferences['results'], true));
+ self::assertEquals(1, count($fixedCalendarsPref['value']), 'did not find personal container in value: '
+ . print_r($fixedCalendarsPref, true));
+ $container = $fixedCalendarsPref['value'][0];
+ self::assertTrue(is_array($container));
+ self::assertEquals($personalContainer->getId(), $container['id']);
+ self::assertTrue(isset($fixedCalendarsPref['uiconfig']), 'uiconfig missing: '
+ . print_r($fixedCalendarsPref, true));
+ $fixedCalendarIds = Calendar_Controller_Event::getInstance()->getFixedCalendarIds();
+ self::assertTrue(in_array($personalContainer->getId(), $fixedCalendarIds));
+ }
+
/******************** protected helper funcs ************************/
/**
$eventNotificationController->doSendNotifications($event, null, 'tentative');
}
}
+
+ /**
+ * returns active fixed calendars for users (combines config and preference)
+ *
+ * @return array
+ * @throws Tinebase_Exception_NotFound
+ */
+ public function getFixedCalendarIds()
+ {
+ $fixedCalendars = (array) Calendar_Config::getInstance()->get(Calendar_Config::FIXED_CALENDARS);
+
+ // add fixed calendars from user preference
+ $fixedCalendarsPref = Tinebase_Core::getPreference('Calendar')->getValue(Calendar_Preference::FIXED_CALENDARS);
+ if (is_array($fixedCalendarsPref)) {
+ foreach ($fixedCalendarsPref as $container) {
+ if (isset($container['id'])) {
+ $fixedCalendars[] = $container['id'];
+ }
+ }
+ }
+
+ return $fixedCalendars;
+ }
}
$clientFilter = $filter = $this->_decodeFilter($filter, 'Calendar_Model_EventFilter');
// find out if fixed calendars should be used
- $fixedCalendars = Calendar_Config::getInstance()->get(Calendar_Config::FIXED_CALENDARS);
- $useFixedCalendars = is_array($fixedCalendars) && ! empty($fixedCalendars);
+ $fixedCalendarIds = Calendar_Controller_Event::getInstance()->getFixedCalendarIds();
+ $useFixedCalendars = is_array($fixedCalendarIds) && ! empty($fixedCalendarIds);
$periodFilter = $filter->getFilter('period');
// add fixed calendar on demand
if ($useFixedCalendars) {
$fixed = new Calendar_Model_EventFilter(array(), 'AND');
- $fixed->addFilter( new Tinebase_Model_Filter_Text('container_id', 'in', $fixedCalendars));
+ $fixed->addFilter( new Tinebase_Model_Filter_Text('container_id', 'in', $fixedCalendarIds));
$fixed->addFilter($periodFilter);
'filter' => $clientFilter->toArray(TRUE),
);
}
-
+
/**
* get default period filter
*
const DEFAULT_CALENDAR_STRATEGY = 'defaultCalendarStrategy';
/**
+ * fixedCalendars
+ */
+ const FIXED_CALENDARS = 'fixedCalendars';
+
+ /**
* @var string application
*/
protected $_application = 'Calendar';
self::DEFAULTATTENDEE_STRATEGY,
self::DEFAULT_EVENTS_RRIVATE,
self::FIRSTDAYOFWEEK,
- self::DEFAULT_CALENDAR_STRATEGY
+ self::DEFAULT_CALENDAR_STRATEGY,
+ self::FIXED_CALENDARS,
);
if ($cropDays) {
'label' => $translate->_('Default calendar strategy'),
'description' => $translate->_('The calendar for new events if no container is selected'),
),
+ self::FIXED_CALENDARS => array(
+ 'label' => $translate->_('Fixed Calendars'),
+ 'description' => $translate->_('Calendars always selected regardless of all filter parameters.'),
+ ),
);
return $prefDescriptions;
</option>
</options>';
break;
+ case self::FIXED_CALENDARS:
+ $preference->value = array();
+ // TODO set better (?) options / maybe this could be removed
+ $preference->options = '<?xml version="1.0" encoding="UTF-8"?>
+ <options>
+ <special>' . Tinebase_Preference_Abstract::DEFAULTCONTAINER_OPTIONS . '</special>
+ </options>';
+
+ $preference->uiconfig = array(
+ 'xtype' => 'containerspicker',
+ 'appName' => 'Calendar',
+ 'model' => 'Event',
+ );
+ $preference->personal_only = true;
+ break;
default:
throw new Tinebase_Exception_NotFound('Default preference with name ' . $_preferenceName . ' not found.');
}
return $preference;
}
+
+ /**
+ * overwrite this in concrete classes if needed
+ *
+ * @param $_preferenceName
+ * @return array
+ */
+ public function _getPrefRecordConfig($_preferenceName)
+ {
+ switch ($_preferenceName) {
+ case self::FIXED_CALENDARS:
+ return array(
+ 'modelName' => 'Tinebase_Model_Container',
+ );
+ break;
+ default:
+ return parent::_getPrefRecordConfig($_preferenceName);
+ }
+ }
}
*/
constraint: null,
+ cls: 'tw-editdialog',
+
/**
* Constructor.
*/
scope: this
});
- this.bbar = [
+ this.fbar = [
'->',
this.okAction
];
showNode: function(path) {
this.getMainScreen().getCenterPanel().initialLoadAfterRender = false;
Tine.Tinebase.MainScreenPanel.show(this);
+ // NOTE: decodeURIComponent can't cope with +
+ path = Ext.ux.util.urlCoder.decodeURIComponent(path);
// if file, show directory file is in
var dirPath = path;
var _ = window.lodash;
return _.indexOf(['/', Tine.Tinebase.container.getMyFileNodePath(), '/personal', '/shared'], this.get('path')) >= 0;
+ },
+
+ getSystemLink: function() {
+ var _ = window.lodash,
+ encodedPath = _.map(String(this.get('path')).replace(/(^\/|\/$)/, '').split('/'), Ext.ux.util.urlCoder.encodeURIComponent).join('/');
+
+ return [Tine.Tinebase.common.getUrl().replace(/\/$/, ''), '#/Filemanager/showNode', encodedPath].join('/');
+
+
}
});
this.action_download = Tine.Filemanager.nodeActionsMgr.get('download');
this.action_moveRecord = Tine.Filemanager.nodeActionsMgr.get('move');
this.action_publish = Tine.Filemanager.nodeActionsMgr.get('publish');
+ this.action_systemLink = Tine.Filemanager.nodeActionsMgr.get('systemLink');
if (this.previewsEnabled) {
this.action_preview = Tine.Filemanager.nodeActionsMgr.get('preview');
}.createDelegate(this)
});
- var contextActions = [this.action_deleteRecord, 'rename', this.action_moveRecord, this.action_download, 'resume', 'pause', this.action_editFile, this.action_publish];
+ var contextActions = [this.action_deleteRecord, 'rename', this.action_moveRecord, this.action_download, 'resume', 'pause', this.action_editFile, this.action_publish, this.action_systemLink];
if (this.previewsEnabled) {
contextActions.push(this.action_preview);
this.folderContextMenu = Tine.Filemanager.nodeContextMenu.getMenu({
nodeName: Tine.Filemanager.Model.Node.getContainerName(),
- actions: [this.action_deleteRecord, 'rename', this.action_moveRecord, this.action_editFile, this.action_publish],
+ actions: [this.action_deleteRecord, 'rename', this.action_moveRecord, this.action_editFile, this.action_publish, this.action_systemLink],
scope: this,
backend: 'Filemanager',
backendModel: 'Node'
this.action_download,
this.action_deleteRecord,
this.action_editFile,
- this.action_publish
+ this.action_publish,
+ this.action_systemLink
];
if (this.previewsEnabled) {
scale: 'medium',
rowspan: 2,
iconAlign: 'top'
+ }),
+ Ext.apply(new Ext.Button(this.action_systemLink), {
+ scale: 'medium',
+ rowspan: 2,
+ iconAlign: 'top'
})
];
this.ctxMenu = Tine.Filemanager.nodeContextMenu.getMenu({
actionMgr: Tine.Filemanager.nodeActionsMgr,
nodeName: this.recordClass.getContainerName(),
- actions: ['reload', 'createFolder', 'delete', 'rename', 'move', 'edit', 'publish'],
+ actions: ['reload', 'createFolder', 'delete', 'rename', 'move', 'edit', 'publish', 'systemLink'],
scope: this,
backend: 'Filemanager',
backendModel: 'Node'
idProperty: 'accountId'
});
- var disable = window.lodash.get(this.editDialog.record, 'data.notificationProps', []).length === 0;
+ var notificationProps = window.lodash.get(this.editDialog.record, 'data.notificationProps', []);
+ var disable = notificationProps === null || notificationProps.length === 0;
this.notificationGrid = new Tine.Filemanager.NotificationGridPanel({
store: store,
editDialog: this.editDialog
});
+ var featureEnabled = _.get(Tine.Tinebase.configManager.get('filesystem'), 'enableNotifications', false);
+
+ var disabled = false;
+
+ if (!featureEnabled) {
+ disabled = true;
+ } else if (!_.get(this.editDialog, 'record.data.account_grants.adminGrant', false)) {
+ disabled = true;
+ }
+
this.hasOwnNotificationSettings = new Ext.form.Checkbox({
checked: !disable,
- disabled: !_.get(this.editDialog, 'record.data.account_grants.adminGrant', false) && !disable,
+ disabled: disabled,
boxLabel: this.app.i18n._('This folder has own notification settings'),
listeners: {scope: this, check: this.onOwnNotificationCheck}
});
* @package Filemanager
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Alexander Stintzing <a.stintzing@metaways.de>
- * @copyright Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Michael Spahn <m.spahn@metaways.de>
+ * @copyright Copyright (c) 2012-2017 Metaways Infosystems GmbH (http://www.metaways.de)
*
*/
*
* @namespace Tine.Filemanager
* @class Tine.Filemanager.SearchCombo
- * @extends Ext.form.ComboBox
+ * @extends Ext.form.TriggerField
*
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Alexander Stintzing <a.stintzing@metaways.de>
- * @copyright Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Michael Spahn <m.spahn@metaways.de>
+ * @copyright Copyright (c) 2012-2017 Metaways Infosystems GmbH (http://www.metaways.de)
*
* @param {Object} config
* @constructor
* Create a new Tine.Filemanager.SearchCombo
*/
-Tine.Filemanager.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPickerComboBox, {
+Tine.Filemanager.SearchCombo = Ext.extend(Ext.form.TriggerField, {
allowBlank: false,
itemSelector: 'div.search-item',
minListWidth: 200,
+
+ app: null,
+
+ recordClass: null,
+ recordProxy: null,
- //private
initComponent: function(){
this.recordClass = Tine.Filemanager.Model.Node;
- this.recordProxy = Tine.Filemanager.recordBackend;
- this.additionalFilters = [
- {field: 'recursive', operator: 'equals', value: true },
- {field: 'path', operator: 'equals', value: '/' }
- ];
- this.initTemplate();
- Tine.Filemanager.SearchCombo.superclass.initComponent.call(this);
- },
-
- /**
- * init template
- * @private
- */
- initTemplate: function() {
- // Custom rendering Template
- // TODO move style def to css ?
- if (! this.tpl) {
- this.tpl = new Ext.XTemplate(
- '<tpl for="."><div class="search-item">',
- '<table cellspacing="0" cellpadding="2" border="0" style="font-size: 11px;" width="100%">',
- '<tr>',
- '<td ext:qtip="{[this.renderPathName(values)]}" style="height:16px">{[this.renderFileName(values)]}</td>',
- '</tr>',
- '</table>',
- '</div></tpl>',
- {
- renderFileName: function(values) {
- return Ext.util.Format.htmlEncode(values.name);
- },
- renderPathName: function(values) {
- return Ext.util.Format.htmlEncode(values.path.replace(values.name, ''));
- }
-
- }
- );
+ this.recordProxy = Tine.Filemanager.fileRecordBackend;
+
+ if (null === this.app) {
+ this.app = Tine.Tinebase.appMgr.get('Filemanager');
}
+
+ this.supr().initComponent.call(this);
+
+ this.addEvents(
+ /**
+ * @param selected node
+ */
+ 'select'
+ );
},
- getValue: function() {
- return Tine.Filemanager.SearchCombo.superclass.getValue.call(this);
- },
+ onTriggerClick: function () {
+ var filepicker = new Tine.Filemanager.FilePickerDialog({
+ title: this.app.i18n._('Select a file'),
+ singleSelect: true,
+ constraint: 'file'
+ });
- setValue: function (value) {
- return Tine.Filemanager.SearchCombo.superclass.setValue.call(this, value);
- }
+ filepicker.on('selected', function (node) {
+ if (!node || 0 === node.length) {
+ return true;
+ }
+ this.fireEvent('select', node[0]);
+ this.setValue(node[0].path);
+
+ }, this);
+
+ filepicker.openWindow();
+ }
});
Tine.widgets.form.RecordPickerManager.register('Filemanager', 'Node', Tine.Filemanager.SearchCombo);
};
/**
+ * single file or directory node with editGrant
+ */
+Tine.Filemanager.nodeActions.SystemLink = {
+ app: 'Filemanager',
+ requiredGrant: 'readGrant',
+ allowMultiple: false,
+ text: 'System Link', // _('System Link')
+ iconCls: 'action_system_link',
+ disabled: true,
+ // actionType: 'edit',
+ scope: this,
+ handler: function () {
+ var _ = window.lodash,
+ app = this.initialConfig.app,
+ record = this.initialConfig.selections[0];
+
+ Ext.MessageBox.show({
+ title: i18n._('System Link'),
+ // minWidth:
+ maxWidth: screen.availWidth,
+ msg: '<b>' + i18n._('Use this link to share the entry with other system users') + ':</b><br>'
+ + record.getSystemLink(),
+ buttons: Ext.MessageBox.OK,
+ // value: record.getSystemLink(),
+ // prompt: true,
+ icon: Ext.MessageBox.INFO
+ });
+ }
+};
+
+/**
* one or multiple nodes, all need deleteGrant
*/
Tine.Filemanager.nodeActions.Delete = {
* @param integer $number
* @return integer
*/
- public function setNumberZerofill(integer $number = NULL)
+ public function setNumberZerofill($number = NULL)
{
$this->_numberZerofill = $number;
* @param string $prefix
* @return string
*/
- public function setNumberPrefix(string $prefix = NULL)
+ public function setNumberPrefix($prefix = NULL)
{
$this->_numberPrefix = $prefix;
{
public function get()
{
- if (!Tinebase_Core::getUser()->hasGrant($this->_getContainer(), Tinebase_Model_Grants::GRANT_DOWNLOAD)) {
+ $pathRecord = Tinebase_Model_Tree_Node_Path::createFromStatPath($this->_path);
+ if (! $pathRecord->isRecordPath() && ! Tinebase_Core::getUser()->hasGrant(
+ $this->_getContainer(),
+ Tinebase_Model_Grants::GRANT_DOWNLOAD
+ )
+ ) {
throw new Sabre\DAV\Exception\Forbidden('Forbidden to download file: ' . $this->_path);
}
$handle = Tinebase_FileSystem::getInstance()->fopen($this->_path, 'r');
'personal_only' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
// don't allow user to change value
'locked' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
+ // multiselection preference
+ //'multiselect' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => false),
+ 'uiconfig' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => array()),
+ 'recordConfig' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => array()),
);
+
+ /**
+ * TODO remove when converted to ModelConfig
+ * TODO generalize / support other models
+ */
+ public function runConvertToRecord()
+ {
+ // convert value property if necessary
+ if (Tinebase_Helper::is_json($this->value)) {
+ $value = Tinebase_Helper::jsonDecode($this->value);
+ switch ($value['modelName']) {
+ case 'Tinebase_Model_Container':
+ $containers = array();
+ foreach ($value['ids'] as $containerId) {
+ try {
+ $container = Tinebase_Container::getInstance()->getContainerById($containerId);
+ // TODO should be converted to array by json frontend
+ $containers[] = $container->toArray();
+ } catch (Exception $e) {
+ // not found / no access / ...
+ }
+ }
+ $this->value = $containers;
+ break;
+ default:
+ throw new Tinebase_Exception_InvalidArgument('model not supported');
+
+ }
+ } else {
+ parent::runConvertToRecord();
+ }
+ }
+
+ /**
+ * TODO remove when converted to ModelConfig
+ * TODO generalize / support other models
+ */
+ public function runConvertToData()
+ {
+ // convert value property if necessary
+ if (is_array($this->value) && is_array($this->recordConfig) && isset($this->recordConfig['modelName'])) {
+ $value = array(
+ 'modelName' => $this->recordConfig['modelName'],
+ 'ids' => array(),
+ );
+ foreach ($this->value as $record) {
+ if (isset($record['id'])) {
+ $value['ids'][] = $record['id'];
+ }
+ }
+ $this->value = json_encode($value);
+ } else {
+ parent::runConvertToData();
+ }
+ }
}
const FOLDERS_PART = 'folders';
/**
+ * records path part
+ */
+ const RECORDS_PART = 'records';
+
+ /**
* key in $_validators/$_properties array for the field which
* represents the identifier
*
}
/**
+ * returns true if path belongs to a record or record attachment
+ *
+ * @return bool
+ * @throws Tinebase_Exception_InvalidArgument
+ */
+ public function isRecordPath()
+ {
+ $parts = $this->_getPathParts();
+ return (count($parts) > 2 && $parts[2] === self::RECORDS_PART);
+ }
+
+ /**
* validate node/container existance
*
* @throws Tinebase_Exception_NotFound
if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
. ' ' . print_r($queryResult, true));
-
+
if (! $queryResult) {
$pref = $this->getApplicationPreferenceDefaults($_preferenceName, $_accountId, $_accountType);
} else {
'value' => $_value,
'account_id' => $_accountId,
'account_type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
- 'type' => Tinebase_Model_Preference::TYPE_USER
+ 'type' => Tinebase_Model_Preference::TYPE_USER,
+ 'recordConfig' => $this->_getPrefRecordConfig($_preferenceName),
));
$this->create($preference);
$action = 'Created';
$action = 'Reset';
} else {
$preference->value = $_value;
+ $preference->recordConfig = $this->_getPrefRecordConfig($_preferenceName);
$this->update($preference);
$action = 'Updated';
}
}
if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
- . ' ' . $action . ': ' . $_preferenceName . ' for user ' . $_accountId . ' -> ' . $_value);
+ . ' ' . $action . ': ' . $_preferenceName . ' for user ' . $_accountId . ' -> '
+ . (is_array($_value) ? print_r($_value, true) : $_value));
+ }
+
+ /**
+ * overwrite this in concrete classes if needed
+ *
+ * @param $_preferenceName
+ * @return array
+ */
+ public function _getPrefRecordConfig($_preferenceName)
+ {
+ return array();
}
/**
}
}
// add default setting to the top of options
+ if (is_array($valueLabel)) {
+ $valueLabel = implode(',', $valueLabel);
+ }
$defaultLabel = Tinebase_Translation::getTranslation('Tinebase')->_('default') .
' (' . $valueLabel . ')';
Tinebase_Model_Preference::DEFAULT_VALUE,
$defaultLabel,
));
+
+ if (isset($default->uiconfig)) {
+ $_preference->uiconfig = $default->uiconfig;
+ }
}
$_preference->options = $options;
case self::DEFAULTCONTAINER_OPTIONS:
$result = $this->_getDefaultContainerOptions();
break;
-
+
case self::DEFAULTPERSISTENTFILTER:
$result = Tinebase_PersistentFilter::getPreferenceValues($this->_application);
break;
}
return $result;
}
-
+
/**
* adds defaults to default container pref
*
"path": "js/ux/util/"
},
{
+ "text": "urlCoder.js",
+ "path": "js/ux/util/"
+ },
+ {
"text": "FieldLabeler.js",
"path": "js/ux/"
},
)
);
} else {
+ // NOTE: seems to be the expected behavior for non-delegation clients
$containers = $this->_containerController->getContainerByACL(Tinebase_Core::getUser(), $this->_getApplicationName(), array(
Tinebase_Model_Grants::GRANT_READ,
Tinebase_Model_Grants::GRANT_SYNC
}
+.action_system_link {
+ background-image:url(../../images/oxygen/16x16/places/link.png) !important;
+}
+.x-btn-medium .action_system_link {
+ background-image:url(../../images/oxygen/22x22/places/link.png) !important;
+}
+.x-btn-large .action_system_link {
+ background-image:url(../../images/oxygen/32x32/places/link.png) !important;
+}
+
.action_rename {
background-image:url(../../images/oxygen/16x16/actions/document-properties.png) !important;
}
{name: 'description' },
{name: 'personal_only', type: 'boolean' },
{name: 'locked', type: 'boolean' },
+ {name: 'uiconfig' },
{name: 'options' }
]);
nl2br: true,
linkify: true,
+ autoScroll: true,
+
layout: 'fit',
cls: 'x-ux-display-textarea',
--- /dev/null
+/*
+ * Tine 2.0
+ *
+ * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author Cornelius Weiss <c.weiss@metaways.de>
+ * @copyright Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+Ext.ns('Ext.ux.util.urlCoder');
+
+/**
+ * native encodeURIComponent converts space into %20 -> convert to modern +
+ *
+ * @param string
+ * @returns {string}
+ */
+Ext.ux.util.urlCoder.encodeURIComponent = function(string) {
+ return encodeURIComponent(string).replace(/\%20/gm,"+");
+};
+
+/**
+ * native encodeURI converts space into %20 -> convert to modern +
+ *
+ * @param string
+ * @returns {string}
+ */
+Ext.ux.util.urlCoder.encodeURI = function(string) {
+ return encodeURI(string).replace(/\%20/gm,"+");
+};
+
+/**
+ * native decodeURIComponent converts can't cope with modern + for spaces
+ *
+ * @param string
+ * @returns {string}
+ */
+Ext.ux.util.urlCoder.decodeURIComponent = function(string) {
+ return decodeURIComponent((string+'').replace(/\+/gm,"%20"));
+};
+
+/**
+ * native decodeURI converts can't cope with modern + for spaces
+ *
+ * @param string
+ * @returns {string}
+ */
+Ext.ux.util.urlCoder.decodeURI = function(string) {
+ return decodeURI((string+'').replace(/\+/gm,"%20"));
+};
\ No newline at end of file
}
});
+Ext.reg('containerspicker', Tine.widgets.container.FilterModelMultipleValueField);
\ No newline at end of file
if (field) {
if(field.isXType('combo') && Ext.isObject(this.customfieldsValue[name])) {
- var phpClassName = cfConfig.get('model').split('_Model_'),
- recordClass = Tine[phpClassName[0]].Model[phpClassName[1]],
- record = new recordClass(this.customfieldsValue[name]);
-
- field.setValue(record.getId());
- field.selectedRecord = record.data;
+ var record = new field.recordClass(this.customfieldsValue[name]);
+ field.setValue(record);
} else {
field.setValue(this.customfieldsValue[name]);
}
var options = pref.get('options');
// NOTE: some prefs have no default and only one option (e.g. std email account)
if (options.length > 1 || (options.length == 1 && options[0][0] !== '_default_')) {
- Ext.apply(fieldDef, {
- xtype: (this.adminMode ? 'lockCombo' : 'combo'),
- store: pref.get('options'),
- mode: 'local',
- forceSelection: true,
- allowBlank: false,
- triggerAction: 'all'
- });
+ if (pref.get('uiconfig') && pref.get('uiconfig').xtype) {
+ // TODO support admin mode / currently this is personal_only
+ Ext.apply(fieldDef, {
+ xtype: pref.get('uiconfig').xtype,
+ recordClass: Tine[pref.get('uiconfig').appName].Model[pref.get('uiconfig').model],
+ value: pref.get('value') === '_default_' ? [] : pref.get('value'),
+ });
+ } else {
+ Ext.apply(fieldDef, {
+ xtype: (this.adminMode ? 'lockCombo' : 'combo'),
+ store: pref.get('options'),
+ mode: 'local',
+ forceSelection: true,
+ allowBlank: false,
+ triggerAction: 'all'
+ });
+ }
} else {
Ext.apply(fieldDef, {
xtype: (this.adminMode ? 'lockTextfield' : 'textfield'),
}
});
-Ext.reg('widget-detailspanel', Tine.widgets.grid.DetailsPanel)
\ No newline at end of file
+Ext.reg('widget-detailspanel', Tine.widgets.grid.DetailsPanel);
\ No newline at end of file
initComponent: function () {
Tine.widgets.grid.PickerGridLayerCombo.superclass.initComponent.call(this);
+ this.store = new Ext.data.SimpleStore({
+ fields: this.gridRecordClass
+ });
this.on('beforecollapse', this.onBeforeCollapse, this);
},
this.pickerGrid = new Tine.widgets.grid.PickerGridPanel({
recordClass: this.gridRecordClass,
height: this.layerHeight - 40 || 'auto',
- onStoreChange: Ext.emptyFn
+ onStoreChange: Ext.emptyFn,
+ store: this.store
});
return [this.pickerGrid];
* @return {Ext.form.Field} this
*/
setValue: function (value) {
+ var _ = window.lodash;
+
+ this.setStoreFromArray(value);
+ if (this.rendered) {
+ var titles = _.reduce(this.store.data.items, function(result, record) {
+ return result.concat(record.getTitle());
+ }, []);
+ this.setRawValue(titles.join(', '));
+ }
this.currentValue = value;
+
// to string overwrite, to make sure record is changed.
Tine.Tinebase.common.assertComparable(this.currentValue);
return this;
},
+ afterRender: function () {
+
+ Tine.widgets.grid.PickerGridLayerCombo.superclass.afterRender.apply(this, arguments);
+ if (this.currentValue) {
+ this.setValue(this.currentValue);
+ }
+ },
/**
* sets values to innerForm (grid)
*/
*
* @param {Array}
*
- * TODO move to picker grid?
*/
setStoreFromArray: function(data) {
//this.pickerGrid.getStore().clearData();
- var rawData = [];
- this.pickerGrid.getStore().removeAll();
+ this.store.removeAll();
for (var i = data.length-1; i >=0; --i) {
var recordData = data[i],
newRecord = new this.gridRecordClass(recordData);
- this.pickerGrid.getStore().insert(0, newRecord);
- rawData.push(newRecord.getTitle());
+ this.store.insert(0, newRecord);
}
-
- if (rawData) {
- this.setRawValue(rawData.join(', '));
- }
-
},
/**
*
* @return {Array}
*
- * TODO move to picker grid?
*/
getFromStoreAsArray: function() {
var result = [];
- this.pickerGrid.getStore().each(function(record) {
+ this.store.each(function(record) {
result.push(record.data);
}, this);
/**
* is called when selecting a record in the searchCombo (relationpickercombo)
*/
- onAddRecordFromCombo: function() {
- var record = this.getActiveSearchCombo().store.getById(this.getActiveSearchCombo().getValue());
-
+ onAddRecordFromCombo: function(node) {
+ var record = null;
+
+ if (this.getActiveSearchCombo().hasOwnProperty('store')) {
+ record = this.getActiveSearchCombo().store.getById(this.getActiveSearchCombo().getValue())
+ } else {
+ record = node;
+ }
+
if (! record) {
return;
}
}
this.onAddRecord(record, relconf);
-
- this.getActiveSearchCombo().collapse();
- this.getActiveSearchCombo().reset();
+
+ if (this.getActiveSearchCombo().hasOwnProperty('collapse')) {
+ this.getActiveSearchCombo().collapse();
+ this.getActiveSearchCombo().reset();
+ }
},
/**
* call to add relation from an external component
- *
+ *
+ * @todo refactor this trash
+ *
* @param {Tine.Tinebase.data.Record} record
* @param {Object} relconf
*/
if (! relconf) {
relconf = {};
}
- if (record.data.hasOwnProperty('relations')) {
+ if (record.data && record.data.hasOwnProperty('relations')) {
record.data.relations = null;
delete record.data.relations;
}
var rc = this.getActiveSearchCombo().recordClass;
var relatedPhpModel = rc.getPhpClassName();
-
+
var app = rc.getMeta('appName'), model = rc.getMeta('modelName'), f = app + model;
var type = '';
-
+
if (this.constraintsConfig[f] && this.constraintsConfig[f].length) {
// per default the first defined type is used
var type = this.constraintsConfig[f][0].type;
type = '';
var relationRecord = new Tine.Tinebase.Model.Relation(Ext.apply(this.getRelationDefaults(), Ext.apply({
- related_record: record.data,
+ related_record: record.data || record,
related_id: record.id,
related_model: relatedPhpModel,
type: type,
this.onAddNewRelationToStore(relationRecord, record);
}
}
-
- // reset search combo
- this.getActiveSearchCombo().collapse();
- this.getActiveSearchCombo().reset();
},
/**
var relatedApp = Tine.Tinebase.appMgr.get(appName);
var relatedConstrainsConfig = relatedApp.getRegistry().get('relatableModels');
var ownRecordClassName = this.editDialog.recordClass.getMeta('modelName');
- var relatedRecordProxy = Tine[appName][(model.toLowerCase() + 'Backend')];
+ var relatedRecordProxy = this.getActiveSearchCombo().recordProxy || Tine[appName][(model.toLowerCase() + 'Backend')];
if (! Ext.isFunction(record.get)) {
record = relatedRecordProxy.recordReader({responseText: Ext.encode(record)});
* @param {Tine.Tinebase.data.Record} record
*/
onAddNewRelationToStore: function(relationRecord, record) {
- relationRecord.data.related_record.relations = null;
- delete relationRecord.data.related_record.relations;
-
+ var _ = window.lodash;
+
+ if (_.get(relationRecord, 'data.related_record.relations', false)) {
+ relationRecord.data.related_record.relations = null;
+ delete relationRecord.data.related_record.relations;
+ }
+
if (this.relationCheck(relationRecord)) {
Tine.log.debug('Adding new relation:');
Tine.log.debug(relationRecord);