6 * @subpackage Configuration
7 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
8 * @copyright Copyright (c) 2013-2016 Metaways Infosystems GmbH (http://www.metaways.de)
9 * @author Alexander Stintzing <a.stintzing@metaways.de>
13 * Tinebase_ModelConfiguration
16 * @subpackage Configuration
19 class Tinebase_ModelConfiguration {
22 * this holds (caches) the availability info of applications globally
26 static protected $_availableApplications = array('Tinebase' => TRUE);
33 protected $_idProperty = 'id';
40 protected $_table = array();
47 protected $_version = null;
50 protected $_identifier;
53 * Human readable name of the record
54 * add plural translation information in comments like:
55 * // ngettext('Record Name', 'Records Name', 1);
59 protected $_recordName = NULL;
62 * Human readable name of multiple records
63 * add plural translation information in comments like:
64 * // ngettext('Record Name', 'Records Name', 2);
68 protected $_recordsName = NULL;
71 * The property of the container, if any
75 protected $_containerProperty = NULL;
78 * set this to false, if no filter and grid column should be created
82 protected $_containerUsesFilter = TRUE;
85 * set this to false, if personal containers should be ommited
89 protected $_hasPersonalContainer = TRUE;
92 * The property of the title, if any
94 * if an array is given, the second item is the array of arguments for vsprintf, the first the format string
98 protected $_titleProperty = 'title';
102 * If this is true, the json api (smd) is generated automatically
106 protected $_exposeJsonApi = NULL;
109 * Human readable name of the container
110 * add plural translation information in comments like:
111 * // ngettext('Record Name', 'Records Name', 2);
115 protected $_containerName = NULL;
118 * Human readable name of multiple containers
119 * add plural translation information in comments like:
120 * // ngettext('Record Name', 'Records Name', 2);
124 protected $_containersName = NULL;
127 * If this is true, the record has relations
131 protected $_hasRelations = NULL;
134 * If this is true, the record has customfields
138 protected $_hasCustomFields = NULL;
141 * If this is true, the record has notes
145 protected $_hasNotes = NULL;
148 * If this is true, the record has tags
152 protected $_hasTags = NULL;
155 * If this is true, the record has file attachments
159 protected $_hasAttachments = NULL;
162 * If this is true, a modlog will be created
166 protected $_modlogActive = NULL;
169 * If this is true, multiple edit of records of this model is possible.
173 protected $_multipleEdit = NULL;
176 * If multiple edit requires a special right, it's defined here
180 protected $_multipleEditRequiredRight = NULL;
183 * if this is set to true, this model will be added to the global add splitbutton
185 * @todo add this to a "frontend configuration"
189 protected $_splitButton = FALSE;
192 * Group name of this model (will create a parent node in the modulepanel with this name)
193 * add translation information in comments like: // _('Group')
197 protected $_moduleGroup = NULL;
200 * Set the default Filter (defaults to query)
204 protected $_defaultFilter = 'query';
207 * Set the default sort info for the gridpanel (Tine.widgets.grid.GridPanel.defaultSortInfo)
208 * set as array('field' => 'title', 'direction' => 'DESC')
212 protected $_defaultSortInfo = NULL;
215 * Defines the right to see this model
219 protected $_requiredRight = NULL;
226 protected $_singularContainerMode = NULL;
229 * Holds the field definitions in an associative array where the key
230 * corresponds to the db-table name. Possible definitions and their defaults:
232 * !! Get sure to have at least one default value set and added one field to the query filter !!
234 * - validators: Use Zend Input Filters to validate the values.
235 * @type: Array, @default: array(Zend_Filter_Input::ALLOW_EMPTY => true)
237 * - label: The human readable label of the field. If this is set to null, this won't be shown in the auto FE Grid or EditDialog.
238 * Add translation information in comments like: // _('Title')
239 * @type: String, @default: NULL
241 * - default: The default value of the field.
242 * Add translation information in comments like: // _('New Car')
243 * @type: as defined (see DEFAULT MAPPING), @default: NULL
245 * - duplicateCheckGroup: All Fields having the same group will be combined for duplicate
246 * check. If no group is given, no duplicate check will be done.
247 * @type: String, @default: NULL
249 * - type: The type of the Value
250 * @type: String, @default: "string"
252 * - specialType: Defines the type closer
253 * @type: String, @default: NULL
255 * - filterDefinition: Overwrites the default filter used for this field
256 * Definition knows all from Tinebase_Model_Filter_FilterGroup._filterModel
257 * @type: Array, @default: array('filter' => 'Tinebase_Model_Filter_Text') or the default used for this type (see DEFAULT MAPPING)
259 * - inputFilters: zend input filters to use for this field
260 * @type: Array, use array(<InPutFilterClassName> => <constructorData>, ...)
262 * - queryFilter: If this is set to true, this field will be used by the "query" filter
263 * @type: Boolean, @default: NULL
265 * - duplicateOmit: Will neither be shown nor handled on duplicate resolving
266 * @type: Boolean, @default: NULL
268 * - copyOmit: If this is set to true, the field won't be used on copy the record
269 * @type: Boolean, @default: NULL
271 * - readOnly: If this is set to true, the field can't be updated and will be shown as readOnly in the frontend
272 * @type: Boolean, @default: NULL
274 * - disabled: If this is set to true, the field can't be updated and will be shown as readOnly in the frontend
275 * @type: Boolean, @default: NULL
277 * - group: Add this field to a group. Each group will be shown as a separate FieldSet of the
278 * EditDialog and group in the DuplicateResolveGridPanel. If any field of this model
279 * has a group set, FieldSets will be created and fields without a group set will be
280 * added to a group with the same name as the RecordName.
281 * Add translation information in comments like: // _('Group')
282 * @type: String, @default: NULL
284 * - dateFormat: If type is a date, the format can be overwritten here
285 * @type: String, @default: NULL or the default used for this type (see DEFAULT MAPPING)
287 * - shy: If this is set to true, the row for this field won't be shown in the grid, but can be activated
289 * - sortable: If this is set to false, no sort by this field is possible in the gridpanel, defaults to true
291 * // TODO: generalize, currently only in ContractGridPanel, take it from there:
292 * - showInDetailsPanel: auto show in details panel if any is defined in the js gridpanel class
294 * - useGlobalTranslation: if set, the global translation is used
298 * Field-Type specialType Human-Type SQL-Type JS-Type PHP-Type PHP-Filter dateFormat JS-FilterType
300 * date Date datetime date Tinebase_DateTime Tinebase_Model_Filter_Date ISO8601Short
301 * datetime Date with time datetime date Tinebase_DateTime Tinebase_Model_Filter_Date ISO8601Long
302 * time Time datetime date Tinebase_DateTime Tinebase_Model_Filter_Date ISO8601Time
303 * string Text varchar string string Tinebase_Model_Filter_Text
304 * text Text with lnbr. text string string Tinebase_Model_Filter_Text
305 * boolean Boolean boolean bool bool Tinebase_Model_Filter_Bool
306 * integer Integer integer integer int Tinebase_Model_Filter_Int number
307 * integer bytes Bytes integer integer int Tinebase_Model_Filter_Int
308 * integer seconds Seconds integer integer int Tinebase_Model_Filter_Int
309 * integer minutes Minutes integer integer int Tinebase_Model_Filter_Int
310 * float Float float float float Tinebase_Model_Filter_Int
311 * float usMoney Dollar in Cent float float int Tinebase_Model_Filter_Int
312 * float euMoney Euro in Cent float float int Tinebase_Model_Filter_Int
313 * json Json String text string array Tinebase_Model_Filter_Text
314 * container Container string Tine.Tinebase.Model.Container Tinebase_Model_Container tine.widget.container.filtermodel
316 * user User string Tinebase_Model_Filter_User
319 * Field Type "virtual" has a config property which holds the field configuration.
320 * An additional property is "function". If this property is set, the given function
321 * will be called to resolve the field in Tinebase_Convert_Json.
322 * If an array with two values is given, the first value will be handled as a class,
323 * the second one would be handled as a statically callable method.
324 * if the array is an associative one with one key and one value,
325 * the key will be used for the classname of a singleton (callable by getInstance),
326 * the value will be used as method name.
328 * * record 1:1 - Relation text Tine.<APP>.Model.<MODEL> Tinebase_Record_Abstract Tinebase_Model_Filter_ForeignId Tine.widgets.grid.ForeignRecordFilter
329 * * records 1:n - Relation - Array of Record.data Objects Tinebase_Record_RecordSet - -
330 * * relation m:m - Relation - Tinebase.Model.Relation Tinebase_Model_Relation Tinebase_Model_Filter_Relation
331 * * keyfield String string <as defined> string Tinebase_Model_Filter_Text
333 * * Accepts additional parameter: 'config' => array with these keys:
334 * - @string appName (the name of the application of the referenced record/s)
335 * - @string modelName (the name of the model of the referenced record/s)
337 * Config for 'record' and 'records' accepts also these keys: (optionally if record class name doesn't fit the convention, will be autogenerated, if not set)
338 * - @string recordClassName
339 * - @string controllerClassName
341 * Config for 'records' accepts also these keys:
342 * - @string refIdField (the field of the foreign record referencing the idProperty of the own record)
343 * - @array paging (accepts the parameters as Tinebase_Model_Pagination does)
344 * - @string filterClassName
345 * - @array addFilters define them as array like Tinebase_Model_Filter_FilterGroup
347 * record accepts keys additionally
348 * - @string isParent set this to true if the field is the parent property of an dependent record. This field will be hidden in an edit dialog nested grid
350 * records accepts keys additionally
351 * - @string omitOnSearch set this to FALSE, if the field should be resolved on json-search (defaults to TRUE)
357 * 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
359 * 'duplicateCheckGroup' => 'number'
368 protected $_fields = array();
371 * if this is set to true, all virtual fields get resolved by the record controller method "resolveVirtualFields"
375 protected $_resolveVFGlobally = FALSE;
378 * holds all field definitions of type records
382 protected $_recordsFields = NULL;
385 * holds all field definitions of type record (foreignId fields)
389 protected $_recordFields = NULL;
392 * if this is set to true, related data will be fetched on fetching dependent records by frontend json
393 * look at: Tinebase_Convert_Json._resolveMultipleRecordFields
397 protected $_resolveRelated = FALSE;
400 * holds virtual field definitions used for non-persistent fields getting calculated on each call of the record
401 * no backend property will be build, no filters etc. will exist. they must be filled in frontend json
405 protected $_virtualFields = NULL;
408 * maps fieldgroup keys to their names
409 * Add translation information in comments like: // _('Banking Information')
412 * 'banking' => 'Banking Information', // _('Banking Information')
413 * 'private' => 'Private Information', // _('Private Information')
418 protected $_fieldGroups = NULL;
421 * here you can define one right (Tinebase_Acl_Rights_Abstract) for each field
422 * group ('group'-property of a field definition of this._fields), the user must
423 * have to see/edit this group, otherwise the fields of the edit dialog will be disabled/readOnly
426 * 'private' => array(
427 * 'see' => HumanResources_Acl_Rights::SEE_PRIVATE,
428 * 'edit' => HumanResources_Acl_Rights::EDIT_PRIVATE,
430 * 'banking' => array(
431 * 'see' => HumanResources_Acl_Rights::SEE_BANKING,
432 * 'edit' => HumanResources_Acl_Rights::EDIT_BANKING,
438 protected $_fieldGroupRights = array();
441 * every field group will be nested into a fieldset, here you can define the defaults (Ext.Container.defaults)
445 protected $_fieldGroupFeDefaults = array();
447 protected $_createModule = FALSE;
450 * auto set by the constructor
454 * If any field has a group, this will be set to true (autoset by the constructor)
458 protected $_useGroups = FALSE;
461 * the application this configuration belongs to (if the class has the name "Calendar_Model_Event", this will be resolved to "Calendar")
465 protected $_appName = NULL; // this should be used everytime, everywhere
467 protected $_application = NULL;
468 protected $_applicationName = NULL;
470 * the name of the model (if the class has the name "Calendar_Model_Event", this will be resolved to "Event")
474 protected $_modelName = NULL;
477 * holds the keys of all fields
481 protected $_fieldKeys = array();
484 * holds the time fields
488 protected $_timeFields = array();
491 * holds the fields which will be omitted in the modlog
495 protected $_modlogOmitFields = array();
498 * these fields will just be readOnly
502 protected $_readOnlyFields = array();
505 * holds the datetime fields
509 protected $_datetimeFields = array();
512 * holds the date fields (maybe we use Tinebase_Date sometimes)
516 protected $_dateFields = array();
519 * holds the alarm datetime fields
523 protected $_alarmDateTimeField = array();
526 * The calculated filters for this model (auto set)
530 protected $_filterModel = array();
533 * holds the validators for the model (auto set)
535 protected $_validators = array();
538 * holds validators which will be instanciated on model construction
542 protected $_ownValidators = array();
545 * if a record is dependent to another, this is true
549 protected $_isDependent = FALSE;
552 * input filters (will be set by field configuration)
559 * converters (will be set by field configuration)
563 protected $_converters = array();
566 * Holds the default Data for the model (autoset from field config)
570 protected $_defaultData = array();
573 * holds the fields / groups to check for duplicates (will be auto set by field configuration)
575 protected $_duplicateCheckFields = NULL;
578 * properties to collect for the filters (_appName and _modelName are set in the filter)
582 protected $_filterProperties = array('_filterModel', '_defaultFilter', '_modelName', '_applicationName');
585 * properties to collect for the model
589 protected $_modelProperties = array(
590 '_identifier', '_timeFields', '_dateFields', '_datetimeFields', '_alarmDateTimeField', '_validators', '_modlogOmitFields',
591 '_application', '_readOnlyFields', '_filters'
595 * properties to collect for the frontend
599 protected $_frontendProperties = array(
600 'containerProperty', 'containersName', 'containerName', 'defaultSortInfo', 'fieldKeys', 'filterModel',
601 'defaultFilter', 'requiredRight', 'singularContainerMode', 'fields', 'defaultData', 'titleProperty',
602 'useGroups', 'fieldGroupFeDefaults', 'fieldGroupRights', 'multipleEdit', 'multipleEditRequiredRight',
603 'recordName', 'recordsName', 'appName', 'modelName', 'createModule', 'virtualFields', 'group', 'isDependent',
604 'hasCustomFields', 'modlogActive', 'hasAttachments', 'idProperty', 'splitButton', 'attributeConfig',
605 'hasPersonalContainer', 'copyOmitFields'
609 * the module group (will be on the same leaf of the content type tree panel)
613 protected $_group = NULL;
616 * the backend properties holding the collected properties
620 protected $_modelConfiguration = NULL;
623 * holds the collected values for the frontendconfig (autoset on first call of getFrontendConfiguration)
627 protected $_frontendConfiguration = NULL;
630 * the backend properties holding the collected properties
634 protected $_filterConfiguration = NULL;
640 protected $_attributeConfig = NULL;
647 * This defines the filters use for all known types
650 protected $_filterModelMapping = array(
651 'date' => 'Tinebase_Model_Filter_Date',
652 'datetime' => 'Tinebase_Model_Filter_DateTime',
653 'time' => 'Tinebase_Model_Filter_Date',
654 'string' => 'Tinebase_Model_Filter_Text',
655 'text' => 'Tinebase_Model_Filter_Text',
656 'json' => 'Tinebase_Model_Filter_Text',
657 'boolean' => 'Tinebase_Model_Filter_Bool',
658 'integer' => 'Tinebase_Model_Filter_Int',
659 'float' => 'Tinebase_Model_Filter_Float',
660 'record' => 'Tinebase_Model_Filter_ForeignId',
661 'relation' => 'Tinebase_Model_Filter_Relation',
663 'keyfield' => 'Tinebase_Model_Filter_Text',
664 'container' => 'Tinebase_Model_Filter_Container',
665 'tag' => 'Tinebase_Model_Filter_Tag',
666 'user' => 'Tinebase_Model_Filter_User',
670 * This maps field types to own validators, which will be instanciated in the constructor.
674 protected $_inputFilterDefaultMapping = array(
675 'text' => array('Tinebase_Model_InputFilter_CrlfConvert'),
679 * This maps field types to their default validators, just zendfw validators can be used here.
680 * For using own validators, use _ownValidatorMapping instead. If no validator is given,
681 * "array(Zend_Filter_Input::ALLOW_EMPTY => true)" will be used
685 protected $_validatorMapping = array(
686 'record' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
687 'relation' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
691 * This maps field types to their default converter
695 protected $_converterDefaultMapping = array(
696 'json' => array('Tinebase_Model_Converter_Json'),
700 * Collection of copy omit properties for frontend
704 protected $_copyOmitFields = NULL;
707 * the constructor (must be called by the singleton pattern)
709 * @var array $modelClassConfiguration
710 * @throws Tinebase_Exception_Record_DefinitionFailure
712 public function __construct($modelClassConfiguration)
714 if (! $modelClassConfiguration) {
715 throw new Tinebase_Exception('The model class configuration must be submitted!');
718 $this->_appName = $this->_application = $this->_applicationName = $modelClassConfiguration['appName'];
720 // add appName to available applications
721 self::$_availableApplications[$this->_appName] = TRUE;
723 $this->_modelName = $modelClassConfiguration['modelName'];
724 $this->_idProperty = $this->_identifier = (isset($modelClassConfiguration['idProperty']) || array_key_exists('idProperty', $modelClassConfiguration)) ? $modelClassConfiguration['idProperty'] : 'id';
726 $this->_table = isset($modelClassConfiguration['table']) ? $modelClassConfiguration['table'] : $this->_table;
727 $this->_version = isset($modelClassConfiguration['version']) ? $modelClassConfiguration['version'] : $this->_version;
729 // some cruid validating
730 foreach ($modelClassConfiguration as $propertyName => $propertyValue) {
731 $this->{'_' . $propertyName} = $propertyValue;
734 $this->_filters = array();
735 $this->_fields[$this->_idProperty] = array('id' => true, 'label' => NULL, 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'length' => 40);
737 if ($this->_hasCustomFields) {
738 $this->_fields['customfields'] = array('label' => NULL, 'type' => 'custom', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL));
741 if ($this->_hasRelations) {
742 $this->_fields['relations'] = array('label' => NULL, 'type' => 'relation', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL));
745 if ($this->_containerProperty) {
746 $this->_fields[$this->_containerProperty] = array(
749 'label' => $this->_containerUsesFilter ? $this->_containerName : NULL,
751 'type' => 'container',
752 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
753 'filterDefinition' => array(
754 'filter' => $this->_filterModelMapping['container'],
755 'options' => array('applicationName' => $this->_appName)
759 $this->_singularContainerMode = true;
762 // quick hack ('key')
763 if ($this->_hasTags) {
764 $this->_fields['tags'] = array(
768 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
769 'useGlobalTranslation' => TRUE,
770 'filterDefinition' => array(
772 'filter' => $this->_filterModelMapping['tag'],
774 'idProperty' => $this->_idProperty,
775 'applicationName' => $this->_appName
781 if ($this->_hasAttachments) {
782 $this->_fields['attachments'] = array(
784 'type' => 'attachments'
789 if ($this->_modlogActive) {
790 // notes are needed if modlog is active
791 $this->_fields['notes'] = array('label' => NULL, 'type' => 'note', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL), 'useGlobalTranslation' => TRUE);
792 $this->_fields['created_by'] = array('label' => 'Created By', 'type' => 'user', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'shy' => true, 'useGlobalTranslation' => TRUE, 'length' => 40, 'nullable' => true);
793 $this->_fields['creation_time'] = array('label' => 'Creation Time', 'type' => 'datetime', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'shy' => true, 'useGlobalTranslation' => TRUE, 'nullable' => true);
794 $this->_fields['last_modified_by'] = array('label' => 'Last Modified By', 'type' => 'user', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'shy' => true, 'useGlobalTranslation' => TRUE, 'length' => 40, 'nullable' => true);
795 $this->_fields['last_modified_time'] = array('label' => 'Last Modified Time', 'type' => 'datetime', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'shy' => true, 'useGlobalTranslation' => TRUE, 'nullable' => true);
796 $this->_fields['seq'] = array('label' => NULL, 'type' => 'integer', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'shy' => true, 'useGlobalTranslation' => TRUE, 'default' => 0, 'unsigned' => true);
798 // don't show deleted information
799 $this->_fields['deleted_by'] = array('label' => NULL, 'type' => 'user', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'useGlobalTranslation' => TRUE, 'length' => 40, 'nullable' => true);
800 $this->_fields['deleted_time'] = array('label' => NULL, 'type' => 'datetime', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true), 'useGlobalTranslation' => TRUE, 'nullable' => true);
801 $this->_fields['is_deleted'] = array(
804 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
805 'useGlobalTranslation' => TRUE,
809 } elseif ($this->_hasNotes) {
810 $this->_fields['notes'] = array('label' => NULL, 'type' => 'note', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL));
813 // holds the filters used for the query-filter, if any
814 $queryFilters = array();
816 foreach ($this->_fields as $fieldKey => &$fieldDef) {
817 $fieldDef['fieldName'] = $fieldKey;
819 // set default type to string, if no type is given
820 if (! (isset($fieldDef['type']) || array_key_exists('type', $fieldDef))) {
821 $fieldDef['type'] = 'string';
824 // don't handle field if app is not available
825 if ((isset($fieldDef['config']) || array_key_exists('config', $fieldDef)) && ($fieldDef['type'] == 'record' || $fieldDef['type'] == 'records') && (! $this->_isAvailable($fieldDef['config']))) {
826 $fieldDef['type'] = 'string';
827 $fieldDef['label'] = NULL;
831 $fieldDef['key'] = $fieldKey;
833 // if any field has a group set, enable grouping globally
834 if (! $this->_useGroups && (isset($fieldDef['group']) || array_key_exists('group', $fieldDef))) {
835 $this->_useGroups = TRUE;
838 if ($fieldDef['type'] == 'keyfield') {
839 $fieldDef['length'] = 40;
842 if (isset($fieldDef['copyOmit']) && $fieldDef['copyOmit']) {
843 if (!is_array($this->_copyOmitFields)) {
844 $this->_copyOmitFields = array();
846 $this->_copyOmitFields[] = $fieldKey;
849 if ($fieldDef['type'] == 'virtual') {
850 $fieldDef = isset($fieldDef['config']) ? $fieldDef['config'] : array();
851 $fieldDef['key'] = $fieldKey;
852 $fieldDef['sortable'] = FALSE;
853 if ((isset($fieldDef['default']))) {
854 // @todo: better handling of virtualfields
855 $this->_defaultData[$fieldKey] = $fieldDef['default'];
857 $this->_virtualFields[] = $fieldDef;
863 // TODO: implement complex default values
864 if ((isset($fieldDef['default']))) {
865 // // allows dynamic default values
866 // if (is_array($fieldDef['default'])) {
867 // switch ($fieldDef['type']) {
872 // throw new Tinebase_Exception_NotImplemented($_message);
875 $this->_defaultData[$fieldKey] = $fieldDef['default'];
880 // TODO: Split this up in multiple functions
881 // TODO: Refactor: key 'tag' should be 'tags' in filter definition / quick hack
882 // also see ticket 8944 (https://forge.tine20.org/mantisbt/view.php?id=8944)
885 if ((isset($fieldDef['filterDefinition']) || array_key_exists('filterDefinition', $fieldDef))) {
886 // use filter from definition
887 $key = isset($fieldDef['filterDefinition']['key']) ? $fieldDef['filterDefinition']['key'] : $fieldKey;
888 $this->_filterModel[$key] = $fieldDef['filterDefinition'];
889 } else if ((isset($this->_filterModelMapping[$fieldDef['type']]) || array_key_exists($fieldDef['type'], $this->_filterModelMapping))) {
890 // if no filterDefinition is given, try to use the default one
891 $this->_filterModel[$fieldKey] = array('filter' => $this->_filterModelMapping[$fieldDef['type']]);
892 if ((isset($fieldDef['config']) || array_key_exists('config', $fieldDef))) {
893 $this->_filterModel[$fieldKey]['options'] = $fieldDef['config'];
895 // set id filter controller
896 if ($fieldDef['type'] == 'record') {
897 $this->_filterModel[$fieldKey]['options']['filtergroup'] = $fieldDef['config']['appName'] . '_Model_' . $fieldDef['config']['modelName'] . 'Filter';
898 $this->_filterModel[$fieldKey]['options']['controller'] = $fieldDef['config']['appName'] . '_Controller_' . $fieldDef['config']['modelName'];
903 if ((isset($fieldDef['queryFilter']) || array_key_exists('queryFilter', $fieldDef))) {
904 $queryFilters[] = $fieldKey;
908 if ((isset($fieldDef['validators']) || array_key_exists('validators', $fieldDef))) {
909 // use _validators from definition
910 $this->_validators[$fieldKey] = $fieldDef['validators'];
911 } else if ((isset($this->_validatorMapping[$fieldDef['type']]) || array_key_exists($fieldDef['type'], $this->_validatorMapping))) {
912 // if no validatorsDefinition is given, try to use the default one
913 $fieldDef['validators'] = $this->_validators[$fieldKey] = $this->_validatorMapping[$fieldDef['type']];
915 $fieldDef['validators'] = $this->_validators[$fieldKey] = array(Zend_Filter_Input::ALLOW_EMPTY => true);
918 // set input filters, append defined if any or use defaults from _inputFilterDefaultMapping
919 if ((isset($fieldDef['inputFilters']) || array_key_exists('inputFilters', $fieldDef))) {
920 foreach ($fieldDef['inputFilters'] as $if => $val) {
921 if (is_array($val)) {
922 $reflect = new ReflectionClass($if);
923 $this->_filters[$fieldKey][] = $reflect->newInstanceArgs($val);
925 $this->_filters[$fieldKey][] = $if ? new $if($val) : new $val();
928 } else if ((isset($this->_inputFilterDefaultMapping[$fieldDef['type']]) || array_key_exists($fieldDef['type'], $this->_inputFilterDefaultMapping))) {
929 foreach ($this->_inputFilterDefaultMapping[$fieldDef['type']] as $if => $val) {
930 $this->_filters[$fieldKey][] = $if ? new $if($val) : new $val();
934 // add field to modlog omit, if configured and modlog is used
935 if ($this->_modlogActive && (isset($fieldDef['modlogOmit']) || array_key_exists('modlogOmit', $fieldDef))) {
936 $this->_modlogOmitFields[] = $fieldKey;
940 if (isset($fieldDef['converters']) && is_array($fieldDef['converters'])) {
941 if (count($fieldDef['converters'])) {
942 $this->_converters[$fieldKey] = $fieldDef['converters'];
944 } elseif(isset($this->_converterDefaultMapping[$fieldDef['type']])) {
945 $this->_converters[$fieldKey] = $this->_converterDefaultMapping[$fieldDef['type']];
948 $this->_populateProperties($fieldKey, $fieldDef);
952 // set some default filters
953 if (count($queryFilters)) {
954 $this->_getQueryFilter($queryFilters);
956 $this->_filterModel[$this->_idProperty] = array('filter' => 'Tinebase_Model_Filter_Id', 'options' => array('idProperty' => $this->_idProperty, 'modelName' => $this->_appName . '_Model_' . $this->_modelName));
957 $this->_fieldKeys = array_keys($this->_fields);
961 * constructs the query filter
963 * adds ExplicitRelatedRecords-filters to query filter (relatedModels) to allow search in relations
965 * @param array $queryFilters
967 * @see 0011494: activate advanced search for contracts (customers, ...)
969 protected function _getQueryFilter($queryFilters)
971 $queryFilterData = array(
972 'label' => 'Quick Search',
974 'filter' => 'Tinebase_Model_Filter_Query',
975 'useGlobalTranslation' => true,
977 'fields' => $queryFilters,
978 'modelName' => $this->_getPhpClassName(),
982 $relatedModels = array();
983 foreach ($this->_filterModel as $name => $filter) {
984 if ($filter['filter'] === 'Tinebase_Model_Filter_ExplicitRelatedRecord') {
985 $relatedModels[] = $filter['options']['related_model'];
988 if (count($relatedModels) > 0) {
989 $queryFilterData['options']['relatedModels'] = array_unique($relatedModels);
992 $this->_filterModel['query'] = $queryFilterData;
996 * get modelconfig for an array of models
998 * @param array $models
999 * @param string $appname
1002 public static function getFrontendConfigForModels($models, $appname = null)
1004 $modelconfig = array();
1005 foreach ($models as $modelName) {
1006 $recordClass = $appname ? $appname . '_Model_' . $modelName : $modelName;
1007 $modelName = preg_replace('/^.+_Model_/', '', $modelName);
1008 $config = $recordClass::getConfiguration();
1010 $modelconfig[$modelName] = $config->getFrontendConfiguration();
1014 return $modelconfig;
1017 public function getIdProperty()
1019 return $this->_idProperty;
1022 public function getTable()
1024 return $this->_table;
1027 public function getVersion()
1029 return $this->_version;
1032 public function getApplName()
1034 return $this->_appName;
1037 public function getModelName()
1039 return $this->_modelName;
1043 * populate model config properties
1045 * @param string $fieldKey
1046 * @param array $fieldDef
1048 protected function _populateProperties($fieldKey, $fieldDef)
1050 switch ($fieldDef['type']) {
1060 // add to datetime fields
1061 $this->_dateFields[] = $fieldKey;
1064 // add to alarm fields
1065 if ((isset($fieldDef['alarm']) || array_key_exists('alarm', $fieldDef))) {
1066 $this->_alarmDateTimeField = $fieldKey;
1068 // add to datetime fields
1069 $this->_datetimeFields[] = $fieldKey;
1072 // add to timefields
1073 $this->_timeFields[] = $fieldKey;
1076 $fieldDef['config'] = array(
1077 'refIdField' => 'id',
1079 'appName' => 'Addressbook',
1080 'modelName' => 'Contact',
1081 'recordClassName' => 'Addressbook_Model_Contact',
1082 'controllerClassName' => 'Addressbook_Controller_Contact',
1083 'filterClassName' => 'Addressbook_Model_ContactFilter',
1084 'addFilters' => array(
1085 array('field' => 'type', 'operator' => 'equals', 'value' => 'user')
1088 $this->_recordFields[$fieldKey] = $fieldDef;
1091 $this->_filterModel[$fieldKey]['options']['controller'] = $this->_getPhpClassName($this->_filterModel[$fieldKey]['options'], 'Controller');
1092 $this->_filterModel[$fieldKey]['options']['filtergroup'] = $this->_getPhpClassName($this->_filterModel[$fieldKey]['options'], 'Model') . 'Filter';
1094 $fieldDef['config']['recordClassName'] = (isset($fieldDef['config']['recordClassName']) || array_key_exists('recordClassName', $fieldDef['config'])) ? $fieldDef['config']['recordClassName'] : $this->_getPhpClassName($fieldDef['config']);
1095 $fieldDef['config']['controllerClassName'] = (isset($fieldDef['config']['controllerClassName']) || array_key_exists('controllerClassName', $fieldDef['config'])) ? $fieldDef['config']['controllerClassName'] : $this->_getPhpClassName($fieldDef['config'], 'Controller');
1096 $fieldDef['config']['filterClassName'] = (isset($fieldDef['config']['filterClassName']) || array_key_exists('filterClassName', $fieldDef['config'])) ? $fieldDef['config']['filterClassName'] : $this->_getPhpClassName($fieldDef['config']) . 'Filter';
1097 if ($fieldDef['type'] == 'record') {
1098 $fieldDef['config']['length'] = 40;
1099 $this->_recordFields[$fieldKey] = $fieldDef;
1101 $fieldDef['config']['dependentRecords'] = (isset($fieldDef['config']['dependentRecords']) || array_key_exists('dependentRecords', $fieldDef['config'])) ? $fieldDef['config']['dependentRecords'] : FALSE;
1102 $this->_recordsFields[$fieldKey] = $fieldDef;
1107 // prepend table name to id prop because of ambiguous ids
1108 // TODO find a better way to get table name, maybe we should put it in the modelconfig?
1109 $backend = Tinebase_Core::getApplicationInstance(
1110 $this->_applicationName, $this->_modelName, /* $_ignoreACL */ true)->getBackend();
1111 $tableName = $backend->getTableName();
1112 $this->_filterModel['customfield'] = array(
1113 'filter' => 'Tinebase_Model_Filter_CustomField',
1115 'idProperty' => $tableName . '.' . $this->_idProperty
1118 } catch (Exception $e) {
1119 // no customfield filter available (yet?)
1120 Tinebase_Exception::log($e);
1129 * returns an instance of the record controller
1131 * @return Tinebase_Controller_Record_Interface
1133 public function getControllerInstance()
1135 return Tinebase_Core::getApplicationInstance($this->_appName, $this->_modelName);
1139 * gets phpClassName by field definition['config']
1141 * @param array $_fieldConfig
1142 * @param string $_type
1145 protected function _getPhpClassName($_fieldConfig = null, $_type = 'Model')
1147 if (! $_fieldConfig) {
1148 $_fieldConfig = array('appName' => $this->_appName, 'modelName' => $this->_modelName);
1151 return $_fieldConfig['appName'] . '_' . $_type . '_' . $_fieldConfig['modelName'];
1155 * checks if app and model is available for the user at record and records fields
1156 * later this can be used to use field acl
1158 * @param array $_fieldConfig the field configuration
1160 protected function _isAvailable($_fieldConfig)
1162 if (! (isset(self::$_availableApplications[$_fieldConfig['appName']]) || array_key_exists($_fieldConfig['appName'], self::$_availableApplications))) {
1163 self::$_availableApplications[$_fieldConfig['appName']] = Tinebase_Application::getInstance()->isInstalled($_fieldConfig['appName'], TRUE);
1165 return self::$_availableApplications[$_fieldConfig['appName']];
1169 * returns the filter configuration needed in the filtergroup for this model
1173 public function getFilterModel()
1175 // add calculated values to filter configuration
1176 if (! $this->_filterConfiguration) {
1177 foreach ($this->_filterProperties as $prop) {
1178 $this->_filterConfiguration[$prop] = $this->{$prop};
1180 // @todo: remove this as in the filtergroup
1181 $this->_filterConfiguration['_className'] = $this->_appName . '_Model_' . $this->_modelName . 'Filter';
1183 return $this->_filterConfiguration;
1187 * returns the properties needed for the model
1191 public function toArray()
1193 if (! $this->_modelConfiguration) {
1194 // add calculated values to model configuration
1195 foreach ($this->_modelProperties as $prop) {
1196 $this->_modelConfiguration[$prop] = $this->{$prop};
1200 return $this->_modelConfiguration;
1204 * returns the frontend configuration for creating js models, filters, defaults and some other js stubs.
1205 * this will be included in the registry.
1206 * Look at Tinebase/js/ApplicationStarter.js
1208 public function getFrontendConfiguration()
1210 if (! $this->_frontendConfiguration) {
1212 $this->_frontendConfiguration = array();
1214 // add calculated values to frontend configuration
1215 foreach ($this->_frontendProperties as $prop) {
1216 $this->_frontendConfiguration[$prop] = $this->{'_' . $prop};
1219 return $this->_frontendConfiguration;
1223 * returns default data for this model
1227 public function getDefaultData()
1229 return $this->_defaultData;
1233 * returns the field configuration of the model
1235 public function getFields()
1237 return $this->_fields;
1241 * returns the converters of the model
1243 public function getConverters()
1245 return $this->_converters;
1249 * get protected property
1251 * @param string name of property
1252 * @throws Tinebase_Exception_UnexpectedValue
1253 * @return mixed value of property
1255 public function __get($_property)
1257 if (! property_exists($this, '_' . $_property)) {
1258 throw new Tinebase_Exception_UnexpectedValue('Property does not exist: ' . $_property);
1260 return $this->{'_' . $_property};