Merge branch '2013.03'
authorPhilipp Schüle <p.schuele@metaways.de>
Tue, 14 May 2013 12:52:47 +0000 (14:52 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Tue, 14 May 2013 12:52:47 +0000 (14:52 +0200)
429 files changed:
tests/setup/Setup/ControllerTest.php
tests/tine20/Addressbook/Convert/Contact/VCard/AllTests.php
tests/tine20/Addressbook/Convert/Contact/VCard/EMClientTest.php [new file with mode: 0644]
tests/tine20/Addressbook/Import/files/emclient_addressbook.vcf [new file with mode: 0644]
tests/tine20/Calendar/JsonTests.php
tests/tine20/Courses/JsonTest.php
tests/tine20/HumanResources/AllTests.php
tests/tine20/HumanResources/CliTests.php
tests/tine20/HumanResources/ControllerTests.php [new file with mode: 0644]
tests/tine20/HumanResources/JsonTests.php
tests/tine20/HumanResources/TestCase.php
tests/tine20/Inventory/Import/CsvTest.php
tests/tine20/Inventory/Import/files/inv_tine_import_csv.xml
tests/tine20/Inventory/JsonTest.php
tests/tine20/Phone/Backend/Snom/AllTests.php
tests/tine20/Phone/Backend/Snom/CallTest.php [moved from tests/tine20/Phone/Backend/Snom/CallhistoryTest.php with 81% similarity]
tests/tine20/Phone/JsonTest.php
tests/tine20/Tinebase/AllTests.php
tests/tine20/Tinebase/ModelConfigurationTest.php [new file with mode: 0644]
tests/tine20/Tinebase/Record/RecordTest.php
tine20/Addressbook/Addressbook.jsb2
tine20/Addressbook/Backend/List.php
tine20/Addressbook/Controller.php
tine20/Addressbook/Convert/Contact/VCard/Abstract.php
tine20/Addressbook/Convert/Contact/VCard/EMClient.php [new file with mode: 0644]
tine20/Addressbook/Convert/Contact/VCard/Factory.php
tine20/Addressbook/Frontend/Json.php
tine20/Addressbook/Model/List.php
tine20/Addressbook/Model/ListHiddenFilter.php
tine20/Addressbook/Setup/Update/Release7.php
tine20/Addressbook/Setup/setup.xml
tine20/Addressbook/js/Addressbook.js
tine20/Addressbook/js/ContactEditDialog.js
tine20/Addressbook/js/ContactModel.js [moved from tine20/Addressbook/js/Model.js with 91% similarity]
tine20/Addressbook/js/EmailModel.js [new file with mode: 0644]
tine20/Addressbook/js/ListEditDialog.js [new file with mode: 0644]
tine20/Addressbook/js/ListGrid.js [new file with mode: 0644]
tine20/Addressbook/js/ListGridDetailsPanel.js [new file with mode: 0644]
tine20/Addressbook/js/ListMemberGridPanel.js [new file with mode: 0644]
tine20/Addressbook/js/ListModel.js [new file with mode: 0644]
tine20/Addressbook/js/SearchCombo.js
tine20/Addressbook/translations/de.po
tine20/Admin/Acl/Rights.php
tine20/Admin/Event/DeleteAccount.php [new file with mode: 0644]
tine20/Admin/Setup/DemoData.php
tine20/Admin/js/Admin.js
tine20/Admin/js/Applications.js
tine20/Admin/js/customfield/EditDialog.js
tine20/Admin/js/user/EditDialog.js
tine20/Calendar/Convert/Event/VCalendar/Abstract.php
tine20/Calendar/Frontend/Cli.php
tine20/Calendar/js/AddressbookGridPanelHook.js
tine20/Calendar/js/AttendeeFilterGrid.js
tine20/Calendar/js/AttendeeGridPanel.js
tine20/Calendar/js/ColorManager.js
tine20/Calendar/js/MainScreenCenterPanel.js
tine20/Calendar/js/Model.js
tine20/Calendar/js/TreePanel.js
tine20/Courses/js/Courses.js
tine20/Crm/Controller.php
tine20/Crm/Controller/Lead.php
tine20/Crm/js/AddressbookGridPanelHook.js
tine20/Crm/js/Crm.js
tine20/Crm/js/Model.js
tine20/Felamimail/Backend/Cache/Sql/Message.php
tine20/Felamimail/Backend/Folder.php
tine20/Felamimail/css/Felamimail.css
tine20/Felamimail/js/ComposeEditor.js
tine20/Felamimail/js/ContactSearchCombo.js
tine20/Felamimail/js/Felamimail.js
tine20/Felamimail/js/GridPanelHook.js
tine20/Felamimail/js/RecipientGrid.js
tine20/Filemanager/Acl/Rights.php
tine20/Filemanager/Controller.php
tine20/Filemanager/Setup/Initialize.php
tine20/Filemanager/js/Filemanager.js
tine20/HumanResources/Backend/Account.php [new file with mode: 0644]
tine20/HumanResources/Backend/CostCenter.php
tine20/HumanResources/Backend/ExtraFreeTime.php [new file with mode: 0644]
tine20/HumanResources/Backend/FreeDay.php
tine20/HumanResources/Backend/FreeTime.php
tine20/HumanResources/Backend/WorkingTime.php
tine20/HumanResources/Config.php
tine20/HumanResources/Controller.php
tine20/HumanResources/Controller/Account.php [new file with mode: 0644]
tine20/HumanResources/Controller/Contract.php
tine20/HumanResources/Controller/CostCenter.php
tine20/HumanResources/Controller/Employee.php
tine20/HumanResources/Controller/ExtraFreeTime.php [new file with mode: 0644]
tine20/HumanResources/Controller/FreeDay.php
tine20/HumanResources/Controller/FreeTime.php
tine20/HumanResources/Controller/WorkingTime.php
tine20/HumanResources/Exception/ContractTooOld.php [new file with mode: 0644]
tine20/HumanResources/Exception/NoCurrentContract.php
tine20/HumanResources/Frontend/Cli.php
tine20/HumanResources/Frontend/Json.php
tine20/HumanResources/HumanResources.jsb2
tine20/HumanResources/Model/Account.php [new file with mode: 0644]
tine20/HumanResources/Model/AccountFilter.php [new file with mode: 0644]
tine20/HumanResources/Model/Contract.php
tine20/HumanResources/Model/ContractFilter.php
tine20/HumanResources/Model/CostCenter.php
tine20/HumanResources/Model/CostCenterFilter.php
tine20/HumanResources/Model/Employee.php
tine20/HumanResources/Model/EmployeeEmployedFilter.php
tine20/HumanResources/Model/EmployeeFilter.php
tine20/HumanResources/Model/ExtraFreeTime.php [new file with mode: 0644]
tine20/HumanResources/Model/ExtraFreeTimeFilter.php [new file with mode: 0644]
tine20/HumanResources/Model/ExtraFreeTimeType.php [new file with mode: 0644]
tine20/HumanResources/Model/FreeDay.php
tine20/HumanResources/Model/FreeDayFilter.php
tine20/HumanResources/Model/FreeTime.php
tine20/HumanResources/Model/FreeTimeFilter.php
tine20/HumanResources/Model/FreeTimeType.php
tine20/HumanResources/Model/WorkingTime.php
tine20/HumanResources/Setup/DemoData.php
tine20/HumanResources/Setup/Initialize.php
tine20/HumanResources/Setup/Update/Release6.php
tine20/HumanResources/Setup/Update/Release7.php
tine20/HumanResources/Setup/setup.xml
tine20/HumanResources/css/HumanResources.css
tine20/HumanResources/js/AccountEditDialog.js [new file with mode: 0644]
tine20/HumanResources/js/AccountGridPanel.js [new file with mode: 0644]
tine20/HumanResources/js/ContractDetailsPanel.js [new file with mode: 0644]
tine20/HumanResources/js/ContractEditDialog.js [new file with mode: 0644]
tine20/HumanResources/js/ContractGridPanel.js
tine20/HumanResources/js/ContractRecordPicker.js [deleted file]
tine20/HumanResources/js/CostCenterGridPanel.js
tine20/HumanResources/js/DatePicker.js
tine20/HumanResources/js/EmployeeEditDialog.js
tine20/HumanResources/js/EmployeeGridPanel.js
tine20/HumanResources/js/ExceptionHandler.js
tine20/HumanResources/js/ExtraFreeTimeDetailsPanel.js [new file with mode: 0644]
tine20/HumanResources/js/ExtraFreeTimeEditDialog.js [new file with mode: 0644]
tine20/HumanResources/js/ExtraFreeTimeGridPanel.js [new file with mode: 0644]
tine20/HumanResources/js/FreeDayGridPanel.js
tine20/HumanResources/js/FreeTimeEditDialog.js
tine20/HumanResources/js/FreeTimeGridPanel.js
tine20/HumanResources/js/HumanResources.js
tine20/HumanResources/js/Models.js
tine20/HumanResources/js/SicknessEditDialog.js [new file with mode: 0644]
tine20/HumanResources/js/VacationEditDialog.js [new file with mode: 0644]
tine20/HumanResources/js/WorkingTimeEditDialog.js [deleted file]
tine20/HumanResources/translations/de.po
tine20/HumanResources/translations/template.pot
tine20/Inventory/Controller/InventoryItem.php
tine20/Inventory/Frontend/Json.php
tine20/Inventory/Import/examples/inv_tine_import.csv
tine20/Inventory/Model/InventoryItem.php
tine20/Inventory/Model/InventoryItemFilter.php
tine20/Inventory/js/InventoryItemEditDialog.js
tine20/Phone/Backend/Factory.php
tine20/Phone/Backend/Snom/Call.php [moved from tine20/Phone/Backend/Snom/Callhistory.php with 89% similarity]
tine20/Phone/Controller.php
tine20/Phone/Controller/Call.php [new file with mode: 0644]
tine20/Phone/Controller/MyPhone.php
tine20/Phone/Frontend/Json.php
tine20/Phone/Model/Call.php
tine20/Phone/Model/CallFilter.php
tine20/Phone/Model/MyPhone.php
tine20/Phone/Model/MyPhoneFilter.php [new file with mode: 0644]
tine20/Phone/Phone.jsb2
tine20/Phone/Setup/DemoData.php [new file with mode: 0644]
tine20/Phone/Setup/Initialize.php
tine20/Phone/Setup/Update/Release7.php [new file with mode: 0644]
tine20/Phone/Setup/setup.xml
tine20/Phone/js/AddressbookGridPanelHook.js
tine20/Phone/js/CallGridPanel.js [new file with mode: 0644]
tine20/Phone/js/DialerPanel.js [new file with mode: 0644]
tine20/Phone/js/Models.js [deleted file]
tine20/Phone/js/Phone.js
tine20/Phone/js/PhoneTreePanel.js [new file with mode: 0644]
tine20/Phone/translations/de.po
tine20/Phone/translations/template.pot
tine20/Projects/js/AddressbookGridPanelHook.js
tine20/Projects/js/ProjectGridPanel.js
tine20/Sales/js/Sales.js
tine20/Setup/Backend/Oracle.php
tine20/Setup/Backend/Pgsql.php
tine20/Setup/Controller.php
tine20/Setup/js/AuthenticationPanel.js
tine20/SimpleFAQ/SimpleFAQ.jsb2
tine20/SimpleFAQ/js/Model.js
tine20/SimpleFAQ/js/SearchCombo.js [new file with mode: 0644]
tine20/SimpleFAQ/js/SimpleFAQ.js
tine20/Sipgate/Backend/Api.php
tine20/Sipgate/Controller/Line.php
tine20/Sipgate/js/AddressbookGridPanelHook.js
tine20/Sipgate/js/ConnectionGridPanel.js
tine20/Sipgate/js/LineGridPanel.js
tine20/Sipgate/js/Sipgate.js
tine20/Timetracker/js/Timetracker.js
tine20/Tinebase/Autoloader.php
tine20/Tinebase/Backend/Sql/Abstract.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/Backend/Sql/Filter/FilterGroup.php
tine20/Tinebase/Config.php
tine20/Tinebase/Controller.php
tine20/Tinebase/Controller/Record/Abstract.php
tine20/Tinebase/Convert/Json.php
tine20/Tinebase/Core.php
tine20/Tinebase/CustomField.php
tine20/Tinebase/Frontend/Json.php
tine20/Tinebase/Frontend/Json/Abstract.php
tine20/Tinebase/Import/Abstract.php
tine20/Tinebase/Log.php [new file with mode: 0644]
tine20/Tinebase/Log/Filter/User.php
tine20/Tinebase/Model/Filter/CustomField.php
tine20/Tinebase/Model/Filter/FilterGroup.php
tine20/Tinebase/Model/Filter/Id.php
tine20/Tinebase/Model/Filter/Query.php
tine20/Tinebase/Model/Filter/Relation.php
tine20/Tinebase/Model/PersistentFilterFilter.php
tine20/Tinebase/ModelConfiguration.php [new file with mode: 0644]
tine20/Tinebase/PersistentFilter.php
tine20/Tinebase/Preference/Abstract.php
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/Tinebase.jsb2
tine20/Tinebase/View.php [new file with mode: 0644]
tine20/Tinebase/css/ux/grid/GridDropZone.css [new file with mode: 0644]
tine20/Tinebase/css/ux/grid/PagingToolbar.css
tine20/Tinebase/js/AppManager.js
tine20/Tinebase/js/ApplicationStarter.js
tine20/Tinebase/js/LoginPanel.js
tine20/Tinebase/js/Models.js
tine20/Tinebase/js/configManager.js
tine20/Tinebase/js/data/RecordProxy.js
tine20/Tinebase/js/extFixes.js
tine20/Tinebase/js/tineInit.js
tine20/Tinebase/js/ux/Percentage.js
tine20/Tinebase/js/ux/WindowFactory.js
tine20/Tinebase/js/ux/display/DisplayPanel.js
tine20/Tinebase/js/ux/grid/GridDropZone.js [new file with mode: 0644]
tine20/Tinebase/js/ux/grid/PagingToolbar.js
tine20/Tinebase/js/widgets/ActionUpdater.js
tine20/Tinebase/js/widgets/ContentTypeTreePanel.js
tine20/Tinebase/js/widgets/container/FilterModel.js
tine20/Tinebase/js/widgets/dialog/DuplicateResolveGridPanel.js
tine20/Tinebase/js/widgets/dialog/EditDialog.js
tine20/Tinebase/js/widgets/form/RecordPickerComboBox.js
tine20/Tinebase/js/widgets/grid/FilterPanel.js
tine20/Tinebase/js/widgets/grid/FilterPlugin.js
tine20/Tinebase/js/widgets/grid/FilterToolbar.js
tine20/Tinebase/js/widgets/grid/GridPanel.js
tine20/Tinebase/js/widgets/grid/OwnRecordFilter.js
tine20/Tinebase/js/widgets/grid/QuickaddGridPanel.js
tine20/Tinebase/js/widgets/mainscreen/WestPanel.js
tine20/Tinebase/js/widgets/persistentfilter/Model.js
tine20/Tinebase/js/widgets/persistentfilter/PickerPanel.js
tine20/Tinebase/views/jsclient.php
tine20/Voipmanager/Model/Snom/LineFilter.php
tine20/Voipmanager/Model/Snom/PhoneFilter.php
tine20/Zend/Db/Adapter/Oracle.php
tine20/Zend/Db/Table/Abstract.php
tine20/index.php
tine20/library/jsb2tk/jsb2tk.php
tine20/styles/tine20.css [deleted file]
tine20/themes/tine20/resources/css/layout.css [moved from tine20/styles/layout.css with 98% similarity]
tine20/themes/tine20/resources/css/tine20.css [new file with mode: 0644]
tine20/themes/tine20/resources/images/tine20/box/corners-blue.gif [moved from tine20/styles/images/tine20/box/corners-blue.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/corners.gif [moved from tine20/styles/images/tine20/box/corners.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/l-blue.gif [moved from tine20/styles/images/tine20/box/l-blue.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/l.gif [moved from tine20/styles/images/tine20/box/l.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/r-blue.gif [moved from tine20/styles/images/tine20/box/r-blue.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/r.gif [moved from tine20/styles/images/tine20/box/r.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/tb-blue.gif [moved from tine20/styles/images/tine20/box/tb-blue.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/box/tb.gif [moved from tine20/styles/images/tine20/box/tb.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/arrow.gif [moved from tine20/styles/images/tine20/button/arrow.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/btn.gif [moved from tine20/styles/images/tine20/button/btn.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/group-cs.gif [moved from tine20/styles/images/tine20/button/group-cs.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/group-lr.gif [moved from tine20/styles/images/tine20/button/group-lr.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/group-tb.gif [moved from tine20/styles/images/tine20/button/group-tb.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/s-arrow-b-noline.gif [moved from tine20/styles/images/tine20/button/s-arrow-b-noline.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/s-arrow-b.gif [moved from tine20/styles/images/tine20/button/s-arrow-b.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/s-arrow-bo.gif [moved from tine20/styles/images/tine20/button/s-arrow-bo.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/s-arrow-noline.gif [moved from tine20/styles/images/tine20/button/s-arrow-noline.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/s-arrow-o.gif [moved from tine20/styles/images/tine20/button/s-arrow-o.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/button/s-arrow.gif [moved from tine20/styles/images/tine20/button/s-arrow.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/dd/drop-add.gif [moved from tine20/styles/images/tine20/tree/drop-add.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/dd/drop-no.gif [moved from tine20/styles/images/tine20/dd/drop-no.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/dd/drop-yes.gif [moved from tine20/styles/images/tine20/dd/drop-yes.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/clear-trigger.gif [moved from tine20/styles/images/tine20/form/clear-trigger.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/clear-trigger.psd [moved from tine20/styles/images/tine20/form/clear-trigger.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/date-trigger.gif [moved from tine20/styles/images/tine20/form/date-trigger.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/date-trigger.psd [moved from tine20/styles/images/tine20/form/date-trigger.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/error-tip-corners.gif [moved from tine20/styles/images/tine20/form/error-tip-corners.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/exclamation.gif [moved from tine20/styles/images/tine20/form/exclamation.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/search-trigger.gif [moved from tine20/styles/images/tine20/form/search-trigger.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/search-trigger.psd [moved from tine20/styles/images/tine20/form/search-trigger.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/text-bg.gif [moved from tine20/styles/images/tine20/form/text-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/trigger.gif [moved from tine20/styles/images/tine20/form/trigger.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/form/trigger.psd [moved from tine20/styles/images/tine20/form/trigger.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/col-move-bottom.gif [moved from tine20/styles/images/tine20/grid/col-move-bottom.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/col-move-top.gif [moved from tine20/styles/images/tine20/grid/col-move-top.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/columns.gif [moved from tine20/styles/images/tine20/grid/columns.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/dirty.gif [moved from tine20/styles/images/tine20/grid/dirty.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid-blue-split.gif [moved from tine20/styles/images/tine20/grid/grid-blue-split.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid-hrow.gif [moved from tine20/styles/images/tine20/grid/grid-hrow.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid-split.gif [moved from tine20/styles/images/tine20/grid/grid-split.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid3-hd-btn.gif [moved from tine20/styles/images/tine20/grid/grid3-hd-btn.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid3-hrow-over.gif [moved from tine20/styles/images/tine20/grid/grid3-hrow-over.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid3-hrow.gif [moved from tine20/styles/images/tine20/grid/grid3-hrow.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid3-special-col-bg.gif [moved from tine20/styles/images/tine20/grid/grid3-special-col-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/grid3-special-col-sel-bg.gif [moved from tine20/styles/images/tine20/grid/grid3-special-col-sel-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/group-by.gif [moved from tine20/styles/images/tine20/grid/group-by.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/group-collapse.gif [moved from tine20/styles/images/tine20/grid/group-collapse.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/group-expand.gif [moved from tine20/styles/images/tine20/grid/group-expand.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/hd-pop.gif [moved from tine20/styles/images/tine20/grid/hd-pop.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/hmenu-asc.gif [moved from tine20/styles/images/tine20/grid/hmenu-asc.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/hmenu-desc.gif [moved from tine20/styles/images/tine20/grid/hmenu-desc.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/hmenu-lock.gif [moved from tine20/styles/images/tine20/grid/hmenu-lock.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/hmenu-lock.png [moved from tine20/styles/images/tine20/grid/hmenu-lock.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/hmenu-unlock.gif [moved from tine20/styles/images/tine20/grid/hmenu-unlock.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/invalid_line.gif [moved from tine20/styles/images/tine20/grid/invalid_line.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/loading.gif [moved from tine20/styles/images/tine20/tree/loading.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-first-disabled.gif [moved from tine20/styles/images/tine20/grid/page-first-disabled.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-first.gif [moved from tine20/styles/images/tine20/grid/page-first.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-last-disabled.gif [moved from tine20/styles/images/tine20/grid/page-last-disabled.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-last.gif [moved from tine20/styles/images/tine20/grid/page-last.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-next-disabled.gif [moved from tine20/styles/images/tine20/grid/page-next-disabled.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-next.gif [moved from tine20/styles/images/tine20/grid/page-next.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-prev-disabled.gif [moved from tine20/styles/images/tine20/grid/page-prev-disabled.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/page-prev.gif [moved from tine20/styles/images/tine20/grid/page-prev.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/refresh.gif [moved from tine20/styles/images/tine20/grid/refresh.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/row-check-sprite.gif [moved from tine20/styles/images/tine20/grid/row-check-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/row-expand-sprite.gif [moved from tine20/styles/images/tine20/grid/row-expand-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/row-over.gif [moved from tine20/styles/images/tine20/grid/row-over.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/sort-hd.gif [moved from tine20/styles/images/tine20/grid/sort-hd.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/sort_asc.gif [moved from tine20/styles/images/tine20/grid/sort_asc.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/grid/sort_desc.gif [moved from tine20/styles/images/tine20/grid/sort_desc.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/layout/mini-bottom.gif [moved from tine20/styles/images/tine20/layout/mini-bottom.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/layout/mini-left.gif [moved from tine20/styles/images/tine20/layout/mini-left.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/layout/mini-right.gif [moved from tine20/styles/images/tine20/layout/mini-right.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/layout/mini-top.gif [moved from tine20/styles/images/tine20/layout/mini-top.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/layout/panel-title-light-bg.gif [moved from tine20/styles/images/tine20/layout/panel-title-light-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/menu/checked.gif [moved from tine20/styles/images/tine20/menu/checked.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/menu/group-checked.gif [moved from tine20/styles/images/tine20/menu/group-checked.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/menu/item-over.gif [moved from tine20/styles/images/tine20/menu/item-over.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/menu/menu-parent.gif [moved from tine20/styles/images/tine20/menu/menu-parent.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/menu/menu.gif [moved from tine20/styles/images/tine20/menu/menu.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/menu/unchecked.gif [moved from tine20/styles/images/tine20/menu/unchecked.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/naviactive.gif [moved from tine20/styles/images/tine20/naviactive.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/corners-sprite.gif [moved from tine20/styles/images/tine20/panel/corners-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/left-right.gif [moved from tine20/styles/images/tine20/panel/left-right.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/light-hd.gif [moved from tine20/styles/images/tine20/panel/light-hd.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/tool-sprites.gif [moved from tine20/styles/images/tine20/panel/tool-sprites.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/top-bottom.gif [moved from tine20/styles/images/tine20/panel/top-bottom.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/top-bottom.png [moved from tine20/styles/images/tine20/panel/top-bottom.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/panel/white-top-bottom.gif [moved from tine20/styles/images/tine20/panel/white-top-bottom.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_blue_1.gif [moved from tine20/styles/images/tine20/preview/bordercorner_blue_1.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_blue_2.gif [moved from tine20/styles/images/tine20/preview/bordercorner_blue_2.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_blue_3.gif [moved from tine20/styles/images/tine20/preview/bordercorner_blue_3.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_blue_4.gif [moved from tine20/styles/images/tine20/preview/bordercorner_blue_4.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_gray_1.gif [moved from tine20/styles/images/tine20/preview/bordercorner_gray_1.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_gray_2.gif [moved from tine20/styles/images/tine20/preview/bordercorner_gray_2.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_gray_3.gif [moved from tine20/styles/images/tine20/preview/bordercorner_gray_3.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/preview/bordercorner_gray_4.gif [moved from tine20/styles/images/tine20/preview/bordercorner_gray_4.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/progress/progress-bg-y.gif [moved from tine20/styles/images/tine20/progress/progress-bg-y.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/progress/progress-bg.gif [moved from tine20/styles/images/tine20/progress/progress-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/qtip/bg.gif [moved from tine20/styles/images/tine20/qtip/bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/qtip/close.gif [moved from tine20/styles/images/tine20/qtip/close.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/qtip/tip-anchor-sprite.gif [moved from tine20/styles/images/tine20/qtip/tip-anchor-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/qtip/tip-sprite.gif [moved from tine20/styles/images/tine20/qtip/tip-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shadow-c.png [moved from tine20/styles/images/tine20/shadow-c.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shadow-lr.png [moved from tine20/styles/images/tine20/shadow-lr.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shadow.png [moved from tine20/styles/images/tine20/shadow.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shared/glass-bg.gif [moved from tine20/styles/images/tine20/shared/glass-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shared/hd-sprite.gif [moved from tine20/styles/images/tine20/shared/hd-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shared/left-btn.gif [moved from tine20/styles/images/tine20/shared/left-btn.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shared/loading-balls.gif [moved from tine20/styles/images/tine20/shared/loading-balls.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/shared/right-btn.gif [moved from tine20/styles/images/tine20/shared/right-btn.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/e-handle.gif [moved from tine20/styles/images/tine20/sizer/e-handle.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/ne-handle-dark.gif [moved from tine20/styles/images/tine20/sizer/ne-handle-dark.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/ne-handle.gif [moved from tine20/styles/images/tine20/sizer/ne-handle.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/nw-handle.gif [moved from tine20/styles/images/tine20/sizer/nw-handle.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/s-handle.gif [moved from tine20/styles/images/tine20/sizer/s-handle.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/se-handle.gif [moved from tine20/styles/images/tine20/sizer/se-handle.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/sizer/sw-handle.gif [moved from tine20/styles/images/tine20/sizer/sw-handle.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/slider/slider-bg.png [moved from tine20/styles/images/tine20/slider/slider-bg.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/slider/slider-thumb.png [moved from tine20/styles/images/tine20/slider/slider-thumb.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/slider/slider-v-bg.png [moved from tine20/styles/images/tine20/slider/slider-v-bg.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/slider/slider-v-thumb.png [moved from tine20/styles/images/tine20/slider/slider-v-thumb.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/scroll-left.gif [moved from tine20/styles/images/tine20/tabs/scroll-left.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/scroll-right.gif [moved from tine20/styles/images/tine20/tabs/scroll-right.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-btm-inactive-left-bg.gif [moved from tine20/styles/images/tine20/tabs/tab-btm-inactive-left-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-btm-inactive-right-bg.gif [moved from tine20/styles/images/tine20/tabs/tab-btm-inactive-right-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-btm-left-bg.gif [moved from tine20/styles/images/tine20/tabs/tab-btm-left-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-btm-right-bg.gif [moved from tine20/styles/images/tine20/tabs/tab-btm-right-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-close.gif [moved from tine20/styles/images/tine20/tabs/tab-close.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-strip-bg.gif [moved from tine20/styles/images/tine20/tabs/tab-strip-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tab-strip-btm-bg.gif [moved from tine20/styles/images/tine20/tabs/tab-strip-btm-bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tabs/tabs-sprite.gif [moved from tine20/styles/images/tine20/tabs/tabs-sprite.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/toolbar/bg.gif [moved from tine20/styles/images/tine20/toolbar/bg.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/toolbar/btn-arrow-light.gif [moved from tine20/styles/images/tine20/toolbar/btn-arrow-light.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/toolbar/more.gif [moved from tine20/styles/images/tine20/toolbar/more.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/arrows.gif [moved from tine20/styles/images/tine20/tree/arrows.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/drop-add.gif [moved from tine20/styles/images/tine20/dd/drop-add.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/drop-between.gif [moved from tine20/styles/images/tine20/tree/drop-between.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/drop-over.gif [moved from tine20/styles/images/tine20/tree/drop-over.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/drop-under.gif [moved from tine20/styles/images/tine20/tree/drop-under.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-end-minus-nl.gif [moved from tine20/styles/images/tine20/tree/elbow-end-minus-nl.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-end-minus.gif [moved from tine20/styles/images/tine20/tree/elbow-end-minus.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-end-plus-nl.gif [moved from tine20/styles/images/tine20/tree/elbow-end-plus-nl.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-end-plus.gif [moved from tine20/styles/images/tine20/tree/elbow-end-plus.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-end.gif [moved from tine20/styles/images/tine20/tree/elbow-end.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-line.gif [moved from tine20/styles/images/tine20/tree/elbow-line.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-minus-nl.gif [moved from tine20/styles/images/tine20/tree/elbow-minus-nl.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-minus.gif [moved from tine20/styles/images/tine20/tree/elbow-minus.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-plus-nl.gif [moved from tine20/styles/images/tine20/tree/elbow-plus-nl.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/elbow-plus.gif [moved from tine20/styles/images/tine20/tree/elbow-plus.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/folder-open.gif [moved from tine20/styles/images/tine20/tree/folder-open.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/folder.gif [moved from tine20/styles/images/tine20/tree/folder.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/leaf.gif [moved from tine20/styles/images/tine20/tree/leaf.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/tree/loading.gif [moved from tine20/styles/images/tine20/grid/loading.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/icon-error.gif [moved from tine20/styles/images/tine20/window/icon-error.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/icon-info.gif [moved from tine20/styles/images/tine20/window/icon-info.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/icon-question.gif [moved from tine20/styles/images/tine20/window/icon-question.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/icon-warning.gif [moved from tine20/styles/images/tine20/window/icon-warning.gif with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/left-corners.png [moved from tine20/styles/images/tine20/window/left-corners.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/left-corners.psd [moved from tine20/styles/images/tine20/window/left-corners.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/left-right.png [moved from tine20/styles/images/tine20/window/left-right.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/left-right.psd [moved from tine20/styles/images/tine20/window/left-right.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/right-corners.png [moved from tine20/styles/images/tine20/window/right-corners.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/right-corners.psd [moved from tine20/styles/images/tine20/window/right-corners.psd with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/top-bottom.png [moved from tine20/styles/images/tine20/window/top-bottom.png with 100% similarity]
tine20/themes/tine20/resources/images/tine20/window/top-bottom.psd [moved from tine20/styles/images/tine20/window/top-bottom.psd with 100% similarity]

index c9a1b67..e700356 100644 (file)
@@ -4,7 +4,7 @@
  *
  * @package     Setup
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2008-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  *
  */
@@ -125,39 +125,45 @@ class Setup_ControllerTest extends PHPUnit_Framework_TestCase
     public function testSaveAuthenticationRedirectSettings()
     {
         $originalRedirectSettings = array(
-          Tinebase_Config::REDIRECTURL => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, ''),
-          Tinebase_Config::REDIRECTTOREFERRER => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, '')
+            Tinebase_Config::REDIRECTURL => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, ''),
+            Tinebase_Config::REDIRECTTOREFERRER => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, FALSE)
         );
          
         $newRedirectSettings = array(
-          Tinebase_Config::REDIRECTURL => 'http://tine20.org',
-          Tinebase_Config::REDIRECTTOREFERRER => 1
+            Tinebase_Config::REDIRECTURL => 'http://tine20.org',
+            Tinebase_Config::REDIRECTTOREFERRER => TRUE
         );
         
         $this->_uit->saveAuthentication(array('redirectSettings' => $newRedirectSettings));
         
         $storedRedirectSettings = array(
-          Tinebase_Config::REDIRECTURL => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, ''),
-          Tinebase_Config::REDIRECTTOREFERRER => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, '')
+            Tinebase_Config::REDIRECTURL => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL),
+            Tinebase_Config::REDIRECTTOREFERRER => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER)
         );
         
-        $this->assertEquals($storedRedirectSettings, $newRedirectSettings);
-        
+        $configNames = array(Tinebase_Config::REDIRECTURL, Tinebase_Config::REDIRECTTOREFERRER);
+        foreach ($configNames as $configName) {
+            $this->assertEquals($storedRedirectSettings[$configName], $newRedirectSettings[$configName],
+                'new setting should match stored settings: ' . print_r($newRedirectSettings, TRUE));
+        }
         
-        //test empty redirectUrl
+        // test empty redirectUrl
         $newRedirectSettings = array(
-          Tinebase_Config::REDIRECTURL => '',
-          Tinebase_Config::REDIRECTTOREFERRER => 0
+            Tinebase_Config::REDIRECTURL => '',
+            Tinebase_Config::REDIRECTTOREFERRER => FALSE
         );
         
         $this->_uit->saveAuthentication(array('redirectSettings' => $newRedirectSettings));
         
         $storedRedirectSettings = array(
-          Tinebase_Config::REDIRECTURL => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, ''),
-          Tinebase_Config::REDIRECTTOREFERRER => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER, '')
+            Tinebase_Config::REDIRECTURL => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL),
+            Tinebase_Config::REDIRECTTOREFERRER => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTTOREFERRER)
         );
         
-        $this->assertEquals($storedRedirectSettings, $newRedirectSettings);
+        foreach ($configNames as $configName) {
+            $this->assertEquals($storedRedirectSettings[$configName], $newRedirectSettings[$configName],
+                'new setting should match stored settings (with empty redirect URL): ' . print_r($newRedirectSettings, TRUE));
+        }
         
         $this->_uit->saveAuthentication($originalRedirectSettings);
     }
index da3d52a..1d4c80f 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2011-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
  */
 require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
 
-if (! defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Addressbook_Convert_Contact_VCard_AllTests::main');
-}
-
 class Addressbook_Convert_Contact_VCard_AllTests
 {
     public static function main ()
@@ -32,11 +28,8 @@ class Addressbook_Convert_Contact_VCard_AllTests
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_IOSTest');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_MacOSXTest');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_SogoTest');
+        $suite->addTestSuite('Addressbook_Convert_Contact_VCard_EMClientTest');
         
         return $suite;
     }
 }
-
-if (PHPUnit_MAIN_METHOD == 'Addressbook_Convert_Contact_VCard_AllTests::main') {
-    Addressbook_Convert_Contact_VCard_AllTests::main();
-}
diff --git a/tests/tine20/Addressbook/Convert/Contact/VCard/EMClientTest.php b/tests/tine20/Addressbook/Convert/Contact/VCard/EMClientTest.php
new file mode 100644 (file)
index 0000000..b8f622a
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Thomas Pawassarat <tomp@topanet.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+/**
+ * Test class for Addressbook_Convert_Contact_VCard_EMClient
+ */
+class Addressbook_Convert_Contact_VCard_EMClientTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @var array test objects
+     */
+    protected $objects = array();
+    
+    /**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Addressbook WebDAV EMClient Contact Tests');
+        PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up the fixture.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+    }
+
+    /**
+     * Tears down the fixture
+     * This method is called after a test is executed.
+     *
+     * @access protected
+     */
+    protected function tearDown()
+    {
+    }
+    
+    /**
+     * test converting vcard from sogo connector to Addressbook_Model_Contact 
+     * 
+     * @return Addressbook_Model_Contact
+     */
+    public function testConvertToTine20Model()
+    {
+        $vcardStream = fopen(dirname(__FILE__) . '/../../../Import/files/emclient_addressbook.vcf', 'r');
+        
+        $converter = Addressbook_Convert_Contact_VCard_Factory::factory(Addressbook_Convert_Contact_VCard_Factory::CLIENT_EMCLIENT);
+        
+        $contact = $converter->toTine20Model($vcardStream);
+        
+        $this->assertEquals('COUNTRY BUSINESS',        $contact->adr_one_countryname);
+        $this->assertEquals('City Business',           $contact->adr_one_locality);
+        $this->assertEquals('12345',                   $contact->adr_one_postalcode);
+        $this->assertEquals(null,                      $contact->adr_one_region);
+        $this->assertEquals('Address Business',        $contact->adr_one_street);
+        $this->assertEquals(null,                      $contact->adr_one_street2);
+        $this->assertEquals('COUNTRY PRIVAT',          $contact->adr_two_countryname);
+        $this->assertEquals('City Privat',             $contact->adr_two_locality);
+        $this->assertEquals('98765',                   $contact->adr_two_postalcode);
+        $this->assertEquals(null,                      $contact->adr_two_region);
+        $this->assertEquals('Address Privat',          $contact->adr_two_street);
+        $this->assertEquals(null,                      $contact->adr_two_street2);
+        $this->assertEquals('business@email.de',       $contact->email);
+        $this->assertEquals('privat@email.de',         $contact->email_home);
+        $this->assertEquals('Nach',                    $contact->n_family);
+        $this->assertEquals('Nach, Vor',               $contact->n_fileas);
+        $this->assertEquals('Vor',                     $contact->n_given);
+        $this->assertEquals(null,                      $contact->n_middle);
+        $this->assertEquals('Prefix',                  $contact->n_prefix);
+        $this->assertEquals(null,                      $contact->n_suffix);
+        $this->assertEquals("Notes\nwith\nbreaks",     $contact->note);
+        $this->assertEquals('Firma',                   $contact->org_name);
+        $this->assertEquals('Abteilung',               $contact->org_unit);
+        $this->assertEquals('+49 MOBIL',               $contact->tel_cell);
+        $this->assertEquals('+49 MOBIL2',              $contact->tel_cell_private);
+        $this->assertEquals('+49 FAX',                 $contact->tel_fax);
+        $this->assertEquals('+49 PRIVATFAX',           $contact->tel_fax_home);
+        $this->assertEquals('+49 PRIVAT',              $contact->tel_home);
+        $this->assertEquals(null,                      $contact->tel_pager);
+        $this->assertEquals('+49 BUSINESS',            $contact->tel_work);
+        $this->assertEquals('Position',                $contact->title);
+        $this->assertEquals('www.business.de',         $contact->url);
+        $this->assertEquals(null,                      $contact->url_home);
+                
+        return $contact;
+    }
+
+    public function testConvertToVCard()
+    {
+        $contact = $this->testConvertToTine20Model();
+        
+        $converter = Addressbook_Convert_Contact_VCard_Factory::factory(Addressbook_Convert_Contact_VCard_Factory::CLIENT_EMCLIENT);
+        
+        $vcard = $converter->fromTine20Model($contact)->serialize();
+        
+        // required fields
+        $this->assertContains('VERSION:3.0', $vcard, $vcard);
+        $this->assertContains('PRODID:-//tine20.org//Tine 2.0//EN', $vcard, $vcard);
+        
+        // @todo can not test for folded lines
+        $this->assertContains('ADR;TYPE=HOME:;;Address Privat;City Privat;;98765;COUNTRY PRIVAT', $vcard, $vcard);
+        $this->assertContains('ADR;TYPE=WORK:;;Address Business;City Business;;12345;COUNTRY BUSINESS', $vcard, $vcard);
+        $this->assertContains('EMAIL:privat@email.de', $vcard, $vcard);
+        $this->assertContains('EMAIL;TYPE=PREF:business@email.de', $vcard, $vcard);
+        $this->assertContains('N:Nach;Vor;;Prefix', $vcard, $vcard);
+        $this->assertContains('NOTE:Notes\nwith\nbreaks', $vcard, $vcard);
+        $this->assertContains('ORG:Firma;Abteilung', $vcard, $vcard);
+        $this->assertContains('TEL;TYPE=CELL:+49 MOBIL', $vcard, $vcard);
+        $this->assertContains('TEL;TYPE=HOME;TYPE=FAX:+49 PRIVATFAX', $vcard, $vcard);
+        $this->assertContains('TEL;TYPE=WORK;TYPE=FAX:+49 FAX', $vcard, $vcard);
+        $this->assertContains('TEL;TYPE=HOME;TYPE=VOICE:+49 PRIVAT', $vcard, $vcard);
+        $this->assertContains('TEL;TYPE=OTHER:+49 MOBIL2', $vcard, $vcard);
+        $this->assertContains('TEL;TYPE=WORK;TYPE=VOICE:+49 BUSINESS', $vcard, $vcard);
+        
+    }
+}
diff --git a/tests/tine20/Addressbook/Import/files/emclient_addressbook.vcf b/tests/tine20/Addressbook/Import/files/emclient_addressbook.vcf
new file mode 100644 (file)
index 0000000..0ad5c02
--- /dev/null
@@ -0,0 +1,23 @@
+BEGIN:VCARD\r
+VERSION:3.0\r
+UID:b41b1529-bfd2-437f-b441-19dc97b44d11\r
+N:Nach;Vor;;Prefix\r
+SORT-STRING:Nach\, Vor\r
+FN:Prefix Vor Nach Suffix\r
+TITLE:Position\r
+EMAIL:privat@email.de\r
+EMAIL;TYPE=PREF:business@email.de\r
+TEL;TYPE=HOME,VOICE:+49 PRIVAT\r
+TEL;TYPE=CELL:+49 MOBIL\r
+TEL;TYPE="OTHER":+49 MOBIL2\r
+TEL;TYPE=WORK,FAX:+49 FAX\r
+TEL;TYPE=HOME,FAX:+49 PRIVATFAX\r
+TEL;TYPE=WORK,VOICE,PREF:+49 BUSINESS\r
+ADR;TYPE=HOME:;;Address Privat;City Privat;;98765;COUNTRY PRIVAT\r
+ADR;TYPE=WORK,PREF:;;Address Business;City Business;;12345;COUNTRY BUSINESS\r
+BDAY;VALUE=DATE:1950-01-26\r
+ORG:Firma;Abteilung\r
+NOTE:Notes\nwith\nbreaks\r
+URL:www.business.de\r
+PRODID:-//eM Client/5.0.17944.0\r
+END:VCARD\r
index 2d1c796..5a08d6c 100644 (file)
@@ -222,6 +222,8 @@ class Calendar_JsonTests extends Calendar_TestCase
         
         $filter = $this->_getEventFilterArray();
         $searchResultData = $this->_uit->searchEvents($filter, array());
+        
+        $this->assertTrue(! empty($searchResultData['results']));
         $resultEventData = $searchResultData['results'][0];
         
         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
@@ -300,6 +302,7 @@ class Calendar_JsonTests extends Calendar_TestCase
         $filter[] = array('field' => 'organizer', 'operator' => 'equals', 'value' => Addressbook_Model_Contact::CURRENTCONTACT);
         
         $searchResultData = $this->_uit->searchEvents($filter, array());
+        $this->assertTrue(! empty($searchResultData['results']));
         $resultEventData = $searchResultData['results'][0];
         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to search event');
         
@@ -311,7 +314,6 @@ class Calendar_JsonTests extends Calendar_TestCase
     
     /**
      * search event with alarm
-     *
      */
     public function testSearchEventsWithAlarm()
     {
@@ -319,6 +321,7 @@ class Calendar_JsonTests extends Calendar_TestCase
         $persistentEventData = $this->_uit->saveEvent($eventData);
         
         $searchResultData = $this->_uit->searchEvents($this->_getEventFilterArray(), array());
+        $this->assertTrue(! empty($searchResultData['results']));
         $resultEventData = $searchResultData['results'][0];
         
         $this->_assertJsonEvent($persistentEventData, $resultEventData, 'failed to search event with alarm');
@@ -640,6 +643,7 @@ class Calendar_JsonTests extends Calendar_TestCase
         ));
         
         $searchResultData = $this->_uit->searchEvents($filter, array());
+        $this->assertTrue(! empty($searchResultData['results']));
         $resultEventData = $searchResultData['results'][0];
         
         $this->_assertJsonEvent($eventData, $resultEventData, 'failed to filter for me as attender');
index 959607f..0cc8bbe 100644 (file)
@@ -284,7 +284,7 @@ class Courses_JsonTest extends PHPUnit_Framework_TestCase
         $pref = $coursesPrefs['results'][0];
         
         $this->assertEquals(Tinebase_Preference_Abstract::DEFAULTPERSISTENTFILTER, $pref['name']);
-        $this->assertEquals(2, count($pref['options']));
+        $this->assertGreaterThanOrEqual(2, count($pref['options']));
     }
 
     /**
index b4e7883..c59a920 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     HumanResources
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Alexander Stintzing <a.stintzing@metaways.de>
  */
 
@@ -25,6 +25,7 @@ class HumanResources_AllTests
         $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 HumanResources All Tests');
         $suite->addTestSuite('HumanResources_JsonTests');
         $suite->addTestSuite('HumanResources_CliTests');
+        $suite->addTestSuite('HumanResources_ControllerTests');
         return $suite;
     }
 }
index 8ccee61..3c3b40a 100644 (file)
@@ -4,7 +4,7 @@
  *
  * @package     HumanResources
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
@@ -47,7 +47,6 @@ class HumanResources_CliTests extends HumanResources_TestCase
     protected function tearDown()
     {
         HumanResources_Controller_Employee::getInstance()->delete($this->_idsToDelete);
-        //HumanResources_Controller_Employee::getInstance()->delete(HumanResources_Controller_Employee::getInstance()->getAll()->getArrayOfIds());
     }
         
     /**
@@ -55,9 +54,9 @@ class HumanResources_CliTests extends HumanResources_TestCase
      */
     public function testImportEmployee()
     {
-        $cc = $this->_getCostCenter(7);
+        $cc = $this->_getSalesCostCenter(7);
         
-        $this->_doImport();
+        $this->_doImport(true);
         
         $susan = $this->_getSusan();
         
@@ -68,7 +67,7 @@ class HumanResources_CliTests extends HumanResources_TestCase
         $this->assertEquals('1973-12-11 23:00:00', $susan->bday->__toString(), print_r($susan->toArray(), TRUE));
         
         $susan->contracts = HumanResources_Controller_Contract::getInstance()->getContractsByEmployeeId($susan->getId());
-        $this->assertEquals(1, count($susan->contracts), 'no contracts found');
+        $this->assertEquals(1, $susan->contracts->count(), 'no contracts found');
         
         return $susan;
     }
@@ -95,7 +94,6 @@ class HumanResources_CliTests extends HumanResources_TestCase
         ob_start();
         $result = $this->_cli->importEmployee($opts);
         $out = ob_get_clean();
-        
         $this->assertEquals(0, $result, 'import failed: ' . $out);
         
         if ($checkOutput) {
@@ -110,11 +108,7 @@ class HumanResources_CliTests extends HumanResources_TestCase
      */
     protected function _getSusan()
     {
-        $employees = HumanResources_Controller_Employee::getInstance()->search(new HumanResources_Model_EmployeeFilter(array(array(
-            'field'     => 'creation_time',
-            'operator'  => 'after',
-            'value'     => Tinebase_DateTime::now()->subMinute(10)
-        ))));
+        $employees = HumanResources_Controller_Employee::getInstance()->search(new HumanResources_Model_EmployeeFilter(array()));
         $this->_idsToDelete = $employees->getArrayOfIds();
         
         $this->assertEquals(2, count($employees), 'should import 2 employees: ' . print_r($employees->toArray(), TRUE));
@@ -145,7 +139,6 @@ class HumanResources_CliTests extends HumanResources_TestCase
         $susan = $this->_getSusan();
         $susan->bank_name = 'xyz';
         $susan->contracts = HumanResources_Controller_Contract::getInstance()->getContractsByEmployeeId($susan->getId());
-        $susan->contracts = $susan->contracts->toArray();
         HumanResources_Controller_Employee::getInstance()->update($susan);
         
         sleep(1);
@@ -154,8 +147,8 @@ class HumanResources_CliTests extends HumanResources_TestCase
         $this->assertEquals('Hypo Real Estate', $susan->bank_name, print_r($susan->toArray(), TRUE));
         
         // cost center check
-        $cc = $this->_getCostCenter(7);
+        $cc = $this->_getSalesCostCenter(7);
         $susan->contracts = HumanResources_Controller_Contract::getInstance()->getContractsByEmployeeId($susan->getId());
-        $this->assertEquals(1, count($susan->contracts), 'no contracts found');
+        $this->assertEquals(1, $susan->contracts->count(), 'no contracts found');
     }
 }
diff --git a/tests/tine20/HumanResources/ControllerTests.php b/tests/tine20/HumanResources/ControllerTests.php
new file mode 100644 (file)
index 0000000..5df9c56
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ *
+ * @package     HumanResources
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Alexander Stintzing <a.stintzing@metaways.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+/**
+ * Test class for HumanResources Controller(s)
+ */
+class HumanResources_ControllerTests extends HumanResources_TestCase
+{
+    /**
+     * tests for the contract controller
+     */
+    public function testUpdateContract()
+    {
+        $contractController = HumanResources_Controller_Contract::getInstance();
+        $employeeController = HumanResources_Controller_Employee::getInstance();
+        $contractBackend = new HumanResources_Backend_Contract();
+
+        $employee = $employeeController->create($this->_getEmployee('unittest'));
+
+        $now = new Tinebase_DateTime();
+
+        $inAMonth = clone $now;
+        $inAMonth->addMonth(1);
+
+        $threeHrAgo = clone $now;
+        $threeHrAgo->subHour(3);
+
+        $startDate1 = clone $now;
+        $startDate1->subMonth(2);
+
+        $startDate2 = clone $now;
+        $startDate2->subMonth(1);
+
+        // contract1 in the past, but created a second ago
+        $contract1 = $this->_getContract();
+        $contract1->employee_id = $employee->getId();
+        $contract1->start_date = $startDate1;
+        $contract1->creation_time = $now;
+        $contract1 = $contractBackend->create($contract1);
+
+        // contract2 created more than 2 hrs ago, start date is in the past. update must fail
+        $contract2 = $this->_getContract();
+        $contract2->employee_id = $employee->getId();
+        $contract2->creation_time = $threeHrAgo;
+        $contract2->start_date    = $startDate2;
+        $contract2 = $contractBackend->create($contract2);
+        
+        // contract3 created more than 3 hrs ago, but start date is in the future
+        $contract3 = $this->_getContract();
+        $contract3->employee_id = $employee->getId();
+        $contract3->creation_time = $threeHrAgo;
+        $contract3->start_date    = $inAMonth;
+        $contract3 =  $contractBackend->create($contract3);
+
+        // change calendar an update
+        $newCalendar = $this->_getFeastCalendar(true);
+
+        // no error should occur, the creation time is not older than 2 hours
+        $contract1->feast_calendar_id = $newCalendar->getId();
+        $contract1 = $contractController->update($contract1);
+
+        // no error should occur, the start_date is in the future
+        $contract3->feast_calendar_id = $newCalendar->getId();
+        $contract3 = $contractController->update($contract3);
+
+        // LAST ASSERTION, do not add assertions after a expected Exception, they won't be executed
+
+        $this->setExpectedException('HumanResources_Exception_ContractTooOld');
+
+        $contract2->feast_calendar_id = $newCalendar->getId();
+        $contract2 = $contractController->update($contract2);
+    
+        // no more assertions here!
+    }
+    
+    /**
+     * some contract tests (more in jsontests)
+     */
+    public function testContract()
+    {
+        $contractController = HumanResources_Controller_Contract::getInstance();
+        $contract = $this->_getContract();
+        $contract->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
+        
+        // create feast days
+        $feastDays2013 = array(
+            '2013-01-01', '2013-03-29', '2013-04-01', '2013-05-01', '2013-05-09',
+            '2013-05-20', '2013-10-03', '2013-12-25', '2013-12-26', '2013-12-31'
+        );
+        
+        $feastCalendar = $this->_getFeastCalendar();
+        $contract->feast_calendar_id = $feastCalendar->getId();
+        
+        foreach($feastDays2013 as $day) {
+            $date = new Tinebase_DateTime($day . ' 12:00:00');
+            $this->_createFeastDay($date);
+        }
+        
+        // test "calculateVacationDays"
+        
+        $start  = new Tinebase_DateTime('2013-01-01');
+        $stop   = new Tinebase_DateTime('2013-12-31');
+        
+        $contract->start_date = $start;
+        $contract->end_date   = $stop;
+        
+        $this->assertEquals(30, $contractController->calculateVacationDays($contract, $start, $stop));
+
+        $newStartDate = new Tinebase_DateTime('2013-07-01');
+        $contract->start_date = $newStartDate;
+        
+        $this->assertEquals(15, $contractController->calculateVacationDays($contract, $start, $stop));
+        
+        // test "getDatesToWorkOn"
+        $contract->start_date = $start;
+        
+        // 2013 has 365 days, 52 Saturdays and 52 Sundays, all of the 10 feast days are at working days (a good year for an employee!)
+        // so we expect 365-52-52-10 = 251 days
+        $workingDates = $contractController->getDatesToWorkOn($contract, $start, $stop);
+        $this->assertEquals(251, count($workingDates['results']));
+        
+        // test "getFeastDays"
+        $feastDays = $contractController->getFeastDays($contract, $start, $stop);
+        
+        // we expect 10 here
+        $this->assertEquals(10, count($feastDays));
+    }
+    
+    /**
+     * tests if a special property exists in the record set
+     */
+    public function testRecordSet()
+    {
+        $recordSet = new Tinebase_Record_RecordSet('HumanResources_Model_Employee');
+        $employee = $this->_getEmployee();
+        $recordSet->addRecord($employee);
+        $this->assertEquals(1, count($recordSet->supervisor_id));
+    }
+    
+    /**
+     * tests if the filter for the employee model gets created properly
+     */
+    public function testFilters()
+    {
+        
+        // prepare dates
+        $today = new Tinebase_DateTime();
+        $oneMonthAgo = clone $today;
+        $oneMonthAgo->subMonth(1);
+        $oneMonthAhead = clone $today;
+        $oneMonthAhead->addMonth(1);
+        $twoMonthsAgo = clone $oneMonthAgo;
+        $twoMonthsAgo->subMonth(1);
+        
+        $employeeController = HumanResources_Controller_Employee::getInstance();
+        
+        $employee1 = $this->_getEmployee('pwulf');
+        $employee1->employment_begin = $oneMonthAgo;
+        $employee1->employment_end = $oneMonthAhead;
+        $employee1 = $employeeController->create($employee1);
+        
+        $employee2 = $this->_getEmployee('rwright');
+        $employee2->employment_begin = $oneMonthAgo;
+        $employee2 = $employeeController->create($employee2);
+        
+        $filter = new HumanResources_Model_EmployeeFilter(array(
+            array('field' => 'n_given', 'operator' => 'equals', 'value' => 'Paul')
+        ));
+        $result = $employeeController->search($filter);
+        
+        $this->assertEquals(1, $result->count());
+        $this->assertEquals('Paul', $result->getFirstRecord()->n_given);
+        
+        // test employed filter
+        
+        // employee3 is not yet employed
+        $employee3 = $this->_getEmployee('jmcblack');
+        $employee3->employment_begin = $oneMonthAhead;
+        $employee3 = $employeeController->create($employee3);
+        
+        // employee4 has been employed
+        $employee4 = $this->_getEmployee('jsmith');
+        $employee4->employment_begin = $twoMonthsAgo;
+        $employee4->employment_end = $oneMonthAgo;
+        $employee4 = $employeeController->create($employee4);
+        
+        $filter = new HumanResources_Model_EmployeeFilter(array(
+            array('field' => 'is_employed', 'operator' => 'equals', 'value' => TRUE)
+        ));
+        $result = $employeeController->search($filter);
+        $msg = 'rwright and pwulf should have been found';
+        $this->assertEquals(2, $result->count(), $msg);
+        
+        $names = $result->n_fn;
+        
+        // just rwright and pwulf should have been found
+        $this->assertContains('Roberta Wright', $names, $msg);
+        $this->assertContains('Paul Wulf', $names, $msg);
+        
+        $filter = new HumanResources_Model_EmployeeFilter(array(
+            array('field' => 'is_employed', 'operator' => 'equals', 'value' => FALSE)
+        ));
+        $result = $employeeController->search($filter);
+        
+        $msg = 'jsmith and jmcblack should have been found';
+        $this->assertEquals(2, $result->count(), $msg);
+        
+        $names = $result->n_fn;
+        
+        // just jsmith and jmcblack should have been found
+        $this->assertContains('John Smith', $names, $msg);
+        $this->assertContains('James McBlack', $names, $msg);
+    }
+}
index 9e7a2bb..8e8d697 100644 (file)
@@ -4,7 +4,7 @@
  *
  * @package     HumanResources
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Alexander Stintzing <a.stintzing@metaways.de>
  */
 
@@ -29,6 +29,7 @@ class HumanResources_JsonTests extends HumanResources_TestCase
         parent::setUp();
         $this->_json = new HumanResources_Frontend_Json();
     }
+    
     /**
      * Creates an employee with contracts and contact, account etc.
      * tests auto end_date of old contract
@@ -36,22 +37,54 @@ class HumanResources_JsonTests extends HumanResources_TestCase
     public function testEmployee()
     {
         $e = $this->_getEmployee();
+        
+        $date = new Tinebase_DateTime();
+        $date->subMonth(5);
+        
+        $firstDate = substr($date->toString(), 0, 10);
+        
+        $costCenter1 = $this->_getCostCenter($date);
+        
         $e->contracts = array($this->_getContract()->toArray());
+        $e->costcenters = array($costCenter1->toArray());
+        
         $savedEmployee = $this->_json->saveEmployee($e->toArray());
 
+        $this->assertArrayHasKey('account_id', $savedEmployee);
+        $this->assertTrue(is_array($savedEmployee['account_id']));
+        
+        $this->assertArrayHasKey('contracts', $savedEmployee);
+        $this->assertArrayHasKey('costcenters', $savedEmployee);
+        
         $this->assertEquals($e->n_fn, $savedEmployee['n_fn']);
+        
         $this->assertEquals(1, count($savedEmployee['contracts']));
+        $this->assertEquals(1, count($savedEmployee['costcenters']));
 
+        // check if accounts has been created properly on aftercreate
+        $filter = new HumanResources_Model_AccountFilter(array());
+        $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'employee_id', 'operator' => 'equals', 'value' => $savedEmployee['id'])));
+        $result = HumanResources_Controller_Account::getInstance()->search($filter);
+        $this->assertEquals(2, $result->count());
+       
+        
+        $date->addMonth(2);
+        $costCenter2 = $this->_getCostCenter($date);
+        
         $newContract = $this->_getContract();
         $newContract->start_date->addMonth(5);
-        $savedEmployee['contracts'][] = $newContract->toArray();
+        
+        $savedEmployee['contracts'][]   = $newContract->toArray();
+        $savedEmployee['costcenters'][] = $costCenter2->toArray();
+        
         $savedEmployee = $this->_json->saveEmployee($savedEmployee);
-        $this->assertEquals(2, count($savedEmployee['contracts']));
-
+        
+        $this->assertEquals(2, count($savedEmployee['contracts']),   'There should be 2 Contracts');
+        $this->assertEquals(2, count($savedEmployee['costcenters']), 'There should be 2 CostCenters');
+        
         $this->assertEquals(null, $savedEmployee['contracts'][1]['end_date'], 'The end_date should have a null value.');
-        $this->assertEquals('2012-12-12', substr($savedEmployee['costcenters'][0]['start_date'], 0, 10));
         
-        $this->assertEquals($savedEmployee['contracts'][0]['workingtime_json'], $savedEmployee['contracts'][0]['workingtime_id']['json'], 'The json definition of the contract should be the same as the corresponding wt json');
+        $this->assertEquals($firstDate, substr($savedEmployee['costcenters'][0]['start_date'], 0, 10), 'The start_date of the first costcenter should begin with the first date of the employee!');
         
         $date1 = new Tinebase_DateTime($savedEmployee['contracts'][0]['end_date']);
         $date2 = new Tinebase_DateTime($savedEmployee['contracts'][1]['start_date']);
@@ -59,23 +92,8 @@ class HumanResources_JsonTests extends HumanResources_TestCase
         $this->assertEquals($date1->addDay(1)->toString(), $date2->toString());
 
         $freeTimes = $this->_json->getFeastAndFreeDays($savedEmployee['id']);
-
-        $this->assertEquals($savedEmployee['contracts'][0]['id'], $freeTimes['contract']['id']);
-    }
-    
-    /**
-     * test working time
-     */
-    public function testWorkingTimeTemplate()
-    {
-        $recordData = array('title' => 'lazy worker', 'type' => 'static', 'json' => '{"days":[1,1,1,1,1,0,0]}', 'working_hours' => 5);
-        $savedWT = $this->_json->saveWorkingTime($recordData);
-        
-        $this->assertEquals($savedWT['title'], 'lazy worker');
         
-        // test duplicate exception
-        $this->setExpectedException('Tinebase_Exception_Duplicate');
-        $this->_json->saveWorkingTime($recordData);
+        $this->assertEquals($savedEmployee['id'], $freeTimes['results']['contracts'][0]['employee_id']);
     }
 
     /**
@@ -111,14 +129,245 @@ class HumanResources_JsonTests extends HumanResources_TestCase
         $e->contracts = array($this->_getContract()->toArray());
         $savedEmployee = $this->_json->saveEmployee($e->toArray());
 
-        $r = $this->_json->searchContracts(
-            array(array('field' => 'employee_id', 'operator' => 'equals', 'value' => $savedEmployee['id'])), 
-            array()
+        
+        $r = $this->_json->searchEmployees(
+            array(array('field' => 'id', 'operator' => 'equals', 'value' => $savedEmployee['id']))
+        , NULL);
+
+        $this->assertEquals($r['results'][0]['contracts'][0]['employee_id'], $savedEmployee['id']);
+        
+        $r = $this->_json->getEmployee($savedEmployee['id']);
+        
+        $this->assertTrue(is_array($r['contracts'][0]['feast_calendar_id']));
+    }
+    
+    /**
+     * test employee creation/update with contracts
+     */
+    public function testContract()
+    {
+        $sdate = new Tinebase_DateTime();
+        $sdate->subMonth(4);
+        $edate = new Tinebase_DateTime();
+        $edate->subMonth(3)->subDay(1);
+        
+        $now = new Tinebase_DateTime();
+        $now->subHour(3);
+        
+        $nextMonth = clone $now;
+        $nextMonth->addMonth(1); 
+        
+        $fcId = $this->_getFeastCalendar();
+        
+        $contracts = array(array(
+            'start_date' => clone $sdate,
+            'end_date'   => clone $edate,
+            'vacation_days' => 23,
+            'feast_calendar_id' => $fcId,
+            'creation_time' => $now
+        ));
+        
+        $sdate->addMonth(1);
+        $edate->addMonth(1);
+        
+        $contracts[] = array(
+            'start_date' => clone $sdate,
+            'end_date'   => clone $edate,
+            'vacation_days' => 27,
+            'feast_calendar_id' => $fcId,
+            'creation_time' => $now
         );
-        $this->assertEquals($r['results'][0]['employee_id']['id'], $savedEmployee['id']);
+        
+        $es = $this->_json->searchEmployees(array(), array());
+        $eIds = array();
+        foreach ($es['results'] as $e) {
+            $eIds = $e['id'];
+        }
+        $this->_json->deleteEmployees($eIds);
+        
+        $employee = $this->_getEmployee('unittest')->toArray();
+        $employee['contracts'] = $contracts;
+        
+        $employee = $this->_json->saveEmployee($employee);
+        $this->assertEquals(2, count($employee['contracts']));
+        
+        $es = $this->_json->searchEmployees(array(), array());
+        $eIds = array();
+        foreach ($es['results'] as $e) {
+            $eIds = $e['id'];
+        }
+        $this->_json->deleteEmployees($eIds);
+        
+        // remove ids
+        unset($employee['contracts'][0]['id']);
+        unset($employee['contracts'][0]['employee_id']);
+        unset($employee['contracts'][1]['id']);
+        unset($employee['contracts'][1]['employee_id']);
+        unset($employee['id']);
+        
+        // test overlapping
+        
+        // create overlapping contract
+        $sdate1 = clone $sdate;
+        $edate1 = clone $edate;
+        $sdate1->addDay(3);
+        $edate1->addMonth(1);
+        
+        $employee['contracts'][] = array(
+            'start_date' => $sdate1,
+            'end_date' => $nextMonth,
+            'vacation_days' => 22,
+            'feast_calendar_id' => $fcId,
+            'creation_time' => $now->toString()
+        );
+        
+        // doing this manually, this won't be the last assertion, and more assertions are needed
+        // $this->setExpectedException('Tinebase_Exception_Data');
+        
+        $exception = new Exception('no exception has been thrown');
+        
+        try {
+            $this->_json->saveEmployee($employee);
+        } catch (Tinebase_Exception_Data $exception) {
+            // thrown in HR_Controller_Employee
+        }
+        
+        $this->assertEquals('The contracts must not overlap!', $exception->getMessage());
+        
+        // test startdate after end_date
+        
+        $employee['contracts'][2] = array(
+            'start_date' => $edate1->toString(),
+            'end_date' => $sdate1->toString(),
+            'vacation_days' => 22,
+            'feast_calendar_id' => $fcId,
+            'creation_time' => $now->toString()
+        );
+
+        try {
+            $this->_json->saveEmployee($employee);
+        } catch (Tinebase_Exception_Record_Validation $exception) {
+            // thrown in HR_Controller_Contract
+        }
+        
+        $this->assertEquals('The start date of the contract must be before the end date!', $exception->getMessage());
+    }
+    
+    /**
+     * test working time
+     */
+    public function testWorkingTimeTemplate()
+    {
+         $recordData = array('title' => 'lazy worker', 'type' => 'static', 'json' => '{"days":[1,1,1,1,1,0,0]}', 'working_hours' => 5);
+         $savedWT = $this->_json->saveWorkingTime($recordData);
 
-        $this->assertTrue(is_array($r['results'][0]['workingtime_id']));
-        $this->assertTrue(is_array($r['results'][0]['feast_calendar_id']));
+         $this->assertEquals($savedWT['title'], 'lazy worker');
 
+         // test duplicate exception
+         $this->setExpectedException('Tinebase_Exception_Duplicate');
+         $this->_json->saveWorkingTime($recordData);
+    }
+    
+    /**
+     * tests account summary
+     */
+    public function testAccount()
+    {
+        $employmentBegin  = new Tinebase_DateTime('2012-12-15');
+        $employmentChange = new Tinebase_DateTime('2014-01-01');
+        $employmentEnd    = new Tinebase_DateTime('2014-06-30');
+    
+        $contractController = HumanResources_Controller_Contract::getInstance();
+        $employeeController = HumanResources_Controller_Employee::getInstance();
+        $contractBackend = new HumanResources_Backend_Contract();
+    
+        $employee = $this->_getEmployee('unittest');
+        $employee->employment_begin = $employmentBegin;
+        $employee->employment_end = $employmentEnd;
+        
+        $contract1 = $this->_getContract();
+        $contract1->start_date = $employmentBegin;
+        $contract1->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
+        $contract1->vacation_days = 25;
+        
+        $contract2 = $this->_getContract();
+        $contract2->start_date = $employmentChange;
+        $contract2->end_date = $employmentEnd;
+        $contract2->workingtime_json = '{"days": [4,4,4,4,4,4,4]}';
+    
+        $rs = new Tinebase_Record_RecordSet('HumanResources_Model_Contract');
+        $rs->addRecord($contract1);
+        $rs->addRecord($contract2);
+        
+        $employee->contracts = $rs;
+    
+        $employee = $employeeController->create($employee);
+    
+        $json = new HumanResources_Frontend_Json();
+        $accountController = HumanResources_Controller_Account::getInstance();
+        $accountsFilter = array(array('field' => "employee_id", 'operator' => "AND", 'value' => array(
+            array('field' => ':id', 'operator' => 'equals', 'value' => $employee->getId())
+        )));
+
+        // should not be created, exist already
+        $accountController->createMissingAccounts(2013, $employee);
+        $accountController->createMissingAccounts(2014, $employee);
+        
+        // create feast days
+        $feastDays2013 = array(
+            '2013-01-01', '2013-03-29', '2013-04-01', '2013-05-01', '2013-05-09',
+            '2013-05-20', '2013-10-03', '2013-12-25', '2013-12-26', '2013-12-31'
+        );
+        
+        foreach ($feastDays2013 as $day) {
+            $date = new Tinebase_DateTime($day . ' 12:00:00');
+            $this->_createFeastDay($date);
+        }
+        
+        $result = $json->searchAccounts($accountsFilter, array('sort' => 'year', 'dir' => 'DESC'));
+        $this->assertEquals('3', $result['totalcount'], 'Three accounts should have been found!');
+        
+        $accountId2013 = $result['results'][1]['id'];
+        $account2013 = $json->getAccount($accountId2013);
+        
+        $this->assertEquals(25, $account2013['possible_vacation_days']);
+        $this->assertEquals(226, $account2013['working_days']);
+        
+        // add 5 extra free days to the account
+        $eft1 = new HumanResources_Model_ExtraFreeTime(array('days' => 2, 'account_id' => $accountId2013));
+        $eft2 = new HumanResources_Model_ExtraFreeTime(array('days' => 3, 'account_id' => $accountId2013));
+        
+        $eftController = HumanResources_Controller_ExtraFreeTime::getInstance();
+        $eftController->create($eft1);
+        $eftController->create($eft2);
+        
+        $account2013 = $json->getAccount($accountId2013);
+        $this->assertEquals(30, $account2013['possible_vacation_days']);
+        $this->assertEquals(221, $account2013['working_days']);
+    }
+    
+    /**
+     * tests datetime conversion of dependent records
+     */
+    public function testDateTimeConversion()
+    {
+        $employmentBegin  = new Tinebase_DateTime('2012-12-15');
+        $employmentEnd    = new Tinebase_DateTime('2014-06-30');
+        $employmentBegin->setTime(12,0,0);
+        
+        $employee = $this->_getEmployee('unittest');
+        $employee->employment_begin = $employmentBegin;
+        $employee->employment_end = $employmentEnd;
+        
+        $contract = $this->_getContract();
+        $contract->start_date = $employmentBegin;
+        $contract->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
+        $contract->vacation_days = 25;
+        
+        $employee->contracts = array($contract->toArray());
+        $json = new HumanResources_Frontend_Json();
+        $savedEmployee = $json->saveEmployee($json->saveEmployee($employee->toArray()));
+        $this->assertStringEndsWith('12:00:00', $savedEmployee['employment_begin']);
+        $this->assertStringEndsWith('12:00:00', $savedEmployee['contracts'][0]['start_date']);
     }
 }
index 0ce6d5a..325bfb1 100644 (file)
@@ -4,7 +4,7 @@
  *
  * @package     HumanResources
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Alexander Stintzing <a.stintzing@metaways.de>
  */
 
@@ -29,6 +29,8 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
      */
     protected $_json = array();
 
+    protected $_lastEmployeeNumber = 0;
+    
     /**
      * test department
      *
@@ -79,9 +81,10 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
      */
     protected function _getAccount($loginName = NULL)
     {
-        if($loginName) {
+        if ($loginName) {
             return Tinebase_User::getInstance()->getFullUserByLoginName($loginName);
         }
+        
         return Tinebase_Core::getUser();
     }
 
@@ -98,11 +101,24 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
 
     /**
      * returns the default feast calendar
+     * 
+     * @param boolean $anotherone if another than the default one should be returned
      * @return Tinebase_Model_Container
      */
-    protected function _getFeastCalendar()
+    protected function _getFeastCalendar($anotherone = false)
     {
-        if(!$this->_feast_calendar) {
+        if ($anotherone) {
+            return Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
+                'name'           => Tinebase_Record_Abstract::generateUID(),
+                'type'           => Tinebase_Model_Container::TYPE_SHARED,
+                'owner_id'       => Tinebase_Core::getUser(),
+                'backend'        => 'SQL',
+                'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
+                'color'          => '#00FF00'
+            ), true));
+        }
+        
+        if(! $this->_feast_calendar) {
             $this->_feast_calendar = Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
                 'name'           => 'Feast Calendar',
                 'type'           => Tinebase_Model_Container::TYPE_SHARED,
@@ -120,18 +136,24 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
      * returns the contact of the current user
      * @return Addressbook_Model_Contact
      */
-    protected function _getContact()
+    protected function _getContact($loginName = NULL)
     {
-        return Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
+        if ($loginName) {
+            $user = Tinebase_User::getInstance()->getFullUserByLoginName($loginName);
+        } else {
+            $user = Tinebase_Core::getUser();
+        }
+        
+        return Addressbook_Controller_Contact::getInstance()->getContactByUserId($user->getId());
     }
 
     /**
-     * get cost center
+     * get sales cost center
      * 
      * @param string
      * @return Sales_Model_CostCenter
      */
-    protected function _getCostCenter($number = NULL)
+    protected function _getSalesCostCenter($number = NULL)
     {
         if ($number !== NULL) {
             $c = Sales_Controller_CostCenter::getInstance()->search(new Sales_Model_CostCenterFilter(array(array(
@@ -139,6 +161,7 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
                 'operator' => 'equals',
                 'value'    => $number,
             ))))->getFirstRecord();
+            
             if ($c) {
                 return $c;
             }
@@ -149,13 +172,28 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
         ));
         
         $c = Sales_Controller_CostCenter::getInstance()->create($c);
-        $startDate = new Tinebase_DateTime('2012-12-12');
-        $hrc = array('cost_center_id' => $c->getId(), 'start_date' => $startDate);
         
-        return $hrc;
+        return $c;
     }
 
     /**
+     * get hr cost center 
+     * @param Tinebase_DateTime $startDate
+     */
+    protected function _getCostCenter($startDate = NULL)
+    {
+        if (! $startDate) {
+            $startDate = new Tinebase_DateTime();
+        }
+        
+        $scs = $this->_getSalesCostCenter();
+        return new HumanResources_Model_CostCenter(array(
+            'start_date' => $startDate->toString(), 
+            'cost_center_id' => $scs->getId()
+        ));
+    }
+    
+    /**
      * returns a new contract
      * return HumanResources_Model_Contract
      */
@@ -170,7 +208,6 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
             'employee_id' => null,
             'vacation_days' => 30,
             'feast_calendar_id' => $this->_getFeastCalendar(),
-            'workingtime_id' => $this->_getWorkingTime()
         ), true);
 
         return $c;
@@ -183,17 +220,56 @@ class HumanResources_TestCase extends PHPUnit_Framework_TestCase
     protected function _getEmployee($loginName = NULL) 
     {
         $a = $this->_getAccount($loginName);
-        $c = $this->_getContact();
-
-        $e = new HumanResources_Model_Employee(
-            array(
-                'number' => 1,
-                'n_fn' => $c->n_fn,
-                'account_id' => $a->getId(),
-                'costcenters' => array($this->_getCostCenter())
-            )
+        $c = $this->_getContact($loginName);
+
+        $this->_lastEmployeeNumber++;
+        
+        $ea = array(
+            'number'     => $this->_lastEmployeeNumber,
+            'n_fn'       => $c->n_fn,
+            'n_given'    => $c->n_given,
+            'n_family'   => $c->n_family,
+            'account_id' => $a->getId()
         );
 
-        return $e;
+        return new HumanResources_Model_Employee($ea);
+    }
+    
+
+    /**
+     * adds feast days to feast calendar
+     *
+     * @param Tinebase_DateTime $date
+     */
+    protected function _createFeastDay(Tinebase_DateTime $date)
+    {
+        if (! $this->_feast_calendar) {
+            $this->_getFeastCalendar();
+        }
+        $organizer = Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId());
+    
+        $event = new Calendar_Model_Event(array(
+            'summary'     => 'Wakeup',
+            'dtstart'     => $date->format('Y-m-d') .' 06:00:00',
+            'dtend'       => $date->format('Y-m-d') .' 06:15:00',
+            'description' => Tinebase_Record_Abstract::generateUID(10),
+    
+            'container_id' => $this->_feast_calendar->getId(),
+            'organizer'    => $organizer->getId(),
+            'uid'          => Calendar_Model_Event::generateUID(),
+    
+            'attendee' => new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(array(
+                'user_id'        => $organizer->getId(),
+                'user_type'      => Calendar_Model_Attender::USERTYPE_USER,
+                'role'           => Calendar_Model_Attender::ROLE_REQUIRED,
+                'status_authkey' => Tinebase_Record_Abstract::generateUID(),
+            ))),
+    
+            Tinebase_Model_Grants::GRANT_READ    => true,
+            Tinebase_Model_Grants::GRANT_EDIT    => true,
+            Tinebase_Model_Grants::GRANT_DELETE  => true,
+        ));
+    
+        Calendar_Controller_Event::getInstance()->create($event)->toArray();
     }
 }
index 4df67e9..0396491 100644 (file)
@@ -168,6 +168,30 @@ class Inventory_Import_CsvTest extends PHPUnit_Framework_TestCase
     }
     
     /**
+     * Tests if import of the example file works
+     */
+    public function testImportOfExampleFile ()
+    {
+        $filename = dirname(__FILE__) . '/files/inv_tine_import_csv_nohook.xml';
+        $applicationId = Tinebase_Application::getInstance()->getApplicationByName('Inventory')->getId();
+        $definition = Tinebase_ImportExportDefinition::getInstance()->getFromFile($filename, $applicationId);
+        
+        $this->_filename = dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/tine20/Inventory/Import/examples/inv_tine_import.csv'; 
+        $this->_deleteImportFile = FALSE;
+        
+        $result = $this->_doImport(array(), $definition);
+        $this->_deletePersonalInventoryItems = TRUE;
+        
+        $translation = Tinebase_Translation::getTranslation('Tinebase');
+        $translatedString = sprintf($translation->_("The following fields weren't imported: %s"), "\n");
+        
+        $this->assertEquals($result['results'][0]['name'], 'Tine 2.0 für Einsteiger');
+        $this->assertEquals($result['results'][0]['added_date']->setTimezone('Europe/Berlin')->toString(), '2013-12-06 00:00:00');
+        $this->assertEquals($result['results'][0]['inventory_id'], '133331666');
+        $this->assertContains($translatedString, $result['results'][0]['description']);
+    }
+    
+    /**
      * import helper
      *
      * @param array $_options
index b6bdcab..604c383 100644 (file)
@@ -15,7 +15,7 @@
     <example>Inventory/Import/examples/inv_tine_import.csv</example>
     <extension>csv</extension>
     <postMappingHook>
-        <path>Inventory/Import/files/testScript.php</path>
+        <path>../tests/tine20/Inventory/Import/files/testScript.php</path>
     </postMappingHook>
     <autotags>
         <tag>
index 08ba18f..a6ce4f3 100644 (file)
@@ -31,6 +31,18 @@ class Inventory_JsonTest extends Inventory_TestCase
     }
     
     /**
+     * tests if model gets created properly
+     */
+    public function testModelCreation()
+    {
+        $fields = Inventory_Model_InventoryItem::getConfiguration()->getFields();
+        $this->assertArrayHasKey('container_id', $fields);
+        
+        $filters = Inventory_Model_InventoryItem::getConfiguration()->getFilterModel();
+        $this->assertArrayHasKey('container_id', $filters['_filterModel']);
+    }
+    
+    /**
      * test creation of an InventoryItem
      */
     public function testCreateInventoryItem()
index ab8e0a3..1828acb 100644 (file)
@@ -27,7 +27,7 @@ class Phone_Backend_Snom_AllTests
     public static function suite ()
     {
         $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 Phone All Snom Backend Tests');
-        $suite->addTestSuite('Phone_Backend_Snom_CallhistoryTest');
+        $suite->addTestSuite('Phone_Backend_Snom_CallTest');
         return $suite;
     }
 }
 require_once dirname(dirname(dirname(dirname(__FILE__)))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
 
 if (!defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Phone_Backend_Snom_CallhistoryTest::main');
+    define('PHPUnit_MAIN_METHOD', 'Phone_Backend_Snom_CallTest::main');
 }
 
 /**
- * Test class for Phone_Backend_Snom_CallhistoryTest
+ * Test class for Phone_Backend_Snom_CallTest
  */
-class Phone_Backend_Snom_CallhistoryTest extends PHPUnit_Framework_TestCase
+class Phone_Backend_Snom_CallTest extends PHPUnit_Framework_TestCase
 {
     /**
      * Runs the test methods of this class.
      */
     public static function main()
     {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Phone Snom Callhistory Backend Tests');
+        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Phone Snom Call Backend Tests');
         PHPUnit_TextUI_TestRunner::run($suite);
     }
 
index 2d46a85..e033313 100644 (file)
@@ -187,7 +187,7 @@ class Phone_JsonTest extends PHPUnit_Framework_TestCase
         );
 
         $this->_objects['filter3'] = array(
-            array('field' => 'phone_id', 'operator' => 'equals', 'value' => $this->_objects['phone']->getId())
+            array('field' => 'phone_id', 'operator' => 'AND', 'value' => array(array('field' => ':id', 'operator' => 'equals', 'value' => $this->_objects['phone']->getId())))
         );
         
         // create calls
index 1af536a..8b1966e 100644 (file)
@@ -34,6 +34,7 @@ class Tinebase_AllTests
     public static function suite()
     {
         $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 Tinebase All Tests');
+        $suite->addTestSuite('Tinebase_ModelConfigurationTest');
         $suite->addTestSuite('Tinebase_DateTimeTest');
         $suite->addTestSuite('Tinebase_Record_RecordTest');
         $suite->addTestSuite('Tinebase_Record_RecordSetTest');
diff --git a/tests/tine20/Tinebase/ModelConfigurationTest.php b/tests/tine20/Tinebase/ModelConfigurationTest.php
new file mode 100644 (file)
index 0000000..c642893
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ *
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Alexander Stintzing <a.stintzing@metaways.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+/**
+ * Test class for Tinebase_ModelConfiguration, using the test class from hr
+ */
+class Tinebase_ModelConfigurationTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * tests if the modelconfiguration gets created using the static call
+     */
+    public function testModelCreation()
+    {
+        $employeeCObj = HumanResources_Model_Employee::getConfiguration();
+        $fields = $employeeCObj->getFields();
+
+        // test modlog field
+        $this->assertArrayHasKey('deleted_time', $fields);
+        // test supervisor_id field
+        $this->assertArrayHasKey('supervisor_id', $fields);
+        $this->assertArrayHasKey('notes', $fields);
+        $this->assertArrayHasKey('tags', $fields);
+
+        $contractCObj = HumanResources_Model_Contract::getConfiguration();
+        $fields = $contractCObj->getFields();
+
+        // test supervisor_id field not existing in contact configuration object
+        $this->assertArrayNotHasKey('supervisor_id', $fields);
+        $this->assertArrayHasKey('employee_id', $fields);
+
+        // test employee config again, so nothing gets overwritten
+        $employeeCObj = HumanResources_Model_Employee::getConfiguration();
+        $fields = $employeeCObj->getFields();
+        $this->assertArrayHasKey('deleted_time',  $fields);
+        $this->assertArrayHasKey('supervisor_id', $fields);
+        $this->assertArrayHasKey('id',            $fields);
+        
+        $account = Tinebase_Core::getUser();
+
+        $employee = new HumanResources_Model_Employee(array(
+            'account_id' => $account->getId(),
+            'n_family'   => $account->accountLastName,
+            'n_given'    => $account->accountFirstName,
+        ));
+        
+        // test record fields
+        $modelConfig = $employee::getConfiguration();
+        
+        $resolveFields = $modelConfig->recordFields;
+        $this->assertArrayHasKey('account_id',       $resolveFields);
+        $this->assertArrayHasKey('division_id',      $resolveFields);
+        $this->assertArrayHasKey('created_by',       $resolveFields);
+        $this->assertArrayHasKey('last_modified_by', $resolveFields);
+        $this->assertArrayHasKey('deleted_by',       $resolveFields);
+
+        $contact = new Addressbook_Model_Contact(array('n_family' => 'Spencer', 'n_given' => 'Bud'));
+        $co = $contact::getConfiguration();
+        $this->assertNull($co);
+        
+        // test the created filter model
+        $filterModel = $employee::getConfiguration()->filterModel;
+        
+        $this->assertArrayHasKey('id',               $filterModel);
+        $this->assertArrayHasKey('query',            $filterModel);
+        $this->assertArrayHasKey('account_id',       $filterModel);
+        $this->assertArrayHasKey('supervisor_id',    $filterModel);
+        
+        $this->assertArrayHasKey('options',          $filterModel['supervisor_id']);
+        $this->assertArrayHasKey('controller',       $filterModel['supervisor_id']['options']);
+        $this->assertArrayHasKey('filtergroup',      $filterModel['supervisor_id']['options']);
+        
+        $this->assertArrayHasKey('division_id',      $filterModel);
+        $this->assertArrayHasKey('created_by',       $filterModel);
+        $this->assertArrayHasKey('last_modified_by', $filterModel);
+        $this->assertArrayHasKey('deleted_by',       $filterModel);
+        
+        $this->assertArrayNotHasKey('employee_id',    $filterModel);
+        
+        $this->assertEquals('Tinebase_Model_Filter_Query', $filterModel['query']['filter']);
+    }
+
+    /**
+     * tests if the modelconfiguration gets created using the instance call
+     */
+    public function testModelCreationByInstance()
+    {
+        // test instance
+        $account = Tinebase_Core::getUser();
+
+        $employee = new HumanResources_Model_Employee(array(
+            'account_id' => $account->getId(),
+            'n_family' => $account->accountLastName,
+            'n_given' => $account->accountFirstName,
+        ));
+
+        $employeeCObj = $employee::getConfiguration();
+        $fields = $employeeCObj->getFields();
+        $this->assertArrayHasKey('deleted_time', $fields);
+        $this->assertArrayHasKey('supervisor_id', $fields);
+        
+        // test if sortable is set to false on tags-field
+        $this->assertArrayHasKey('sortable', $fields['tags']);
+        $this->assertFalse($fields['tags']['sortable']);
+        
+        $this->assertEquals($account->accountLastName, $employee->n_family);
+        $this->assertEquals($account->accountFirstName, $employee->n_given);
+    }
+
+    /**
+     * tests if the modelconfiguration gets created for the traditional models
+     */
+    public function testModelCreationTraditional()
+    {
+        $contact = new Addressbook_Model_Contact(array('n_family' => 'Spencer', 'n_given' => 'Bud'));
+        $cObj = $contact->getConfiguration();
+
+        // at first this is just null
+        $this->assertNull($cObj);
+    }
+}
index e8b7bc2..530a5ed 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Record
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Matthias Greiling <m.greiling@metaways.de>
  */
 
@@ -338,42 +338,4 @@ class Tinebase_Record_RecordTest extends Tinebase_Record_AbstractTest
         $recordToTest->inarray = 'value1';
         $this->assertTrue($recordToTest->isValid());
     }
-    
-    /**
-     * test auto modeling of record
-     */
-    public function testAutoRecord()
-    {
-        $addressbook = Tinebase_Application::getInstance()->getApplicationByName('Addressbook');
-        $pref = new Tinebase_Model_Preference(array(
-            'account_type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
-            'type' => Tinebase_Model_Preference::TYPE_USER,
-            'name' => 'autotest',
-            'application_id' => $addressbook->getId(),
-            'value' => 'BLABLUB'
-        ));
-        // must be null
-        $this->assertNull($pref::getResolveForeignIdFields());
-        
-        $date = new Tinebase_DateTime();
-        $date->setDate(2010, 11, 12);
-        $date->setTime(0,0);
-        
-        $auto = new Tinebase_Record_AutoRecord();
-        $auto->text = 'Hello World';
-        $auto->date = $date;
-        $crtime = clone $date;
-        $crtime->addYear(2);
-        $auto->creation_time = $crtime; 
-        
-        $this->assertEquals(15, count($auto->getFields()));
-        
-        $autoArray = $auto->toArray();
-        
-        $this->assertEquals('2010-11-12 00:00:00', $autoArray['date']);
-        $this->assertEquals('2012-11-12 00:00:00', $autoArray['creation_time']);
-        
-        // must still be null
-        $this->assertNull($pref::getResolveForeignIdFields());
-    }
 }
index 39f251b..b3a8906 100644 (file)
           "path": "js/"
         },
         {
-          "text": "Model.js",
+          "text": "ContactModel.js",
+          "path": "js/"
+        },
+        {
+          "text": "ListModel.js",
+          "path": "js/"
+        },
+        {
+          "text": "EmailModel.js",
           "path": "js/"
         },
         {
           "path": "js/"
         },
         {
+          "text": "ListGridDetailsPanel.js",
+          "path": "js/"
+        },
+        {
           "text": "ContactGrid.js",
           "path": "js/"
         },
           "path": "js/"
         },
         {
+          "text": "ListEditDialog.js",
+          "path": "js/"
+        },
+        {
+          "text": "ListMemberGridPanel.js",
+          "path": "js/"
+        },
+        {
+          "text": "ListGrid.js",
+          "path": "js/"
+        },
+        {
           "text": "SearchCombo.js",
           "path": "js/"
         },
@@ -65,4 +89,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
index 7141a61..9a65d8f 100644 (file)
@@ -68,6 +68,34 @@ class Addressbook_Backend_List extends Tinebase_Backend_Sql_Abstract
     );
 
     /**
+     * the constructor
+     * 
+     * allowed options:
+     *  - modelName
+     *  - tableName
+     *  - tablePrefix
+     *  - modlogActive
+     *  - useSubselectForCount
+     *  
+     * @param Zend_Db_Adapter_Abstract $_db (optional)
+     * @param array $_options (optional)
+     * @throws Tinebase_Exception_Backend_Database
+     */
+    public function __construct($_dbAdapter = NULL, $_options = array())
+    {
+        parent::__construct($_dbAdapter, $_options);
+        
+        $this->_additionalColumns['emails'] = new Zend_Db_Expr('(' . 
+            $this->_db->select()
+                ->from($this->_tablePrefix . 'addressbook', array($this->_dbCommand->getAggregate('email')))
+                ->where($this->_db->quoteIdentifier('id') . ' IN ?', $this->_db->select()
+                    ->from(array('addressbook_list_members' => $this->_tablePrefix . 'addressbook_list_members'), array('contact_id'))
+                    ->where($this->_db->quoteIdentifier('addressbook_list_members.list_id') . ' = ' . $this->_db->quoteIdentifier('addressbook_lists.id'))
+            ) . 
+        ')');
+    }
+
+    /**
      * converts record into raw data for adapter
      *
      * @param  Tinebase_Record_Abstract $_record
index cac3c3a..c375a92 100644 (file)
@@ -80,7 +80,9 @@ class Addressbook_Controller extends Tinebase_Controller_Event implements Tineba
                 $this->createPersonalFolder($_eventObject->account);
                 break;
             case 'Admin_Event_DeleteAccount':
-                $this->deletePersonalFolder($_eventObject->account);
+                foreach ($_eventObject->accountIds as $accountId) {
+                    $this->deletePersonalFolder($accountId);
+                }
                 break;
         }
     }
index c8bad05..388cd1a 100644 (file)
@@ -72,8 +72,10 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
                 case 'ADR':
                     $type = null;
                     foreach($property['TYPE'] as $typeProperty) {
-                        if(strtolower($typeProperty) == 'home' || strtolower($typeProperty) == 'work') {
-                            $type = strtolower($typeProperty);
+                        // delete ,PREF added by eM Client
+                        $typeProperty = str_replace(",pref","",strtolower($typeProperty));
+                        if (($typeProperty == 'home') || ($typeProperty == 'work')) {
+                            $type = $typeProperty;
                             break;
                         }
                     }
diff --git a/tine20/Addressbook/Convert/Contact/VCard/EMClient.php b/tine20/Addressbook/Convert/Contact/VCard/EMClient.php
new file mode 100644 (file)
index 0000000..e3a70eb
--- /dev/null
@@ -0,0 +1,260 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Thomas Pawassarat <tomp@topanet.de>
+ * @copyright   Copyright (c) 2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * class to convert a eM Client vcard to contact model and back again
+ *
+ * @package     Addressbook
+ * @subpackage  Convert
+ */
+class Addressbook_Convert_Contact_VCard_EMClient extends Addressbook_Convert_Contact_VCard_Abstract
+{
+    // eM Client/5.0.17595.0
+    const HEADER_MATCH = '/eM Client\/(?P<version>.*)/';
+    
+    protected $_emptyArray = array(
+        'adr_one_countryname'   => null,
+        'adr_one_locality'      => null,
+        'adr_one_postalcode'    => null,
+        'adr_one_region'        => null,
+        'adr_one_street'        => null,
+        'adr_one_street2'       => null,
+        'adr_two_countryname'   => null,
+        'adr_two_locality'      => null,
+        'adr_two_postalcode'    => null,
+        'adr_two_region'        => null,
+        'adr_two_street'        => null,
+        'adr_two_street2'       => null,
+        #'assistent'             => null,
+        'bday'                  => null,
+        #'calendar_uri'          => null,
+        'email'                 => null,
+        'email_home'            => null,
+        'jpegphoto'             => null,
+        #'freebusy_uri'          => null,
+        'note'                  => null,
+        #'role'                  => null,
+        #'salutation'            => null,
+        'title'                 => null,
+        'url'                   => null,
+        'url_home'              => null,
+        'n_family'              => null,
+        'n_fileas'              => null,
+        #'n_fn'                  => null,
+        'n_given'               => null,
+        #'n_middle'              => null,
+        'n_prefix'              => null,
+        'n_suffix'              => null,
+        'org_name'              => null,
+        'org_unit'              => null,
+        #'pubkey'                => null,
+        #'room'                  => null,
+        #'tel_assistent'         => null,
+        #'tel_car'               => null,
+        'tel_cell'              => null,
+        'tel_cell_private'      => null,
+        'tel_fax'               => null,
+        'tel_fax_home'          => null,
+        'tel_home'              => null,
+        #'tel_pager'             => null,
+        'tel_work'              => null,
+        #'tel_other'             => null,
+        #'tel_prefer'            => null,
+        #'tz'                    => null,
+        #'geo'                   => null,
+        #'lon'                   => null,
+        #'lat'                   => null,
+        'tags'                  => null,
+        'notes'                 => null,
+    );
+    
+    /**
+     * converts Addressbook_Model_Contact to vcard
+     * 
+     * @param  Addressbook_Model_Contact  $_record
+     * @return Sabre_VObject_Component
+     */
+    public function fromTine20Model(Tinebase_Record_Abstract $_record)
+    {
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' contact ' . print_r($_record->toArray(), true));
+        
+        $card = new Sabre_VObject_Component('VCARD');
+        
+        // required vcard fields
+        $card->add(new Sabre_VObject_Property('VERSION', '3.0'));
+        $card->add(new Sabre_VObject_Property('FN', $_record->n_fileas));
+        $card->add(new Sabre_VObject_Element_MultiValue('N', array($_record->n_family, $_record->n_given, $_record->n_middle, $_record->n_prefix, $_record->n_suffix)));
+        
+        $card->add(new Sabre_VObject_Property('PRODID', '-//tine20.org//Tine 2.0//EN'));
+        $card->add(new Sabre_VObject_Property('UID', $_record->getId()));
+
+        // optional fields
+        $card->add(new Sabre_VObject_Element_MultiValue('ORG', array($_record->org_name, $_record->org_unit)));
+        $card->add(new Sabre_VObject_Property('TITLE', $_record->title));
+        
+        $tel = new Sabre_VObject_Property('TEL', $_record->tel_work);
+        $tel->add('TYPE', 'WORK');
+        $tel->add('TYPE', 'VOICE');
+        $card->add($tel);
+        
+        $tel = new Sabre_VObject_Property('TEL', $_record->tel_home);
+        $tel->add('TYPE', 'HOME');
+        $tel->add('TYPE', 'VOICE');
+        $card->add($tel);
+        
+        $tel = new Sabre_VObject_Property('TEL', $_record->tel_cell);
+        $tel->add('TYPE', 'CELL');
+        $card->add($tel);
+        
+        $tel = new Sabre_VObject_Property('TEL', $_record->tel_cell_private);
+        $tel->add('TYPE', 'OTHER');
+        $card->add($tel);
+        
+        $tel = new Sabre_VObject_Property('TEL', $_record->tel_fax);
+        $tel->add('TYPE', 'WORK');
+        $tel->add('TYPE', 'FAX');
+        $card->add($tel);
+        
+        $tel = new Sabre_VObject_Property('TEL', $_record->tel_fax_home);
+        $tel->add('TYPE', 'HOME');
+        $tel->add('TYPE', 'FAX');
+        $card->add($tel);
+        
+        $adr = new Sabre_VObject_Element_MultiValue('ADR', array(null, $_record->adr_one_street2, $_record->adr_one_street, $_record->adr_one_locality, $_record->adr_one_region, $_record->adr_one_postalcode, $_record->adr_one_countryname));
+        $adr->add('TYPE', 'WORK');
+        $card->add($adr);
+        
+        $adr = new Sabre_VObject_Element_MultiValue('ADR', array(null, $_record->adr_two_street2, $_record->adr_two_street, $_record->adr_two_locality, $_record->adr_two_region, $_record->adr_two_postalcode, $_record->adr_two_countryname));
+        $adr->add('TYPE', 'HOME');
+        $card->add($adr);
+        
+        $email = new Sabre_VObject_Property('EMAIL', $_record->email);
+        $email->add('TYPE', 'PREF');
+        $card->add($email);
+        
+        $email = new Sabre_VObject_Property('EMAIL', $_record->email_home);
+        $card->add($email);
+        
+        $card->add(new Sabre_VObject_Property('URL;TYPE=work', $_record->url));
+        $card->add(new Sabre_VObject_Property('URL;TYPE=home', $_record->url_home));
+        
+        $card->add(new Sabre_VObject_Property('NOTE', $_record->note));
+        
+        if ($_record->bday instanceof Tinebase_DateTime) {
+            $date = $_record->bday;
+            $date->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
+            $date = $date->format('Y-m-d');
+            $card->add(new Sabre_VObject_Property('BDAY', $date));
+        }
+        
+        if(! empty($_record->jpegphoto)) {
+            try {
+                $image = Tinebase_Controller::getInstance()->getImage('Addressbook', $_record->getId());
+                $jpegData = $image->getBlob('image/jpeg');
+                $photo = new Sabre_VObject_Property('PHOTO', $jpegData);
+                $photo->add('ENCODING', 'b');
+                $photo->add('TYPE', 'JPEG');
+                $card->add($photo);
+            } catch (Exception $e) {
+                Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Image for contact {$_record->getId()} not found or invalid");
+            }
+        
+        
+        }
+        if(isset($_record->tags) && count($_record->tags) > 0) {
+            $card->add(new Sabre_VObject_Property('CATEGORIES', Sabre_VObject_Element_List((array) $_record->tags->name)));
+        }
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' card ' . $card->serialize());
+        
+        return $card;
+    }
+    
+    protected function _toTine20ModelParseEmail(&$_data, $_property)
+    {
+        $type = null;
+        if (isset($_property['TYPE'])) {
+            foreach($_property['TYPE'] as $typeProperty) {
+                if(strtolower($typeProperty) == 'pref') {
+                    $type = 'work';
+                    break;
+                }
+            }
+        }
+        
+        switch ($type) {
+            case 'work':
+                $_data['email'] = $_property->value;
+                break;
+                
+            default:
+                $_data['email_home'] = $_property->value;
+                break;
+        
+        }
+    }
+    
+    protected function _toTine20ModelParseTel(&$_data, $_property)
+    {
+        $telField = null;
+        $types    = array();
+        
+        if (isset($_property['TYPE'])) {
+            // get all types
+            // delete ,PREF added by eM Client
+            foreach($_property['TYPE'] as $typeProperty) {
+                $types[] = str_replace(",PREF","",strtoupper($typeProperty->value));
+            }
+            
+            // CELL
+            if (in_array('CELL', $types)) {
+                $telField = 'tel_cell';
+            } elseif (in_array('OTHER', $types)) {
+                $telField = 'tel_cell_private';
+     
+            // TEL
+            } elseif (in_array('WORK,VOICE', $types)) {
+                $telField = 'tel_work';            
+            } elseif (in_array('HOME,VOICE', $types)) {
+                $telField = 'tel_home';            
+
+            // FAX
+            } elseif (in_array('WORK,FAX', $types)) {
+                $telField = 'tel_fax';
+            } elseif (in_array('HOME,FAX', $types)) {
+                $telField = 'tel_fax_home';
+            }
+            
+            
+        }
+        
+        if (!empty($telField)) {
+            $_data[$telField] = $_property->value;
+        } else {
+            parent::_toTine20ModelParseTel($_data, $_property);
+        }
+        
+    }
+    
+    /**
+     * parse birthday
+     * 
+     * @param array $data
+     * @param Sabre_VObject_Element $property
+     */
+    protected function _toTine20ModelParseBday(&$_data, $_property)
+    {
+        $tzone = new DateTimeZone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
+        $_data['bday'] = new Tinebase_DateTime($_property->value, $tzone);
+        $_data['bday']->setTimezone(new DateTimeZone('UTC'));
+    }    
+}
index 7cb5f7f..94bd53e 100644 (file)
@@ -23,6 +23,7 @@ class Addressbook_Convert_Contact_VCard_Factory
     const CLIENT_KDE     = 'kde';
     const CLIENT_MACOSX  = 'macosx';
     const CLIENT_SOGO    = 'sogo';
+    const CLIENT_EMCLIENT= 'emclient';
     
     /**
      * factory function to return a selected phone backend class
@@ -56,9 +57,14 @@ class Addressbook_Convert_Contact_VCard_Factory
                 
             case Addressbook_Convert_Contact_VCard_Factory::CLIENT_SOGO:
                 return new Addressbook_Convert_Contact_VCard_Sogo($_version);
-                 
+                
+                break;
+                
+            case Addressbook_Convert_Contact_VCard_Factory::CLIENT_EMCLIENT:
+                return new Addressbook_Convert_Contact_VCard_EMClient($_version);
+                
                 break;
-                     
+                
         }
     }
     
@@ -91,6 +97,10 @@ class Addressbook_Convert_Contact_VCard_Factory
             $backend = Addressbook_Convert_Contact_VCard_Factory::CLIENT_KDE;
             $version = $matches['version'];
         
+        // eM Client addressbook
+        } elseif (preg_match(Addressbook_Convert_Contact_VCard_EMClient::HEADER_MATCH, $_userAgent, $matches)) {
+            $backend = Addressbook_Convert_Contact_VCard_Factory::CLIENT_EMCLIENT;
+            $version = $matches['version'];
         // generic client
         } else {
             $backend = Addressbook_Convert_Contact_VCard_Factory::CLIENT_GENERIC;
index a404583..58a0d24 100755 (executable)
@@ -61,6 +61,44 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     {
         return $this->_search($filter, $paging, Addressbook_Controller_Contact::getInstance(), 'Addressbook_Model_ContactFilter');
     }    
+
+    /**
+     * Search for Email Addresses with the Email Model in Lists and Contacts
+     *
+     * @param  array $filter
+     * @param  array $paging
+     * @return array
+     *
+     * Record Proxy Pluralizer doesnt correctly pluralize EmailAddress, thus EmailAddresss is used
+     *
+     */
+    public function searchEmailAddresss($filter, $paging)
+    {
+        $results = array();
+        $contacts = $this->_search($filter, $paging, Addressbook_Controller_Contact::getInstance(), 'Addressbook_Model_ContactFilter');
+        foreach ($contacts["results"] as $contact) {
+            array_push($results, array("n_fileas" => $contact["n_fileas"],"n_fn" => $contact["n_fn"],"org_unit" => $contact["org_unit"], "emails" => $contact["email"], "email" => $contact["email"], "email_home" => $contact["email_home"]));
+        }
+
+        $dont_add = false;
+        if (isset($paging["start"])) {
+            $paging["start"] = $paging["start"] - $contacts["totalcount"] + count($results);
+            $paging["limit"] = $paging["limit"] - count($results);
+            if (($paging["limit"] <= 0) || ($paging["start"] < 0)) {
+                $dont_add = true;
+                $paging["limit"] = 1;
+                $paging["start"] = 0;
+            }
+        }
+        
+        $lists = $this->_search($filter, $paging, Addressbook_Controller_List::getInstance(), 'Addressbook_Model_ListFilter');
+        if (!$dont_add) {
+            foreach ($lists["results"] as $list) {
+                array_push($results, array("n_fileas" => $list["name"], "emails" => $list["emails"]));
+            }
+         }
+         return array("results" => $results, "totalcount" => $lists["totalcount"]+$contacts["totalcount"]);
+    } 
     
     /**
      * return autocomplete suggestions for a given property and value
@@ -100,6 +138,31 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     }
     
     /**
+     * get one list identified by $id
+     *
+     * @param string $id
+     * @return array
+     */
+    public function getList($id)
+    {
+        return $this->_get($id, Addressbook_Controller_List::getInstance());
+    }
+
+    /**
+     * save one list
+     *
+     * if $recordData['id'] is empty the list gets added, otherwise it gets updated
+     *
+     * @param  array $recordData an array of list properties
+     * @param  boolean $duplicateCheck
+     * @return array
+     */
+    public function saveList($recordData, $duplicateCheck = FALSE)
+    {
+        return $this->_save($recordData, Addressbook_Controller_List::getInstance(), 'List', 'id', array($duplicateCheck));
+    }
+
+    /**
      * Search for lists matching given arguments
      *
      * @param  array $filter
index 6ad88cb..696c8d6 100644 (file)
@@ -69,6 +69,7 @@ class Addressbook_Model_List extends Tinebase_Record_Abstract
         'is_deleted'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'deleted_time'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'deleted_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'emails'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         
         // list specific fields
         'name'                  => array('presence' => 'required'),
index b538758..ed84028 100644 (file)
@@ -34,13 +34,13 @@ class Addressbook_Model_ListHiddenFilter extends Tinebase_Model_Filter_Bool
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Query all lists.');
         } else {
             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Only query visible lists.');
-            
-            $_select->join(
+            $_select->joinLeft(
                 /* table  */ array('groupvisibility' => $db->table_prefix . 'groups'), 
                 /* on     */ $db->quoteIdentifier('groupvisibility.list_id') . ' = ' . $db->quoteIdentifier('addressbook_lists.id'),
                 /* select */ array()
             );
             $_select->where($db->quoteIdentifier('groupvisibility.visibility').' = ?', 'displayed');
+            $_select->orWhere($db->quoteInto($db->quoteIdentifier('addressbook_lists.type'). ' = ?', Addressbook_Model_List::LISTTYPE_LIST));
         }
     }
 }
index 6fef1f8..c03b939 100644 (file)
@@ -31,4 +31,27 @@ class Addressbook_Setup_Update_Release7 extends Setup_Update_Abstract
         
         $this->setApplicationVersion('Addressbook', '7.1');
     }
+
+    /**
+     * update to 7.2
+     * - make name in address_book_lists non-unique
+     * 
+     */
+    public function update_1()
+    {        
+        $this->_backend->dropIndex('addressbook_lists', 'name');
+        $declaration = new Setup_Backend_Schema_Index_Xml('
+            <index>
+                <name>name</name>
+                <unique>false</unique>
+                <field>
+                    <name>name</name>
+                </field>
+            </index>
+        ');
+        $this->_backend->addIndex('addressbook_lists', $declaration);
+
+        $this->setTableVersion('addressbook_lists', 3);
+        $this->setApplicationVersion('Addressbook', '7.2');
+    }
 }
index 4bb10b7..e4e8eb4 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Addressbook</name>
-    <version>7.1</version>
+    <version>7.2</version>
     <order>10</order>
     <depends>
         <application>Admin</application>
         </table>
         <table>
             <name>addressbook_lists</name>
-            <version>2</version>
+            <version>3</version>
             <declaration>
                 <field>
                     <name>id</name>
                 </index>
                 <index>
                     <name>name</name>
-                    <unique>true</unique>
+                    <unique>false</unique>
                     <field>
                         <name>name</name>
                     </field>
             </field>
         </record>
     </defaultRecords>
-</application>
\ No newline at end of file
+</application>
index 826cb5a..297d6c2 100755 (executable)
@@ -42,20 +42,33 @@ Tine.Addressbook.Application = Ext.extend(Tine.Tinebase.Application, {
  * @author      Cornelius Weiss <c.weiss@metaways.de>
  */
 Tine.Addressbook.MainScreen = Ext.extend(Tine.widgets.MainScreen, {
-    activeContentType: 'Contact'
+    activeContentType: 'Contact',
+    contentTypes: [
+        {modelName: 'Contact', requiredRight: null, singularContainerMode: false},
+        {modelName: 'List',    requiredRight: null, singularContainerMode: false}
+        ]
 });
 
-
 Tine.Addressbook.ContactTreePanel = function(config) {
     Ext.apply(this, config);
     
-    this.id = 'Addressbook_Tree';
+    this.id = 'Addressbook_Contact_Tree';
     this.filterMode = 'filterToolbar';
     this.recordClass = Tine.Addressbook.Model.Contact;
     Tine.Addressbook.ContactTreePanel.superclass.constructor.call(this);
 };
 Ext.extend(Tine.Addressbook.ContactTreePanel , Tine.widgets.container.TreePanel);
 
+Tine.Addressbook.ListTreePanel = function(config) {
+    Ext.apply(this, config);
+    
+    this.id = 'Addressbook_List_Tree';
+    this.filterMode = 'filterToolbar';
+    this.recordClass = Tine.Addressbook.Model.List;
+    Tine.Addressbook.ListTreePanel.superclass.constructor.call(this);
+};
+Ext.extend(Tine.Addressbook.ListTreePanel , Tine.widgets.container.TreePanel);
+
 Tine.Addressbook.handleRequestException = Tine.Tinebase.ExceptionHandler.handleRequestException;
 
 Tine.Addressbook.ContactFilterPanel = function(config) {
@@ -65,4 +78,13 @@ Tine.Addressbook.ContactFilterPanel = function(config) {
 
 Ext.extend(Tine.Addressbook.ContactFilterPanel, Tine.widgets.persistentfilter.PickerPanel, {
     filter: [{field: 'model', operator: 'equals', value: 'Addressbook_Model_ContactFilter'}]
-});
\ No newline at end of file
+});
+
+Tine.Addressbook.ListFilterPanel = function(config) {
+    Ext.apply(this, config);
+    Tine.Addressbook.ListFilterPanel.superclass.constructor.call(this);
+};
+
+Ext.extend(Tine.Addressbook.ListFilterPanel, Tine.widgets.persistentfilter.PickerPanel, {
+    filter: [{field: 'model', operator: 'equals', value: 'Addressbook_Model_ListFilter'}]
+});
index 729d18d..2dfbc7d 100644 (file)
@@ -435,6 +435,10 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
             
             isValid = false;
         }
+        else {
+            form.findField('n_family').clearInvalid();
+            form.findField('org_name').clearInvalid();
+        }
         
         return isValid && Tine.Addressbook.ContactEditDialog.superclass.isValid.apply(this, arguments);
     },
similarity index 91%
rename from tine20/Addressbook/js/Model.js
rename to tine20/Addressbook/js/ContactModel.js
index 0658819..b74767b 100644 (file)
@@ -69,11 +69,11 @@ Tine.Addressbook.Model.ContactArray = Tine.Tinebase.Model.genericFields.concat([
     {name: 'tz', omitDuplicateResolving: true},
     {name: 'pubkey', omitDuplicateResolving: true},
     {name: 'jpegphoto', omitDuplicateResolving: true},
-    {name: 'account_id', isMetaField: true},
+    {name: 'account_id', omitDuplicateResolving: true},
     {name: 'tags'},
     {name: 'notes', omitDuplicateResolving: true},
     {name: 'relations', omitDuplicateResolving: true},
-    {name: 'customfields', isMetaField: true},
+    {name: 'customfields', omitDuplicateResolving: true},
     {name: 'type', omitDuplicateResolving: true}
 ]);
 
@@ -173,37 +173,3 @@ Tine.Addressbook.contactBackend = new Tine.Tinebase.data.RecordProxy({
     modelName: 'Contact',
     recordClass: Tine.Addressbook.Model.Contact
 });
-
-/**
- * list model
- */
-Tine.Addressbook.Model.List = Tine.Tinebase.data.Record.create([
-   {name: 'id'},
-   {name: 'container_id'},
-   {name: 'created_by'},
-   {name: 'creation_time'},
-   {name: 'last_modified_by'},
-   {name: 'last_modified_time'},
-   {name: 'is_deleted'},
-   {name: 'deleted_time'},
-   {name: 'deleted_by'},
-   {name: 'name'},
-   {name: 'description'},
-   {name: 'members'},
-   {name: 'email'},
-   {name: 'type'},
-   {name: 'group_id'}
-], {
-    appName: 'Addressbook',
-    modelName: 'List',
-    idProperty: 'id',
-    titleProperty: 'name',
-    // ngettext('List', 'Lists', n); gettext('Lists');
-    recordName: 'List',
-    recordsName: 'Lists',
-    containerProperty: 'container_id',
-    // ngettext('Addressbook', 'Addressbooks', n); gettext('Addressbooks');
-    containerName: 'Addressbook',
-    containersName: 'Addressbooks',
-    copyOmitFields: ['group_id']
-});
diff --git a/tine20/Addressbook/js/EmailModel.js b/tine20/Addressbook/js/EmailModel.js
new file mode 100644 (file)
index 0000000..abc8077
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * email address model
+ */
+Tine.Addressbook.Model.EmailAddress = Tine.Tinebase.data.Record.create([
+   {name: 'n_fileas'},
+   {name: 'emails'},
+   {name: 'email'},
+   {name: 'email_home'},
+   {name: 'n_fn'},
+   {name: 'org_unit'}
+   ],
+   {
+    appName: 'Addressbook',
+    modelName: 'EmailAddress',
+    titleProperty: 'name',
+    // ngettext('Email Address', 'Email Addresses', n); gettext('Email Addresses');
+    recordName: 'Email Address',
+    recordsName: 'Email Addresses',
+    containerProperty: 'container_id',
+    // ngettext('Addressbook', 'Addressbooks', n); gettext('Addressbooks');
+    containerName: 'Addressbook',
+    containersName: 'Addressbooks',
+    copyOmitFields: ['group_id'],
+
+    getPreferedEmail: function(prefered) {
+        var emails = this.get("emails");
+        if (!this.get("email")) {
+            return  this.get("emails");
+        } else {
+            var prefered = prefered || 'email',
+            other = prefered == 'email' ? 'email_home' : 'email';
+            return (this.get(prefered) || this.get(other));
+        }
+    }
+});
+
+
+/**
+ * get filtermodel of emailaddress model
+ * 
+ * @namespace Tine.Addressbook.Model
+ * @static
+ * @return {Array} filterModel definition
+ */ 
+Tine.Addressbook.Model.EmailAddress.getFilterModel = function() {
+    var app = Tine.Tinebase.appMgr.get('Addressbook');
+    
+    var typeStore = [['contact', app.i18n._('Contact')], ['user', app.i18n._('User Account')]];
+    
+    return [
+        {label: _('Quick search'),       field: 'query',              operators: ['contains']}
+    ];
+};
\ No newline at end of file
diff --git a/tine20/Addressbook/js/ListEditDialog.js b/tine20/Addressbook/js/ListEditDialog.js
new file mode 100644 (file)
index 0000000..db183dc
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/*global Ext, Tine*/
+
+Ext.ns('Tine.Addressbook');
+
+/**
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.ListEditDialog
+ * @extends     Tine.widgets.dialog.EditDialog
+ * Addressbook Edit Dialog <br>
+ * 
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ */
+Tine.Addressbook.ListEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
+    
+    /**
+     * parse address button
+     * @type Ext.Button 
+     */
+    parseAddressButton: null,
+    
+    windowNamePrefix: 'ListEditWindow_',
+    appName: 'Addressbook',
+    recordClass: Tine.Addressbook.Model.List,
+    showContainerSelector: true,
+    multipleEdit: true,
+    
+    // TODO: Add History and Tagging Functionality
+    getFormItems: function () {
+        return {
+            xtype: 'tabpanel',
+            border: false,
+            plain: true,
+            activeTab: 0,
+            plugins: [{
+                ptype : 'ux.tabpanelkeyplugin'
+            }],
+            items: [{
+                title: this.app.i18n.n_('List', 'Lists', 1),
+                border: false,
+                frame: true,
+                layout: 'border',
+                items: [{
+                    region: 'center',
+                    layout: 'border',
+                    items: [{
+                        xtype: 'fieldset',
+                        region: 'north',
+                        autoHeight: true,
+                        title: this.app.i18n._('List Information'),
+                        items: [{
+                            xtype: 'panel',
+                            layout: 'hbox',
+                            align: 'stretch',
+                            items: [{
+                                flex: 1,
+                                xtype: 'columnform',
+                                autoHeight: true,
+                                style:'padding-right: 5px;',
+                                items: [[{
+                                    columnWidth: 1,
+                                    fieldLabel: this.app.i18n._('Name'),
+                                    name: 'name',
+                                    maxLength: 64
+                                }]]
+                            }]
+                        }]
+                    }, this.memberGridPanel]
+                }, {
+                    // activities and tags
+                    region: 'east',
+                    layout: 'accordion',
+                    animate: true,
+                    width: 210,
+                    split: true,
+                    collapsible: true,
+                    collapseMode: 'mini',
+                    header: false,
+                    margins: '0 5 0 5',
+                    border: true,
+                    items: [
+                        new Ext.Panel({
+                            // @todo generalise!
+                            title: this.app.i18n._('Description'),
+                            iconCls: 'descriptionIcon',
+                            layout: 'form',
+                            labelAlign: 'top',
+                            border: false,
+                            items: [{
+                                style: 'margin-top: -4px; border 0px;',
+                                labelSeparator: '',
+                                xtype: 'textarea',
+                                name: 'description',
+                                hideLabel: true,
+                                grow: false,
+                                preventScrollbars: false,
+                                anchor: '100% 100%',
+                                emptyText: this.app.i18n._('Enter description'),
+                                requiredGrant: 'editGrant'
+                            }]
+                        })/*,
+                        new Tine.widgets.activities.ActivitiesPanel({
+                            app: 'Addressbook',
+                            showAddNoteForm: false,
+                            border: false,
+                            bodyStyle: 'border:1px solid #B5B8C8;'
+                        }),
+                        new Tine.widgets.tags.TagPanel({
+                            app: 'Addressbook',
+                            border: false,
+                            bodyStyle: 'border:1px solid #B5B8C8;'
+                        })*/
+                    ]
+                }]
+            },
+            /*new Tine.widgets.activities.ActivitiesTabPanel({
+                app: this.appName,
+                record_id: (this.record && ! this.copyRecord) ? this.record.id : '',
+                record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
+            })*/
+            ]
+        };
+    },
+    
+    /**
+     * init component
+     */
+    initComponent: function () {    
+        this.memberGridPanel = new Tine.Addressbook.ListMemberGridPanel({ region: "center", frame: true, margins: '6 0 0 0' });           
+        this.supr().initComponent.apply(this, arguments);
+    },
+    
+    /**
+     * checks if form data is valid
+     * 
+     * @return {Boolean}
+     */
+    isValid: function () {
+        var form = this.getForm();
+        var isValid = true;
+        
+        // you need to fill in one of: n_given n_family org_name
+        // @todo required fields should depend on salutation ('company' -> org_name, etc.)
+        //       and not required fields should be disabled (n_given, n_family, etc.)
+        if (form.findField('name').getValue() === '') {
+            var invalidString = String.format(this.app.i18n._('{0} must be given'), this.app.i18n._('Name'));
+            
+            form.findField('name').markInvalid(invalidString);
+            
+            isValid = false;
+        }
+        
+        return isValid && Tine.Addressbook.ListEditDialog.superclass.isValid.apply(this, arguments);
+    },
+    
+    /**
+     * onRecordLoad
+     */
+    onRecordLoad: function () {
+        this.memberGridPanel.setMembers(this.record.get("members")); 
+        // NOTE: it comes again and again till
+        if (this.rendered) {
+            var container = this.record.get('container_id');
+            
+            // handle default container
+            // TODO is this still needed? don't we already have generic default container handling?
+            if (! this.record.id) {
+                if (this.forceContainer) {
+                    container = this.forceContainer;
+                    // only force initially!
+                    this.forceContainer = null;
+                } else if (! Ext.isObject(container)) {
+                    container = Tine.Addressbook.registry.get('defaultAddressbook');
+                }
+                
+                this.record.set('container_id', '');
+                this.record.set('container_id', container);
+            }
+        }
+        this.supr().onRecordLoad.apply(this, arguments);
+    },
+
+    /**
+     * onRecordUpdate
+     */
+    onRecordUpdate: function() {
+        Tine.Calendar.EventEditDialog.superclass.onRecordUpdate.apply(this, arguments);
+        this.record.set("members", this.memberGridPanel.getMembers());
+    },
+});
+
+/**
+ * Opens a new List edit dialog window
+ * 
+ * @return {Ext.ux.Window}
+ */
+Tine.Addressbook.ListEditDialog.openWindow = function (config) {
+    
+    // if a container is selected in the tree, take this as default container
+    var treeNode = Ext.getCmp('Addressbook_Tree') ? Ext.getCmp('Addressbook_Tree').getSelectionModel().getSelectedNode() : null;
+    if (treeNode && treeNode.attributes && treeNode.attributes.container.type) {
+        config.forceContainer = treeNode.attributes.container;
+    } else {
+        config.forceContainer = null;
+    }
+    
+    var id = (config.record && config.record.id) ? config.record.id : 0;
+    var window = Tine.WindowFactory.getWindow({
+        width: 800,
+        height: 610,
+        name: Tine.Addressbook.ListEditDialog.prototype.windowNamePrefix + id,
+        contentPanelConstructor: 'Tine.Addressbook.ListEditDialog',
+        contentPanelConstructorConfig: config
+    });
+    return window;
+};
diff --git a/tine20/Addressbook/js/ListGrid.js b/tine20/Addressbook/js/ListGrid.js
new file mode 100644 (file)
index 0000000..b9fba0a
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+Ext.ns('Tine.Addressbook');
+
+/**
+ * List grid panel
+ * 
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.ListGridPanel
+ * @extends     Tine.widgets.grid.GridPanel
+ * 
+ * <p>List Grid Panel</p>
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ * @param       {Object} config
+ * @constructor
+ * Create a new Tine.Addressbook.ListGridPanel
+ */
+Tine.Addressbook.ListGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
+    /**
+     * record class
+     * @cfg {Tine.Addressbook.Model.List} recordClass
+     */
+    recordClass: Tine.Addressbook.Model.List,
+    
+    /**
+     * grid specific
+     * @private
+     */ 
+    defaultSortInfo: {field: 'name', direction: 'ASC'},
+    copyEditAction: true,
+    felamimail: false,
+    multipleEdit: false,
+    duplicateResolvable: false,
+    
+    /**
+     * @cfg {Bool} hasDetailsPanel 
+     */
+    hasDetailsPanel: true,
+    
+    /**
+     * inits this cmp
+     * @private
+     */
+    initComponent: function() {
+        this.recordProxy = Tine.Addressbook.listBackend;
+        
+        // check if felamimail is installed and user has run right and wants to use felamimail in adb
+        if (Tine.Felamimail && Tine.Tinebase.common.hasRight('run', 'Felamimail') && Tine.Felamimail.registry.get('preferences').get('useInAdb')) {
+            this.felamimail = (Tine.Felamimail.registry.get('preferences').get('useInAdb') == 1);
+        }
+        this.gridConfig.cm = this.getColumnModel();
+        this.filterToolbar = this.filterToolbar || this.getFilterToolbar();
+
+        if (this.hasDetailsPanel) {
+            this.detailsPanel = this.getDetailsPanel();
+        }
+
+        this.plugins = this.plugins || [];
+        this.plugins.push(this.filterToolbar);
+        
+        Tine.Addressbook.ListGridPanel.superclass.initComponent.call(this);
+    },
+    
+    /**
+     * returns column model
+     * 
+     * @return Ext.grid.ColumnModel
+     * @private
+     */
+    getColumnModel: function() {
+        return new Ext.grid.ColumnModel({
+            defaults: {
+                sortable: true,
+                hidden: true,
+                resizable: true
+            },
+            columns: this.getColumns()
+        });
+    },
+    
+    /**
+     * returns array with columns
+     * 
+     * @return {Array}
+     */
+    getColumns: function() {
+        return [
+            { id: 'type', header: this.app.i18n._('Type'), dataIndex: 'type', width: 30, renderer: this.listTypeRenderer.createDelegate(this), hidden: false },
+            { id: 'name', header: this.app.i18n._('Name'), dataIndex: 'name', width: 30, hidden: false },
+            { id: 'emails', header: this.app.i18n._('Emails'), dataIndex: 'emails', hidden: false },
+        ].concat(this.getModlogColumns().concat(this.getCustomfieldColumns()));
+    },
+    
+    /**
+     * @private
+     */
+    initActions: function() {        
+        Tine.Addressbook.ListGridPanel.superclass.initActions.call(this);
+    },
+        
+    /**
+     * tid renderer
+     * 
+     * @private
+     * @return {String} HTML
+     */
+    listTypeRenderer: function(data, cell, record) {
+        if (data == "group") {
+            return '<div style="background-position:0px;" class="renderer_typeAccountIcon">&#160</div>';
+        } else {
+            return '<div style="background-position:0px;" class="renderer_typeContactIcon">&#160</div>';
+        }
+    },
+    
+    /**
+     * returns details panel
+     * 
+     * @private
+     * @return {Tine.Addressbook.ListGridDetailsPanel}
+     */
+    getDetailsPanel: function() {
+        return new Tine.Addressbook.ListGridDetailsPanel({
+            gridpanel: this,
+            il8n: this.app.i18n,
+            felamimail: this.felamimail
+        });
+    }
+});
diff --git a/tine20/Addressbook/js/ListGridDetailsPanel.js b/tine20/Addressbook/js/ListGridDetailsPanel.js
new file mode 100644 (file)
index 0000000..3e7f056
--- /dev/null
@@ -0,0 +1,94 @@
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2007-2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+Ext.ns('Tine.Addressbook');
+
+/**
+ * the details panel (shows List details)
+ * 
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.ListGridDetailsPanel
+ * @extends     Tine.widgets.grid.DetailsPanel
+ */
+Tine.Addressbook.ListGridDetailsPanel = Ext.extend(Tine.widgets.grid.DetailsPanel, {
+    
+    il8n: null,
+    felamimail: false,
+    
+    /**
+     * init
+     */
+    initComponent: function() {
+
+        // init templates
+        this.initTemplate();
+        this.initDefaultTemplate();
+        
+        Tine.Addressbook.ListGridDetailsPanel.superclass.initComponent.call(this);
+    },
+
+    /**
+     * add on click event after render
+     */
+    afterRender: function() {
+        Tine.Addressbook.ListGridDetailsPanel.superclass.afterRender.apply(this, arguments);
+    },
+    
+    /**
+     * init default template
+     */
+    initDefaultTemplate: function() {
+        
+        this.defaultTpl = new Ext.XTemplate(
+            '<div class="preview-panel-list-nobreak">',    
+                '<!-- Preview contacts -->',
+                '<div class="preview-panel preview-panel-list-left">',
+                    '<div class="bordercorner_1"></div>',
+                    '<div class="bordercorner_2"></div>',
+                    '<div class="bordercorner_3"></div>',
+                    '<div class="bordercorner_4"></div>',
+                    '<div class="preview-panel-declaration">' + this.il8n._('Lists') + '</div>',
+                    '<div class="preview-panel-list-leftside preview-panel-left">',
+                        '<span class="preview-panel-bold">',
+                            this.il8n._('Select list') + '<br/>',
+                            '<br/>',
+                            '<br/>',
+                            '<br/>',
+                        '</span>',
+                    '</div>',
+                '</div>',
+            '</div>'        
+        );
+    },
+    
+    /**
+     * init single List template (this.tpl)
+     */
+    initTemplate: function() {
+        this.tpl = new Ext.XTemplate(
+            '<div class="preview-panel-list-nobreak">',    
+                '<!-- Preview contacts -->',
+                '<div class="preview-panel preview-panel-list-left">',
+                    '<div class="bordercorner_1"></div>',
+                    '<div class="bordercorner_2"></div>',
+                    '<div class="bordercorner_3"></div>',
+                    '<div class="bordercorner_4"></div>',
+                    '<div class="preview-panel-declaration">' + this.il8n._('Lists') + '</div>',
+                    '<div class="preview-panel-list-leftside preview-panel-left" style="width: 100%">',
+                        '<span class="preview-panel-bold">',
+                            this.il8n._('List') + '<br/>',
+                        '</span>',
+                        '<div style="word-wrap:break-word;">{[values.emails]}</div>',
+                    '</div>',
+                '</div>',
+            '</div>'
+        );
+    }
+});
diff --git a/tine20/Addressbook/js/ListMemberGridPanel.js b/tine20/Addressbook/js/ListMemberGridPanel.js
new file mode 100644 (file)
index 0000000..83ec286
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+Ext.ns('Tine.Addressbook');
+
+/**
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.ListMemberGridPanel
+ * @extends     Ext.grid.EditorGridPanel
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ */
+Tine.Addressbook.ListMemberGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
+    clicksToEdit: 1,
+
+    /**
+     * init component
+     */
+    initComponent: function() {
+        this.app = this.app ? this.app : Tine.Tinebase.appMgr.get('Addressbook');
+        
+        this.title = this.hasOwnProperty('title') ? this.title : this.app.i18n._('Members');
+        this.plugins = this.plugins || [];
+
+        this.sm = new Ext.grid.RowSelectionModel({singleSelect:true});
+        this.sm.on('selectionchange', function(sm){
+            this.removeBtn.setDisabled(sm.getCount() < 1);
+        }, this);
+
+        this.tbar = [{
+            text: this.app.i18n._('Add'),
+            handler: function(){
+                this.stopEditing();
+                this.store.insert(0, new Tine.Addressbook.Model.Contact({id: ""}));
+                this.getView().refresh();
+                this.getSelectionModel().selectRow(0);
+                this.startEditing(0, 0);
+            }.createDelegate(this)
+        },{
+            ref: '../removeBtn',
+            text: this.app.i18n._('Remove'),
+            disabled: true,
+            handler: function(){
+                this.stopEditing();
+                var s = this.getSelectionModel().getSelections();
+                for(var i = 0, r; r = s[i]; i++){
+                    this.store.remove(r);
+                }
+            }.createDelegate(this)
+        }]
+
+        this.initColumns();
+        this.store = this.store = new Ext.data.Store({
+            autoSave: false,
+            fields:  Tine.Addressbook.Model.Contact,
+            proxy: Tine.Addressbook.contactBackend,
+            reader: Tine.Addressbook.contactBackend.getReader(),
+        });
+
+        this.addListener("afteredit", this._afterEdit, this);
+
+        Tine.Addressbook.ListMemberGridPanel.superclass.initComponent.call(this);
+    },
+
+    /**
+     * initialises grid with an array of member uids
+     */
+    setMembers: function(members) {
+        if (members) {
+            var options = {params: {filter: [ { "field":"id","operator":"in", "value": members } ]}};
+            this.store.load(options);
+            this.store.sort("n_fileas");
+        }
+    },
+
+    /**
+     * returns current array of member uids
+     */
+    getMembers: function() {
+        var result = [];
+       for (var i = 0; i < this.store.getCount(); i++){
+            var item = this.store.getAt(i).data;
+            if (item.id != "") {
+                result.push(item.id);
+            }
+        } 
+        return result;
+    },
+
+    /**
+     * init columns
+     */
+    initColumns: function() {
+        this.editor = new Tine.Addressbook.SearchCombo({});
+        this.columns = [
+        {
+            id: 'n_fileas',
+            dataIndex: 'n_fileas',
+            width: 200,
+            sortable: true,
+            header: this.app.i18n._('Name'),
+            editor: this.editor
+        },
+        {
+            id: 'email',
+            dataIndex: 'email',
+            width: 300,
+            sortable: true,
+            header: this.app.i18n._('Email'),
+            editor: this.editor
+        }];
+    },
+
+    /**
+     * afteredit Event Handler
+     */
+    _afterEdit: function(e) {
+        this.store.removeAt(e.row);
+        this.store.insert(e.row, this.editor.selectedRecord);
+    }
+
+});
diff --git a/tine20/Addressbook/js/ListModel.js b/tine20/Addressbook/js/ListModel.js
new file mode 100644 (file)
index 0000000..cee9537
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Frederic Heihoff <heihoff@sh-systems.eu>
+ * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * list model
+ */
+Tine.Addressbook.Model.List = Tine.Tinebase.data.Record.create([
+   {name: 'id'},
+   {name: 'container_id'},
+   {name: 'created_by'},
+   {name: 'creation_time'},
+   {name: 'last_modified_by'},
+   {name: 'last_modified_time'},
+   {name: 'is_deleted'},
+   {name: 'deleted_time'},
+   {name: 'deleted_by'},
+   {name: 'name'},
+   {name: 'emails'},
+   {name: 'description'},
+   {name: 'members'},
+   {name: 'email'},
+   {name: 'type'},
+   {name: 'group_id'}
+], {
+    appName: 'Addressbook',
+    modelName: 'List',
+    idProperty: 'id',
+    titleProperty: 'name',
+    // ngettext('List', 'Lists', n); gettext('Lists');
+    recordName: 'List',
+    recordsName: 'Lists',
+    containerProperty: 'container_id',
+    // ngettext('Addressbook', 'Addressbooks', n); gettext('Addressbooks');
+    containerName: 'Addressbook',
+    containersName: 'Addressbooks',
+    copyOmitFields: ['group_id']
+});
+
+/**
+ * get filtermodel of list model
+ * 
+ * @namespace Tine.Addressbook.Model
+ * @static
+ * @return {Array} filterModel definition
+ */ 
+Tine.Addressbook.Model.List.getFilterModel = function() {
+    var app = Tine.Tinebase.appMgr.get('Addressbook');
+    
+    var typeStore = [['list', app.i18n._('List')], ['user', app.i18n._('User Account')]];
+    
+    return [
+        {label: _('Quick search'),                                                      field: 'query',              operators: ['contains']},
+        {filtertype: 'tine.widget.container.filtermodel', app: app, recordClass: Tine.Addressbook.Model.Contact},
+        {filtertype: 'addressbook.listMember', app: app},
+        {label: app.i18n._('Name'),                                               field: 'name' },
+        {label: app.i18n._('Description'),                                                field: 'description'},
+        {label: _('Last Modified Time'),                                                field: 'last_modified_time', valueType: 'date'},
+        {label: _('Last Modified By'),                                                  field: 'last_modified_by',   valueType: 'user'},
+        {label: _('Creation Time'),                                                     field: 'creation_time',      valueType: 'date'},
+        {label: _('Created By'),                                                        field: 'created_by',         valueType: 'user'}
+    ];
+};
+
+/**
+ * default list backend
+ */
+Tine.Addressbook.listBackend = new Tine.Tinebase.data.RecordProxy({
+    appName: 'Addressbook',
+    modelName: 'List',
+    recordClass: Tine.Addressbook.Model.List
+});
\ No newline at end of file
index 3bbb58e..bb53246 100644 (file)
@@ -46,6 +46,11 @@ Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPicke
      * @type Array
      */
     additionalFilters: null,
+
+    /**
+     * @cfg {Boolean} onlyContacts
+     */
+    onlyContacts: true,
     
     /**
      * use account objects/records in get/setValue
@@ -63,8 +68,13 @@ Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPicke
     
     //private
     initComponent: function(){
-        this.recordClass = Tine.Addressbook.Model.Contact;
-        this.recordProxy = Tine.Addressbook.contactBackend;
+        if (this.onlyContacts) {
+            this.recordClass = Tine.Addressbook.Model.Contact;
+            this.recordProxy = Tine.Addressbook.contactBackend;
+        } else {
+            this.recordClass = Tine.Addressbook.Model.EmailAddress;
+            this.recordProxy = Tine.Addressbook.emailaddressBackend;
+        }
 
         this.initTemplate();
         Tine.Addressbook.SearchCombo.superclass.initComponent.call(this);
index b106439..2ac57d9 100644 (file)
@@ -468,6 +468,10 @@ msgstr "Kontakte"
 msgid "Select contact"
 msgstr "Kontakt auswählen"
 
+#: js/ListGridDetailsPanel.js
+msgid "Select list"
+msgstr "Liste auswählen"
+
 #: js/ContactGridDetailsPanel.js:142 js/ContactGridDetailsPanel.js:166
 #: js/ContactEditDialog.js:213 js/Model.js:62 js/Model.js:146
 msgid "E-Mail"
@@ -694,3 +698,19 @@ msgstr "Neuer Kontakt"
 #: Controller/Contact.php:329
 msgid "Uploaded new contact image."
 msgstr "Ein neues Kontaktbild wurde hochgeladen."
+
+#: js/ListMemberGridPanel.js
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: js/ListMemberGridPanel.js
+msgid "Remove"
+msgstr "Entfernen"
+
+#: js/ListEditDialog.js
+msgid "Members"
+msgstr "Mitglieder"
+
+#: js/ListEditDialog.js
+msgid "List Information"
+msgstr "Listen Information"
index 0f541a7..c5b5c6c 100644 (file)
@@ -69,6 +69,12 @@ class Admin_Acl_Rights extends Tinebase_Acl_Rights_Abstract
     const MANAGE_CONTAINERS = 'manage_containers';
     
     /**
+     * the right to manage customfields
+     * @staticvar string
+     */
+    const MANAGE_CUSTOMFIELDS = 'manage_customfields';
+    
+    /**
      * the right to view roles
      * @staticvar string
      */
@@ -105,6 +111,20 @@ class Admin_Acl_Rights extends Tinebase_Acl_Rights_Abstract
     const VIEW_CONTAINERS = 'view_containers';
     
     /**
+     * the right to customfields
+     * @staticvar string
+     */
+    const VIEW_CUSTOMFIELDS = 'view_customfields';
+        
+    /**
+     * MOD: added right
+     * 
+     * the right to manage serverinfo
+     * @staticvar string
+     */
+    const VIEW_SERVERINFO = 'view_serverinfo';
+        
+    /**
      * holds the instance of the singleton
      *
      * @var Admin_Acl_Rights
@@ -159,12 +179,15 @@ class Admin_Acl_Rights extends Tinebase_Acl_Rights_Abstract
             self::MANAGE_ROLES,
             self::MANAGE_COMPUTERS,
             self::MANAGE_CONTAINERS,
+            self::MANAGE_CUSTOMFIELDS,
             self::VIEW_ACCESS_LOG,
             self::VIEW_ACCOUNTS,
             self::VIEW_APPS, 
             self::VIEW_ROLES,
             self::VIEW_COMPUTERS,
             self::VIEW_CONTAINERS,
+            self::VIEW_CUSTOMFIELDS,
+            self::VIEW_SERVERINFO,
         );
         $allRights = array_merge($allRights, $addRights);
         
@@ -209,6 +232,10 @@ class Admin_Acl_Rights extends Tinebase_Acl_Rights_Abstract
                 'text'          => $translate->_('manage containers'),
                 'description'   => $translate->_('add, delete and edit containers and manage container grants'),
             ),
+            self::MANAGE_CUSTOMFIELDS   => array(
+                'text'          => $translate->_('manage customfields'),
+                'description'   => $translate->_('add and edit customfields'),
+            ),            
             self::VIEW_ACCESS_LOG   => array(
                 'text'          => $translate->_('view access log'),
                 'description'   => $translate->_('view access log list'),
@@ -233,11 +260,17 @@ class Admin_Acl_Rights extends Tinebase_Acl_Rights_Abstract
                 'text'          => $translate->_('view containers'),
                 'description'   => $translate->_('view personal and shared containers'),
             ),
+            self::VIEW_CUSTOMFIELDS   => array(
+                'text'          => $translate->_('view customfields'),
+                'description'   => $translate->_('view customfields list'),
+            ),
+            self::VIEW_SERVERINFO   => array(
+                'text'          => $translate->_('view serverinfo'),
+                'description'   => $translate->_('view serverinfo list'),
+            ),
         );
         
         $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
         return $rightDescriptions;
     }
-
-    
 }
diff --git a/tine20/Admin/Event/DeleteAccount.php b/tine20/Admin/Event/DeleteAccount.php
new file mode 100644 (file)
index 0000000..49659fa
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Admin
+ * @license     
+ * @copyright   
+ * @author      
+ */
+
+/**
+ * event class for deleted accounts
+ *
+ *    MOD: added
+ *
+ * @package     Admin
+ */
+class Admin_Event_DeleteAccount extends Tinebase_Event_Abstract
+{
+    /**
+     * array of account ids
+     *
+     * @var array
+     */
+    public $accountIds;
+
+}
index 508ceeb..a067f16 100644 (file)
@@ -83,7 +83,13 @@ class Admin_Setup_DemoData extends Tinebase_Setup_DemoData_Abstract
                 $members[] = $this->_personas[$member]->getId();
             }
             
-            $this->_groups[$groupArray['groupData']['name']] = $fe->saveGroup($groupArray['groupData'], $members);
+            try {
+                $this->_groups[$groupArray['groupData']['name']] = $fe->saveGroup($groupArray['groupData'], $members);
+            } catch (Exception $e) {
+                echo 'Group "' . $groupArray['groupData']['name'] . '" already exists. Skipping...' . PHP_EOL;
+                $gr = Tinebase_Group::getInstance()->getGroupByName($groupArray['groupData']['name']);
+                $this->_groups[$groupArray['groupData']['name']] = $fe->getGroup($gr->getId());
+            }
         }
     }
     
@@ -105,9 +111,13 @@ class Admin_Setup_DemoData extends Tinebase_Setup_DemoData_Abstract
             // resolve rights
             $roleRights = array();
             foreach($roleArray['roleRights'] as $application => $rights) {
-                $appId = Tinebase_Application::getInstance()->getApplicationByName($application)->getId();
-                foreach($rights as $rightName) { 
-                    $roleRights[] = array('application_id' => $appId, 'right' => $rightName);
+                try {
+                    $appId = Tinebase_Application::getInstance()->getApplicationByName($application)->getId();
+                    foreach($rights as $rightName) {
+                        $roleRights[] = array('application_id' => $appId, 'right' => $rightName);
+                    }
+                } catch (Exception $e) {
+                    echo 'Application "' . $application . '" not installed. Skipping...' . PHP_EOL;
                 }
             }
             
@@ -199,4 +209,4 @@ class Admin_Setup_DemoData extends Tinebase_Setup_DemoData_Abstract
         $this->_createGroups();
         $this->_createRoles();
     }
-}
+}
\ No newline at end of file
index 0e1ca84..ee2edae 100644 (file)
@@ -143,7 +143,8 @@ Tine.Admin = function () {
             children: [],
             leaf: null,
             expanded: true,
-            dataPanelType: "serverinfo"
+            dataPanelType: "serverinfo",
+            viewRight: 'serverinfo'
         }];
     };
 
index b79d162..8485334 100644 (file)
@@ -38,9 +38,11 @@ Tine.Admin.Applications.Main = function() {
     };
     
     var _openSettingsWindow = function(appName) {
+        var translation = new Locale.Gettext();
+        translation.textdomain('Admin');
         Tine[appName].AdminPanel.openWindow({
             record: (Tine[appName].Model.Settings) ? new Tine[appName].Model.Settings(appName) : null,
-            title: String.format(_('{0} Settings'), translateAppTitle(appName)),
+            title: String.format(translation.gettext('{0} Settings'), translateAppTitle(appName)),
             listeners: {
                 scope: this,
                 'update': (Tine[appName].AdminPanel.onUpdate) ? Tine[appName].AdminPanel.onUpdate : Ext.emptyFn
index 8dcb6da..0b4e837 100644 (file)
@@ -512,7 +512,7 @@ Tine.Admin.CustomfieldEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
             layoutConfig: {
                 align: 'stretch',
                 pack: 'start'
-            },
+            }, 
             border: false,
             items: [{
                 xtype: 'columnform',
index 7fd3e85..d34bc58 100644 (file)
@@ -765,7 +765,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
         });
         this.passwordConfirmWindow.render(document.body);
         
-        return {
+        var config = {
             xtype: 'tabpanel',
             deferredRender: false,
             border: false,
@@ -1015,6 +1015,7 @@ Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                 items: this.initSmtp()
             }]
         };
+        return config;
     }
 });
 
index 11840a0..88801ab 100644 (file)
@@ -45,15 +45,15 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
     }
     
     /**
-     * convert Calendar_Model_Event to Sabre_VObject_Component
+     * convert Tinebase_Record_RecordSet to Sabre_VObject_Component
      *
-     * @param  Calendar_Model_Event  $_record
+     * @param  Tinebase_Record_RecordSet  $_records
      * @return Sabre_VObject_Component
      */
-    public function fromTine20Model(Tinebase_Record_Abstract $_record)
+    public function fromTine20RecordSet(Tinebase_Record_RecordSet $_records)
     {
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
-            . ' event ' . print_r($_record->toArray(), true));
+            . ' Events: ' . print_r($_records->toArray(), true));
         
         $vcalendar = new Sabre_VObject_Component('VCALENDAR');
         
@@ -66,20 +66,22 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
         $vcalendar->VERSION  = '2.0';
         $vcalendar->CALSCALE = 'GREGORIAN';
         
-        $vcalendar->add(new Sabre_VObject_Component_VTimezone($_record->originator_tz));
+        $vcalendar->add(new Sabre_VObject_Component_VTimezone($_records->getFirstRecord()->originator_tz));
         
-        $vevent = $this->_convertCalendarModelEvent($_record);
-        $vcalendar->add($vevent);
-        
-        if ($_record->exdate instanceof Tinebase_Record_RecordSet) {
-            $_record->exdate->addIndices(array('is_deleted'));
-            $eventExceptions = $_record->exdate->filter('is_deleted', false);
+        foreach ($_records as $_record) {
+            $vevent = $this->_convertCalendarModelEvent($_record);
+            $vcalendar->add($vevent);
             
-            foreach ($eventExceptions as $eventException) {
-                $vevent = $this->_convertCalendarModelEvent($eventException, $_record);
-                $vcalendar->add($vevent);
+            if ($_record->exdate instanceof Tinebase_Record_RecordSet) {
+                $_record->exdate->addIndices(array('is_deleted'));
+                $eventExceptions = $_record->exdate->filter('is_deleted', false);
+                
+                foreach ($eventExceptions as $eventException) {
+                    $vevent = $this->_convertCalendarModelEvent($eventException, $_record);
+                    $vcalendar->add($vevent);
+                }
+                
             }
-            
         }
         
         $this->_afterFromTine20Model($vcalendar);
@@ -88,6 +90,18 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
         
         return $vcalendar;
     }
+
+    /**
+     * convert Calendar_Model_Event to Sabre_VObject_Component
+     *
+     * @param  Calendar_Model_Event  $_record
+     * @return Sabre_VObject_Component
+     */
+    public function fromTine20Model(Tinebase_Record_Abstract $_record)
+    {
+        $_records = new Tinebase_Record_RecordSet(get_class($_record), array($_record), true, false);
+        return $this->fromTine20RecordSet($_records);
+    }
     
     /**
      * convert calendar event to Sabre_VObject_Component
index 58ed1a7..ff7dd0c 100644 (file)
@@ -38,6 +38,10 @@ class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
                 'dbname'   => 'dbname'
             )
         ),
+        'exportICS' => array(  
+            'description' => "export calendar as ics", 
+            'params' => array('container_id') 
+        ),
     );
     
     /**
@@ -51,6 +55,29 @@ class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
     }
     
     /**
+     * exports calendars as ICS
+     *
+     * @param Zend_Console_Getopt $_opts
+     */
+    public function exportICS($_opts)
+    {
+        $opts = $_opts->getRemainingArgs();
+        $container_id = $opts[0];
+        $filter = new Calendar_Model_EventFilter(array(
+            array(
+                'field'     => 'container_id',
+                'operator'  => 'equals',
+                'value'     => $container_id
+            )
+
+        ));
+        $result = Calendar_Controller_MSEventFacade::getInstance()->search($filter, null, false, false, 'get');
+        $converter = Calendar_Convert_Event_VCalendar_Factory::factory("generic");
+        $result = $converter->fromTine20RecordSet($result);
+        print $result->serialize();
+    }
+    
+    /**
      * delete duplicate events
      * 
      * @see 0008182: event with lots of exceptions breaks calendar sync
index 81f99f6..ae68127 100644 (file)
@@ -57,8 +57,8 @@ Tine.Calendar.AddressbookGridPanelHook = function(config) {
         }
     });
     
-    Ext.ux.ItemRegistry.registerItem('Addressbook-GridPanel-ContextMenu-Add', this.addEventAction, 100);
-    Ext.ux.ItemRegistry.registerItem('Addressbook-GridPanel-ContextMenu-New', this.newEventAction, 100);
+    Ext.ux.ItemRegistry.registerItem('Addressbook-Contact-GridPanel-ContextMenu-Add', this.addEventAction, 100);
+    Ext.ux.ItemRegistry.registerItem('Addressbook-Contact-GridPanel-ContextMenu-New', this.newEventAction, 100);
     
 };
 
index 558bf94..badc3c0 100644 (file)
@@ -27,23 +27,24 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
     cls: 'x-cal-attendee-filter-grid',
     addNewAttendeeText: 'Add attendee', // _('Add attendee')
     
+    enableDragDrop: true,
+    ddGroup: 'Tine.Calendar.AttendeeFilterGrid.Sort',
+    
     initComponent: function() {
         this.record = new Tine.Calendar.Model.Event({
             editGrant: true
         });
         
-        this.selModel = new Ext.grid.RowSelectionModel();
+        this.selModel = new Ext.grid.RowSelectionModel({singleSelect: true});
         this.selModel.on('beforerowselect', this.onBeforeRowSelect, this);
         
         Tine.Calendar.AttendeeFilterGrid.superclass.initComponent.call(this);
         
-        // apply  initial state
-        var explicitAttendee = Ext.state.Manager.get(this.stateId);
-        this.applyState(explicitAttendee);
-        
         this.store.on('add', this.onStoreAdd, this);
         this.store.on('remove', this.onStoreRemove, this);
         this.store.on('update', this.onStoreUpdate, this, {buffer: 100});
+        
+        this.ddText = this.app.i18n._('Sort Attendee');
     },
     
     initColumns: function() {
@@ -63,6 +64,45 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
         this.plugins.push(this.columns[0]);
     },
     
+    afterRender: function() {
+        Tine.Calendar.AttendeeFilterGrid.superclass.afterRender.apply(this, arguments);
+        this.dropZone = new Ext.ux.grid.GridDropZone(this, {
+            ddGroup: this.ddGroup,
+            isValidDropPoint: function(target, pt, dd, e, data) {
+                var addIdx = this.grid.store.getCount() -1;
+                return this.view.findRowIndex(target) != addIdx && data.rowIndex != addIdx;
+            },
+            onNodeDrop : function(target, dd, e, data){
+                var pt = this.getDropPoint(e, target, dd),
+                    targetRowIndex = this.view.findRowIndex(target),
+                    targetPos = pt == 'above' ? targetRowIndex : targetRowIndex + 1;
+                
+                if (this.isValidDropPoint(target, pt, dd, e, data)) {
+                    var store = this.grid.getStore();
+                    
+                    store.suspendEvents();
+                    
+                    Ext.each(data.selections, function(r) {
+                        var currentIdx = store.indexOf(r);
+                        if (currentIdx >= 0) {
+                            targetPos = targetPos > currentIdx ? targetPos -1 : targetPos;
+                            store.remove(r);
+                        }
+                        store.insert(targetPos, data.selections);
+                    }, this);
+                    
+                    store.each(function(r,idx) {r.set('sort', idx)});
+                    store.sort('sort', 'ASC');
+                    store.resumeEvents();
+                    
+                    this.grid.getView().refresh();
+                    this.grid.getView().updateSortIcon('sort', 'ASC');
+                    this.grid.fireEvent('sortchange', this.grid, this.grid.getState());
+                }
+            }
+        });
+    },
+    
     onBeforeRowSelect: function(sm, idx, keep, attendee) {
         if (! attendee.get('user_id')) {
             return false;
@@ -72,12 +112,12 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
     onStoreAdd: function() {
         // don't save initial 'add attendee' record
         if (this.store.getCount() > 1) {
-            Ext.state.Manager.set(this.stateId, this.getState());
+            this.saveState();
         }
     },
     
     onStoreRemove: function() {
-        Ext.state.Manager.set(this.stateId, this.getState());
+        this.saveState();
         this.onStoreChange();
     },
     
@@ -107,7 +147,10 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
         } catch (e) {}
     },
     
-    applyState: function(explicitAttendee) {
+    applyState: function(state) {
+        this.stateful = false;
+        var explicitAttendee = (!state || Ext.isArray(state)) ? state : state.explicitAttendee;
+        
         var rs = [];
         Ext.each(explicitAttendee, function(attendeeData) {
             var attendee = new Tine.Calendar.Model.Attender(attendeeData, 'new-' + Ext.id());
@@ -117,18 +160,41 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
         
         this.store.removeAll();
         this.store.add(rs);
+        
+        if(state && state.sort){
+            if (state.sort.field == 'sort') {
+                this.store.each(function(attendee) {
+                    var idx = state.sort.order.indexOf(attendee.get('user_type') + '-' + attendee.getUserId());
+                    attendee.data.sort = idx >= 0 ? idx : 1000;
+                }, this);
+            }
+            this.store.sort(state.sort.field, state.sort.direction);
+            this.getView().sortState = state.sort;
+        }
+        this.stateful = true;
     },
     
     getState: function() {
-        var explicitAttendee = [];
+        var explicitAttendee = [],
+            sort = this.store.getSortState(),
+            order = [];
         
         this.store.each(function(attendee) {
-            if (attendee.get('user_id') && attendee.explicitlyAdded) {
-                explicitAttendee.push(attendee.data)
+            if (attendee.get('user_id')){
+                order.push(attendee.get('user_type') + '-' + attendee.getUserId());
+                if (attendee.explicitlyAdded) {
+                    explicitAttendee.push(attendee.data);
+                }
             }
         }, this);
         
-        return explicitAttendee;
+        if (sort.field == 'sort') {
+            sort.order = order;
+        }
+        return {
+            explicitAttendee: explicitAttendee,
+            sort: sort
+        }
     },
     
     /**
@@ -185,11 +251,11 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
     setValue: function(value) {
         var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(value),
             selections = this.getSelectionModel().getSelections(),
-            explicitAttendee = Ext.state.Manager.get(this.stateId),
-            activeEditor = this.activeEditor;
+            activeEditor = this.activeEditor,
+            state = Ext.state.Manager.get(this.stateId);
         
         this.store.suspendEvents();
-        this.applyState(explicitAttendee);
+        this.applyState(state);
         
         this.store.each(function(attendee) {
             attendee.set('checked', false);
@@ -198,6 +264,11 @@ Tine.Calendar.AttendeeFilterGrid = Ext.extend(Tine.Calendar.AttendeeGridPanel, {
         attendeeStore.each(function(attendee) {
             var currentAttendee = Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(this.store, attendee);
             if (! currentAttendee) {
+                if (state && state.sort && state.sort.order) {
+                    var idx = state.sort.order.indexOf(attendee.get('user_type') + '-' + attendee.getUserId());
+                    attendee.data.sort = idx >= 0 ? idx : 1000;
+                }
+                
                 this.store.add(attendee);
                 attendee.set('checked', true);
             } else {
index f151025..09bbcdd 100644 (file)
@@ -86,8 +86,24 @@ Tine.Calendar.AttendeeGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
         }
         
         this.store = new Ext.data.SimpleStore({
-            fields: Tine.Calendar.Model.Attender.getFieldDefinitions(),
-            sortInfo: {field: 'user_id', direction: 'ASC'}
+            fields: Tine.Calendar.Model.Attender.getFieldDefinitions().concat('sort'),
+            sortInfo: {field: 'user_id', direction: 'ASC'},
+            sortData : function(f, direction){
+                direction = direction || 'ASC';
+                var st = this.fields.get(f).sortType;
+                var fn = function(r1, r2){
+                    // make sure new-attendee line is on the bottom
+                    if (!r1.data.user_id) return direction == 'ASC';
+                    if (!r2.data.user_id) return direction != 'ASC';
+                    
+                    var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
+                    return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+                };
+                this.data.sort(direction, fn);
+                if(this.snapshot && this.snapshot != this.data){
+                    this.snapshot.sort(direction, fn);
+                }
+            }
         });
         
         this.on('beforeedit', this.onBeforeAttenderEdit, this);
index 7e0b4d8..48e9f4c 100644 (file)
@@ -11,7 +11,6 @@ Ext.ns('Tine.Calendar');
 /**
  * @namespace Tine.Calendar
  * @class Tine.Calendar.ColorManager
- * @extends Ext.util.Observable
  * Colormanager for Coloring Calendar Events <br>
  * 
  * @constructor
@@ -23,84 +22,13 @@ Ext.ns('Tine.Calendar');
  */
 Tine.Calendar.ColorManager = function(config) {
     Ext.apply(this, config);
-    
-    this.colorMap = {};
-    
-    // allthough we don't extend component as we have nothing to render, we borrow quite some stuff from it
-    this.id = this.stateId;
-    Ext.ComponentMgr.register(this);
-    
-    this.addEvents(
-        /**
-         * @event beforestaterestore
-         * Fires before the state of this colormanager is restored. Return false to stop the restore.
-         * @param {Tine.Calendar.ColorManager} this
-         * @param {Object} state The hash of state values
-         */
-        'beforestaterestore',
-        /**
-         * @event staterestore
-         * Fires after the state of tthis colormanager is restored.
-         * @param {Tine.Calendar.ColorManager} this
-         * @param {Object} state The hash of state values
-         */
-        'staterestore',
-        /**
-         * @event beforestatesave
-         * Fires before the state of this colormanager is saved to the configured state provider. Return false to stop the save.
-         * @param {Tine.Calendar.ColorManager} this
-         * @param {Object} state The hash of state values
-         */
-        'beforestatesave',
-        /**
-         * @event statesave
-         * Fires after the state of this colormanager is saved to the configured state provider.
-         * @param {Tine.Calendar.ColorManager} this
-         * @param {Object} state The hash of state values
-         */
-        'statesave'
-    );
-    
-    if (this.stateful) {
-        this.initState();
-    }
-   
 };
 
-Ext.extend(Tine.Calendar.ColorManager, Ext.util.Observable, {
-    /**
-     * @cfg {String} schemaName
-     * Name of color schema to use
-     */
-    schemaName: 'standard',
-    
-    /**
-     * @cfg {String} stateId
-     * State id to use
-     */
-    stateId: 'cal-color-mgr-containers',
-    
+Ext.apply(Tine.Calendar.ColorManager.prototype, {
     /**
-     * @cfg {Boolean} stateful
-     * Is this component statefull?
+     * @type String
      */
-    stateful: false,
-    
-    /**
-     * current color map 
-     * 
-     * @type Object 
-     * @propertycolorMap
-     */
-    colorMap: null,
-    
-    /**
-     * pointer to current color set in color schema 
-     * 
-     * @type Number 
-     * @property colorSchemataPointer
-     */
-    colorSchemataPointer: 0,
+    strategy: 'container',
     
     /**
      * gray color set
@@ -111,14 +39,6 @@ Ext.extend(Tine.Calendar.ColorManager, Ext.util.Observable, {
     gray: {color: '#808080', light: '#EDEDED', text: '#FFFFFF', lightText: '#FFFFFF'},
     
     /**
-     * color palette from Ext.ColorPalette
-     * 
-     * @type Array
-     * @property colorPalette
-     */
-    colorPalette: Ext.ColorPalette.prototype.colors,
-    
-    /**
      * color sets for colors from colorPalette
      * 
      * @type Array 
@@ -168,6 +88,10 @@ Ext.extend(Tine.Calendar.ColorManager, Ext.util.Observable, {
         "FFFFFF" : {color: '#DFDFDF', light: '#F8F8F8', text: '#000000', lightText: '#000000'}
     },
     
+    getStrategy: function() {
+        return Tine.Calendar.colorStrategies[this.strategy];
+    },
+    
     /**
      * hack for container only support
      * 
@@ -175,29 +99,14 @@ Ext.extend(Tine.Calendar.ColorManager, Ext.util.Observable, {
      * @return {Object} colorset
      */
     getColor: function(event) {
-        var container = null,
-            color = null,
-            schema = null;
+        var color = this.getStrategy().getColor(event);
         
-        if (! Ext.isFunction(event.get)) {
-            // tree comes with containers only
-            container = event;
-        } else {
-            
-            container = event.get('container_id');
-            
-            // take displayContainer if user has no access to origin
-            if (Ext.isPrimitive(container)) {
-                container = event.getDisplayContainer();
-            }
-        }
-        
-        color = String(container.color).replace('#', '');
+        color = String(color).replace('#', '');
         if (! color.match(/[0-9a-fA-F]{6}/)) {
             return this.gray;
         }
         
-        schema = this.colorSchemata[container.color.replace('#', '')];
+        var schema = this.colorSchemata[color];
         return schema ? schema : this.getCustomSchema(color);
     },
     
@@ -237,4 +146,35 @@ Tine.Calendar.ColorManager.compare = function(color1, color2, abs) {
     var diff = [c1[0] - c2[0], c1[1] - c2[1], c1[2] - c2[2]];
     
     return abs ? (Math.abs(diff[0]) + Math.abs(diff[1]) + Math.abs(diff[2])) : diff;
-};
\ No newline at end of file
+};
+
+/**
+ * Color Strategies Registry
+ * 
+ * @type Object
+ */
+Tine.Calendar.colorStrategies = {};
+Tine.Calendar.colorStrategies['container'] = {
+    getColor: function(event) {
+        var container = null,
+            color = null,
+            schema = null;
+        
+        if (! Ext.isFunction(event.get)) {
+            // tree comes with containers only
+            container = event;
+        } else {
+            
+            container = event.get('container_id');
+            
+            // take displayContainer if user has no access to origin
+            if (Ext.isPrimitive(container)) {
+                container = event.getDisplayContainer();
+            }
+        }
+        
+        return String(container.color).replace('#', '');
+    }
+};
+
+
index ca6fc42..160547a 100644 (file)
@@ -312,12 +312,16 @@ Tine.Calendar.MainScreenCenterPanel = Ext.extend(Ext.Panel, {
     onRender: function(ct, position) {
         Tine.Calendar.MainScreenCenterPanel.superclass.onRender.apply(this, arguments);
         
-        var defaultFavorite = Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName),
+        var defaultFavorite = Tine.widgets.persistentfilter.model.PersistentFilter.getDefaultFavorite(this.app.appName, this.recordClass.prototype.modelName),
             favoritesPanel  = this.app.getMainScreen().getWestPanel().getFavoritesPanel();
         
         this.loadMask = new Ext.LoadMask(this.body, {msg: this.loadMaskText});
         
-        favoritesPanel.selectFilter(defaultFavorite);
+        if (defaultFavorite) {
+            favoritesPanel.selectFilter(defaultFavorite);
+        } else {
+            this.refresh();
+        }
     },
     
     getViewParts: function (view) {
index e124a7d..ad485aa 100644 (file)
@@ -502,7 +502,7 @@ Tine.Calendar.Model.Attender.getAttendeeStore = function(attendeeData) {
     
     Ext.each(attendeeData, function(attender) {
         if (attender) {
-            var record = new Tine.Calendar.Model.Attender(attender, attender.id);
+            var record = new Tine.Calendar.Model.Attender(attender, attender.id && Ext.isString(attender.id) ? attender.id : Ext.id());
             attendeeStore.addSorted(record);
         }
     });
index 5477159..492772e 100644 (file)
@@ -35,26 +35,23 @@ Tine.Calendar.FilterPanel = Ext.extend(Tine.widgets.persistentfilter.PickerPanel
     storeOnBeforeload: function(store, options) {
         store.un('beforeload', this.storeOnBeforeload, this);
         
-        var selection = this.getSelectionModel().getSelectedNode();
-        if (selection && selection.id) {
-            options.params.filter = this.store.getById(selection.id).get('filters');
-            
-            // take a full clone to not taint the original filter
-            options.params.filter = Ext.decode(Ext.encode(options.params.filter));
-            
-            var cp = Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getCenterPanel();
-            var period = cp.getCalendarPanel(cp.activeView).getView().getPeriod();
-            
-            // remove all existing period filters
-            Ext.each(options.params.filter, function(filter) {
-                if (filter.field === 'period') {
-                    options.params.filter.remove(filter);
-                    return false;
-                }
-            }, this);
-            
-            options.params.filter.push({field: 'period', operator: 'within', value: period});
-        }
+        options.params.filter = options.persistentFilter.get('filters');
+        
+        // take a full clone to not taint the original filter
+        options.params.filter = Ext.decode(Ext.encode(options.params.filter));
+        
+        var cp = Tine.Tinebase.appMgr.get('Calendar').getMainScreen().getCenterPanel();
+        var period = cp.getCalendarPanel(cp.activeView).getView().getPeriod();
+        
+        // remove all existing period filters
+        Ext.each(options.params.filter, function(filter) {
+            if (filter.field === 'period') {
+                options.params.filter.remove(filter);
+                return false;
+            }
+        }, this);
+        
+        options.params.filter.push({field: 'period', operator: 'within', value: period});
     }
 });
 
index 419724d..afb2c9e 100644 (file)
@@ -13,7 +13,7 @@ Ext.namespace('Tine.Courses');
 Tine.Courses.MainScreen = Ext.extend(Tine.widgets.MainScreen, {
     activeContentType: 'Course',
     contentTypes: [
-        {model: 'Course',  requiredRight: null, singularContainerMode: true}
+        {modelName: 'Course',  requiredRight: null, singularContainerMode: true}
     ]
 });
 
index bd083c5..d718ae9 100644 (file)
@@ -69,11 +69,11 @@ class Crm_Controller extends Tinebase_Controller_Event implements Tinebase_Conta
      */
     public static function getInstance() 
     {
-        if (self::$_instance === NULL) {
-            self::$_instance = new self();
+        if (static::$_instance === NULL) {
+            static::$_instance = new self();
         }
         
-        return self::$_instance;
+        return static::$_instance;
     }    
         
     /********************* event handler and personal folder ***************************/
index 6a54105..89d98b1 100644 (file)
@@ -61,7 +61,7 @@ class Crm_Controller_Lead extends Tinebase_Controller_Record_Abstract
     public static function getInstance() 
     {
         if (self::$_instance === NULL) {
-            self::$_instance = new Crm_Controller_Lead();
+            self::$_instance = new self();
         }
         
         return self::$_instance;
index 544948e..403cbbb 100644 (file)
@@ -60,8 +60,8 @@ Tine.Crm.AddressbookGridPanelHook = function(config) {
     });
     
     // register in contextmenu
-    Ext.ux.ItemRegistry.registerItem('Addressbook-GridPanel-ContextMenu-New', this.addLeadAction, 80);
-    Ext.ux.ItemRegistry.registerItem('Addressbook-GridPanel-ContextMenu-Add', this.updateLeadAction, 80);
+    Ext.ux.ItemRegistry.registerItem('Addressbook-Contact-GridPanel-ContextMenu-New', this.addLeadAction, 80);
+    Ext.ux.ItemRegistry.registerItem('Addressbook-Contact-GridPanel-ContextMenu-Add', this.updateLeadAction, 80);
 
 };
 
index 43e90e5..2df0cf5 100644 (file)
@@ -47,7 +47,7 @@ Tine.Crm.Application = Ext.extend(Tine.Tinebase.Application, {
 Tine.Crm.MainScreen = Ext.extend(Tine.widgets.MainScreen, {
     activeContentType: 'Lead',
     contentTypes: [
-        {model: 'Lead', requiredRight: null, singularContainerMode: false}
+        {modelName: 'Lead', requiredRight: null, singularContainerMode: false}
         ]
 });
 
index a65c6ac..ad08aa9 100644 (file)
@@ -40,7 +40,7 @@ Tine.Crm.Model.Lead = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.gener
         {name: 'products'},
         {name: 'tags'},
         {name: 'notes'},
-        {name: 'customfields', isMetaField: true}
+        {name: 'customfields', omitDuplicateResolving: true}
     ]), {
     appName: 'Crm',
     modelName: 'Lead',
index d6e2903..417b969 100644 (file)
@@ -318,56 +318,4 @@ class Felamimail_Backend_Cache_Sql_Message extends Tinebase_Backend_Sql_Abstract
         
         return $this->_db->delete($this->_tablePrefix . $this->_tableName, $where);
     }
-
-    /**
-     * converts record into raw data for adapter
-     *
-     * @param  Tinebase_Record_Abstract $_record
-     * @return array
-     */
-    protected function _recordToRawData($_record)
-    {
-        $result = parent::_recordToRawData($_record);
-        
-        if(isset($result['structure'])) {
-            $result['structure'] = Zend_Json::encode($result['structure']);
-        }
-        
-        return $result;
-    }
-    
-    /**
-     * converts raw data from adapter into a single record
-     *
-     * @param  array $_rawData
-     * @return Tinebase_Record_Abstract
-     */
-    protected function _rawDataToRecord(array $_rawData)
-    {
-        if (isset($_rawData['structure'])) {
-            $_rawData['structure'] = Zend_Json::decode($_rawData['structure']);
-        }
-        
-        $result = parent::_rawDataToRecord($_rawData);
-                
-        return $result;
-    }
-    
-    /**
-     * converts raw data from adapter into a set of records
-     *
-     * @param  array $_rawDatas of arrays
-     * @return Tinebase_Record_RecordSet
-     */
-    protected function _rawDataToRecordSet(array $_rawDatas)
-    {
-        foreach($_rawDatas as &$_rawData) {
-            if(isset($_rawData['structure'])) {
-                $_rawData['structure'] = Zend_Json::decode($_rawData['structure']);
-            }
-        }
-        $result = parent::_rawDataToRecordSet($_rawDatas);
-        
-        return $result;
-    }
 }
index 5a6a0e5..eed9ea1 100644 (file)
@@ -60,7 +60,7 @@ class Felamimail_Backend_Folder extends Tinebase_Backend_Sql_Abstract
                 array('cache_totalcount' => new Zend_Db_Expr('COUNT(DISTINCT(felamimail_cache_msg_flag.message_id))'))
             )
             ->where($this->_db->quoteIdentifier('felamimail_cache_msg_flag.folder_id') . ' = ?', $folderId)
-            ->where($this->_db->quoteIdentifier('felamimail_cache_msg_flag.flag') . ' = ?', '\\Seen');
+            ->where($this->_db->quoteIdentifier('felamimail_cache_msg_flag.flag') . ' = ?', $this->_dbCommand->escapeSpecialChar(Zend_Mail_Storage::FLAG_SEEN));
         
         $stmt = $this->_db->query($select);
         $seenCount = $stmt->fetchColumn(0);
index 361d480..7c1296f 100644 (file)
     color: #aaaaaa;
 }
 
+/*------------ ExtJS clears the following default browser's styles -----------*/
+strong { 
+    font-weight: bold; 
+    font-style: inherit;
+}
+em { 
+    font-style: italic; 
+    font-weight: inherit;
+}
+
index 2e140fd..a85be7f 100644 (file)
@@ -37,7 +37,7 @@ Tine.Felamimail.ComposeEditor = Ext.extend(Ext.form.HtmlEditor, {
             + '<title></title>'
             + '<style type="text/css">'
                 // standard css reset
-                + "html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}img,body,html{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}"
+                + "html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}img,body,html{border:0;}address,caption,cite,code,dfn,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}"
                 // small forms
                 + "html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{font-size: small;}"
                 // lists
index a70c4a9..32106f6 100644 (file)
@@ -34,6 +34,16 @@ Tine.Felamimail.ContactSearchCombo = Ext.extend(Tine.Addressbook.SearchCombo, {
     forceSelection: false,
     
     /**
+     * @cfg {Boolean} onlyContacts
+     */
+    onlyContacts: false,
+
+    /**
+     * @private
+     */ 
+    valueIsList: false,
+
+    /**
      * @private
      */
     initComponent: function() {
@@ -43,23 +53,36 @@ Tine.Felamimail.ContactSearchCombo = Ext.extend(Tine.Addressbook.SearchCombo, {
         this.tpl = new Ext.XTemplate(
             '<tpl for="."><div class="search-item">',
                 '{[this.encode(values.n_fileas)]}',
-                ' (<b>{[this.encode(values.email, values.email_home)]}</b>)',
+                ' (<b>{[this.encode(values.email, values.email_home, this.shorten(values.emails))]}</b>)',
             '</div></tpl>',
             {
-                encode: function(email, email_home) {
+                encode: function(email, email_home, emails) {
                     if (email) {
                         return Ext.util.Format.htmlEncode(email);
                     } else if (email_home) {
                         return Ext.util.Format.htmlEncode(email_home);
+                    } else if (emails) {
+                        return Ext.util.Format.htmlEncode(emails);
                     } else {
                         return '';
                     }
+                },
+                shorten: function(text) {
+                    if (text) {
+                        if (text.length < 50) {
+                            return text;
+                        } else {
+                            return text.substr(0,50) + "...";
+                        }
+                    } else {
+                        return "";
+                    }
                 }
             }
         );
         
         Tine.Felamimail.ContactSearchCombo.superclass.initComponent.call(this);
-        
+
         this.store.on('load', this.onStoreLoad, this);
     },
     
@@ -71,13 +94,26 @@ Tine.Felamimail.ContactSearchCombo = Ext.extend(Tine.Addressbook.SearchCombo, {
      * @private
      */
     onSelect: function(record, index) {
-        var value = Tine.Felamimail.getEmailStringFromContact(record);
-        this.setValue(value);
+        if (!record.get("emails")) {
+            var value = Tine.Felamimail.getEmailStringFromContact(record);
+            this.setValue(value);
+            this.valueIsList = false;
+        } else {
+            this.setValue(record.get("emails"));
+            this.valueIsList = true;
+        }
         
         this.collapse();
         this.fireEvent('blur', this);
         this.fireEvent('select', this, record, index);
     },
+
+    /** 
+     * @return bool
+     */
+    getValueIsList: function() {
+        return this.valueIsList;
+    },
     
     /**
      * always return raw value
@@ -87,6 +123,16 @@ Tine.Felamimail.ContactSearchCombo = Ext.extend(Tine.Addressbook.SearchCombo, {
     getValue: function() {
         return this.getRawValue();
     },
+
+    /**
+     * always set valueIsList to false
+     *
+     * @param String value
+     */
+    setValue: function(value) {
+       this.valueIsList = false;
+       Tine.Felamimail.ContactSearchCombo.superclass.setValue.call(this, value); 
+    },   
     
     /**
      * on load handler of combo store
index ebd966a..9bab0f5 100644 (file)
@@ -106,14 +106,21 @@ Tine.Felamimail.Application = Ext.extend(Tine.Tinebase.Application, {
             }
             
             this.showActiveVacation();
-            var adbHook = new Tine.Felamimail.GridPanelHook({
+            var adbHook1 = new Tine.Felamimail.GridPanelHook({
                 app: this,
-                foreignAppName: 'Addressbook'
+                foreignAppName: 'Addressbook',
+                recordTypeName: 'Contact'
+            });
+            var adbHook2 = new Tine.Felamimail.GridPanelHook({
+                app: this,
+                foreignAppName: 'Addressbook',
+                recordTypeName: 'List'
             });
             var crmHook = new Tine.Felamimail.GridPanelHook({
                 app: this,
                 foreignAppName: 'Crm',
                 contactInRelation: true,
+                recordTypeName: 'Lead',
                 relationType: 'CUSTOMER'
             });
         }
index 32beb5d..a9544e9 100644 (file)
@@ -48,10 +48,11 @@ Tine.Felamimail.GridPanelHook = function(config) {
         rowspan: 2,
         iconAlign: 'top'
     });
-    
+
+
     // register in toolbar + contextmenu
-    Ext.ux.ItemRegistry.registerItem(this.foreignAppName + '-GridPanel-ActionToolbar-leftbtngrp', this.composeMailBtn, 30);
-    Ext.ux.ItemRegistry.registerItem(this.foreignAppName + '-GridPanel-ContextMenu', this.composeMailAction, 80);
+    Ext.ux.ItemRegistry.registerItem(this.foreignAppName + '-' + this.recordTypeName + '-GridPanel-ActionToolbar-leftbtngrp', this.composeMailBtn, 30);
+    Ext.ux.ItemRegistry.registerItem(this.foreignAppName + '-' + this.recordTypeName + '-GridPanel-ContextMenu', this.composeMailAction, 80);
 };
 
 Ext.apply(Tine.Felamimail.GridPanelHook.prototype, {
@@ -100,7 +101,6 @@ Ext.apply(Tine.Felamimail.GridPanelHook.prototype, {
         if (! this.gridPanel) {
             this.gridPanel = Tine.Tinebase.appMgr.get(this.foreignAppName).getMainScreen().getCenterPanel();
         }
-        
         return this.gridPanel;
     },
     
@@ -119,11 +119,11 @@ Ext.apply(Tine.Felamimail.GridPanelHook.prototype, {
             if (this.contactInRelation && record.get('relations')) {
                 Ext.each(record.get('relations'), function(relation) {
                     if (relation.type === this.relationType) {
-                       this.addMailFromContact(mailAddresses, relation.related_record);
+                       this.addMailFromAddressbook(mailAddresses, relation.related_record);
                     }
                 }, this);
             } else {
-                this.addMailFromContact(mailAddresses, record);
+                this.addMailFromAddressbook(mailAddresses, record);
             }
             
         }, this);
@@ -142,7 +142,7 @@ Ext.apply(Tine.Felamimail.GridPanelHook.prototype, {
      * @param {Array} mailAddresses
      * @param {Tine.Addressbook.Model.Contact|Object} contact
      */
-    addMailFromContact: function(mailAddresses, contact) {
+    addMailFromAddressbook: function(mailAddresses, contact) {
         if (! contact) {
             return;
         }
@@ -150,10 +150,12 @@ Ext.apply(Tine.Felamimail.GridPanelHook.prototype, {
             contact = new Tine.Addressbook.Model.Contact(contact);
         }
         
-        var mailAddress = (contact.getPreferedEmail()) ? Tine.Felamimail.getEmailStringFromContact(contact) : null;
-        
-        if (mailAddress) {
-            mailAddresses.push(mailAddress);
+        if (!contact.get("members")) {
+            mailAddresses.push(Tine.Felamimail.getEmailStringFromContact(contact));
+        } else {
+            Ext.each(contact.get("emails").split(";"), function(mail) {
+                mailAddresses.push(mail);
+            })
         }
     },
     
index 0e2501e..5f78ee3 100644 (file)
@@ -281,7 +281,9 @@ Tine.Felamimail.RecipientGrid = Ext.extend(Ext.grid.EditorGridPanel, {
             if (this.activeEditor && value !== null && this.activeEditor.record.get('address') != value) {
                 this.activeEditor.record.set('address', value);
             }
-            this.onSearchComboSelect(combo);
+            if (!this.searchCombo.getValueIsList()) {
+                this.onSearchComboSelect(combo);
+            }
         } else if (this.activeEditor && e.getKey() == e.BACKSPACE) {
             Tine.log.debug('Tine.Felamimail.MessageEditDialog::onSearchComboSpecialkey() -> BACKSPACE');
             // remove row on backspace if we have more than 1 rows in grid
@@ -320,8 +322,18 @@ Tine.Felamimail.RecipientGrid = Ext.extend(Ext.grid.EditorGridPanel, {
         Tine.log.debug('Tine.Felamimail.MessageEditDialog::onSearchComboSelect()');
         
         var value = combo.getValue();
-        if (value !== '') {
+        if (combo.getValueIsList()) {
+            var emails = value.split(";");
+            emails.sort();
+            this._addRecipients(emails, "to");
+            this.setFixedHeight(false);
+            this.ownerCt.doLayout();
+            this.store.remove(this.activeEditor ? this.activeEditor.record : this.lastEditedRecord);
             this.addRowAndDoLayout(this.activeEditor ? this.activeEditor.record : this.lastEditedRecord);
+        } else {
+            if (value !== '') {
+                this.addRowAndDoLayout(this.activeEditor ? this.activeEditor.record : this.lastEditedRecord);
+            }
         }
     },
     
index 0e0de36..a75c4cc 100644 (file)
 class Filemanager_Acl_Rights extends Tinebase_Acl_Rights_Abstract
 {
     /**
+     * 
+     * the right to manage filemanager module
+     * @staticvar string
+     */
+    const MANAGE_MAIN_SCREEN = 'manage_main_screen';
+    
+    /**
      * holds the instance of the singleton
      *
      * @var Filemanager_Acl_Rights
@@ -76,7 +83,8 @@ class Filemanager_Acl_Rights extends Tinebase_Acl_Rights_Abstract
         $allRights = parent::getAllApplicationRights();
         
         $addRights = array(
-            Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS
+            Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS,
+            self::MANAGE_MAIN_SCREEN,
         );
         $allRights = array_merge($allRights, $addRights);
         
@@ -97,6 +105,10 @@ class Filemanager_Acl_Rights extends Tinebase_Acl_Rights_Abstract
                 'text'          => $translate->_('manage shared folders'),
                 'description'   => $translate->_('Create new shared folders'),
             ),
+            self::MANAGE_MAIN_SCREEN   => array(
+                'text'          => $translate->_('manage module'),
+                'description'   => $translate->_('show module main screen'),
+               ),
         );
         
         $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
index abcb4c4..1585663 100644 (file)
@@ -79,7 +79,9 @@ class Filemanager_Controller extends Tinebase_Controller_Event implements Tineba
                 $this->createPersonalFolder($_eventObject->account);
                 break;
             case 'Admin_Event_DeleteAccount':
-                $this->deletePersonalFolder($_eventObject->account);
+                foreach ($_eventObject->accountIds as $accountId) {
+                    $this->deletePersonalFolder($accountId);
+                }
                 break;
         }
     }
index 8024591..58e6c00 100644 (file)
  */
 class Filemanager_Setup_Initialize extends Setup_Initialize
 {
+    /**
+     * 
+     * init folders
+     */
+    public function _initializeFolders(Tinebase_Model_Application $_application, $_options = null)
+    {
+        // initialize folders for installed apps
+        foreach (Tinebase_Application::getInstance()->getApplications() as $app) {
+            $reflectionClass = new ReflectionClass($app->name . '_Setup_Initialize');
+            $methods = $reflectionClass->getMethods();
+            foreach ($methods as $method) {
+               &nb