Merge branch '2016.11' into 2016.11-develop
authorPhilipp Schüle <p.schuele@metaways.de>
Mon, 6 Mar 2017 20:33:08 +0000 (21:33 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Mon, 6 Mar 2017 20:33:08 +0000 (21:33 +0100)
575 files changed:
scripts/packaging/satisserver/init tasks [new file with mode: 0644]
scripts/packaging/satisserver/updateTine20Conf.php [new file with mode: 0644]
tests/tine20/Addressbook/AllTests.php
tests/tine20/Addressbook/Import/CsvTest.php
tests/tine20/Addressbook/JsonTest.php
tests/tine20/Addressbook/LdapSyncTest.php [new file with mode: 0644]
tests/tine20/AllServerTests.php
tests/tine20/AllTests.php
tests/tine20/ExampleApplication/AllTests.php
tests/tine20/ExampleApplication/ImportTest.php [new file with mode: 0644]
tests/tine20/ExampleApplication/JsonTest.php
tests/tine20/ExampleApplication/TestCase.php
tests/tine20/ExampleApplication/files/import.csv [new file with mode: 0644]
tests/tine20/Felamimail/Frontend/JsonTest.php
tests/tine20/Felamimail/files/multipart_attachments.eml [new file with mode: 0644]
tests/tine20/Felamimail/files/tine20_alarm_notifictation.eml [new file with mode: 0644]
tests/tine20/Filemanager/Frontend/JsonTests.php
tests/tine20/ImportTestCase.php
tests/tine20/Inventory/JsonTest.php
tests/tine20/MailFiler/AllTests.php [new file with mode: 0644]
tests/tine20/MailFiler/Frontend/AllTests.php [new file with mode: 0644]
tests/tine20/MailFiler/Frontend/JsonTests.php [new file with mode: 0644]
tests/tine20/Projects/JsonTest.php
tests/tine20/Sales/InvoiceControllerTests.php
tests/tine20/Sales/JsonTest.php
tests/tine20/TestCase.php
tests/tine20/Timetracker/ControllerTest.php
tests/tine20/Tinebase/ApplicationTest.php
tests/tine20/Tinebase/ConfigTest.php
tests/tine20/Tinebase/ContainerTest.php
tests/tine20/Tinebase/Frontend/CliTest.php
tests/tine20/Tinebase/Frontend/HttpTest.php
tests/tine20/Tinebase/Frontend/Json/PersistentFilterTest.php
tests/tine20/Tinebase/Frontend/JsonTest.php
tests/tine20/Tinebase/GroupTest.php
tests/tine20/Tinebase/Log/FormatterTest.php
tests/tine20/Tinebase/Server/HttpTests.php [new file with mode: 0644]
tests/tine20/Tinebase/User/LdapTest.php
tests/tine20/Tinebase/User/SqlTest.php
tests/tine20/Tinebase/files/configExampleAppTest.inc.php [new file with mode: 0644]
tests/tine20/Tinebase/files/configtest.inc.php
tine20/ActiveSync/Controller/SyncDevices.php
tine20/ActiveSync/Setup/Update/Release9.php [new file with mode: 0644]
tine20/ActiveSync/Setup/setup.xml
tine20/Addressbook/Addressbook.jsb2
tine20/Addressbook/Backend/Factory.php
tine20/Addressbook/Backend/Ldap.php
tine20/Addressbook/Backend/List.php
tine20/Addressbook/Backend/Sql.php
tine20/Addressbook/Backend/Sync/Ldap.php [new file with mode: 0644]
tine20/Addressbook/Config.php
tine20/Addressbook/Controller.php
tine20/Addressbook/Controller/Contact.php
tine20/Addressbook/Controller/Industry.php [new file with mode: 0644]
tine20/Addressbook/Controller/List.php
tine20/Addressbook/Convert/Contact/String.php
tine20/Addressbook/Convert/Contact/VCard/Abstract.php
tine20/Addressbook/Convert/Contact/VCard/Factory.php
tine20/Addressbook/Convert/List/Json.php
tine20/Addressbook/Export/Doc.php
tine20/Addressbook/Export/Pdf.php
tine20/Addressbook/Frontend/ActiveSync.php
tine20/Addressbook/Frontend/CardDAV/AllContacts.php
tine20/Addressbook/Frontend/Cli.php
tine20/Addressbook/Frontend/Http.php
tine20/Addressbook/Frontend/Json.php
tine20/Addressbook/Frontend/WebDAV.php
tine20/Addressbook/Frontend/WebDAV/Contact.php
tine20/Addressbook/Frontend/WebDAV/Container.php
tine20/Addressbook/Import/Csv.php
tine20/Addressbook/Import/VCard.php
tine20/Addressbook/Model/Contact.php
tine20/Addressbook/Model/ContactDisabledFilter.php
tine20/Addressbook/Model/ContactFilter.php
tine20/Addressbook/Model/Industry.php [new file with mode: 0644]
tine20/Addressbook/Model/IndustryFilter.php [new file with mode: 0644]
tine20/Addressbook/Model/List.php
tine20/Addressbook/Model/ListHiddenFilter.php
tine20/Addressbook/Setup/Initialize.php
tine20/Addressbook/Setup/Update/Release10.php [new file with mode: 0644]
tine20/Addressbook/Setup/Update/Release9.php
tine20/Addressbook/Setup/setup.xml
tine20/Addressbook/js/Addressbook.js
tine20/Addressbook/js/ContactEditDialog.js
tine20/Addressbook/js/ContactGrid.js
tine20/Addressbook/js/IndustryEditDialog.js [new file with mode: 0644]
tine20/Addressbook/js/IndustrySearchCombo.js [new file with mode: 0644]
tine20/Addressbook/js/ListMemberRoleGridPanel.js
tine20/Addressbook/js/MapPanel.js
tine20/Addressbook/js/Model.js
tine20/Addressbook/translations/de.po
tine20/Addressbook/translations/en.po
tine20/Addressbook/translations/template.pot
tine20/Admin/Controller/AccessLog.php
tine20/Admin/Controller/Config.php
tine20/Admin/Controller/SambaMachine.php
tine20/Admin/Controller/User.php
tine20/Admin/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Admin/Setup/setup.xml
tine20/Admin/css/Admin.css
tine20/Admin/js/AccessLog.js
tine20/Admin/js/Admin.js
tine20/Admin/js/Applications.js
tine20/Admin/js/Groups.js
tine20/Admin/js/Roles.js
tine20/Admin/js/Tags.js
tine20/Admin/js/user/Users.js
tine20/Calendar/Backend/Sql.php
tine20/Calendar/Controller.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Controller/EventNotifications.php
tine20/Calendar/Controller/MSEventFacade.php
tine20/Calendar/Export/GenericTrait.php
tine20/Calendar/Frontend/WebDAV/Backend.php
tine20/Calendar/Setup/Update/Release10.php [new file with mode: 0644]
tine20/Calendar/Setup/Update/Release9.php
tine20/Calendar/Setup/setup.xml
tine20/Calendar/js/AttendeeGridPanel.js
tine20/Calendar/js/Calendar.js
tine20/Calendar/js/CalendarPanel.js
tine20/Calendar/js/CalendarPanelSplitPlugin.js
tine20/Calendar/js/DaysView.js
tine20/Calendar/js/EventDetailsPanel.js
tine20/Calendar/js/MainScreenCenterPanel.js
tine20/Calendar/js/RrulePanel.js
tine20/Calendar/js/WestPanel.js
tine20/CoreData/Setup/Update/Release1.php [new file with mode: 0644]
tine20/CoreData/Setup/setup.xml
tine20/CoreData/js/CoreData.js
tine20/Courses/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Courses/Setup/setup.xml
tine20/Crm/Controller/Lead.php
tine20/Crm/Setup/Update/Release9.php
tine20/Crm/Setup/setup.xml
tine20/Crm/css/Crm.css
tine20/Crm/js/LinkGridPanel.js
tine20/Crm/js/Product.js
tine20/Events/Setup/Update/Release1.php [new file with mode: 0644]
tine20/Events/Setup/setup.xml
tine20/ExampleApplication/Config.php
tine20/ExampleApplication/Frontend/Cli.php
tine20/ExampleApplication/Model/ExampleRecord.php
tine20/ExampleApplication/Setup/setup.xml
tine20/Felamimail/Backend/Cache/Sql/Message.php
tine20/Felamimail/Backend/Folder.php
tine20/Felamimail/Config.php
tine20/Felamimail/Controller/Account.php
tine20/Felamimail/Controller/Folder.php
tine20/Felamimail/Controller/Message.php
tine20/Felamimail/Controller/Message/File.php [new file with mode: 0644]
tine20/Felamimail/Controller/Message/Move.php
tine20/Felamimail/Controller/Message/Send.php
tine20/Felamimail/Frontend/Http.php
tine20/Felamimail/Frontend/Json.php
tine20/Felamimail/Message.php
tine20/Felamimail/Model/MessageFilter.php
tine20/Felamimail/Setup/Update/Release10.php [new file with mode: 0644]
tine20/Felamimail/Setup/Update/Release9.php
tine20/Felamimail/Setup/setup.xml
tine20/Felamimail/css/Felamimail.css
tine20/Felamimail/js/ContactGrid.js
tine20/Felamimail/js/GridPanel.js
tine20/Felamimail/js/Model.js
tine20/Felamimail/js/sieve/RulesDialog.js
tine20/Felamimail/js/sieve/RulesGridPanel.js
tine20/Felamimail/translations/de.po
tine20/Felamimail/translations/en.po
tine20/Felamimail/translations/template.pot
tine20/Filemanager/Controller/Node.php
tine20/Filemanager/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Filemanager/Setup/setup.xml
tine20/Filemanager/js/DownloadLinkGridPanel.js
tine20/Filemanager/js/NodeEditDialog.js
tine20/Filemanager/js/NodeGridPanel.js
tine20/Filemanager/js/NodeTreePanel.js
tine20/Filemanager/translations/template.pot
tine20/HumanResources/Model/Employee.php
tine20/HumanResources/Setup/Update/Release10.php [new file with mode: 0644]
tine20/HumanResources/Setup/Update/Release9.php [new file with mode: 0644]
tine20/HumanResources/Setup/setup.xml
tine20/HumanResources/js/AccountEditDialog.js
tine20/Inventory/Export/definitions/i_default_ods.xml
tine20/Inventory/Export/definitions/i_default_xls.xml
tine20/Inventory/Import/Csv.php
tine20/Inventory/Import/definitions/inv_tine_import_csv.xml
tine20/Inventory/Model/InventoryItem.php
tine20/Inventory/Setup/Update/Release10.php
tine20/Inventory/Setup/setup.xml
tine20/Inventory/js/InventoryItemEditDialog.js
tine20/Inventory/js/InventoryItemGridPanel.js
tine20/Inventory/translations/bg.po
tine20/Inventory/translations/ca.po
tine20/Inventory/translations/cs.po
tine20/Inventory/translations/cs_CZ.po
tine20/Inventory/translations/da.po
tine20/Inventory/translations/de.po
tine20/Inventory/translations/el_GR.po
tine20/Inventory/translations/en.po
tine20/Inventory/translations/en_AU.po
tine20/Inventory/translations/en_NZ.po
tine20/Inventory/translations/es.po
tine20/Inventory/translations/es_MX.po
tine20/Inventory/translations/et.po
tine20/Inventory/translations/fa_IR.po
tine20/Inventory/translations/fi.po
tine20/Inventory/translations/fr.po
tine20/Inventory/translations/he.po
tine20/Inventory/translations/he_IL.po
tine20/Inventory/translations/hr_HR.po
tine20/Inventory/translations/hu.po
tine20/Inventory/translations/it.po
tine20/Inventory/translations/ja.po
tine20/Inventory/translations/ja_JP.po
tine20/Inventory/translations/ko.po
tine20/Inventory/translations/ko_KR.po
tine20/Inventory/translations/lt.po
tine20/Inventory/translations/nb.po
tine20/Inventory/translations/nl_NL.po
tine20/Inventory/translations/pl.po
tine20/Inventory/translations/pt_BR.po
tine20/Inventory/translations/ro_RO.po
tine20/Inventory/translations/ru.po
tine20/Inventory/translations/sk.po
tine20/Inventory/translations/sl.po
tine20/Inventory/translations/sq.po
tine20/Inventory/translations/sv_SE.po
tine20/Inventory/translations/template.pot
tine20/Inventory/translations/th.po
tine20/Inventory/translations/tr_TR.po
tine20/Inventory/translations/vi.po
tine20/Inventory/translations/vi_VN.po
tine20/Inventory/translations/zh_CN.po
tine20/Inventory/translations/zh_TW.po
tine20/MailFiler/Acl/Rights.php [new file with mode: 0644]
tine20/MailFiler/Backend/Message.php [new file with mode: 0644]
tine20/MailFiler/Config.php [new file with mode: 0644]
tine20/MailFiler/Controller.php [new file with mode: 0644]
tine20/MailFiler/Controller/DownloadLink.php [new file with mode: 0644]
tine20/MailFiler/Controller/Message.php [new file with mode: 0644]
tine20/MailFiler/Controller/Node.php [new file with mode: 0644]
tine20/MailFiler/Convert/Node/Json.php [new file with mode: 0644]
tine20/MailFiler/Exception.php [new file with mode: 0644]
tine20/MailFiler/Exception/DestinationIsOwnChild.php [new file with mode: 0644]
tine20/MailFiler/Exception/DestinationIsSameNode.php [new file with mode: 0644]
tine20/MailFiler/Exception/NodeExists.php [new file with mode: 0644]
tine20/MailFiler/Frontend/Cli.php [new file with mode: 0644]
tine20/MailFiler/Frontend/Download.php [new file with mode: 0644]
tine20/MailFiler/Frontend/Http.php [new file with mode: 0644]
tine20/MailFiler/Frontend/Json.php [new file with mode: 0644]
tine20/MailFiler/Frontend/WebDAV.php [new file with mode: 0644]
tine20/MailFiler/Frontend/WebDAV/Container.php [new file with mode: 0644]
tine20/MailFiler/Frontend/WebDAV/Directory.php [new file with mode: 0644]
tine20/MailFiler/Frontend/WebDAV/File.php [new file with mode: 0644]
tine20/MailFiler/MailFiler.jsb2 [new file with mode: 0644]
tine20/MailFiler/Model/DownloadLink.php [new file with mode: 0644]
tine20/MailFiler/Model/DownloadLinkFilter.php [new file with mode: 0644]
tine20/MailFiler/Model/Message.php [new file with mode: 0644]
tine20/MailFiler/Model/MessageFilter.php [new file with mode: 0644]
tine20/MailFiler/Model/Node.php [new file with mode: 0644]
tine20/MailFiler/Model/NodeFilter.php [new file with mode: 0644]
tine20/MailFiler/Setup/Initialize.php [new file with mode: 0644]
tine20/MailFiler/Setup/setup.xml [new file with mode: 0644]
tine20/MailFiler/css/MailFiler.css [new file with mode: 0644]
tine20/MailFiler/js/DownloadLinkDialog.js [new file with mode: 0644]
tine20/MailFiler/js/DownloadLinkGridPanel.js [new file with mode: 0644]
tine20/MailFiler/js/ExceptionHandler.js [new file with mode: 0644]
tine20/MailFiler/js/FolderSelect.js [new file with mode: 0644]
tine20/MailFiler/js/GridContextMenu.js [new file with mode: 0644]
tine20/MailFiler/js/GridDetailsPanel.js [new file with mode: 0644]
tine20/MailFiler/js/MailFiler.js [new file with mode: 0644]
tine20/MailFiler/js/Model.js [new file with mode: 0644]
tine20/MailFiler/js/NodeEditDialog.js [new file with mode: 0644]
tine20/MailFiler/js/NodeGridPanel.js [new file with mode: 0644]
tine20/MailFiler/js/NodeTreePanel.js [new file with mode: 0644]
tine20/MailFiler/js/PathFilterModel.js [new file with mode: 0644]
tine20/MailFiler/js/PathFilterPlugin.js [new file with mode: 0644]
tine20/MailFiler/js/SearchCombo.js [new file with mode: 0644]
tine20/MailFiler/translations/de.po [new file with mode: 0644]
tine20/MailFiler/translations/en.po [new file with mode: 0644]
tine20/MailFiler/translations/template.pot [new file with mode: 0644]
tine20/MailFiler/views/file.phtml [new file with mode: 0644]
tine20/MailFiler/views/folder.phtml [new file with mode: 0644]
tine20/MailFiler/views/notfound.phtml [new file with mode: 0644]
tine20/Phone/Controller/Call.php
tine20/Phone/Setup/Update/Release10.php [new file with mode: 0644]
tine20/Phone/Setup/Update/Release9.php
tine20/Phone/Setup/setup.xml
tine20/Phone/js/AddressbookGridPanelHook.js
tine20/Phone/js/PhoneTreePanel.js
tine20/Projects/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Projects/Setup/setup.xml
tine20/Sales/Config.php
tine20/Sales/Controller/Invoice.php
tine20/Sales/Controller/NumberableAbstract.php
tine20/Sales/Frontend/Json.php
tine20/Sales/Model/Contract.php
tine20/Sales/Model/Filter/ContractProductAggregateFilter.php [new file with mode: 0644]
tine20/Sales/Sales.jsb2
tine20/Sales/Setup/Update/Release10.php [new file with mode: 0644]
tine20/Sales/Setup/Update/Release9.php
tine20/Sales/Setup/setup.xml
tine20/Sales/js/AddressEditDialog.js
tine20/Sales/js/AddressGridPanel.js
tine20/Sales/js/ContractProductFilterModel.js [new file with mode: 0644]
tine20/Sales/js/ProductAggregateGridPanel.js
tine20/Sales/js/PurchaseInvoiceEditDialog.js
tine20/Sales/translations/de.po
tine20/Setup/Backend/Abstract.php
tine20/Setup/Backend/Interface.php
tine20/Setup/Backend/Mysql.php
tine20/Setup/Backend/Oracle.php
tine20/Setup/Backend/Pgsql.php
tine20/Setup/Backend/Schema/Table/Xml.php
tine20/Setup/Controller.php
tine20/Setup/Frontend/Http.php
tine20/Setup/Initialize.php
tine20/Setup/Update/Abstract.php
tine20/Setup/js/ApplicationGridPanel.js
tine20/Setup/js/ConfigManagerPanel.js
tine20/Setup/views/jsclient.php
tine20/SimpleFAQ/Setup/Update/Release8.php [new file with mode: 0644]
tine20/SimpleFAQ/Setup/setup.xml
tine20/Tasks/Frontend/WebDAV/Backend.php
tine20/Tasks/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Tasks/Setup/setup.xml
tine20/Timetracker/Controller/Timesheet.php
tine20/Timetracker/Export/Ods/Timeaccount.php
tine20/Timetracker/Export/Ods/Timesheet.php
tine20/Timetracker/Model/Timeaccount.php
tine20/Timetracker/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Timetracker/Setup/setup.xml
tine20/Tinebase/Acl/Rights.php
tine20/Tinebase/Acl/Rights/Abstract.php
tine20/Tinebase/Acl/Roles.php
tine20/Tinebase/ActionQueue.php
tine20/Tinebase/ActionQueue/Backend/Direct.php
tine20/Tinebase/ActionQueue/Backend/Interface.php
tine20/Tinebase/ActionQueue/Backend/Redis.php
tine20/Tinebase/ActionQueue/Worker.php
tine20/Tinebase/ActiveDirectory/DomainConfigurationTrait.php
tine20/Tinebase/Alarm.php
tine20/Tinebase/Application.php
tine20/Tinebase/Auth/CredentialCache.php
tine20/Tinebase/Auth/CredentialCache/Adapter/Config.php
tine20/Tinebase/Auth/Factory.php
tine20/Tinebase/Auth/Http/Resolver/Basic.php [deleted file]
tine20/Tinebase/Auth/Imap.php
tine20/Tinebase/Auth/Ldap.php
tine20/Tinebase/Auth/ModSsl.php
tine20/Tinebase/Auth/Sql.php
tine20/Tinebase/Backend/Interface.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/GroupSelect.php
tine20/Tinebase/Backend/Sql/Grants.php
tine20/Tinebase/Backend/Sql/Interface.php
tine20/Tinebase/Cache/PerRequest.php
tine20/Tinebase/Config.php
tine20/Tinebase/Config/Abstract.php
tine20/Tinebase/Config/Interface.php [new file with mode: 0644]
tine20/Tinebase/Config/KeyField.php
tine20/Tinebase/Config/KeyFieldRecord.php
tine20/Tinebase/Config/Struct.php
tine20/Tinebase/Container.php
tine20/Tinebase/Controller/Abstract.php
tine20/Tinebase/Controller/Event.php
tine20/Tinebase/Controller/Record/Abstract.php
tine20/Tinebase/Controller/Record/Grants.php
tine20/Tinebase/Controller/Record/Interface.php
tine20/Tinebase/Controller/ScheduledImport.php
tine20/Tinebase/Controller/SearchInterface.php
tine20/Tinebase/Convert/Factory.php
tine20/Tinebase/Convert/ImportExportDefinition/Json.php
tine20/Tinebase/Convert/Json.php
tine20/Tinebase/Convert/VCalendar/Abstract.php
tine20/Tinebase/Core.php
tine20/Tinebase/CustomField.php
tine20/Tinebase/CustomField/Config.php
tine20/Tinebase/DateTime.php
tine20/Tinebase/Db/Table.php
tine20/Tinebase/EmailUser/Imap/Cyrus.php
tine20/Tinebase/EmailUser/Imap/Dbmail.php
tine20/Tinebase/Export.php
tine20/Tinebase/Export/Abstract.php
tine20/Tinebase/FileSystem.php
tine20/Tinebase/FileSystem/RecordAttachments.php
tine20/Tinebase/Frontend/Abstract.php
tine20/Tinebase/Frontend/Cli/Abstract.php
tine20/Tinebase/Frontend/Http.php
tine20/Tinebase/Frontend/Http/Abstract.php
tine20/Tinebase/Frontend/Http/Generic.php [new file with mode: 0644]
tine20/Tinebase/Frontend/Json.php
tine20/Tinebase/Frontend/Json/Abstract.php
tine20/Tinebase/Frontend/Json/Container.php
tine20/Tinebase/Helper.php
tine20/Tinebase/Http/Server.php
tine20/Tinebase/ImageHelper.php
tine20/Tinebase/Import/Abstract.php
tine20/Tinebase/Import/Csv/Generic.php [new file with mode: 0644]
tine20/Tinebase/Import/Interface.php
tine20/Tinebase/ImportExportDefinition.php
tine20/Tinebase/Log.php
tine20/Tinebase/Model/Application.php
tine20/Tinebase/Model/Container.php
tine20/Tinebase/Model/Converter/Json.php
tine20/Tinebase/Model/CredentialCache.php
tine20/Tinebase/Model/EmailUser.php
tine20/Tinebase/Model/Filter/FilterGroup.php
tine20/Tinebase/Model/Filter/ForeignId.php
tine20/Tinebase/Model/Filter/FullText.php [new file with mode: 0644]
tine20/Tinebase/Model/FullUser.php
tine20/Tinebase/Model/Group.php
tine20/Tinebase/Model/Image.php
tine20/Tinebase/Model/Import.php
tine20/Tinebase/Model/ImportExportDefinition.php
tine20/Tinebase/Model/Pagination.php
tine20/Tinebase/Model/Relation.php
tine20/Tinebase/Model/Tag.php
tine20/Tinebase/Model/Tree/Node.php
tine20/Tinebase/Model/Tree/Node/Path.php
tine20/Tinebase/ModelConfiguration.php
tine20/Tinebase/Notes.php
tine20/Tinebase/Numberable.php [new file with mode: 0644]
tine20/Tinebase/Numberable/Abstract.php [new file with mode: 0644]
tine20/Tinebase/Numberable/Backend/Sql/Abstract.php [new file with mode: 0644]
tine20/Tinebase/Numberable/String.php [new file with mode: 0644]
tine20/Tinebase/PersistentFilter.php
tine20/Tinebase/PersistentFilter/Backend/Sql.php
tine20/Tinebase/Pluggable/Abstract.php
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/Record/Diff.php
tine20/Tinebase/Record/DoctrineMappingDriver.php
tine20/Tinebase/Record/Interface.php
tine20/Tinebase/Record/Iterator.php
tine20/Tinebase/Relations.php
tine20/Tinebase/Server/Abstract.php
tine20/Tinebase/Server/Http.php
tine20/Tinebase/Server/Json.php
tine20/Tinebase/Setup/Update/Release10.php [new file with mode: 0644]
tine20/Tinebase/Setup/Update/Release9.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/Tags.php
tine20/Tinebase/TempFile.php
tine20/Tinebase/Timemachine/ModificationLog.php
tine20/Tinebase/Tinebase.jsb2
tine20/Tinebase/Translation.php
tine20/Tinebase/Tree/FileObject.php
tine20/Tinebase/User.php
tine20/Tinebase/User/Abstract.php
tine20/Tinebase/User/Interface.php
tine20/Tinebase/User/Ldap.php
tine20/Tinebase/User/Plugin/Interface.php
tine20/Tinebase/User/Sql.php
tine20/Tinebase/WebDav/Collection/AbstractContainerTree.php
tine20/Tinebase/WebDav/Container/Abstract.php
tine20/Tinebase/css/Tinebase.css
tine20/Tinebase/css/ux/Wizard.css
tine20/Tinebase/css/ux/form/HtmlEditor.css
tine20/Tinebase/js/Application.js
tine20/Tinebase/js/ApplicationStarter.js
tine20/Tinebase/js/CanonicalPath.js [new file with mode: 0644]
tine20/Tinebase/js/ExceptionHandler.js
tine20/Tinebase/js/Locale.js
tine20/Tinebase/js/MainContextMenu.js [new file with mode: 0644]
tine20/Tinebase/js/MainMenu.js
tine20/Tinebase/js/MainScreen.js
tine20/Tinebase/js/Tinebase.js
tine20/Tinebase/js/common.js
tine20/Tinebase/js/data/Record.js
tine20/Tinebase/js/jsb2-loader.js [new file with mode: 0644]
tine20/Tinebase/js/npm-shrinkwrap.json
tine20/Tinebase/js/package.json
tine20/Tinebase/js/tineInit.js
tine20/Tinebase/js/ux/Notification.js
tine20/Tinebase/js/ux/PopupWindow.js
tine20/Tinebase/js/ux/Printer/renderers/Base.js
tine20/Tinebase/js/ux/SearchField.js
tine20/Tinebase/js/ux/display/DisplayField.js
tine20/Tinebase/js/ux/file/BrowsePlugin.js
tine20/Tinebase/js/ux/file/Upload.js
tine20/Tinebase/js/ux/form/ClearableComboBox.js
tine20/Tinebase/js/ux/form/ImageField.js
tine20/Tinebase/js/ux/form/NumberField.js
tine20/Tinebase/js/ux/grid/PagingToolbar.js
tine20/Tinebase/js/ux/layout/HorizontalFitLayout.js
tine20/Tinebase/js/ux/util/Cookie.js
tine20/Tinebase/js/webpack-dev-includes.js [new file with mode: 0644]
tine20/Tinebase/js/webpack.config.js
tine20/Tinebase/js/widgets/ActivitiesGridPanel.js
tine20/Tinebase/js/widgets/ActivitiesPanel.js
tine20/Tinebase/js/widgets/ContentTypeTreePanel.js
tine20/Tinebase/js/widgets/MainScreen.js
tine20/Tinebase/js/widgets/MapPanel.js
tine20/Tinebase/js/widgets/container/ContainerSelect.js
tine20/Tinebase/js/widgets/container/TreePanel.js
tine20/Tinebase/js/widgets/customfields/Field.js
tine20/Tinebase/js/widgets/dialog/AlarmPanel.js
tine20/Tinebase/js/widgets/dialog/AttachmentsGridPanel.js
tine20/Tinebase/js/widgets/dialog/EditDialog.js
tine20/Tinebase/js/widgets/dialog/ImportDialog.js
tine20/Tinebase/js/widgets/display/DefaultDisplayPanel.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/display/RecordDisplayPanel.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/form/FieldManager.js
tine20/Tinebase/js/widgets/form/RecordForm.js
tine20/Tinebase/js/widgets/form/RecordPickerComboBox.js
tine20/Tinebase/js/widgets/form/UidTriggerField.js
tine20/Tinebase/js/widgets/grid/DetailsPanel.js
tine20/Tinebase/js/widgets/grid/FileUploadGrid.js
tine20/Tinebase/js/widgets/grid/FilterPanel.js
tine20/Tinebase/js/widgets/grid/FilterStructureTreePanel.js
tine20/Tinebase/js/widgets/grid/FilterToolbar.js
tine20/Tinebase/js/widgets/grid/GridPanel.js
tine20/Tinebase/js/widgets/grid/PickerGridPanel.js
tine20/Tinebase/js/widgets/grid/QuickaddGridPanel.js
tine20/Tinebase/js/widgets/keyfield/ConfigGrid.js
tine20/Tinebase/js/widgets/mainscreen/WestPanel.js
tine20/Tinebase/js/widgets/persistentfilter/PickerPanel.js
tine20/Tinebase/js/widgets/relation/GenericPickerGridPanel.js
tine20/Tinebase/js/widgets/tags/TagsPanel.js
tine20/Tinebase/js/widgets/tree/ContextMenu.js
tine20/Tinebase/views/includeJsAndCss.php
tine20/Tinebase/views/jsclient.php
tine20/Tinebase/views/postal.xwindow.php
tine20/Voipmanager/Backend/Asterisk/SipPeer.php
tine20/Voipmanager/Backend/Asterisk/Voicemail.php
tine20/Voipmanager/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Voipmanager/Setup/setup.xml
tine20/build.xml
tine20/composer.lock
tine20/library/Console/Daemon.php
tine20/library/CryptoJS/aes.js [deleted file]
tine20/library/CryptoJS/cipher-core.js [deleted file]
tine20/library/CryptoJS/core.js [deleted file]
tine20/library/CryptoJS/enc-base64.js [deleted file]
tine20/library/CryptoJS/enc-utf16.js [deleted file]
tine20/library/CryptoJS/evpkdf.js [deleted file]
tine20/library/CryptoJS/hmac.js [deleted file]
tine20/library/CryptoJS/md5.js [deleted file]
tine20/library/CryptoJS/mode-cfb.js [deleted file]
tine20/library/CryptoJS/mode-ctr.js [deleted file]
tine20/library/CryptoJS/mode-ecb.js [deleted file]
tine20/library/CryptoJS/mode-ofb.js [deleted file]
tine20/library/CryptoJS/pad-ansix923.js [deleted file]
tine20/library/CryptoJS/pad-iso10126.js [deleted file]
tine20/library/CryptoJS/pad-iso97971.js [deleted file]
tine20/library/CryptoJS/pad-nopadding.js [deleted file]
tine20/library/CryptoJS/pad-zeropadding.js [deleted file]
tine20/library/CryptoJS/pbkdf2.js [deleted file]
tine20/library/CryptoJS/rabbit.js [deleted file]
tine20/library/CryptoJS/rc4.js [deleted file]
tine20/library/CryptoJS/sha1.js [deleted file]
tine20/library/CryptoJS/sha224.js [deleted file]
tine20/library/CryptoJS/sha256.js [deleted file]
tine20/library/CryptoJS/sha384.js [deleted file]
tine20/library/CryptoJS/sha512.js [deleted file]
tine20/library/CryptoJS/tripledes.js [deleted file]
tine20/library/CryptoJS/x64-core.js [deleted file]
tine20/library/jsb2tk/JSBuilder2/JSB2FileFormat.txt [deleted file]
tine20/library/jsb2tk/JSBuilder2/JSBuilder2.jar [deleted file]
tine20/library/jsb2tk/JSBuilder2/Readme.txt [deleted file]
tine20/library/jsb2tk/jsb2tk.php [deleted file]
tine20/library/jsb2tk/tests/core/core.jsb2 [deleted file]
tine20/library/jsb2tk/tests/core/resources/css/main.css [deleted file]
tine20/library/jsb2tk/tests/core/resources/images/tine20.jpg [deleted file]
tine20/library/jsb2tk/tests/core/src/ModulSel.js [deleted file]
tine20/library/jsb2tk/tests/core/src/main.js [deleted file]
tine20/library/jsb2tk/tests/jsb2tkTest.php [deleted file]
tine20/library/jsb2tk/tests/modul/modul.jsb2 [deleted file]
tine20/library/jsb2tk/tests/modul/resources/css/main.css [deleted file]
tine20/library/jsb2tk/tests/modul/resources/images/tine_small_rect_med.png [deleted file]
tine20/library/jsb2tk/tests/modul/src/SomeComponent.js [deleted file]
tine20/library/jsb2tk/tests/modul/src/model.js [deleted file]

diff --git a/scripts/packaging/satisserver/init tasks b/scripts/packaging/satisserver/init tasks
new file mode 100644 (file)
index 0000000..5cec155
--- /dev/null
@@ -0,0 +1,15 @@
+git init .
+git remote add tine20com https://gerrit.tine20.com/customers/tine20.com
+git remote add tine20org https://gerrit.tine20.org/tine20/tine20
+
+git fetch tine20com
+git fetch tine20org
+git branch -a
+git show remotes/tine20com/2016.11-develop:tine20/composer.json > ./foo1
+
+cronjobs:
+# try to build conf file, if successfull, rebuild satis
+0 */4 * * * php /home/ubuntu/updateTine20Conf.php > /home/ubuntu/tine20.conf && /home/ubuntu/satis/bin/satis build /home/ubuntu/tine20.conf /home/ubuntu/tine20Mirror
+
+# check that there are json files newer than 10 days, then delete all files older than 10 days
+23 3 * * * /bin/bash -c 'expr `find /home/ubuntu/tine20Mirror/include/ -mtime -10 -type f | grep -c json` ">" 0 && find /home/ubuntu/tine20Mirror/include/ -mtime +10 -type f | xargs rm' > /dev/null
diff --git a/scripts/packaging/satisserver/updateTine20Conf.php b/scripts/packaging/satisserver/updateTine20Conf.php
new file mode 100644 (file)
index 0000000..4a7c419
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * script to update the tine20.conf file
+ *
+ * * git fetch [both remotes]
+ * * git branch -a [show all branches]
+ * * iterate over all branches newer or equal to 2015.11
+ * * get tine20/composer.json from each branch
+ * * merge data and write it to tine20.conf
+ */
+
+
+$path = '/home/ubuntu/tine20Repos';
+$remotes = array('tine20org', 'tine20com');
+
+foreach($remotes as $r) {
+    $fetchResult = myProcOpen('git fetch ' . $r, $path);
+    if ($fetchResult[0] !== 0) {
+        exit('git fetch failed for ' . $r .': ' . print_r($fetchResult, true));
+    }
+}
+unset($fetchResult);
+
+$branchResult = myProcOpen('git branch -a', $path);
+if ($branchResult[0] !== 0) {
+    exit('git branch -a failed: ' . print_r($branchResult, true));
+}
+
+$branches = explode(PHP_EOL, $branchResult[1]);
+unset($branchResult);
+
+$repositories = array();
+$require = array();
+
+foreach($branches as $branch) {
+    if (preg_match('/(\d\d\d\d\.\d\d)/', $branch, $match) && version_compare($match[1], '2015.11') >= 0) {
+
+        $gitShowResult = myProcOpen('git show ' . $branch . ':tine20/composer.json', $path);
+        if ($gitShowResult[0] !== 0) {
+            exit('git show failed for branch: ' . $branch . PHP_EOL . print_r($gitShowResult, true));
+        }
+
+        if (($composerJson = json_decode($gitShowResult[1], true)) === NULL || !is_array($composerJson)) {
+            exit('could not json_decode composer.json from branch: ' . $branch . PHP_EOL . $gitShowResult[1]);
+        }
+
+        foreach($composerJson['repositories'] as $repo) {
+            if (!isset($repositories[$repo['type']]))
+                $repositories[$repo['type']] = array();
+            $repositories[$repo['type']][$repo['url']] = true;
+        }
+
+        foreach($composerJson['require'] as $req => $version) {
+            if (!isset($require[$req]) || version_compare($version, $require[$req]) > 0) {
+                $require[$req] = $version;
+            }
+        }
+
+        foreach($composerJson['require-dev'] as $req => $version) {
+            if (!isset($require[$req]) || version_compare($version, $require[$req]) > 0) {
+                $require[$req] = $version;
+            }
+        }
+    }
+}
+
+$repositories['composer'] = array('https://packagist.org' => true);
+
+
+$result = '{
+        "name": "Tine20 Composer Mirror",
+        "homepage": "http://79.99.84.34",
+
+        "archive": {
+                "directory": "dist1",
+                "format": "zip"
+        },
+
+        "repositories": [' . PHP_EOL;
+
+$first = true;
+foreach($repositories as $type => $data) {
+    foreach($data as $url => $foo) {
+        $url = str_replace('http://gerrit.tine20.com', 'https://gerrit.tine20.com', $url);
+        if (!$first) {
+            $result .= ',';
+        } else {
+            $first = false;
+        }
+
+        $result .= '        {
+            "type": "' . $type . '",
+            "url": "' . $url . '"
+        }';
+    }
+}
+
+$result .= '],
+        "require": {' . PHP_EOL;
+
+$first = true;
+foreach($require as $name => $version) {
+    if (!$first) {
+        $result .= ',' . PHP_EOL;
+    } else {
+        $first = false;
+    }
+
+    $result .= '            "' . $name .'": "' . $version .'"';
+}
+
+$result .= PHP_EOL . '      },' . PHP_EOL . '       "require-dependencies": true' . PHP_EOL . '}';
+
+echo $result;
+
+
+
+function myProcOpen($cmd, $path)
+{
+    $descriptorspec = array(
+        0 => array("pipe", "r"),
+        1 => array("pipe", "w"),
+        2 => array("pipe", "w")
+    );
+
+    $process = proc_open($cmd, $descriptorspec, $pipes, $path);
+
+    $result = array(
+        0 => false,
+        1 => '',
+        2 => ''
+    );
+
+    if (is_resource($process)) {
+        fclose($pipes[0]);
+
+        $result[1] = stream_get_contents($pipes[1]);
+        fclose($pipes[1]);
+
+        $result[2] = stream_get_contents($pipes[2]);
+        fclose($pipes[2]);
+
+        $result[0] = proc_close($process);
+    }
+
+    return $result;
+}
\ No newline at end of file
index e8e66d3..e5759d2 100644 (file)
@@ -37,6 +37,11 @@ class Addressbook_AllTests
         $suite->addTestSuite('Addressbook_Model_ContactIdFilterTest');
         $suite->addTestSuite('Addressbook_Export_DocTest');
         $suite->addTestSuite('Addressbook_Export_XlsTest');
+
+        if (Tinebase_User::getConfiguredBackend() === Tinebase_User::LDAP) {
+            $suite->addTestSuite('Addressbook_LdapSyncTest');
+        }
+
         // TODO: enable this again, when its fast
 //         $suite->addTestSuite('Addressbook_Setup_DemoDataTests');
         return $suite;
index 9d91f89..64cc588 100644 (file)
@@ -16,7 +16,6 @@ class Addressbook_Import_CsvTest extends ImportTestCase
     protected $_deletePersonalContacts = false;
 
     protected $_importerClassName = 'Addressbook_Import_Csv';
-    protected $_exporterClassName = 'Addressbook_Export_Csv';
     protected $_modelName = 'Addressbook_Model_Contact';
 
     /**
@@ -39,11 +38,6 @@ class Addressbook_Import_CsvTest extends ImportTestCase
      */
     protected function tearDown()
     {
-        // cleanup
-        if (file_exists($this->_filename) && $this->_deleteImportFile) {
-            unlink($this->_filename);
-        }
-        
         if ($this->_deletePersonalContacts) {
             Addressbook_Controller_Contact::getInstance()->deleteByFilter(new Addressbook_Model_ContactFilter(array(array(
                 'field' => 'container_id', 'operator' => 'equals', 'value' => Addressbook_Controller_Contact::getInstance()->getDefaultAddressbook()->getId()
index be1826e..e0e1c6c 100644 (file)
@@ -576,7 +576,6 @@ class Addressbook_JsonTest extends TestCase
 
     /**
      * test getting contact
-     *
      */
     public function testGetContact()
     {
@@ -588,6 +587,27 @@ class Addressbook_JsonTest extends TestCase
     }
 
     /**
+     * @see 0012280: Add Industries to Contact
+     */
+    public function testUpdateContactWithIndustry()
+    {
+        if (! Addressbook_Config::getInstance()->featureEnabled(Addressbook_Config::FEATURE_INDUSTRY)) {
+            $this->markTestSkipped('feature disabled');
+        }
+
+        // create industry
+        $industry = $this->_testSimpleRecordApi('Industry', /* $nameField */ 'name', /* $descriptionField */ 'description', /* $delete */ false);
+
+        // use industry in contact
+        $newContactData = $this->_getContactData();
+        $newContactData['industry'] = $industry['id'];
+        $contact = $this->_uit->saveContact($newContactData);
+
+        // check if industry is resolved in contact
+        $this->assertTrue(is_array($contact['industry']), 'Industry not resolved: ' . print_r($contact, true));
+    }
+
+    /**
      * test updating of a contact (including geodata)
      */
     public function testUpdateContactWithGeodata()
diff --git a/tests/tine20/Addressbook/LdapSyncTest.php b/tests/tine20/Addressbook/LdapSyncTest.php
new file mode 100644 (file)
index 0000000..4ed512c
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ *
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ */
+
+/**
+ * Test class for Addressbook_Backend_Sync_Ldap
+ */
+class Addressbook_LdapSyncTest extends TestCase
+{
+
+    protected $_oldSyncBackendsConfig = array();
+
+    /**
+     * @var Tinebase_Ldap
+     */
+    protected $_ldap = NULL;
+
+    protected $_ldapBaseDN = 'ou=ab,dc=example,dc=org';
+
+    /**
+     * Sets up the fixture.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $ldapOptions = array(
+            'host' => 'localhost',
+            'port' => 389,
+            'username' => 'cn=Manager,dc=example,dc=org',
+            'password' => 'tine20',
+            'bindRequiresDn' => true,
+            'baseDn' => 'dc=example,dc=org'
+        );
+
+        $this->_oldSyncBackendsConfig = Addressbook_Config::getInstance()->get('syncBackends');
+        Addressbook_Config::getInstance()->set('syncBackends', array(
+            '0' => array(
+                'class'     => 'Addressbook_Backend_Sync_Ldap',
+                'options'   => array(
+                    /* 'attributesMap' => array(
+                        'n_fn' => 'commonName',
+                        'n_family' => 'surname',
+                    ), */
+                    'baseDN' => $this->_ldapBaseDN,
+                    'ldapConnection' => $ldapOptions
+                ),
+                'filter'    => array(
+                    array('field' => 'query', 'operator' => 'contains', 'value' => 'test')
+                )
+            )
+        ));
+
+        Addressbook_Controller_Contact::getInstance()->resetSyncBackends();
+
+        $this->_ldap = new Tinebase_Ldap($ldapOptions);
+    }
+
+    /**
+     * tear down tests
+     */
+    protected function tearDown()
+    {
+        Addressbook_Config::getInstance()->set('syncBackends', $this->_oldSyncBackendsConfig);
+        Addressbook_Controller_Contact::getInstance()->resetSyncBackends();
+
+        parent::tearDown();
+    }
+
+    protected function _checkUIDinBaseDN($uid, $presence = true)
+    {
+        $filter = Zend_Ldap_Filter::equals(
+            'uid', Zend_Ldap::filterEscape($uid)
+        );
+
+        $result = $this->_ldap->search($filter, $this->_ldapBaseDN);
+
+        if (($presence === true && $result->count() > 0) ||
+            ($presence === false && $result->count() === 0)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * test sync to sync backend works, we need to match filter {n_fn contains "test"}
+     */
+    public function testSyncBackend()
+    {
+        $contactData = array(
+            'n_given'       => 'testGiven',
+            'n_family'      => 'testFamily',
+        );
+
+        // this contact should be in the sync backend now
+        $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact($contactData));
+        $this->assertTrue($this->_checkUIDinBaseDN($contact->getId()), "did not find newly created contact in sync backend");
+
+        $contact->n_given = 'given';
+        $contact->n_family = 'family';
+
+        // now the contact should be removed from the sync backend
+        $contact = Addressbook_Controller_Contact::getInstance()->update($contact);
+        $this->assertTrue($this->_checkUIDinBaseDN($contact->getId(), false), "did find modified contact in sync backend, though it doesnt match the filter");
+
+        $contact->n_given = 'test';
+
+        // now the contact should be added the sync backend again
+        $contact = Addressbook_Controller_Contact::getInstance()->update($contact);
+        $this->assertTrue($this->_checkUIDinBaseDN($contact->getId()), "did not find modified created contact in sync backend, though it should be there");
+
+        // now the contact should be removed from the sync backend again
+        Addressbook_Controller_Contact::getInstance()->delete($contact);
+        $this->assertTrue($this->_checkUIDinBaseDN($contact->getId(), false), "did find contact in sync backend, though it was deleted");
+
+        // test users
+    }
+}
\ No newline at end of file
index 55dfa52..8b55b1e 100644 (file)
@@ -23,6 +23,7 @@ class AllServerTests
         $suite->addTestSuite('Tinebase_ControllerServerTest');
         $suite->addTestSuite('Tinebase_Server_WebDAVTests');
         $suite->addTestSuite('Tinebase_Server_JsonTests');
+        $suite->addTestSuite('Tinebase_Server_HttpTests');
 
         return $suite;
     }
index 2b2cda2..fbbde82 100644 (file)
@@ -39,6 +39,7 @@ class AllTests
         $suite->addTest(Courses_AllTests::suite());
         $suite->addTest(ActiveSync_AllTests::suite());
         $suite->addTest(Filemanager_AllTests::suite());
+        $suite->addTest(MailFiler_AllTests::suite());
         $suite->addTest(Projects_AllTests::suite());
         $suite->addTest(HumanResources_AllTests::suite());
         $suite->addTest(Inventory_AllTests::suite());
index 0f25b1a..e2fe10d 100644 (file)
@@ -26,6 +26,7 @@ class ExampleApplication_AllTests
         $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 ExampleApplication All Tests');
         
         $suite->addTestSuite('ExampleApplication_JsonTest');
+        $suite->addTestSuite('ExampleApplication_ImportTest');
         return $suite;
     }
 }
diff --git a/tests/tine20/ExampleApplication/ImportTest.php b/tests/tine20/ExampleApplication/ImportTest.php
new file mode 100644 (file)
index 0000000..5364f4a
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     ExampleApplication
+ * @subpackage  Import
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2012-2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+/**
+ * Test class for ExampleApplication_ImportTest
+ */
+class ExampleApplication_ImportTest extends ImportTestCase
+{
+    protected $_modelName = 'ExampleApplication_Model_ExampleRecord';
+    protected $_deleteImportFile = false;
+
+    public function testGenericImportExport()
+    {
+        $this->_filename = __DIR__ . '/files/import.csv';
+        $result = $this->_doImport();
+
+        $this->assertEquals(1, $result['totalcount'], print_r($result, true));
+        $this->assertEquals(0, $result['failcount'], print_r($result, true));
+        $record = $result['results']->getFirstRecord();
+        $this->assertEquals('minimal example record by PHPUnit::ExampleApplication_ImportTest', $record->name, print_r($record->toArray(), true));
+    }
+
+    public function testCliImport()
+    {
+        $this->_filename = __DIR__ . '/files/import.csv';
+        $cli = new ExampleApplication_Frontend_Cli();
+        $opts = new Zend_Console_Getopt('abp:');
+        $opts->setArguments(array(
+            'model=' . $this->_modelName,
+            $this->_filename
+        ));
+
+        ob_start();
+        $cli->import($opts);
+        $out = ob_get_clean();
+
+        $this->assertContains('Imported 1 records', $out);
+    }
+}
index ece137f..680de62 100644 (file)
@@ -19,11 +19,8 @@ require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php'
  */
 class ExampleApplication_JsonTest extends ExampleApplication_TestCase
 {
-    /**
-     * Backend
-     *
-     * @var ExampleApplication_Frontend_Json
-     */
+    protected $_recordsToDelete = array();
+
     public function setUp()
     {
         Tinebase_Application::getInstance()->setApplicationState(array(
@@ -32,6 +29,26 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
         
         parent::setUp();
         $this->_json = new ExampleApplication_Frontend_Json();
+        $this->_recordsToDelete = array();
+    }
+
+    protected function tearDown()
+    {
+        if (count($this->_recordsToDelete) > 0)
+        {
+            $this->_json->deleteExampleRecords(array_keys($this->_recordsToDelete));
+
+            foreach($this->_recordsToDelete as $record) {
+                $className = get_class($record);
+                $configuration = $record->getConfiguration();
+                foreach ($configuration->getAutoincrementFields() as $fieldDef) {
+                    $numberable = Tinebase_Numberable::getNumberable($className, $fieldDef['fieldName'], $fieldDef);
+                    $numberable->free($record->{$fieldDef['fieldName']});
+                }
+            }
+        }
+
+        parent::tearDown();
     }
     
     /**
@@ -49,22 +66,38 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
     /**
      * test creation of an ExampleRecord
      */
-    public function testCreateExampleRecord()
+    public function testCreateExampleRecord($expectedNumber = 1)
     {
-        $ExampleRecord = $this->_getExampleRecord();
+        $exampleRecord = $this->_getExampleRecord();
         
-        $this->assertTrue($ExampleRecord instanceof ExampleApplication_Model_ExampleRecord, 'We have no record the record is instance of wrong object');
+        $this->assertTrue($exampleRecord instanceof ExampleApplication_Model_ExampleRecord, 'We have no record the record is instance of wrong object');
         
-        $ExampleRecordArray = $ExampleRecord->toArray();
-        $this->assertTrue(is_array($ExampleRecordArray), '$ExampleRecordArray is not an array');
+        $exampleRecordArray = $exampleRecord->toArray();
+        $this->assertTrue(is_array($exampleRecordArray), '$exampleRecordArray is not an array');
         
-        $returnedRecord = $this->_json->saveExampleRecord($ExampleRecordArray);
+        $returnedRecord = $this->_json->saveExampleRecord($exampleRecordArray);
         
         $returnedGet = $this->_json->getExampleRecord($returnedRecord['id'], 0 , '');
-        $this->assertEquals($ExampleRecord['name'], $returnedGet['name']);
+        $this->assertEquals($exampleRecord['name'], $returnedGet['name']);
+        $this->assertTrue(isset($returnedGet['number_str']), 'number_str missing');
+        $this->assertEquals('ER-' . $expectedNumber, $returnedGet['number_str']);
+        $this->assertEquals('some words in the description for the fulltext search', $returnedGet['description']);
         
         return $returnedRecord;
     }
+
+    /**
+     * testAutoIncrementNumber
+     *
+     * @see 0012004: add numberable property for containers
+     */
+    public function testAutoIncrementNumber()
+    {
+        $this->testCreateExampleRecord();
+        $exampleRecord2 = $this->_getExampleRecord();
+        $returnedRecord = $this->_json->saveExampleRecord($exampleRecord2->toArray());
+        $this->assertEquals('ER-2', $returnedRecord['number_str']);
+    }
     
     /**
      * test search for ExampleRecords
@@ -73,8 +106,18 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
     {
         $inventoryRecord = $this->testCreateExampleRecord();
         $inventoryRecordID = $inventoryRecord['id'];
-        
-        $searchIDFilter = array(array('field' => 'id', 'operator' => 'equals', 'value' => $inventoryRecordID));
+        $this->_recordsToDelete[$inventoryRecordID] = ExampleApplication_Controller_ExampleRecord::getInstance()->get($inventoryRecordID);
+
+        // commit transaction for full text to work
+        if ($this->_transactionId) {
+            Tinebase_TransactionManager::getInstance()->commitTransaction($this->_transactionId);
+            $this->_transactionId = null;
+        }
+
+        $searchIDFilter = array(
+            array('field' => 'id', 'operator' => 'equals', 'value' => $inventoryRecordID),
+            array('field' => 'description', 'operator' => 'contains', 'value' => 'description fulltext words search')
+        );
         $searchDefaultFilter = $this->_getFilter();
         $mergedSearchFilter = array_merge($searchIDFilter, $searchDefaultFilter);
         
@@ -104,7 +147,7 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
     {
         $exampleRecordWithTag = $this->testCreateExampleRecord();
         // create a second record with no tag
-        $this->testCreateExampleRecord();
+        $this->testCreateExampleRecord(2);
         
         $exampleRecordWithTag['tags'] = array(array(
             'name'    => 'supi',
index 5e45272..45db4bb 100644 (file)
@@ -31,7 +31,8 @@ class ExampleApplication_TestCase extends TestCase
     protected function _getExampleRecord()
     {
         return new ExampleApplication_Model_ExampleRecord(array(
-            'name' => 'minimal example record by PHPUnit::ExampleApplication_JsonTest'
+            'name' => 'minimal example record by PHPUnit::ExampleApplication_JsonTest',
+            'description' => 'some words in the description for the fulltext search'
         ));
     }
     
diff --git a/tests/tine20/ExampleApplication/files/import.csv b/tests/tine20/ExampleApplication/files/import.csv
new file mode 100644 (file)
index 0000000..13f26c5
--- /dev/null
@@ -0,0 +1,2 @@
+"name","status","reason","number_str","number_int","relations","container_id","tags","attachments","notes","seq","tags"
+"minimal example record by PHPUnit::ExampleApplication_ImportTest","IN-PROCESS","","","","","","","","Feb 8, 2017 11:37:03 AM - created by Admin Account, Tine 2.0","1",""
index 90b1448..c45a5f5 100644 (file)
@@ -12,11 +12,6 @@ use Sabre\DAV;
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
  * Test class for Tinebase_Group
  */
 class Felamimail_Frontend_JsonTest extends TestCase
@@ -228,6 +223,9 @@ class Felamimail_Frontend_JsonTest extends TestCase
         }
         
         Tinebase_TransactionManager::getInstance()->rollBack();
+
+        // needed to clear cache of containers
+        Tinebase_Container::getInstance()->resetClassCache();
     }
 
     /************************ test functions *********************************/
@@ -1150,9 +1148,166 @@ class Felamimail_Frontend_JsonTest extends TestCase
         $messageData = $this->_getMessageData('', $subject);
         $this->_foldersToClear[] = 'INBOX';
         $this->_json->saveMessage($messageData);
-        $message = $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
+        $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
     }
-    
+
+    /**
+     * @see 0012160: save emails in filemanager
+     *
+     * @param string  $appName
+     */
+    public function testFileMessages($appName = 'Filemanager')
+    {
+        $personalFilemanagerContainer = $this->_getPersonalContainer($appName . '_Model_Node');
+        $message = $this->_sendMessage();
+        $path = '/' . Tinebase_Model_Container::TYPE_PERSONAL
+            . '/' . Tinebase_Core::getUser()->accountLoginName
+            . '/' . $personalFilemanagerContainer->name;
+        $filter = array(array(
+            'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
+        ));
+        $result = $this->_json->fileMessages($filter, $appName, $path);
+        $this->assertTrue(isset($result['totalcount']));
+        $this->assertEquals(1, $result['totalcount'], 'message should be filed in ' . $appName . ': ' . print_r($result, true));
+
+        // check if message exists in $appName
+        $filter = new Tinebase_Model_Tree_Node_Filter(array(array(
+            'field'    => 'path',
+            'operator' => 'equals',
+            'value'    => $path
+        ), array(
+            'field'    => 'name',
+            'operator' => 'contains',
+            'value'    => $message['subject']
+        )));
+        $nodeController = Tinebase_Core::getApplicationInstance($appName . '_Model_Node');
+        $emlNode = $nodeController->search($filter)->getFirstRecord();
+        $this->assertTrue($emlNode !== null, 'could not find eml file node');
+        $this->assertEquals(Tinebase_Model_Tree_Node::TYPE_FILE, $emlNode->type);
+        $this->assertEquals('message/rfc822', $emlNode->contenttype);
+        $this->assertTrue(preg_match('/[a-f0-9]{10}/', $emlNode->name) == 1, 'no message id hash in node name: ' . print_r($emlNode->toArray(), true));
+
+        $nodeWithDescription = $nodeController->get($emlNode['id']);
+        $this->assertTrue(isset($nodeWithDescription->description), 'description missing from node: ' . print_r($nodeWithDescription->toArray(), true));
+        $this->assertContains($message['received'], $nodeWithDescription->description);
+        $this->assertContains('aaaaaä', $nodeWithDescription->description);
+    }
+
+    /**
+     * @see 0012162: create new MailFiler application
+     */
+    public function testFileMessagesInMailFiler()
+    {
+        $this->testFileMessages('MailFiler');
+
+        $personalFilemanagerContainer = $this->_getPersonalContainer('MailFiler_Model_Node');
+        $path = '/' . Tinebase_Model_Container::TYPE_PERSONAL
+            . '/' . Tinebase_Core::getUser()->accountLoginName
+            . '/' . $personalFilemanagerContainer->name;
+        $filter = array(array(
+            'field'    => 'path',
+            'operator' => 'equals',
+            'value'    => $path
+        ), array(
+            'field'    => 'subject',
+            'operator' => 'equals',
+            'value'    => 'test'
+        ));
+        $mailFilerJson = new MailFiler_Frontend_Json();
+        $emlNodes = $mailFilerJson->searchNodes($filter, array());
+        $this->assertGreaterThan(0, $emlNodes['totalcount'], 'could not find eml file node with subject filter');
+        $emlNode = $emlNodes['results'][0];
+
+        // check email fields
+        $this->assertTrue(isset($emlNode['message']), 'message not found in node array: ' . print_r($emlNodes['results'], true));
+        $this->assertEquals(array(Tinebase_Core::getUser()->accountEmailAddress), $emlNode['message']['to'], print_r($emlNode['message'], true));
+        $this->assertTrue(isset($emlNode['message']['structure']) && is_array($emlNode['message']['structure']), 'structure not found or not an array: ' . print_r($emlNode['message'], true));
+        $this->assertTrue(isset($emlNode['message']['body']) && is_string($emlNode['message']['body']), 'body not found or not a string: ' . print_r($emlNode['message'], true));
+        $this->assertContains('aaaaaä', $emlNode['message']['body'], print_r($emlNode['message'], true));
+    }
+
+    /**
+     * @see 0012162: create new MailFiler application
+     */
+    public function testFileMessagesInMailFilerWithAttachment()
+    {
+        $emlNode = $this->_fileMessageInMailFiler();
+        $this->assertTrue(isset($emlNode['message']['attachments']), 'attachments not found in message node: ' . print_r($emlNode, true));
+        $this->assertEquals(1, count($emlNode['message']['attachments']), 'attachment not found in message node: ' . print_r($emlNode, true));
+        $this->assertEquals('moz-screenshot-83.png', $emlNode['message']['attachments'][0]['filename'], print_r($emlNode['message']['attachments'], true));
+    }
+
+    /**
+     * @param string $messageFile
+     * @return array
+     */
+    protected function _fileMessageInMailFiler($messageFile = 'multipart_related.eml', $subject = 'Tine 2.0 bei Metaways - Verbessurngsvorschlag')
+    {
+        $appName = 'MailFiler';
+        $personalFilemanagerContainer = $this->_getPersonalContainer($appName . '_Model_Node');
+        $testFolder = $this->_getFolder($this->_testFolderName);
+        $message = fopen(dirname(__FILE__) . '/../files/' . $messageFile, 'r');
+        Felamimail_Controller_Message::getInstance()->appendMessage($testFolder, $message);
+
+        $message = $this->_searchForMessageBySubject($subject, $this->_testFolderName);
+        $path = '/' . Tinebase_Model_Container::TYPE_PERSONAL
+            . '/' . Tinebase_Core::getUser()->accountLoginName
+            . '/' . $personalFilemanagerContainer->name;
+        $filter = array(array(
+            'field' => 'id', 'operator' => 'in', 'value' => array($message['id'])
+        ));
+        $this->_json->fileMessages($filter, $appName, $path);
+        $filter = array(array(
+            'field'    => 'path',
+            'operator' => 'equals',
+            'value'    => $path
+        ), array(
+            'field'    => 'subject',
+            'operator' => 'equals',
+            'value'    => $message['subject']
+        ));
+        $mailFilerJson = new MailFiler_Frontend_Json();
+        $emlNodes = $mailFilerJson->searchNodes($filter, array());
+        $this->assertGreaterThan(0, $emlNodes['totalcount'], 'could not find eml file node with subject filter');
+        $emlNode = $emlNodes['results'][0];
+
+        return $emlNode;
+    }
+
+    /**
+     * @see 0012162: create new MailFiler application
+     */
+    public function testFileMessagesInMailFilerWithSingleBodyPart()
+    {
+        $emlNode = $this->_fileMessageInMailFiler('tine20_alarm_notifictation.eml', 'Alarm for event "ss/ss" at Oct 12, 2016 4:00:00 PM');
+        $this->assertContains('Event details', $emlNode['message']['body'], print_r($emlNode['message'], true));
+        $this->assertContains('"ss/ss"', $emlNode['message']['subject'], print_r($emlNode['message'], true));
+        $this->assertContains('"ssss"', $emlNode['name'], print_r($emlNode, true));
+    }
+
+    /**
+     * @see 0012162: create new MailFiler application
+     */
+    public function testFileMessageWithDelete()
+    {
+        $emlNode = $this->_fileMessageInMailFiler();
+        $mailFilerJson = new MailFiler_Frontend_Json();
+        $result = $mailFilerJson->deleteNodes(array($emlNode['path']));
+        self::assertEquals('success', $result['status']);
+    }
+
+    /**
+     * @see 0012162: create new MailFiler application
+     */
+    public function testFileMessageWithMultipartAttachment()
+    {
+        $emlNode = $this->_fileMessageInMailFiler('multipart_attachments.eml', 'Testmail mit Anhang');
+        $this->assertTrue(isset($emlNode['message']['attachments']), 'attachments not found in message node: ' . print_r($emlNode, true));
+        $this->assertEquals(5, count($emlNode['message']['attachments']), 'attachment not found in message node: ' . print_r($emlNode, true));
+        $this->assertEquals('TS Lagerstände.jpg', $emlNode['message']['attachments'][0]['filename'], print_r($emlNode['message']['attachments'], true));
+        $this->assertContains('Siehe Dateien anbei', $emlNode['message']['body'], print_r($emlNode['message'], true));
+    }
+
     /**
      * testMessageWithInvalidICS
      *
diff --git a/tests/tine20/Felamimail/files/multipart_attachments.eml b/tests/tine20/Felamimail/files/multipart_attachments.eml
new file mode 100644 (file)
index 0000000..abf8141
--- /dev/null
@@ -0,0 +1,88 @@
+Return-path: <lala@lolo.com>\r
+Envelope-to: lali@lolo.com\r
+Delivery-date: Thu, 24 Nov 2016 16:39:55 +0100\r
+X-Tine20TestMessage: multipart_attachments.eml\r
+Received: from [81.19.149.138] (helo=mx28lb.world4you.com)\r
+       by mail26.world4you.com with esmtp (Exim 4.77)\r
+       (envelope-from <lala@lolo.com>)\r
+       id 1c9w7v-0006I2-NX\r
+       for lali@lolo.com; Thu, 24 Nov 2016 16:39:55 +0100\r
+Received: from [209.85.213.54] (helo=mail-vk0-f54.google.com)\r
+       by mx28lb.world4you.com with esmtps (TLSv1.2:DHE-RSA-AES256-SHA:256)\r
+       (Exim 4.84_2)\r
+       (envelope-from <lala@lolo.com>)\r
+       id 1c9w7r-0001cj-2Z\r
+       for lali@lolo.com; Thu, 24 Nov 2016 16:39:55 +0100\r
+Received: by mail-vk0-f54.google.com with SMTP id x186so28688524vkd.1\r
+        for <lali@lolo.com>; Thu, 24 Nov 2016 07:39:51 -0800 (PST)\r
+X-Gm-Message-State: AKaTC02K6F2C95RbOBtUJWZs9k+qYB5T/y7c5KlIUkCKODBNItSxfrnvk+J+gExFwHF8scxxjwEYtARNL97z+g==\r
+X-Received: by 10.31.221.66 with SMTP id u63mr1008264vkg.16.1480001989463;\r
+ Thu, 24 Nov 2016 07:39:49 -0800 (PST)\r
+MIME-Version: 1.0\r
+Received: by 10.10.12.139 with HTTP; Thu, 24 Nov 2016 07:39:48 -0800 (PST)\r
+From: Freizeit <lala@lolo.com>\r
+Date: Thu, 24 Nov 2016 16:39:48 +0100\r
+Message-ID: <CAAj1EEy-5JvzguK0iwoKAH_s6FwzN_Sk2kWc-A09xLNrSUgFHQ@mail.gmail.com>\r
+To: lali@lolo.com\r
+Content-Type: multipart/mixed; boundary=94eb2c07db68d9787105420dd049\r
+X-SA-Exim-Connect-IP: 209.85.213.54\r
+X-SA-Exim-Mail-From: lala@lolo.com\r
+X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mx28lb.world4you.com\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0.4 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,\r
+       DKIM_VALID_AU,FILL_THIS_FORM,FILL_THIS_FORM_LONG,FREEMAIL_FROM,\r
+       GREYLIST_ISWHITE,HTML_MESSAGE,SPF_PASS,T_FREEMAIL_DOC_PDF autolearn=disabled\r
+       version=3.3.1\r
+Subject: Testmail mit Anhang\r
+X-SA-Exim-Version: 4.2.1 (built Thu, 10 Mar 2016 11:08:04 +0100)\r
+X-SA-Exim-Scanned: Yes (on mx28lb.world4you.com)\r
+\r
+--94eb2c07db68d9787105420dd049\r
+Content-Type: multipart/alternative; boundary=94eb2c07db68d9786b05420dd047\r
+\r
+--94eb2c07db68d9786b05420dd047\r
+Content-Type: text/plain; charset=UTF-8\r
+\r
+Siehe Dateien anbei\r
+\r
+--94eb2c07db68d9786b05420dd047\r
+Content-Type: text/html; charset=UTF-8\r
+\r
+<div dir="ltr">Siehe Dateien anbei<div><br></div></div>\r
+\r
+--94eb2c07db68d9786b05420dd047--\r
+--94eb2c07db68d9787105420dd049\r
+Content-Type: text/plain; name="=?UTF-8?Q?TS_Lagerst=C3=A4nde=2Ejpg?="\r
+Content-Disposition: attachment; filename="=?UTF-8?Q?TS_Lagerst=C3=A4nde=2Ejpg?="\r
+X-Attachment-Id: f_ivwiy07s0\r
+\r
+ATTACHMENT1\r
+--94eb2c07db68d9787105420dd049\r
+Content-Type: text/plain; name="Ticket-829168.pdf"\r
+Content-Disposition: attachment; filename="Ticket-829168.pdf"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_ivwiy0aj1\r
+\r
+ATTACHMENT2\r
+--94eb2c07db68d9787105420dd049\r
+Content-Type: text/plain; charset=UTF-8; name="adb_tine_import.csv"\r
+Content-Disposition: attachment; filename="adb_tine_import.csv"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_ivwiy0ar2\r
+\r
+ATTACHMENT3\r
+--94eb2c07db68d9787105420dd049\r
+Content-Type: text/plain; name="02-I 11 SPAM-Information v2.doc"\r
+Content-Disposition: attachment; filename="02-I 11 SPAM-Information v2.doc"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_ivwiz1t23\r
+\r
+ATTACHMENT4\r
+--94eb2c07db68d9787105420dd049\r
+Content-Type: text/plain; name="PC-Beschriftung.xls"\r
+Content-Disposition: attachment; filename="PC-Beschriftung.xls"\r
+Content-Transfer-Encoding: base64\r
+X-Attachment-Id: f_ivwiz1t74\r
+\r
+ATTACHMENT5\r
+--94eb2c07db68d9787105420dd049--\r
diff --git a/tests/tine20/Felamimail/files/tine20_alarm_notifictation.eml b/tests/tine20/Felamimail/files/tine20_alarm_notifictation.eml
new file mode 100644 (file)
index 0000000..ffdbf9b
--- /dev/null
@@ -0,0 +1,27 @@
+Return-Path: <noreply@example.org>\r
+Delivered-To: vagrant@example.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits))\r
+       (No client certificate requested)\r
+       by vagrant-ubuntu-trusty-32 (Postfix) with ESMTPS id E16BF419C9\r
+       for <vagrant@example.org>; Wed, 12 Oct 2016 13:53:06 +0000 (UTC)\r
+Subject: Alarm for event "ss/ss" at Oct 12, 2016 4:00:00 PM\r
+X-Tine20-Type: Notification\r
+Precedence: bulk\r
+User-Agent: Tine 2.0 Notification Service(version 12162: 00bc36b852154705a7fac1b892295f4d1ff02317 (2016-10-12 08:39:27) - none)\r
+From: "vagrant" <vagrant@example.org>\r
+Sender: "Tine 2.0 notification service" <noreply@example.org>\r
+To: "vagrant Test" <vagrant@example.org>\r
+Message-Id: <a9a3697ca976383547057332b1fa128a14d333f0@vagrant-ubuntu-trusty-32>\r
+X-MailGenerator: Tine 2.0\r
+X-Tine20TestMessage: tine20_alarm_notifictation.eml\r
+Date: Wed, 12 Oct 2016 13:53:06 +0000\r
+Content-Type: text/plain; charset=UTF-8\r
+Content-Transfer-Encoding: quoted-printable\r
+Content-Disposition: inline\r
+MIME-Version: 1.0\r
+\r
+Event details:=0AStart:              Wednesday, Oct 12, 2016 4:00:00 PM=\r
+=0AEnd:                Wednesday, Oct 12, 2016 5:00:00 PM=0ASummary:   =\r
+         ssss=0ADescription:        dfvsdvsfdv=0AAttender:=0A    Test, v=\r
+agrant (Required, Accepted) =0A=0A\r
index 06d89bb..0118c47 100644 (file)
@@ -76,6 +76,7 @@ class Filemanager_Frontend_JsonTests extends TestCase
         $this->_json = new Filemanager_Frontend_Json();
         $this->_fsController = Tinebase_FileSystem::getInstance();
         $this->_application = Tinebase_Application::getInstance()->getApplicationByName('Filemanager');
+        Tinebase_Container::getInstance()->getDefaultContainer('Filemanager');
     }
     
     /**
@@ -718,6 +719,7 @@ class Filemanager_Frontend_JsonTests extends TestCase
             'operator' => 'equals', 
             'value'    => Tinebase_Model_Tree_Node::TYPE_FOLDER,
         ));
+
         $result = $this->_json->searchNodes($filter, array());
         $this->assertEquals(0, $result['totalcount']);
     }
@@ -729,6 +731,7 @@ class Filemanager_Frontend_JsonTests extends TestCase
      */
     public function testMoveFolderNodesToFolderExisting()
     {
+        sleep(1);
         $targetNode = $this->testCreateContainerNodeInPersonalFolder();
         $testPath = '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/dir1';
         $result = $this->_json->moveNodes(array($targetNode['path']), array($testPath), FALSE);
@@ -1030,7 +1033,28 @@ class Filemanager_Frontend_JsonTests extends TestCase
         
         return $node;
     }
-    
+
+    /**
+     * testAttachTag
+     *
+     * @see 0012284: file type changes to 'directory' if tag is assigned
+     */
+    public function testAttachTagPreserveContentType()
+    {
+        $node = $this->testCreateFileNodeWithTempfile();
+        $node['tags'] = array(array(
+            'type'          => Tinebase_Model_Tag::TYPE_PERSONAL,
+            'name'          => 'file tag',
+        ));
+        $node['path'] = '';
+        // remove hash field that the client does not send
+        unset($node['hash']);
+        $updatedNode = $this->_json->saveNode($node);
+
+        $this->assertEquals(1, count($updatedNode['tags']));
+        $this->assertEquals($node['contenttype'], $updatedNode['contenttype'], 'contenttype  not preserved');
+    }
+
     /**
      * testSetRelation
      * 
@@ -1301,7 +1325,7 @@ class Filemanager_Frontend_JsonTests extends TestCase
     protected function _getPersonalFilemanagerContainer()
     {
         if (!$this->_personalContainer) {
-            $this->_personalContainer = $this->_getPersonalContainer('Filemanager');
+            $this->_personalContainer = $this->_getPersonalContainer('Filemanager_Model_Node');
         }
         
         return $this->_personalContainer;
@@ -1473,4 +1497,43 @@ class Filemanager_Frontend_JsonTests extends TestCase
             $this->assertTrue($e instanceof Tinebase_Exception_NotFound);
         }
     }
+
+    /**
+     * Creating a new shared folder and moving it afterwards to a private one doesn't delete it's container
+     * And moving it back creates a new one and so on.
+     *
+     * This shouldn't happen.
+     *
+     * The correct behaviour would be, if a container is moved inside a folder, it should become a folder node
+     * and it's container should be deleted afterwards.
+     *
+     * https://forge.tine20.org/view.php?id=12740
+     */
+    public function testMovingContainerAround()
+    {
+        $private = $this->testCreateContainerNodeInPersonalFolder();
+
+        // Move shared directory folder to private folder
+        $node = $this->_json->createNode(['/shared/sharedfoldertest'], 'folder');
+        $nodePath = $node['path'];
+
+        $targetPath = $private['path'] . '/sharedfoldertest';
+
+        $this->_json->moveNodes([$nodePath], [$targetPath], false);
+        $this->_json->moveNodes([$targetPath], [$nodePath], false);
+        $this->_json->moveNodes([$nodePath], [$targetPath], false);
+        $this->_json->moveNodes([$targetPath], [$nodePath], false);
+
+        $filter = new Tinebase_Model_ContainerFilter(array(
+            array(
+                'field' => 'name',
+                'operator' => 'equals',
+                'value' => 'sharedfoldertest'
+            ),
+        ));
+
+        $sharedFolderTestContainer = Tinebase_Container::getInstance()->search($filter);
+
+        $this->assertEquals(1, $sharedFolderTestContainer->count());
+    }
 }
index eebc320..05077d7 100644 (file)
@@ -29,13 +29,10 @@ abstract class ImportTestCase extends TestCase
 
     /**
      * @var boolean
-     *
-     * TODO needed here?
      */
     protected $_deleteImportFile = true;
 
-    protected $_importerClassName = null;
-    protected $_exporterClassName = null;
+    protected $_importerClassName = 'Tinebase_Import_Csv_Generic';
     protected $_modelName = null;
     protected $_testContainer = null;
 
@@ -49,6 +46,11 @@ abstract class ImportTestCase extends TestCase
         if ($this->_testContainer) {
             Tinebase_Container::getInstance()->deleteContainer($this->_testContainer);
         }
+
+        // cleanup
+        if (file_exists($this->_filename) && $this->_deleteImportFile) {
+             unlink($this->_filename);
+        }
     }
 
     /**
@@ -60,18 +62,22 @@ abstract class ImportTestCase extends TestCase
      * @throws Tinebase_Exception_NotFound
      * @return array
      */
-    protected function _doImport(array $_options, $_definition, Tinebase_Model_Filter_FilterGroup $_exportFilter = NULL)
+    protected function _doImport(array $_options = array(), $_definition = null, Tinebase_Model_Filter_FilterGroup $_exportFilter = null)
     {
         if (! $this->_importerClassName || ! $this->_modelName) {
             throw new Tinebase_Exception_NotFound('No import class or model name given');
         }
 
-        $definition = ($_definition instanceof Tinebase_Model_ImportExportDefinition) ? $_definition : Tinebase_ImportExportDefinition::getInstance()->getByName($_definition);
+        if ($_definition === null) {
+            $definition = Tinebase_ImportExportDefinition::getInstance()->getGenericImport($this->_modelName);
+        } else {
+            $definition = ($_definition instanceof Tinebase_Model_ImportExportDefinition) ? $_definition : Tinebase_ImportExportDefinition::getInstance()->getByName($_definition);
+        }
         $this->_instance = call_user_func_array($this->_importerClassName . '::createFromDefinition' , array($definition, $_options));
 
         // export first
-        if ($_exportFilter !== NULL && $this->_exporterClassName) {
-            $exporter = new $this->_exporterClassName($_exportFilter, Tinebase_Core::getApplicationInstance($this->_modelName));
+        if ($_exportFilter !== NULL) {
+            $exporter = Tinebase_Export::factory($_exportFilter, 'csv', Tinebase_Core::getApplicationInstance($this->_modelName));
             $this->_filename = $exporter->generate();
         }
 
index a6028ea..61aae0a 100644 (file)
@@ -187,14 +187,14 @@ class Inventory_JsonTest extends Inventory_TestCase
         ));
         
         $inventoryItem = $this->_getInventoryItem();
-        $inventoryItem->costcentre = $cc->getId();
+        $inventoryItem->costcenter = $cc->getId();
         
         $this->_json->saveInventoryItem($inventoryItem->toArray());
     
         $inventoryItem = $this->_getInventoryItem();
         $this->_json->saveInventoryItem($inventoryItem->toArray());
         
-        $filter = Zend_Json::decode('[{"condition":"OR","filters":[{"condition":"AND","filters":[{"field":"costcentre","operator":"AND","value":[{"field":":id","operator":"equals","value":"'.$cc->getId().'"}],"id":"ext-record-2"}],"id":"ext-comp-1135","label":"test"}]}]');
+        $filter = Zend_Json::decode('[{"condition":"OR","filters":[{"condition":"AND","filters":[{"field":"costcenter","operator":"AND","value":[{"field":":id","operator":"equals","value":"'.$cc->getId().'"}],"id":"ext-record-2"}],"id":"ext-comp-1135","label":"test"}]}]');
         
         $result = $this->_json->searchInventoryItems($filter, array());
     
diff --git a/tests/tine20/MailFiler/AllTests.php b/tests/tine20/MailFiler/AllTests.php
new file mode 100644 (file)
index 0000000..f8b6848
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     MailFiler
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2010-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+class MailFiler_AllTests
+{
+    public static function main ()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+    
+    public static function suite ()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 MailFiler All Tests');
+        $suite->addTestSuite('MailFiler_Frontend_AllTests');
+        
+        return $suite;
+    }
+}
diff --git a/tests/tine20/MailFiler/Frontend/AllTests.php b/tests/tine20/MailFiler/Frontend/AllTests.php
new file mode 100644 (file)
index 0000000..22a95c7
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     MailFiler
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2012-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+class MailFiler_Frontend_AllTests
+{
+    public static function main ()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+    
+    public static function suite ()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 MailFiler Frontend Tests');
+        $suite->addTestSuite('MailFiler_Frontend_JsonTests');
+        
+        return $suite;
+    }
+}
diff --git a/tests/tine20/MailFiler/Frontend/JsonTests.php b/tests/tine20/MailFiler/Frontend/JsonTests.php
new file mode 100644 (file)
index 0000000..68f26a4
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     MailFiler
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * 
+ */
+
+/**
+ * Test class for MailFiler_Frontend_Json
+ * 
+ * @package     MailFiler
+ */
+class MailFiler_Frontend_JsonTests extends TestCase
+{
+    /**
+     * uit
+     *
+     * @var MailFiler_Frontend_Json
+     */
+    protected $_json;
+
+    /**
+     * Sets up the fixture.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->_json = new MailFiler_Frontend_Json();
+    }
+
+    /**
+     * Tears down the fixture
+     * This method is called after a test is executed.
+     *
+     * @access protected
+     */
+    protected function tearDown()
+    {
+        parent::tearDown();
+
+        Tinebase_FileSystem::getInstance()->clearStatCache();
+        Tinebase_FileSystem::getInstance()->clearDeletedFilesFromFilesystem();
+    }
+
+    /**
+     * test search nodes (personal)
+     */
+    public function testSearchWithMessageFilter()
+    {
+        $this->testCreateContainerNodeInPersonalFolder();
+        $filter = array(array(
+            'field' => 'path',
+            'operator' => 'equals',
+            'value' => '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/' . 'testcontainer'
+        ), array(
+            'field' => 'to',
+            'operator' => 'contains',
+            'value' => 'vagrant'
+        ), array(
+            'field' => 'flags',
+            'operator' => 'in',
+            'value' => array(
+                '\Tine20'
+            )
+        ));
+        $result = $this->_json->searchNodes($filter, array());
+        self::assertEquals(5, count($result['filter']));
+        self::assertEquals(0, $result['totalcount']);
+    }
+
+    /**
+     * create container in personal folder
+     *
+     * @return array created node
+     */
+    public function testCreateContainerNodeInPersonalFolder($containerName = 'testcontainer')
+    {
+        $testPath = '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/' . $containerName;
+        $result = $this->_json->createNodes($testPath, Tinebase_Model_Tree_Node::TYPE_FOLDER, array(), FALSE);
+        $createdNode = $result[0];
+
+        $this->_objects['containerids'][] = $createdNode['name']['id'];
+
+        self::assertTrue(is_array($createdNode['name']));
+        self::assertEquals($containerName, $createdNode['name']['name']);
+        self::assertEquals(Tinebase_Core::getUser()->getId(), $createdNode['created_by']['accountId']);
+
+        return $createdNode;
+    }
+
+    /**
+     * test move eml node
+     */
+    public function testMoveNode()
+    {
+        $node1 = $this->testCreateContainerNodeInPersonalFolder('testcontainer1');
+        $node2 = $this->testCreateContainerNodeInPersonalFolder('testcontainer2');
+
+        $tempFilename = Tinebase_TempFile::getTempPath();
+        file_put_contents($tempFilename, 'my eml content');
+        $tempFile = Tinebase_TempFile::getInstance()->createTempFile($tempFilename);
+        $filePath = $node1['path'] . '/my.eml';
+        MailFiler_Controller_Node::getInstance()->createNodes(
+            array($filePath),
+            Tinebase_Model_Tree_Node::TYPE_FILE,
+            array($tempFile->getId()),
+            /* $_forceOverwrite */ true
+        )->getFirstRecord();
+
+        // move to testcontainer2
+        $targetFilePath = $node2['path'] . '/my.eml';
+        $result = $this->_json->moveNodes(array($filePath), array($targetFilePath), FALSE);
+
+        self::assertEquals(1, count($result));
+        self::assertEquals($targetFilePath, $result[0]['path']);
+    }
+
+    /**
+     * testAttachTagToFolderNode
+     *
+     * @see 0012370: tags not working
+     */
+    public function testAttachTagToFolderNode()
+    {
+        $node = $this->testCreateContainerNodeInPersonalFolder();
+        $node['tags'] = array(array(
+            'type' => Tinebase_Model_Tag::TYPE_PERSONAL,
+            'name' => 'file tag',
+        ));
+        $node['name'] = $node['name']['id'];
+        $updatedNode = $this->_json->saveNode($node);
+
+        $this->assertEquals(1, count($updatedNode['tags']));
+    }
+}
index 5b097c6..5f1de9a 100644 (file)
@@ -216,7 +216,7 @@ class Projects_JsonTest extends PHPUnit_Framework_TestCase
         $search = $this->_json->searchProjects($filter, $this->_getPaging());
         $this->assertEquals($projectData['description'], $search['results'][0]['description']);
         $this->assertEquals(1, $search['totalcount']);
-        $this->assertEquals(4, count($search['filter'][1]['value']));
+        $this->assertEquals((Addressbook_Controller_Contact::getInstance()->doContainerACLChecks()?4:3), count($search['filter'][1]['value']), print_r($search['filter'][1], true));
         $this->assertEquals(':relation_type', $search['filter'][1]['value'][0]['field']);
         $this->assertEquals(TRUE, $search['filter'][1]['value'][2]['implicit']);
         $this->assertEquals(Tinebase_Core::getUser()->contact_id, 
@@ -367,7 +367,6 @@ class Projects_JsonTest extends PHPUnit_Framework_TestCase
     protected function _createProjectWithContactRelation()
     {
         $project = $this->_getProjectData();
-        $contact = $this->_getContactData();
         $project['relations'] = array(
             array(
                 'own_model'              => 'Projects_Model_Project',
index 7402273..3c22c73 100644 (file)
@@ -928,7 +928,6 @@ class Sales_InvoiceControllerTests extends Sales_InvoiceTestCase
             'title'         => 'Tacss',
             'description'   => 'blabla',
             'is_open'       => 1,
-            'status'        => 'open',
             'budget'        => NULL,
             
         )))->getFirstRecord();
index 8755b06..5ae8b65 100644 (file)
@@ -263,6 +263,45 @@ class Sales_JsonTest extends TestCase
         $this->assertEquals(1, $search['totalcount']);
     }
 
+    /**
+     * try to get a contract by product
+     */
+    public function testSearchContractsByProduct()
+    {
+        // create & add aggregate
+        $prodTest = new Sales_ProductControllerTest();
+        $product = $prodTest->testCreateProduct();
+
+        $contract = $this->_getContract();
+        $contract->products = array(
+            array(
+                'product_id' => $product->getId(),
+                'quantity' => 1,
+                'interval' => 1,
+                'billing_point' => 1,
+            ),
+        );
+        $contractData = $this->_instance->saveContract($contract->toArray());
+
+        // search & check
+        $filter = $this->_getFilter($contract->title);
+        $filter[] = array('field' => 'products', 'operator' => 'contains', 'value' => 'product');
+        $search = $this->_instance->searchContracts($filter, $this->_getPaging());
+        $this->assertEquals($contract->title, $search['results'][0]['title']);
+        $this->assertEquals(1, $search['totalcount']);
+    }
+
+    /**
+     * assert products filter
+     */
+    public function testContractFilterModel()
+    {
+        $config = Sales_Model_Contract::getConfiguration();
+        $filterModel = $config->getFilterModel();
+        $this->assertTrue(isset($filterModel['_filterModel']['customer']), 'customer not found in filter model: ' . print_r($filterModel, true));
+        $this->assertTrue(isset($filterModel['_filterModel']['products']), 'products not found in filter model: ' . print_r($filterModel, true));
+    }
+
     public function testSearchEmptyDateTimeFilter()
     {
         // create
index c3f6216..33d8ab2 100644 (file)
@@ -540,8 +540,9 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
      * test record json api
      *
      * @param $modelName
+     * @return array
      */
-    protected function _testSimpleRecordApi($modelName)
+    protected function _testSimpleRecordApi($modelName, $nameField = 'name', $descriptionField = 'description', $delete = true)
     {
         $uit = $this->_getUit();
         if (!$uit instanceof Tinebase_Frontend_Json_Abstract) {
@@ -549,28 +550,32 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
         }
 
         $newRecord = array(
-            'name' => 'my test ' . $modelName,
-            'description' => 'my test description'
+            $nameField          => 'my test ' . $modelName,
+            $descriptionField   => 'my test description'
         );
         $savedRecord = call_user_func(array($uit, 'save' . $modelName), $newRecord);
 
-        $this->assertEquals('my test ' . $modelName, $savedRecord['name'], print_r($savedRecord, true));
-        $savedRecord['description'] = 'my updated description';
+        $this->assertEquals('my test ' . $modelName, $savedRecord[$nameField], print_r($savedRecord, true));
+        $savedRecord[$descriptionField] = 'my updated description';
 
         $updatedRecord = call_user_func(array($uit, 'save' . $modelName), $savedRecord);
-        $this->assertEquals('my updated description', $updatedRecord['description']);
+        $this->assertEquals('my updated description', $updatedRecord[$descriptionField]);
 
         $filter = array(array('field' => 'id', 'operator' => 'equals', 'value' => $updatedRecord['id']));
         $result = call_user_func(array($uit, 'search' . $modelName . 's'), $filter, array());
         $this->assertEquals(1, $result['totalcount']);
 
-        call_user_func(array($uit, 'delete' . $modelName . 's'), array($updatedRecord['id']));
-        try {
-            call_user_func(array($uit, 'get' . $modelName), $updatedRecord['id']);
-            $this->fail('should delete Record');
-        } catch (Tinebase_Exception_NotFound $tenf) {
-            $this->assertTrue($tenf instanceof Tinebase_Exception_NotFound);
+        if ($delete) {
+            call_user_func(array($uit, 'delete' . $modelName . 's'), array($updatedRecord['id']));
+            try {
+                call_user_func(array($uit, 'get' . $modelName), $updatedRecord['id']);
+                $this->fail('should delete Record');
+            } catch (Tinebase_Exception_NotFound $tenf) {
+                $this->assertTrue($tenf instanceof Tinebase_Exception_NotFound);
+            }
         }
+
+        return $updatedRecord;
     }
 
     /**
index 518bb8e..5618313 100644 (file)
@@ -311,6 +311,8 @@ class Timetracker_ControllerTest extends TestCase
      */
     public function testAddTimesheetExceedingDeadline()
     {
+        $this->markTestSkipped('random failures, maybe Sales Test have random transaction issues?');
+        
         // TODO should work without invoices feature, too ...
         if (! Sales_Config::getInstance()->featureEnabled(Sales_Config::FEATURE_INVOICES_MODULE)) {
             $this->markTestSkipped('needs enabled invoices module');
index 1cf99bf..5bb69ed 100644 (file)
@@ -277,6 +277,7 @@ class Tinebase_ApplicationTest extends TestCase
                 'Addressbook_Model_ListRole',
                 'Addressbook_Model_ListMemberRole',
                 'Addressbook_Model_Contact',
+                'Addressbook_Model_Industry'
             ),
             'Admin' => array(
                 'Admin_Model_Config',
@@ -459,7 +460,7 @@ class Tinebase_ApplicationTest extends TestCase
         );
 
         // remove bogus apps
-        $remove = array('RequestTracker', 'Sipgate', 'Expressodriver');
+        $remove = array('RequestTracker', 'Sipgate', 'Expressodriver', 'MailFiler');
         foreach($remove as $r)
         {
             if (($key = array_search($r, $appNames)) !== false) {
@@ -484,6 +485,14 @@ class Tinebase_ApplicationTest extends TestCase
             }
         }
 
+        // check model dir -> app might have no models
+        foreach ($appNames as $key => $appName) {
+            $modelDir = __DIR__ . "../../tine20/$appName/Model/";
+            if (! file_exists($modelDir)) {
+                unset($appNames[$key]);
+            }
+        }
+        
         // no apps should remain => we found models for each app, expect the bogus ones from above
         $this->assertEquals(0, count($appNames), 'applications found for which no models where found: '.print_r($appNames, true));
 
index 4d62347..1745b0d 100644 (file)
@@ -75,7 +75,7 @@ class Tinebase_ConfigTest extends PHPUnit_Framework_TestCase
         $this->_instance->set(Tinebase_Config::PAGETITLEPOSTFIX, 'phpunit');
         $this->assertEquals('phpunit', $this->_instance->{Tinebase_Config::PAGETITLEPOSTFIX}, 'could not set config');
 
-        $this->_instance->delete(Tinebase_Config::PAGETITLEPOSTFIX, 'phpunit');
+        $this->_instance->delete(Tinebase_Config::PAGETITLEPOSTFIX);
 
         $this->assertEquals('###PHPUNIT-NOTSET###', $this->_instance->get(Tinebase_Config::PAGETITLEPOSTFIX, '###PHPUNIT-NOTSET###'), 'config got not deleted');
 
@@ -88,6 +88,7 @@ class Tinebase_ConfigTest extends PHPUnit_Framework_TestCase
      */
     public function testConfigFromFileOverwrites()
     {
+        /** @noinspection PhpIncludeInspection */
         $configData = include('config.inc.php');
 
         if (!(isset($configData['Overwrite Test']) || array_key_exists('Overwrite Test', $configData))) {
@@ -168,28 +169,26 @@ class Tinebase_ConfigTest extends PHPUnit_Framework_TestCase
      */
     public function testApplicationDefaultConfig()
     {
-        $defaultConfigFile = $this->_getSalesCustomDefaultConfig();
+        $defaultConfigFile = $this->_getExampleApplicationCustomDefaultConfig();
 
-        if (file_exists($defaultConfigFile)) {
-            $this->markTestSkipped('ignore test because Sales default config exists');
-        }
+        $this->assertFalse(file_exists($defaultConfigFile), 'test needs to be recoded because Example Application default config exists');
 
-        $ignoreBillablesConfig = Sales_Config::getInstance()->get(Sales_Config::IGNORE_BILLABLES_BEFORE);
-        $this->assertEquals('2000-01-01 22:00:00', $ignoreBillablesConfig);
+        $exampleString = ExampleApplication_Config::getInstance()->get(ExampleApplication_Config::EXAMPLE_STRING);
+        $this->assertEquals(ExampleApplication_Config::EXAMPLE_STRING, $exampleString);
 
         copy(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'configtest.inc.php', $defaultConfigFile);
         $this->_filenamesToDelete[] = $defaultConfigFile;
 
-        Sales_Config::getInstance()->clearCache();
+        ExampleApplication_Config::getInstance()->clearCache();
 
-        $ignoreBillablesConfigAppDefault = Sales_Config::getInstance()->get(Sales_Config::IGNORE_BILLABLES_BEFORE);
-        $this->assertEquals('1999-10-01 22:00:00', $ignoreBillablesConfigAppDefault);
+        $exampleString = ExampleApplication_Config::getInstance()->get(ExampleApplication_Config::EXAMPLE_STRING);
+        $this->assertEquals('something else', $exampleString);
     }
 
-    protected function _getSalesCustomDefaultConfig()
+    protected function _getExampleApplicationCustomDefaultConfig()
     {
         return dirname(dirname(dirname(dirname(__FILE__)))) . DIRECTORY_SEPARATOR . 'tine20'
-        . DIRECTORY_SEPARATOR . 'Sales' . DIRECTORY_SEPARATOR . 'config.inc.php';
+        . DIRECTORY_SEPARATOR . 'ExampleApplication' . DIRECTORY_SEPARATOR . 'config.inc.php';
     }
 
     /**
@@ -199,14 +198,13 @@ class Tinebase_ConfigTest extends PHPUnit_Framework_TestCase
      */
     public function testFeatureEnabled()
     {
-        $customConfigFilename = $this->_getSalesCustomDefaultConfig();
-        if (file_exists($customConfigFilename)) {
-            $this->markTestSkipped('do not test with existing custom config');
-        }
+        $customConfigFilename = $this->_getExampleApplicationCustomDefaultConfig();
 
-        $invoicesFeatureEnabled = Sales_Config::getInstance()->featureEnabled(Sales_Config::FEATURE_INVOICES_MODULE);
+        $this->assertFalse(file_exists($customConfigFilename), 'test needs to be recoded because Example Application default config exists');
 
-        $this->assertTrue($invoicesFeatureEnabled);
+        $exampleFeatureEnabled = ExampleApplication_Config::getInstance()->featureEnabled(ExampleApplication_Config::EXAMPLE_FEATURE);
+
+        $this->assertTrue($exampleFeatureEnabled);
     }
 
     /**
@@ -250,4 +248,35 @@ class Tinebase_ConfigTest extends PHPUnit_Framework_TestCase
 
         $this->assertEquals(true, Tinebase_Config::getInstance()->get(Tinebase_Config::USE_LOGINNAME_AS_FOLDERNAME));
     }
+
+    public function testConfigStructure()
+    {
+        $exampleConfig = ExampleApplication_Config::getInstance();
+
+        $defaultConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . DIRECTORY_SEPARATOR . 'tine20'
+            . DIRECTORY_SEPARATOR . 'ExampleApplication' . DIRECTORY_SEPARATOR . 'config.inc.php';
+
+        $this->assertFalse(file_exists($defaultConfigFile), 'test needs to be recoded because ExampleApplication default config exists');
+
+        copy(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR . 'configExampleAppTest.inc.php', $defaultConfigFile);
+        $this->_filenamesToDelete[] = $defaultConfigFile;
+
+        $exampleConfig->clearCache();
+
+        $smtpStruct = $exampleConfig->{ExampleApplication_Config::EXAMPLE_MAILCONFIG}->{ExampleApplication_Config::SMTP};
+        $imapStruct = $exampleConfig->{ExampleApplication_Config::EXAMPLE_MAILCONFIG}->{ExampleApplication_Config::IMAP};
+
+        $this->assertTrue($smtpStruct instanceof Tinebase_Config_Struct);
+        $this->assertTrue(is_string($smtpStruct->{ExampleApplication_Config::HOST}) && $smtpStruct->{ExampleApplication_Config::HOST} === 'localhost');
+        $this->assertTrue(is_int($smtpStruct->{ExampleApplication_Config::PORT}) && $smtpStruct->{ExampleApplication_Config::PORT} === 123);
+        $this->assertTrue(is_bool($smtpStruct->{ExampleApplication_Config::SSL}) && $smtpStruct->{ExampleApplication_Config::SSL} === true);
+
+        $this->assertTrue($imapStruct instanceof Tinebase_Config_Struct);
+        $this->assertTrue(is_string($imapStruct->{ExampleApplication_Config::HOST}) && $imapStruct->{ExampleApplication_Config::HOST} === '123');
+        $this->assertTrue(is_int($imapStruct->{ExampleApplication_Config::PORT}) && $imapStruct->{ExampleApplication_Config::PORT} === 999);
+        $this->assertTrue(is_bool($imapStruct->{ExampleApplication_Config::SSL}) && $imapStruct->{ExampleApplication_Config::SSL} === true);
+
+        $this->setExpectedException('Tinebase_Exception_InvalidArgument');
+        $imapStruct->shooo;
+    }
 }
index 9075ca8..04c04dd 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Container
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2008-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
@@ -41,6 +41,17 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
         
         $this->_instance = Tinebase_Container::getInstance();
+
+        $this->objects['personalContainer'] = $this->_instance->addContainer(new Tinebase_Model_Container(array(
+            'name'              => 'personalTine20phpunit',
+            'type'              => Tinebase_Model_Container::TYPE_PERSONAL,
+            'owner_id'          => Tinebase_Core::getUser(),
+            'backend'           => 'Sql',
+            'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Addressbook')->getId(),
+            'model'             => 'Addressbook_Model_Contact'
+        )));
+
+        sleep(1);
         
         $this->objects['initialContainer'] = $this->_instance->addContainer(new Tinebase_Model_Container(array(
             'name'              => 'tine20phpunit',
@@ -207,7 +218,7 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
     }
     
     /**
-     * try to add an existing container. should throw an exception
+     * try to get a deleted container. should throw an exception
      *
      */
     public function testDeleteContainer()
@@ -215,11 +226,11 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         $this->_instance->deleteContainer($this->objects['initialContainer']);
         
         $this->setExpectedException('Tinebase_Exception_NotFound');
-        $container = $this->_instance->getContainerById($this->objects['initialContainer']);
+        $this->_instance->getContainerById($this->objects['initialContainer']);
     }
     
     /**
-     * try to delete an existing container with a contact 
+     * try to delete an existing container with a contact, contact should be gone
      */
     public function testDeleteContainerWithContact()
     {
@@ -232,11 +243,17 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         
         // delete container
         $this->_instance->deleteContainer($this->objects['initialContainer']);
-        
+
+        $catched = false;
+        try {
+            $this->_instance->getContainerById($this->objects['initialContainer']);
+        } catch (Tinebase_Exception_NotFound $tenf) {
+            $catched = true;
+        }
+        static::assertTrue($catched, 'excpected Tinebase_Exception_NotFound was not thrown');
+
         $this->setExpectedException('Tinebase_Exception_NotFound');
-        $container = $this->_instance->getContainerById($this->objects['initialContainer']);
-        
-        Addressbook_Controller_Contact::getInstance()->delete($contact->getId());
+        Addressbook_Controller_Contact::getInstance()->get($contact->getId());
     }
     
     /**
@@ -262,7 +279,7 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         
         $this->setExpectedException('Tinebase_Exception_NotFound');
         
-        $container = $this->_instance->getContainerById($this->objects['initialContainer']);
+        $this->_instance->getContainerById($this->objects['initialContainer']);
     }
     
     /**
@@ -433,7 +450,7 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
                     Tinebase_Model_Grants::GRANT_ADMIN     => true
              ))
         );
-        $grants = $this->_instance->setGrants($this->objects['initialContainer'], $newGrants);
+        $this->_instance->setGrants($this->objects['initialContainer'], $newGrants);
         
         $container = $this->_instance->getContainerById($this->objects['initialContainer']);
         $this->assertTrue(is_object($container));
@@ -450,7 +467,7 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
     {
         $this->assertTrue($this->_instance->hasGrant(Tinebase_Core::getUser(), $this->objects['initialContainer'], Tinebase_Model_Grants::GRANT_READ));
 
-        $readableContainer = $this->_instance->getContainerByAcl(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ);
+        $readableContainer = $this->_instance->getContainerByACL(Tinebase_Core::getUser(), 'Addressbook', Tinebase_Model_Grants::GRANT_READ);
         $this->assertEquals('Tinebase_Record_RecordSet', get_class($readableContainer), 'wrong type');
         $this->assertTrue(count($readableContainer) >= 2);
         foreach($readableContainer as $container) {
@@ -470,7 +487,7 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         $records = new Tinebase_Record_RecordSet('Addressbook_Model_Contact');
         $records->addRecord($contact);
         
-        $grants = $this->_instance->getGrantsOfRecords($records, $userId, 'container_id');
+        $this->_instance->getGrantsOfRecords($records, $userId, 'container_id');
         
         $this->assertTrue($records[0]['container_id'] instanceof Tinebase_Model_Container, 'contaienr_id is not resolved');
         $this->assertTrue(!empty($records[0]['container_id']['path']), 'path is not added');
@@ -614,7 +631,8 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         $result = Tinebase_Container::getInstance()->search($filter);
         
         $this->assertTrue(count($result) > 0);
-        
+
+        /** @var Tinebase_Model_Container $container */
         foreach ($result as $container) {
             $this->assertEquals(Tinebase_Model_Container::TYPE_PERSONAL, $container->type);
             $this->assertTrue(Tinebase_Container::getInstance()->hasGrant(
index 381b4cf..c747252 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Tinebase
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2010-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2010-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
@@ -137,8 +137,12 @@ class Tinebase_Frontend_CliTest extends TestCase
         $deletedContact = $this->_addAndDeleteContact();
         $deletedLead = $this->_addAndDeleteLead();
 
-        // delete personal adb container and tag, too
-        Tinebase_Container::getInstance()->deleteContainer($this->_getPersonalContainer('Addressbook')->getId());
+        // test deleted contact is still there
+        $contactBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
+        $contacts = $contactBackend->getMultipleByProperty($deletedContact->getId(), 'id', TRUE);
+        $this->assertEquals(1, count($contacts));
+
+        // delete tag too
         Tinebase_Tags::getInstance()->deleteTags($deletedContact->tags->getFirstRecord()->getId());
         
         ob_start();
@@ -150,7 +154,7 @@ class Tinebase_Frontend_CliTest extends TestCase
         $this->assertContains('Cleared table metacrm_lead (deleted ', $out);
         $this->assertNotContains('Failed to purge', $out);
 
-        $contactBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
+        // test deleted contact is gone
         $contacts = $contactBackend->getMultipleByProperty($deletedContact->getId(), 'id', TRUE);
         $this->assertEquals(0, count($contacts));
 
index 8adec3f..5646475 100644 (file)
@@ -28,6 +28,9 @@ class Tinebase_Frontend_HttpTest extends PHPUnit_Framework_TestCase
         $this->_uit = new Tinebase_Frontend_Http;
     }
 
+    /**
+     * @group needsbuild
+     */
     public function testMainScreen()
     {
         if (version_compare(PHPUnit_Runner_Version::id(), '3.3.0', '<')) {
index f69c516..00a5ed9 100644 (file)
@@ -164,6 +164,8 @@ class Tinebase_Frontend_Json_PersistentFilterTest extends TestCase
     
     /**
      * testInitialRegistry
+     *
+     * @group needsbuild
      */
     public function testInitialRegistry()
     {
index 1004cda..957050d 100644 (file)
@@ -487,12 +487,14 @@ class Tinebase_Frontend_JsonTest extends TestCase
      * @see 0008310: apps should be sorted the other way round in menu
      * @see 0009130: Can't open login page on Ubuntu "due to a temporary overloading"
      * @see 0012188: add copyOmitFields to modelconfig
+     * @see 0012364: generalize import/export and allow to configure via modelconfig
      */
     public function testGetAllRegistryData()
     {
         $registryData = $this->_instance->getAllRegistryData();
         $currentUser = Tinebase_Core::getUser();
-        
+
+        $this->assertTrue(isset($registryData['Tinebase']['currentAccount']), print_r($registryData['Tinebase'], true));
         $this->assertEquals($currentUser->toArray(), $registryData['Tinebase']['currentAccount']);
         $this->assertEquals(
             Addressbook_Controller_Contact::getInstance()->getContactByUserId($currentUser->getId())->toArray(),
@@ -533,6 +535,11 @@ class Tinebase_Frontend_JsonTest extends TestCase
 
         $this->assertTrue(isset($registryData['Timetracker']['models']['Timeaccount']['copyOmitFields']), 'Timeaccount copyOmitFields empty/missing');
         $this->assertEquals($copyOmitFields, $registryData['Timetracker']['models']['Timeaccount']['copyOmitFields']);
+        $this->assertTrue(isset($registryData['Inventory']['models']['InventoryItem']['export']), 'no InventoryItem export config found: '
+            . print_r($registryData['Inventory']['models']['InventoryItem'], true));
+        $this->assertTrue(isset($registryData['Inventory']['models']['InventoryItem']['export']['supportedFormats']));
+        $this->assertEquals(array('csv', 'ods'), $registryData['Inventory']['models']['InventoryItem']['export']['supportedFormats']);
+        $this->assertTrue(isset($registryData['Inventory']['models']['InventoryItem']['import']));
     }
 
     /**
index 4403f44..f9831ac 100644 (file)
@@ -266,9 +266,8 @@ class Tinebase_GroupTest extends TestCase
         ));
         $contact = Admin_Controller_User::getInstance()->createOrUpdateContact($testUser);
         $testUser->contact_id = $contact->getId();
-        $testUser = Tinebase_User::getInstance()->addUserInSqlBackend($testUser);
-        
-        Tinebase_User::createContactForSyncedUser($testUser);
+        $testUser = Tinebase_User::getInstance()->addUser($testUser);
+
         Tinebase_User::getInstance()->updateUserInSqlBackend($testUser);
         
         $this->testSetGroupMembers($testGroup, array($testUser->accountId));
index cbb572f..cda7734 100644 (file)
@@ -71,13 +71,19 @@ class Tinebase_Log_FormatterTest extends PHPUnit_Framework_TestCase
         $loggerFile = file_get_contents($logfile);
         $writer->shutdown();
         unlink($logfile);
-        
+
+        if (strpos($loggerFile, 'setupuser') !== false) {
+            $username = 'setupuser';
+        } else {
+            $username = Tinebase_Core::getUser()->accountLoginName;
+        }
+
         $this->assertFalse(strpos($loggerFile, $password), 'pw found!');
         $this->assertContains('********', $loggerFile);
         if ($config->logger->logruntime || $config->logger->logdifftime) {
-            $this->assertTrue(preg_match('/' . Tinebase_Core::getUser()->accountLoginName . ' \d/', $loggerFile) === 1);
+            $this->assertTrue(preg_match('/' . $username . ' \d/', $loggerFile) === 1);
         } else {
-            $this->assertContains(Tinebase_Core::getUser()->accountLoginName . ' - ', $loggerFile);
+            $this->assertContains($username . ' - ', $loggerFile);
         }
     }
 }
diff --git a/tests/tine20/Tinebase/Server/HttpTests.php b/tests/tine20/Tinebase/Server/HttpTests.php
new file mode 100644 (file)
index 0000000..e60d73b
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test class for Tinebase_Server_Http
+ * 
+ * @package     Tinebase
+ */
+class Tinebase_Server_HttpTests extends TestCase
+{
+    /**
+     * @group ServerTests
+     *
+     * @see  0012364: generalize import/export and allow to configure via modelconfig
+     */
+    public function testHandleRequestForDynamicAPI()
+    {
+        $server = new Tinebase_Server_Http();
+
+        $request = \Zend\Http\PhpEnvironment\Request::fromString(
+'POST /index.php HTTP/1.1' . "\r\n"
+. 'Host: localhost' . "\r\n"
+. 'User-Agent: Mozilla/5.0 (X11; Linux i686; rv:15.0) Gecko/20120824 Thunderbird/15.0 Lightning/1.7' . "\r\n"
+. 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZQRf6nhpOLbSRcoe' . "\r\n"
+. 'Accept: */*' . "\r\n"
+. 'Referer: http://tine20.vagrant/' . "\r\n"
+. 'Accept-Encoding: gzip, deflate' . "\r\n"
+. 'Accept-Language: en-US,en;q=0.8,de-DE;q=0.6,de;q=0.4' . "\r\n"
+. "\r\n"
+        );
+
+        // set method & params
+        $_REQUEST['method'] = 'ExampleApplication.exportExampleRecords';
+        $_REQUEST['filter'] = Zend_Json::encode(array());
+        $_REQUEST['options'] = Zend_Json::encode(array('format' => 'csv'));
+        ob_start();
+        $server->handle($request);
+        $out = ob_get_clean();
+        //echo $out;
+
+        $this->assertTrue(! empty($out), 'request should not be empty');
+        $this->assertNotContains('Not Authorised', $out);
+        $this->assertNotContains('Method not found', $out);
+        $this->assertNotContains('No Application Controller found', $out);
+        $this->assertNotContains('"error"', $out);
+        $this->assertNotContains('PHP Fatal error', $out);
+        $this->assertContains('"name","description","status","reason","number_str","number_int","relations","container_id","tags","attachments","notes","seq","tags"', $out);
+    }
+
+    /**
+     * @group ServerTests
+     *
+     * @see  0012364: generalize import/export and allow to configure via modelconfig
+     *
+     * TODO create message first?
+     */
+    public function testHandleRequestForReflectionAPI()
+    {
+        $server = new Tinebase_Server_Http();
+
+        $request = \Zend\Http\PhpEnvironment\Request::fromString(
+            'POST /index.php HTTP/1.1' . "\r\n"
+            . 'Host: localhost' . "\r\n"
+            . 'User-Agent: Mozilla/5.0 (X11; Linux i686; rv:15.0) Gecko/20120824 Thunderbird/15.0 Lightning/1.7' . "\r\n"
+            . 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZQRf6nhpOLbSRcoe' . "\r\n"
+            . 'Accept: */*' . "\r\n"
+            . 'Referer: http://tine20.vagrant/' . "\r\n"
+            . 'Accept-Encoding: gzip, deflate' . "\r\n"
+            . 'Accept-Language: en-US,en;q=0.8,de-DE;q=0.6,de;q=0.4' . "\r\n"
+            . "\r\n"
+        );
+
+        // set method & params
+        $_REQUEST['method'] = 'Felamimail.downloadAttachment';
+        $_REQUEST['messageId'] = '1110de84c05316e55be87beab2ae5f0fb877b35f';
+        $_REQUEST['partId'] = '1.1.2';
+        ob_start();
+        $server->handle($request);
+        $out = ob_get_clean();
+        //echo $out;
+
+        $this->assertTrue(empty($out), 'request should be empty - no message with this id + part should be found');
+        $this->assertNotContains('Not Authorised', $out);
+        $this->assertNotContains('Method not found', $out);
+        $this->assertNotContains('No Application Controller found', $out);
+        $this->assertNotContains('"error"', $out);
+        $this->assertNotContains('PHP Fatal error', $out);
+    }
+}
index 56d69ff..49247b1 100644 (file)
@@ -224,7 +224,7 @@ class Tinebase_User_LdapTest extends TestCase
     /**
      * execute Tinebase_User::syncUser
      *
-     * TODO test bday
+     * TODO eventually add birthdate in case its turned on again in Tinebase_User_Ldap::_ldap2Contact
      */
     public function testSyncUsersContactData()
     {
@@ -276,6 +276,50 @@ class Tinebase_User_LdapTest extends TestCase
     }
 
     /**
+     * execute Tinebase_User::syncUser
+     */
+    public function testSyncUsersNoContactData()
+    {
+        // add user in LDAP
+        $user = $this->_backend->addUserToSyncBackend(self::getTestRecord());
+        $this->_usernamesToDelete[] = $user->accountLoginName;
+
+        Tinebase_Config::getInstance()->set(Tinebase_Config::SYNC_USER_CONTACT_DATA, false);
+        $syncedUser = Tinebase_User::syncUser($user);
+        Tinebase_Config::getInstance()->set(Tinebase_Config::SYNC_USER_CONTACT_DATA, true);
+
+        // check if user is synced
+        $this->assertEquals(Tinebase_Model_User::VISIBILITY_DISPLAYED, $syncedUser->visibility,
+            print_r($syncedUser->toArray(), true));
+        $this->assertFalse(empty($syncedUser->contact_id), 'contact id not set');
+        $contact = Addressbook_Controller_Contact::getInstance()->get($syncedUser->contact_id);
+        $this->assertFalse(isset($contact->tel_work), 'tel_work is set');
+
+        // add phone data in ldap and check that it did not reach adb
+        $syncOptions = array(
+            'syncContactData' => true,
+            'syncContactPhoto' => true,
+        );
+        $ldap = $this->_backend->getLdap();
+        $dn = $this->_backend->generateDn($syncedUser);
+        $number = '040-428457634';
+        $jpegImage = file_get_contents(dirname(dirname(dirname(dirname(__DIR__))))
+            . '/tine20/Admin/Setup/DemoData/persona_sclever.jpg');
+        $ldap->updateProperty($dn, array(
+            'telephonenumber' => $number,
+            'jpegphoto'       => $jpegImage,
+        ));
+
+        Tinebase_Config::getInstance()->set(Tinebase_Config::SYNC_USER_CONTACT_DATA, false);
+        $syncedUser = Tinebase_User::syncUser($user, $syncOptions);
+        Tinebase_Config::getInstance()->set(Tinebase_Config::SYNC_USER_CONTACT_DATA, true);
+
+        $contact = Addressbook_Controller_Contact::getInstance()->get($syncedUser->contact_id);
+        $this->assertFalse(isset($contact->tel_work), 'tel_work is set');
+        $this->assertEquals(0, $contact->jpegphoto, 'jpegphoto is not 0');
+    }
+
+    /**
      * @return Tinebase_Model_FullUser
      */
     public static function getTestRecord()
index 58c237b..e3fb8fd 100644 (file)
@@ -68,7 +68,7 @@ class Tinebase_User_SqlTest extends TestCase
         $this->objects['users']['addedUser'] = $this->_backend->addUser($testUser);
         
         $this->assertEquals($testUser->getId(),      $this->objects['users']['addedUser']->getId());
-        $this->assertEquals('hidden',                $this->objects['users']['addedUser']->visibility);
+        $this->assertEquals('displayed',             $this->objects['users']['addedUser']->visibility);
         $this->assertEquals('Tinebase_Model_FullUser', get_class($testUser), 'wrong type');
         
         return $this->objects['users']['addedUser'];
@@ -134,7 +134,7 @@ class Tinebase_User_SqlTest extends TestCase
         $user = $this->_backend->updateUser($testUser);
         
         $this->assertEquals($user->accountLoginName, $user->accountLoginName);
-        $this->assertEquals('hidden',                $user->visibility);
+        $this->assertEquals('displayed',             $user->visibility);
         $this->assertEquals('disabled',              $user->accountStatus);
     }
     
@@ -342,7 +342,7 @@ class Tinebase_User_SqlTest extends TestCase
             'summary' => 'testevent',
             'dtstart' => '2015-12-24 12:00:00',
             'dtend' => '2015-12-24 13:00:00',
-//            'organizer' => $testUser->conta
+            'organizer' => $testUser->accountEmailAddress,
         ), true));
         $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
             'n_given' => 'testcontact'
diff --git a/tests/tine20/Tinebase/files/configExampleAppTest.inc.php b/tests/tine20/Tinebase/files/configExampleAppTest.inc.php
new file mode 100644 (file)
index 0000000..4a69e32
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+return array(
+    ExampleApplication_Config::EXAMPLE_MAILCONFIG => array(
+        ExampleApplication_Config::IMAP => array(
+            ExampleApplication_Config::HOST => 123,
+            ExampleApplication_Config::PORT => '999',
+            ExampleApplication_Config::SSL => 'true'
+        )
+    )
+);
\ No newline at end of file
index 032d471..2a0fad6 100644 (file)
@@ -1,5 +1,5 @@
 <?php
 
 return array(
-    'ignoreBillablesBefore' => '1999-10-01 22:00:00',
+    ExampleApplication_Config::EXAMPLE_STRING => 'something else',
 );
index 6a266cb..f9066c3 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Controller
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2014-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2014-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -82,14 +82,14 @@ class ActiveSync_Controller_SyncDevices extends Tinebase_Controller_Record_Abstr
     /**
      * get list of access log entries
      *
-     * @param Tinebase_Model_Filter_FilterGroup|optional $_filter
-     * @param Tinebase_Model_Pagination|optional $_pagination
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @param Tinebase_Model_Pagination $_pagination
      * @param boolean $_getRelations
      * @param boolean $_onlyIds
      * @param string $_action for right/acl check
      * @return Tinebase_Record_RecordSet|array
      */
-    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
+    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
     {
         $this->checkRight('MANAGE_DEVICES');
         
diff --git a/tine20/ActiveSync/Setup/Update/Release9.php b/tine20/ActiveSync/Setup/Update/Release9.php
new file mode 100644 (file)
index 0000000..fc201d6
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     ActiveSync
+ * @subpackage  Setup
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ */
+
+/**
+ * updates for major release 9
+ *
+ * @package     ActiveSync
+ * @subpackage  Setup
+ */
+class ActiveSync_Setup_Update_Release9 extends Setup_Update_Abstract
+{
+    /**
+     * update to 10.0
+     *
+     * @return void
+     */
+    public function update_0()
+    {
+        $this->setApplicationVersion('ActiveSync', '10.0');
+    }
+}
index 8e51725..786a35e 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>ActiveSync</name>
-    <version>9.0</version>
+    <version>10.0</version>
     <order>90</order>
     <depends>
         <application>Tinebase</application>
index 233028c..f118357 100644 (file)
           "path": "js/"
         },
         {
+          "text": "IndustryEditDialog.js",
+          "path": "js/"
+        },
+        {
+          "text": "IndustrySearchCombo.js",
+          "path": "js/"
+        },
+        {
           "text": "GenericContactGridPanelHook.js",
           "path": "js/"
         },
index 7e1a590..2314be2 100755 (executable)
@@ -5,7 +5,7 @@
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 /**
  * backend factory class for the addressbook
 class Addressbook_Backend_Factory
 {
     /**
-     * object instance
-     *
-     * @var Addressbook_Backend_Factory
-     */
-    private static $_instance = NULL;
-    
-    /**
      * backend object instances
      */
     private static $_backends = array();
@@ -65,18 +58,18 @@ class Addressbook_Backend_Factory
                 }
                 $instance = self::$_backends[$_type];
                 break;
-            case self::LDAP:
+            /*case self::LDAP:
                 if (!isset(self::$_backends[$_type])) {
                     self::$_backends[$_type] = new Addressbook_Backend_Ldap();
                 }
                 $instance = self::$_backends[$_type];
-                break;
-            case self::SALUTATION:
+                break;*/
+            /*case self::SALUTATION:
                 if (!isset(self::$_backends[$_type])) {
                     self::$_backends[$_type] = new Addressbook_Backend_Salutation();
                 }
                 $instance = self::$_backends[$_type];
-                break;
+                break;*/
             default:
                 throw new Addressbook_Exception_InvalidArgument('Unknown backend type (' . $_type . ').');
                 break;
index d61b2e2..f95cc10 100755 (executable)
@@ -7,7 +7,7 @@
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3 
  * @author      Cornelius Weiss <c.weiss@metaways.de>
- * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -280,7 +280,7 @@ class Addressbook_Backend_Ldap implements Tinebase_Backend_Interface
      * @param  Tinebase_Model_Pagination $_pagination
      * @return Tinebase_Record_RecordSet
      */
-    public function search(Tinebase_Record_Interface $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL)
+    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_cols = '*')
     {
         
     }
@@ -288,10 +288,10 @@ class Addressbook_Backend_Ldap implements Tinebase_Backend_Interface
     /**
      * Gets total count of search with $_filter
      * 
-     * @param Tinebase_Record_Interface $_filter
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
      * @return int
      */
-    public function searchCount(Tinebase_Record_Interface $_filter)
+    public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter)
     {
         
     }
@@ -312,7 +312,7 @@ class Addressbook_Backend_Ldap implements Tinebase_Backend_Interface
      * @param string $_id uuid / uidnumber ???
      * @return Tinebase_Record_Interface
      */
-    public function get($_id)
+    public function get($_id, $_getDeleted = FALSE)
     {
         $contactData = $this->_ldap->fetch($this->_baseDn, "entryuuid=$_id", $this->_getSupportedLdapAttributes());
         
@@ -567,4 +567,16 @@ class Addressbook_Backend_Ldap implements Tinebase_Backend_Interface
         
         return $this->_supportedRecordFields;
     }
+
+    /**
+     * Updates multiple entries
+     *
+     * @param array $_ids to update
+     * @param array $_data
+     * @return integer number of affected rows
+     */
+    public function updateMultiple($_ids, $_data)
+    {
+        // TODO: Implement updateMultiple() method.
+    }
 }
index bd722c5..bf8e270 100644 (file)
@@ -75,7 +75,7 @@ class Addressbook_Backend_List extends Tinebase_Backend_Sql_Abstract
      *  - tablePrefix
      *  - modlogActive
      *  
-     * @param Zend_Db_Adapter_Abstract $_db (optional)
+     * @param Zend_Db_Adapter_Abstract $_dbAdapter (optional)
      * @param array $_options (optional)
      * @throws Tinebase_Exception_Backend_Database
      */
@@ -101,10 +101,10 @@ class Addressbook_Backend_List extends Tinebase_Backend_Sql_Abstract
     /**
      * converts record into raw data for adapter
      *
-     * @param  Tinebase_Record_Abstract $_record
+     * @param  Tinebase_Record_Interface $_record
      * @return array
      */
-    protected function _recordToRawData($_record)
+    protected function _recordToRawData(Tinebase_Record_Interface $_record)
     {
         $result = parent::_recordToRawData($_record);
         
@@ -124,6 +124,7 @@ class Addressbook_Backend_List extends Tinebase_Backend_Sql_Abstract
      */
     public function addListMember($_listId, $_newMembers)
     {
+        /** @var Addressbook_Model_List $list */
         $list = $this->get($_listId);
         
         if (empty($_newMembers)) {
@@ -174,6 +175,7 @@ class Addressbook_Backend_List extends Tinebase_Backend_Sql_Abstract
      */
     public function removeListMember($_listId, $_membersToRemove)
     {
+        /** @var Addressbook_Model_List $list */
         $list = $this->get($_listId);
         
         if (empty($_membersToRemove)) {
index 37b72d1..cf0bcdd 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -72,8 +72,9 @@ class Addressbook_Backend_Sql extends Tinebase_Backend_Sql_Abstract
     );
     
     /**
-     * (non-PHPdoc)
      * @see Tinebase_Backend_Sql_Abstract::__construct()
+     * @param Zend_Db_Adapter_Abstract $_dbAdapter (optional)
+     * @param array $_options (optional)
      */
     public function __construct($_dbAdapter = NULL, $_options = array())
     {
@@ -160,7 +161,7 @@ class Addressbook_Backend_Sql extends Tinebase_Backend_Sql_Abstract
      * returns contact image
      *
      * @param int $contactId
-     * @return blob|string
+     * @return string
      */
     public function getImage($contactId)
     {
@@ -177,8 +178,8 @@ class Addressbook_Backend_Sql extends Tinebase_Backend_Sql_Abstract
      * saves image to db
      *
      * @param  string $contactId
-     * @param  blob $imageData
-     * @return blob|string
+     * @param  string $imageData
+     * @return string
      */
     public function _saveImage($contactId, $imageData)
     {
@@ -222,4 +223,14 @@ class Addressbook_Backend_Sql extends Tinebase_Backend_Sql_Abstract
         
         return $imageData;
     }
+
+    public function updateSyncBackendIds($_contactId, $_syncBackendIds)
+    {
+        $where  = array(
+            $this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $_contactId),
+        );
+        $this->_db->update($this->_tablePrefix . 'addressbook', array(
+            'syncBackendIds'         => $_syncBackendIds
+        ), $where);
+    }
 }
diff --git a/tine20/Addressbook/Backend/Sync/Ldap.php b/tine20/Addressbook/Backend/Sync/Ldap.php
new file mode 100644 (file)
index 0000000..c817212
--- /dev/null
@@ -0,0 +1,365 @@
+<?php
+
+/**
+ * contacts ldap sync backend
+ *
+ * @package     Addressbook
+ * @subpackage  Backend
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * contacts ldap sync backend
+ *
+ * this is just a sync backend, not the main backend responsible for persisting the record
+ *
+ * NOTE: LDAP charset is allways UTF-8 (RFC2253) so we don't have to cope with
+ *       charset conversions here ;-)
+ *
+ * @package     Addressbook
+ * @subpackage  Backend
+ */
+class Addressbook_Backend_Sync_Ldap implements Tinebase_Backend_Interface
+{
+    /**
+     * @var Tinebase_Ldap
+     */
+    protected $_ldap = NULL;
+
+    /**
+     * attribute mapping
+     *
+     * @var array
+     */
+    protected  $_attributesMap = array(
+
+        /**
+         * RFC2798: Internet Organizational Person
+         */
+            'n_fn'                  => 'cn',
+            'n_given'               => 'givenname',
+            'n_family'              => 'sn',
+            'sound'                 => 'audio',
+            'note'                  => 'description',
+            'url'                   => 'labeleduri',
+            'org_name'              => 'o',
+            'org_unit'              => 'ou',
+            'title'                 => 'title',
+            'adr_one_street'        => 'street',
+            'adr_one_locality'      => 'l',
+            'adr_one_region'        => 'st',
+            'adr_one_postalcode'    => 'postalcode',
+            'tel_work'              => 'telephonenumber',
+            'tel_home'              => 'homephone',
+            'tel_fax'               => 'facsimiletelephonenumber',
+            'tel_cell'              => 'mobile',
+            'tel_pager'             => 'pager',
+            'email'                 => 'mail',
+            'room'                  => 'roomnumber',
+            'jpegphoto'             => 'jpegphoto',
+            'n_fileas'              => 'displayname',
+            'label'                 => 'postaladdress',
+            'pubkey'                => 'usersmimecertificate',
+
+        /**
+         * Mozilla LDAP Address Book Schema (alpha)
+         *
+         * @link https://wiki.mozilla.org/MailNews:Mozilla_LDAP_Address_Book_Schema
+         * @link https://wiki.mozilla.org/MailNews:LDAP_Address_Books#LDAP_Address_Book_Schema
+         */
+
+            'adr_one_street2'       => 'mozillaworkstreet2',
+            'adr_one_countryname'   => 'c', // 2 letter country code
+            'adr_two_street'        => 'mozillahomestreet',
+            'adr_two_street2'       => 'mozillahomestreet2',
+            'adr_two_locality'      => 'mozillahomelocalityname',
+            'adr_two_region'        => 'mozillahomestate',
+            'adr_two_postalcode'    => 'mozillahomepostalcode',
+            'adr_two_countryname'   => 'mozillahomecountryname',
+            'email_home'            => 'mozillasecondemail',
+            'url_home'              => 'mozillahomeurl',
+            //'' => 'displayName'
+            //'' => 'mozillaCustom1'
+            //'' => 'mozillaCustom2'
+            //'' => 'mozillaCustom3'
+            //'' => 'mozillaCustom4'
+            //'' => 'mozillaHomeUrl'
+            //'' => 'mozillaNickname'
+            //'' => 'mozillaUseHtmlMail'
+            //'' => 'nsAIMid'
+            //'' => 'postOfficeBox'
+    );
+
+    protected $_objectClasses = array(
+        'inetOrgPerson' => array(
+            'cn',
+            'sn',
+        ),
+        'mozillaAbPersonAlpha' => array(
+            //'cn',
+        ),
+    );
+
+    /**
+     * base DN
+     *
+     * @var string
+     */
+    protected $_baseDN = NULL;
+
+    /**
+     * constructor
+     *
+     * @param array $_options
+     * @throws Tinebase_Exception_Backend_Ldap
+     */
+    public function __construct(array $_options)
+    {
+        if (isset($_options['attributesMap']) && is_array($_options['attributesMap']) && count($_options['attributesMap']) > 0) {
+            $this->_attributesMap = $_options['attributesMap'];
+        }
+
+        if (!isset($_options['baseDN']) || empty($_options['baseDN']) || !is_string($_options['baseDN'])) {
+            throw new Tinebase_Exception_Backend_Ldap('baseDN not set in configuration');
+        }
+        $this->_baseDN = $_options['baseDN'];
+
+        // use user backend configuration or own ldap connection configuration?
+        if (isset($_options['ldapConnection'])) {
+            $ldapOptions = $_options['ldapConnection'];
+        } else {
+        //if (isset($_options['useUserBackend'])) {
+            $ldapOptions = Tinebase_User::getBackendConfiguration();
+        }
+
+        $this->_ldap = new Tinebase_Ldap($ldapOptions);
+        try {
+            $this->_ldap->bind();
+        } catch (Zend_Ldap_Exception $zle) {
+            throw new Tinebase_Exception_Backend_Ldap('Could not bind to LDAP: ' . $zle->getMessage());
+        }
+    }
+
+    /**
+     * Search for records matching given filter
+     *
+     *
+     * @param  Tinebase_Model_Filter_FilterGroup    $_filter
+     * @param  Tinebase_Model_Pagination            $_pagination
+     * @param  array|string|boolean                 $_cols columns to get, * per default / use self::IDCOL or TRUE to get only ids
+     * @return Tinebase_Record_RecordSet
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_cols = '*')
+    {
+        throw new Tinebase_Exception_NotImplemented(__METHOD__ . ' is not implemented');
+    }
+
+    /**
+     * Gets total count of search with $_filter
+     *
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @return int
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter)
+    {
+        throw new Tinebase_Exception_NotImplemented(__METHOD__ . ' is not implemented');
+    }
+
+    /**
+     * Return a single record
+     *
+     * @param string $_id
+     * @param boolean $_getDeleted get deleted records
+     * @return Tinebase_Record_Interface
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function get($_id, $_getDeleted = FALSE)
+    {
+        throw new Tinebase_Exception_NotImplemented(__METHOD__ . ' is not implemented');
+    }
+
+    /**
+     * Returns a set of records identified by their id's
+     *
+     * @param string|array $_ids Ids
+     * @param array $_containerIds all allowed container ids that are added to getMultiple query
+     * @return Tinebase_Record_RecordSet of Tinebase_Record_Interface
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function getMultiple($_ids, $_containerIds = NULL)
+    {
+        throw new Tinebase_Exception_NotImplemented(__METHOD__ . ' is not implemented');
+    }
+
+    /**
+     * Gets all entries
+     *
+     * @param string $_orderBy Order result by
+     * @param string $_orderDirection Order direction - allowed are ASC and DESC
+     * @throws Tinebase_Exception_InvalidArgument
+     * @return Tinebase_Record_RecordSet
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function getAll($_orderBy = 'id', $_orderDirection = 'ASC')
+    {
+        throw new Tinebase_Exception_NotImplemented(__METHOD__ . ' is not implemented');
+    }
+
+    /**
+     * Create a new persistent contact
+     *
+     * @param  Tinebase_Record_Interface $_record
+     * @return Tinebase_Record_Interface|void
+     */
+    public function create(Tinebase_Record_Interface $_record)
+    {
+        /** @var Addressbook_Model_Contact $_record */
+        $dn = $this->_generateDn($_record);
+
+        if ($this->_ldap->getEntry($dn) !== null) {
+            $this->update($_record);
+            return;
+        }
+
+        $ldapData = $this->_contact2ldap($_record);
+
+        $ldapData['objectclass'] = array_keys($this->_objectClasses);
+
+        foreach($this->_objectClasses as $reqAttributes) {
+            foreach($reqAttributes as $reqAttrb) {
+                if (!isset($ldapData[$reqAttrb]) || empty($ldapData[$reqAttrb])) {
+                    $ldapData[$reqAttrb] = '-';
+                }
+            }
+        }
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
+            Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' dn: ' . $dn . ' ldapData: ' . print_r($ldapData, true));
+
+        $this->_ldap->add($dn, $ldapData);
+    }
+
+    /**
+     * Upates an existing persistent record
+     *
+     * @param  Tinebase_Record_Interface $_record
+     * @return NULL|Tinebase_Record_Interface|void
+     */
+    public function update(Tinebase_Record_Interface $_record)
+    {
+        /** @var Addressbook_Model_Contact $_record */
+        $dn = $this->_generateDn($_record);
+
+        if (($oldEntry = $this->_ldap->getEntry($dn)) === null) {
+            $this->create($_record);
+            return;
+        }
+
+        $ldapData = $this->_contact2ldap($_record);
+
+        foreach($this->_objectClasses as $reqAttributes) {
+            foreach($reqAttributes as $reqAttrb) {
+                if (!isset($ldapData[$reqAttrb]) || $ldapData[$reqAttrb] === '' ) {
+                    $ldapData[$reqAttrb] = '-';
+                }
+            }
+        }
+
+        foreach($ldapData as $key => $val) {
+            if ($val === '' && !isset($oldEntry[$key])) {
+                unset($ldapData[$key]);
+            }
+        }
+        foreach($oldEntry as $key => $val) {
+            if (!isset($ldapData[$key]) && $key !== 'uid' && $key !== 'dn') {
+                $ldapData[$key] = '';
+            }
+        }
+
+        $ldapData['objectclass'] = array_unique(array_merge($oldEntry['objectclass'], array_keys($this->_objectClasses)));
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
+            Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' dn: ' . $dn . ' ldapData: ' . print_r($ldapData, true));
+
+        $this->_ldap->update($dn, $ldapData);
+    }
+
+    /**
+     * Updates multiple entries
+     *
+     * @param array $_ids to update
+     * @param array $_data
+     * @return integer number of affected rows
+     * @throws Tinebase_Exception_NotImplemented
+     */
+    public function updateMultiple($_ids, $_data)
+    {
+        throw new Tinebase_Exception_NotImplemented(__METHOD__ . ' is not implemented');
+    }
+
+    /**
+     * Deletes one existing persistent record
+     *
+     * @param Tinebase_Record_Interface $_record
+     */
+    public function delete($_record)
+    {
+        /** @var Addressbook_Model_Contact $_record */
+        $dn = $this->_generateDn($_record);
+
+        $this->_ldap->delete($dn);
+    }
+
+    /**
+     * get backend type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return 'Ldap';
+    }
+
+    /**
+     * @param Addressbook_Model_Contact $_record
+     * @return string
+     */
+    protected function _generateDN(Addressbook_Model_Contact $_record)
+    {
+        return 'uid=' . Zend_Ldap_Filter_Abstract::escapeValue($_record->type===Addressbook_Model_Contact::CONTACTTYPE_USER?
+                Tinebase_User::getInstance()->getFullUserById($_record->account_id)->accountLoginName:
+                $_record->getId())
+            . ',' . $this->_baseDN;
+    }
+
+    /**
+     * @param Addressbook_Model_Contact $_record
+     * @return array
+     */
+    protected function _contact2ldap(Addressbook_Model_Contact $_record)
+    {
+        $ldapData = array();
+
+        foreach($_record as $key => $val) {
+            if (isset($this->_attributesMap[$key])) {
+                if ($key === 'jpegphoto') {
+                    if ($val === '0' || $val === 0)
+                    {
+                        $val = '';
+                    } elseif ($val === '1' || $val === 1) {
+                        $abs = new Addressbook_Backend_Sql();
+                        $val = $abs->getImage($_record->getId());
+                    }
+                }
+                $ldapData[$this->_attributesMap[$key]] = (is_null($val) ? '' : $val);
+            }
+        }
+
+        return $ldapData;
+    }
+}
\ No newline at end of file
index 94dc106..fa90b8e 100644 (file)
@@ -57,6 +57,20 @@ class Addressbook_Config extends Tinebase_Config_Abstract
      * @var string
      */
     const FEATURE_LIST_VIEW = 'featureListView';
+    
+    /**
+     * FEATURE_INDUSTRY
+     *
+     * @var string
+     */
+    const FEATURE_INDUSTRY = 'featureIndustry';
+
+    /**
+     * config for the syncBackends
+     *
+     * @var string
+     */
+    const SYNC_BACKENDS = 'syncBackends';
 
     /**
      * (FEATURE_LIST_VIEW-PHPdoc)
@@ -74,11 +88,16 @@ class Addressbook_Config extends Tinebase_Config_Abstract
            'content'               => array(
                self::FEATURE_LIST_VIEW => array(
                    'label'         => 'Addressbook List View', //_('Addressbook List View')
-                   'description'   => 'Shows an additional view for lists inside the addressbook', //_('Shows an additional view for lists inside the addressbook)
+                   'description'   => 'Shows an additional view for lists inside the addressbook', //_('Shows an additional view for lists inside the addressbook')
+               ),
+               self::FEATURE_INDUSTRY => array(
+                       'label'         => 'Addressbook Industries', //_('Addressbook Industries')
+                       'description'   => 'Add Industry field to Adressbook', //_('Add Industry field to Adressbook')
                ),
            ),
            'default'               => array(
-               self::FEATURE_LIST_VIEW => false,
+               self::FEATURE_LIST_VIEW   => false,
+               self::FEATURE_INDUSTRY    => true,
            ),
         ),
         self::CONTACT_DUP_FIELDS => array(
@@ -148,6 +167,16 @@ class Addressbook_Config extends Tinebase_Config_Abstract
             'setByAdminModule'      => true,
             'setBySetupModule'      => true,
         ),
+        self::SYNC_BACKENDS => array(
+            //_('Sync Backends')
+            'label'                 => 'Sync Backends',
+            //_('Sync Backends')
+            'description'           => 'Sync Backends',
+            'type'                  => 'array',
+            'clientRegistryInclude' => false,
+            'setByAdminModule'      => true,
+            'default'               => array()
+        ),
     );
     
     /**
index eabdb2f..944e3c1 100644 (file)
@@ -194,6 +194,14 @@ class Addressbook_Controller extends Tinebase_Controller_Event implements Tineba
             )));
         }
 
+        if (Addressbook_Config::getInstance()->featureEnabled(Addressbook_Config::FEATURE_INDUSTRY)) {
+            $result->addRecord(new CoreData_Model_CoreData(array(
+                    'id' => 'adb_industries',
+                    'application_id' => $application,
+                    'model' => 'Addressbook_Model_Industry',
+                    'label' => 'Industries' // _('Industries')
+            )));
+        }
         return $result;
     }
 }
index 898c447..b6a8b44 100644 (file)
  *
  * @package     Addressbook
  * @subpackage  Controller
+ *
+ * @property Addressbook_Backend_Sql $_backend protected member, you don't have access to that
  */
-class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
+class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract implements Tinebase_User_Plugin_SqlInterface
 {
+
+    const CONTEXT_ALLOW_CREATE_USER = 'context_allow_create_user';
+    const CONTEXT_NO_ACCOUNT_UPDATE = 'context_no_account_update';
+    const CONTEXT_NO_SYNC_PHOTO = 'context_no_sync_photo';
+    const CONTEXT_NO_SYNC_CONTACT_DATA = 'context_no_sync_contact_data';
+
     /**
      * set geo data for contacts
      * 
@@ -26,6 +34,13 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
     protected $_setGeoDataForContacts = FALSE;
 
     /**
+     * configured syncBackends
+     *
+     * @var array|null
+     */
+    protected $_syncBackends = NULL;
+
+    /**
      * the constructor
      *
      * don't use the constructor. use the singleton 
@@ -86,7 +101,7 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
      * gets binary contactImage
      *
      * @param int $_contactId
-     * @return blob
+     * @return string
      */
     public function getImage($_contactId) {
         // ensure user has rights to see image
@@ -119,17 +134,17 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
             $_filter->addFilter($disabledFilter);
         }
     }
-    
+
     /**
      * fetch one contact identified by $_userId
      *
      * @param   int $_userId
      * @param   boolean $_ignoreACL don't check acl grants
-     * @return  Addressbook_Model_Contact
-     * @throws  Addressbook_Exception_AccessDenied if user has no read grant
-     * @throws  Addressbook_Exception_NotFound if contact is hidden from addressbook
-     * 
-     * @todo this is almost always called with ignoreACL = TRUE because contacts can be hidden from addressbook. 
+     * @return Addressbook_Model_Contact
+     * @throws Addressbook_Exception_AccessDenied
+     * @throws Addressbook_Exception_NotFound
+     * @throws Tinebase_Exception_InvalidArgument
+     * @todo this is almost always called with ignoreACL = TRUE because contacts can be hidden from addressbook.
      *       is this the way we want that?
      */
     public function getContactByUserId($_userId, $_ignoreACL = FALSE)
@@ -159,13 +174,12 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
     /**
     * can be called to activate/deactivate if geodata should be set for contacts (ignoring the config setting)
     *
-    * @param  boolean optional
+    * @param  boolean $setTo (optional)
     * @return boolean
     */
-    public function setGeoDataForContacts()
+    public function setGeoDataForContacts($setTo = NULL)
     {
-        $value = (func_num_args() === 1) ? (bool) func_get_arg(0) : NULL;
-        return $this->_setBooleanMemberVar('_setGeoDataForContacts', $value);
+        return $this->_setBooleanMemberVar('_setGeoDataForContacts', $setTo);
     }
     
     /**
@@ -210,6 +224,7 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
             }
 
             try {
+
                 $record = new $this->_modelName($data);
                 $record->__set('jpegphoto', NULL);
                 $updatedRecord = $this->update($record, FALSE);
@@ -251,7 +266,8 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
         unset($contact->jpegphoto);
         
         $userProfile = Tinebase_UserProfile::getInstance()->mergeProfileInfo($contact, $_userProfile);
-        
+
+        /** @var Addressbook_Model_Contact $contact */
         $contact = $this->update($userProfile, FALSE);
         
         $userProfile = Tinebase_UserProfile::getInstance()->doProfileCleanup($contact);
@@ -264,16 +280,209 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
     /**
      * inspect update of one record (after update)
      *
-     * @param   Tinebase_Record_Interface $updatedRecord   the just updated record
-     * @param   Tinebase_Record_Interface $record          the update record
-     * @param   Tinebase_Record_Interface $currentRecord   the current record (before update)
+     * @param   Addressbook_Model_Contact $updatedRecord   the just updated record
+     * @param   Addressbook_Model_Contact $record          the update record
+     * @param   Addressbook_Model_Contact $currentRecord   the current record (before update)
      * @return  void
      */
     protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
     {
+        if (isset($record->account_id) && !isset($updatedRecord->account_id)) {
+            $updatedRecord->account_id = $record->account_id;
+        }
+
         if ($updatedRecord->type === Addressbook_Model_Contact::CONTACTTYPE_USER) {
-            Tinebase_User::getInstance()->updateContact($updatedRecord);
+            if (!is_array($this->_requestContext) || !isset($this->_requestContext[self::CONTEXT_NO_ACCOUNT_UPDATE]) ||
+                !$this->_requestContext[self::CONTEXT_NO_ACCOUNT_UPDATE]) {
+                Tinebase_User::getInstance()->updateContact($updatedRecord);
+            }
+        }
+
+        // assertion
+        if ($updatedRecord->syncBackendIds !== $currentRecord->syncBackendIds) {
+            Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
+                . ' updatedRecord and currentRecord have different syncBackendIds values, must never happen. "'
+                . $updatedRecord->syncBackendIds .'", "' . $currentRecord->syncBackendIds . '"');
+        }
+
+        $oldRecordBackendIds = $currentRecord->syncBackendIds;
+        if (is_string($oldRecordBackendIds)) {
+            $oldRecordBackendIds = explode(',', $currentRecord->syncBackendIds);
+        } else {
+            $oldRecordBackendIds = array();
+        }
+
+        $updateSyncBackendIds = false;
+
+        //get sync backends
+        foreach($this->getSyncBackends() as $backendId => $backendArray)
+        {
+            if (isset($backendArray['filter'])) {
+                $oldACL = $this->doContainerACLChecks(false);
+
+                $filter = new Addressbook_Model_ContactFilter($backendArray['filter']);
+                $filter->addFilter(new Addressbook_Model_ContactIdFilter(
+                    array('field' => $updatedRecord->getIdProperty(), 'operator' => 'equals', 'value' => $updatedRecord->getId())
+                ));
+
+                // record does not match the filter, attention searchCount returns a STRING! "1"...
+                if ($this->searchCount($filter) != 1) {
+
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                        . ' record did not match filter of syncBackend "' . $backendId . '"');
+
+                    // record is stored in that backend, so we remove it from there
+                    if (in_array($backendId, $oldRecordBackendIds)) {
+
+                        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                            . ' deleting record from syncBackend "' . $backendId . '"');
+
+                        try {
+                            $backendArray['instance']->delete($updatedRecord);
+
+                            $updatedRecord->syncBackendIds = trim(preg_replace('/(^|,)' . $backendId . '($|,)/', ',', $updatedRecord->syncBackendIds), ',');
+
+                            $updateSyncBackendIds = true;
+                        } catch (Exception $e) {
+                            Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not delete record from sync backend "' .
+                            $backendId . '": ' . $e->getMessage());
+                            Tinebase_Exception::log($e, false);
+                        }
+                    }
+
+                    $this->doContainerACLChecks($oldACL);
+
+                    continue;
+                }
+                $this->doContainerACLChecks($oldACL);
+            }
+
+            // if record is in this syncbackend, update it
+            if (in_array($backendId, $oldRecordBackendIds)) {
+
+                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                    . ' update record in syncBackend "' . $backendId . '"');
+
+                try {
+                    $backendArray['instance']->update($updatedRecord);
+                } catch (Exception $e) {
+                    Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not update record in sync backend "' .
+                        $backendId . '": ' . $e->getMessage());
+                    Tinebase_Exception::log($e, false);
+                }
+
+            // else create it
+            } else {
+
+                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                    . ' create record in syncBackend "' . $backendId . '"');
+
+                try {
+                    $backendArray['instance']->create($updatedRecord);
+
+                    $updatedRecord->syncBackendIds = (empty($updatedRecord->syncBackendIds)?'':$updatedRecord->syncBackendIds . ',') . $backendId;
+
+                    $updateSyncBackendIds = true;
+                } catch (Exception $e) {
+                    Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not create record in sync backend "' .
+                        $backendId . '": ' . $e->getMessage());
+                    Tinebase_Exception::log($e, false);
+                }
+            }
+        }
+
+        if (true === $updateSyncBackendIds) {
+            $this->_backend->updateSyncBackendIds($updatedRecord->getId(), $updatedRecord->syncBackendIds);
+        }
+    }
+
+    /**
+     * inspect creation of one record (after create)
+     *
+     * @param   Tinebase_Record_Interface $_createdRecord
+     * @param   Tinebase_Record_Interface $_record
+     * @return  void
+     */
+    protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
+    {
+        // assertion
+        if (! empty($_createdRecord->syncBackendIds)) {
+            Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
+                . ' $_createdRecord->syncBackendIds is not empty, must never happen. "' . $_createdRecord->syncBackendIds . '"');
+        }
+        if (isset($_record->account_id) && !isset($_createdRecord->account_id)) {
+            $_createdRecord->account_id = $_record->account_id;
+        }
+
+        $updateSyncBackendIds = false;
+
+        //get sync backends
+        foreach($this->getSyncBackends() as $backendId => $backendArray) {
+            if (isset($backendArray['filter'])) {
+                $oldACL = $this->doContainerACLChecks(false);
+
+                $filter = new Addressbook_Model_ContactFilter($backendArray['filter']);
+                $filter->addFilter(new Addressbook_Model_ContactIdFilter(
+                    array('field' => $_createdRecord->getIdProperty(), 'operator' => 'equals', 'value' => $_createdRecord->getId())
+                ));
+
+                // record does not match the filter, attention searchCount returns a STRING! "1"...
+                if ($this->searchCount($filter) != 1) {
+
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                        . ' record did not match filter of syncBackend "' . $backendId . '"');
+
+                    $this->doContainerACLChecks($oldACL);
+                    continue;
+                }
+                $this->doContainerACLChecks($oldACL);
+            }
+
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                . ' create record in syncBackend "' . $backendId . '"');
+
+            try {
+                $backendArray['instance']->create($_createdRecord);
+
+                $_createdRecord->syncBackendIds = (empty($_createdRecord->syncBackendIds)?'':$_createdRecord->syncBackendIds . ',') . $backendId;
+
+                $updateSyncBackendIds = true;
+            } catch (Exception $e) {
+                Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not create record in sync backend "' .
+                    $backendId . '": ' . $e->getMessage());
+                Tinebase_Exception::log($e, false);
+            }
+        }
+
+        if (true === $updateSyncBackendIds) {
+            $this->_backend->updateSyncBackendIds($_createdRecord->getId(), $_createdRecord->syncBackendIds);
+        }
+    }
+
+    public function resetSyncBackends()
+    {
+        $this->_syncBackends = null;
+    }
+
+    public function getSyncBackends()
+    {
+        if ($this->_syncBackends !== null) {
+            return $this->_syncBackends;
         }
+
+        $this->_syncBackends = Addressbook_Config::getInstance()->get(Addressbook_Config::SYNC_BACKENDS);
+        foreach($this->_syncBackends as $name => &$val) {
+            if (!isset($val['class'])) {
+                throw new Tinebase_Exception_UnexpectedValue('bad addressbook syncbackend configuration: "' . $name . '" missing class');
+            }
+            if (isset($val['options'])) {
+                $val['instance'] = new $val['class']($val['options']);
+            } else {
+                $val['instance'] = new $val['class']();
+            }
+        }
+
+        return $this->_syncBackends;
     }
 
     /**
@@ -285,35 +494,62 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
      */
     protected function _deleteRecord(Tinebase_Record_Interface $_record)
     {
+        /** @var Addressbook_Model_Contact $_record */
         if (!empty($_record->account_id)) {
             throw new Addressbook_Exception_AccessDenied('It is not allowed to delete a contact linked to an user account!');
         }
-        
+
+        $recordBackendIds = $_record->syncBackendIds;
+
         parent::_deleteRecord($_record);
+
+        // delete in syncBackendIds
+        if (is_string($recordBackendIds)) {
+
+            $recordBackends = explode(',', $recordBackendIds);
+            //get sync backends
+            foreach ($this->getSyncBackends() as $backendId => $backendArray) {
+                if (in_array($backendId, $recordBackends)) {
+                    try {
+                        $backendArray['instance']->delete($_record);
+                    } catch (Exception $e) {
+                        Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not delete record from sync backend "' .
+                            $backendId . '": ' . $e->getMessage());
+                        Tinebase_Exception::log($e, false);
+                    }
+                }
+            }
+        }
     }
-    
+
     /**
      * inspect creation of one record
-     * 
+     *
      * @param   Tinebase_Record_Interface $_record
-     * @return  void
+     * @throws Addressbook_Exception_InvalidArgument
      */
     protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
     {
+        /** @var Addressbook_Model_Contact $_record */
         $this->_setGeoData($_record);
         
         if (isset($_record->type) &&  $_record->type == Addressbook_Model_Contact::CONTACTTYPE_USER) {
-            throw new Addressbook_Exception_InvalidArgument('can not add contact of type user');
+            if (!is_array($this->_requestContext) || !isset($this->_requestContext[self::CONTEXT_ALLOW_CREATE_USER]) ||
+                !$this->_requestContext[self::CONTEXT_ALLOW_CREATE_USER]) {
+                throw new Addressbook_Exception_InvalidArgument('can not add contact of type user');
+            }
         }
+
+        // syncBackendIds is read only property!
+        unset($_record->syncBackendIds);
     }
-    
+
     /**
      * inspect update of one record
-     * 
-     * @param   Tinebase_Record_Interface $_record      the update record
-     * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
-     * @return  void
-     * 
+     *
+     * @param   Tinebase_Record_Interface $_record the update record
+     * @param   Tinebase_Record_Interface $_oldRecord the current persistent record
+     * @throws Tinebase_Exception_AccessDenied
      * @todo remove system note for updated jpegphoto when images are modlogged (@see 0000284: modlog of contact images / move images to vfs)
      */
     protected function _inspectBeforeUpdate($_record, $_oldRecord)
@@ -341,22 +577,27 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
 
         if (! empty($_record->account_id) || $_record->type == Addressbook_Model_Contact::CONTACTTYPE_USER) {
 
-            // first check if something changed that requires special rights
-            $changeAccount = false;
-            foreach (Addressbook_Model_Contact::getManageAccountFields() as $field) {
-                if ($_record->{$field} != $_oldRecord->{$field}) {
-                    $changeAccount = true;
-                    break;
+            if ($this->doContainerACLChecks()) {
+                // first check if something changed that requires special rights
+                $changeAccount = false;
+                foreach (Addressbook_Model_Contact::getManageAccountFields() as $field) {
+                    if ($_record->{$field} != $_oldRecord->{$field}) {
+                        $changeAccount = true;
+                        break;
+                    }
                 }
-            }
 
-            // if so, check rights
-            if ($changeAccount) {
-                if (!Tinebase_Core::getUser()->hasRight('Admin', Admin_Acl_Rights::MANAGE_ACCOUNTS)) {
-                    throw new Tinebase_Exception_AccessDenied('No permission to change account properties.');
+                // if so, check rights
+                if ($changeAccount) {
+                    if (!Tinebase_Core::getUser()->hasRight('Admin', Admin_Acl_Rights::MANAGE_ACCOUNTS)) {
+                        throw new Tinebase_Exception_AccessDenied('No permission to change account properties.');
+                    }
                 }
             }
         }
+
+        // syncBackendIds is read only property!
+        unset($_record->syncBackendIds);
     }
 
     /**
@@ -639,6 +880,7 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
         // fetch all groups and role memberships and add to path
         $listIds = Addressbook_Controller_List::getInstance()->getMemberships($record);
         foreach ($listIds as $listId) {
+            /** @var Addressbook_Model_List $list */
             $list = Addressbook_Controller_List::getInstance()->get($listId);
             $listPaths = $this->_getPathsOfRecord($list);
             if (count($listPaths) === 0) {
@@ -677,4 +919,182 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
 
         return $result;
     }
+
+    /**
+     * inspect data used to create user
+     *
+     * @param Tinebase_Model_FullUser $_addedUser
+     * @param Tinebase_Model_FullUser $_newUserProperties
+     */
+    public function inspectAddUser(Tinebase_Model_FullUser $_addedUser, Tinebase_Model_FullUser $_newUserProperties)
+    {
+        $contactId = $_addedUser->contact_id;
+        if (!empty($contactId)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                . " addedUser does have contact_id set: " . $_addedUser->accountLoginName . ' updating existing contact now.');
+
+            $this->inspectUpdateUser($_addedUser, $_newUserProperties);
+            return;
+        }
+
+        // create new contact
+        $contact = Tinebase_User::user2Contact($_addedUser);
+
+        $userController = Tinebase_User::getInstance();
+        if ($userController instanceof Tinebase_User_Interface_SyncAble && Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_CONTACT_DATA, true) &&
+            (!is_array($this->_requestContext) || !isset($this->_requestContext[self::CONTEXT_NO_SYNC_CONTACT_DATA]) || !$this->_requestContext[self::CONTEXT_NO_SYNC_CONTACT_DATA])) {
+            // let the syncbackend e.g. Tinebase_User_Ldap etc. decide what to add to our $contact
+            $userController->updateContactFromSyncBackend($_addedUser, $contact);
+        }
+
+        if (is_array($this->_requestContext) && isset($this->_requestContext[self::CONTEXT_NO_SYNC_PHOTO]) &&
+            $this->_requestContext[self::CONTEXT_NO_SYNC_PHOTO] && isset($contact->jpegphoto)) {
+            unset($contact->jpegphoto);
+        }
+
+        // we need to set context to avoid _inspectBeforeCreate to freak out about $contact->account_id
+        $oldContext = $this->_requestContext;
+        if (!is_array($this->_requestContext)) {
+            $this->_requestContext = array();
+        }
+        if (!isset($this->_requestContext[self::CONTEXT_ALLOW_CREATE_USER])) {
+            $this->_requestContext[self::CONTEXT_ALLOW_CREATE_USER] = true;
+        }
+        if (!isset($this->_requestContext[self::CONTEXT_NO_ACCOUNT_UPDATE])) {
+            $this->_requestContext[self::CONTEXT_NO_ACCOUNT_UPDATE] = true;
+        }
+        $oldACL = $this->doContainerACLChecks(false);
+
+
+        $contact = $this->create($contact, false);
+
+        $this->_requestContext = $oldContext;
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+            . " Added contact " . $contact->n_given);
+
+        $_addedUser->contact_id = $contact->getId();
+        $userController->updateUserInSqlBackend($_addedUser);
+
+        $this->doContainerACLChecks($oldACL);
+        $this->_requestContext = $oldContext;
+    }
+
+    /**
+     * inspect data used to update user
+     *
+     * @param Tinebase_Model_FullUser $_updatedUser
+     * @param Tinebase_Model_FullUser $_newUserProperties
+     */
+    public function inspectUpdateUser(Tinebase_Model_FullUser $_updatedUser, Tinebase_Model_FullUser $_newUserProperties)
+    {
+        $contactId = $_updatedUser->contact_id;
+        if (empty($contactId)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                . " updatedUser does not have contact_id set: " . $_updatedUser->accountLoginName . ' creating new contact now.');
+
+            $this->inspectAddUser($_updatedUser, $_newUserProperties);
+            return;
+        }
+
+        $oldACL = $this->doContainerACLChecks(false);
+
+        try {
+            $oldContact = $this->get($_updatedUser->contact_id);
+        } catch(Tinebase_Exception_NotFound $tenf) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                . " updatedUser does has contact_id set which was not found by get: " . $_updatedUser->accountLoginName . ' creating new contact now.');
+
+            $_updatedUser->contact_id = null;
+            $this->inspectAddUser($_updatedUser, $_newUserProperties);
+            return;
+        }
+
+        // update base information
+        $contact = Tinebase_User::user2Contact($_updatedUser, clone $oldContact);
+
+        $userController = Tinebase_User::getInstance();
+        if ($userController instanceof Tinebase_User_Interface_SyncAble && Tinebase_Config::getInstance()->get(Tinebase_Config::SYNC_USER_CONTACT_DATA, true) &&
+            (!is_array($this->_requestContext) || !isset($this->_requestContext[self::CONTEXT_NO_SYNC_CONTACT_DATA]) || !$this->_requestContext[self::CONTEXT_NO_SYNC_CONTACT_DATA])) {
+            // let the syncbackend e.g. Tinebase_User_Ldap etc. decide what to add to our $contact
+            $userController->updateContactFromSyncBackend($_updatedUser, $contact);
+        }
+
+        if (is_array($this->_requestContext) && isset($this->_requestContext[self::CONTEXT_NO_SYNC_PHOTO]) &&
+            $this->_requestContext[self::CONTEXT_NO_SYNC_PHOTO]) {
+            $syncPhoto = false;
+            unset($contact->jpegphoto);
+        } else {
+            $syncPhoto = true;
+
+            if ($oldContact->jpegphoto == 1) {
+                $adb = new Addressbook_Backend_Sql();
+                $oldContact->jpegphoto = $adb->getImage($oldContact->getId());
+            }
+            if ($contact->jpegphoto == 1) {
+                $contact->jpegphoto = false;
+            }
+        }
+
+        $diff = $contact->diff($oldContact, $syncPhoto ? array('n_fn') : array('jpegphoto', 'n_fn'));
+        if (! $diff->isEmpty() || ($oldContact->jpegphoto === 0 && !empty($contact->jpegphoto))) {
+
+            $oldContext = $this->_requestContext;
+            if (!is_array($this->_requestContext)) {
+                $this->_requestContext = array();
+            }
+            if (!isset($this->_requestContext[self::CONTEXT_NO_ACCOUNT_UPDATE])) {
+                $this->_requestContext[self::CONTEXT_NO_ACCOUNT_UPDATE] = true;
+            }
+
+            $this->update($contact, false);
+
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                . " updated contact " . $contact->n_given);
+
+            $this->_requestContext = $oldContext;
+        }
+
+        $this->doContainerACLChecks($oldACL);
+    }
+
+    /**
+     * delete user by id
+     *
+     * @param   Tinebase_Model_FullUser $_user
+     */
+    public function inspectDeleteUser(Tinebase_Model_FullUser $_user)
+    {
+        if (empty($_user->contact_id)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                . " updatedUser does not have contact_id set: " . $_user->accountLoginName);
+            return;
+        }
+
+        $oldACL = $this->doContainerACLChecks(false);
+
+        $this->delete($_user->contact_id);
+
+        $this->doContainerACLChecks($oldACL);
+    }
+
+    /**
+     * update/set email user password
+     *
+     * @param  string $_userId
+     * @param  string $_password
+     * @param  bool $_encrypt encrypt password
+     */
+    public function inspectSetPassword($_userId, $_password, $_encrypt = TRUE)
+    {
+    }
+
+    /**
+     * inspect get user by property
+     *
+     * @param Tinebase_Model_User $_user the user object
+     */
+    public function inspectGetUserByProperty(Tinebase_Model_User $_user)
+    {
+    }
 }
diff --git a/tine20/Addressbook/Controller/Industry.php b/tine20/Addressbook/Controller/Industry.php
new file mode 100644 (file)
index 0000000..2768f01
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @subpackage  Controller
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ */
+
+/**
+ * Industry controller for Addressbook
+ *
+ * @package     Addressbook
+ * @subpackage  Controller
+ */
+class Addressbook_Controller_Industry extends Tinebase_Controller_Record_Abstract
+{
+    /**
+     * the constructor
+     *
+     * don't use the constructor. use the singleton 
+     */
+    private function __construct()
+    {
+        $this->_doContainerACLChecks = false;
+        $this->_applicationName = 'Addressbook';
+        $this->_modelName = 'Addressbook_Model_Industry';
+        $this->_backend = new Tinebase_Backend_Sql(array(
+            'modelName'     => 'Addressbook_Model_Industry',
+            'tableName'     => 'addressbook_industry',
+            'modlogActive'  => true
+        ));
+        $this->_purgeRecords = FALSE;
+    }
+    
+    /**
+     * don't clone. Use the singleton.
+     *
+     */
+    private function __clone() 
+    {
+    }
+    
+    /**
+     * holds the instance of the singleton
+     *
+     * @var Addressbook_Controller_Industry
+     */
+    private static $_instance = NULL;
+    
+    /**
+     * the singleton pattern
+     *
+     * @return Addressbook_Controller_Industry
+     */
+    public static function getInstance() 
+    {
+        if (self::$_instance === NULL) {
+            self::$_instance = new Addressbook_Controller_Industry();
+        }
+        
+        return self::$_instance;
+    }
+}
index acbb827..dc9e694 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Controller
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2010-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2010-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  */
 
@@ -90,9 +90,11 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
     }
 
     /**
-     * (non-PHPdoc)
-     *
      * @see Tinebase_Controller_Record_Abstract::get()
+     *
+     * @param string $_id
+     * @param int $_containerId
+     * @return Addressbook_Model_List
      */
     public function get($_id, $_containerId = NULL)
     {
@@ -143,11 +145,16 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
     }
 
     /**
-     * (non-PHPdoc)
-     *
      * @see Tinebase_Controller_Record_Abstract::search()
+     *
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @param Tinebase_Model_Pagination $_pagination
+     * @param bool $_getRelations
+     * @param bool $_onlyIds
+     * @param string $_action
+     * @return array|Tinebase_Record_RecordSet
      */
-    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
+    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
     {
         $result = parent::search($_filter, $_pagination, $_getRelations, $_onlyIds, $_action);
 
@@ -159,9 +166,11 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
     }
 
     /**
-     * (non-PHPdoc)
-     *
      * @see Tinebase_Controller_Record_Abstract::getMultiple()
+     *
+     * @param array $_ids
+     * @param bool $_ignoreACL
+     * @return Tinebase_Record_RecordSet
      */
     public function getMultiple($_ids, $_ignoreACL = FALSE)
     {
@@ -280,14 +289,14 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
     /**
      * inspect creation of one record
      *
-     * @param   Tinebase_Record_Interface $_record
-     * @return  void
+     * @param  Tinebase_Record_Interface $_record
+     * @throws Tinebase_Exception_AccessDenied
      */
     protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
     {
         if (isset($_record->type) && $_record->type == Addressbook_Model_List::LISTTYPE_GROUP) {
             if (empty($_record->group_id)) {
-                throw Tinebase_Exception_UnexpectedValue('group_id is empty, must not happen for list type group');
+                throw new Tinebase_Exception_UnexpectedValue('group_id is empty, must not happen for list type group');
             }
 
             // check rights
@@ -307,6 +316,7 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
      */
     protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
     {
+        /** @var Addressbook_Model_List $_createdRecord */
         $this->_fireChangeListeEvent($_createdRecord);
     }
 
@@ -347,9 +357,9 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
     /**
      * inspect update of one record (after update)
      *
-     * @param   Tinebase_Record_Interface $updatedRecord   the just updated record
-     * @param   Tinebase_Record_Interface $record          the update record
-     * @param   Tinebase_Record_Interface $currentRecord   the current record (before update)
+     * @param   Addressbook_Model_List $updatedRecord   the just updated record
+     * @param   Addressbook_Model_List $record          the update record
+     * @param   Addressbook_Model_List $currentRecord   the current record (before update)
      * @return  void
      */
     protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
@@ -477,7 +487,7 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
         }
 
         foreach ($_userIds as $userId) {
-            $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $userId);
+            $user = Tinebase_User::getInstance()->getUserByPropertyFromBackend('accountId', $userId);
             if (!empty($user->contact_id)) {
                 $contactIds[] = $user->contact_id;
             }
@@ -511,6 +521,7 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
      */
     protected function _setRelatedData(Tinebase_Record_Interface $updatedRecord, Tinebase_Record_Interface $record, Tinebase_Record_Interface $currentRecord = null, $returnUpdatedRelatedData = FALSE)
     {
+        /** @var Addressbook_Model_List $record */
         if (isset($record->memberroles)) {
             // get migration
             // TODO add generic helper fn for this?
@@ -550,7 +561,7 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
     /**
      * add related data to record
      *
-     * @param Tinebase_Record_Interface $record
+     * @param Addressbook_Model_List $record
      */
     protected function _getRelatedData($record)
     {
@@ -561,6 +572,10 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
         parent::_getRelatedData($record);
     }
 
+    /**
+     * @param Addressbook_Model_List $record
+     * @return Tinebase_Record_RecordSet|Addressbook_Model_ListMemberRole
+     */
     protected function _getMemberRoles($record)
     {
         $result = $this->_getMemberRolesBackend()->getMultipleByProperty($record->getId(), 'list_id');
index 3d77e17..4d897b1 100644 (file)
@@ -91,7 +91,7 @@ class Addressbook_Convert_Contact_String implements Tinebase_Convert_Interface
     /**
     * converts Addressbook_Model_Contact to string
     *
-    * @param  Addressbook_Model_Contact  $_model
+    * @param  Tinebase_Record_Abstract  $_record
     * @return string
     */
     public function fromTine20Model(Tinebase_Record_Abstract $_record)
index 9813275..f4e0a8e 100644 (file)
@@ -37,6 +37,13 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
      * @var string
      */
     protected $_version;
+
+    /**
+     * should be overwritten by concrete class
+     *
+     * @var array
+     */
+    protected $_emptyArray;
     
     /**
      * @param  string  $_version  the version of the client
@@ -75,7 +82,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
     /**
      * converts vcard to Addressbook_Model_Contact
      * 
-     * @param  \Sabre\VObject\Component|stream|string  $blob       the vcard to parse
+     * @param  \Sabre\VObject\Component|resource|string  $blob       the vcard to parse
      * @param  Tinebase_Record_Abstract                $_record    update existing contact
      * @param  array                                   $options    array of options
      * @return Addressbook_Model_Contact
@@ -92,6 +99,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
         
         $data = $this->_emptyArray;
 
+        /** @var \Sabre\VObject\Property $property */
         foreach ($vcard->children() as $property) {
             switch ($property->name) {
                 case 'VERSION':
@@ -265,38 +273,40 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
         $telField = null;
         
         if (isset($property['TYPE'])) {
+            /** @var \Sabre\VObject\Parameter $typeParameter */
+            $typeParameter = $property['TYPE'];
             // comvert all TYPE's to lowercase and ignore voice and pref
-            $property['TYPE']->setParts(array_diff(
+            $typeParameter->setParts(array_diff(
                 array_map('strtolower', $property['TYPE']->getParts()), 
                 array('voice', 'pref')
             ));
             
             // CELL
-            if ($property['TYPE']->has('cell')) {
-                if (count($property['TYPE']->getParts()) == 1 || $property['TYPE']->has('work')) {
+            if ($typeParameter->has('cell')) {
+                if (count($typeParameter->getParts()) == 1 || $typeParameter->has('work')) {
                     $telField = 'tel_cell';
-                } elseif ($property['TYPE']->has('home')) {
+                } elseif ($typeParameter->has('home')) {
                     $telField = 'tel_cell_private';
                 }
                 
             // PAGER
-            } elseif ($property['TYPE']->has('pager')) {
+            } elseif ($typeParameter->has('pager')) {
                 $telField = 'tel_pager';
                 
             // FAX
-            } elseif ($property['TYPE']->has('fax')) {
-                if (count($property['TYPE']->getParts()) == 1 || $property['TYPE']->has('work')) {
+            } elseif ($typeParameter->has('fax')) {
+                if (count($typeParameter->getParts()) == 1 || $typeParameter->has('work')) {
                     $telField = 'tel_fax';
-                } elseif ($property['TYPE']->has('home')) {
+                } elseif ($typeParameter->has('home')) {
                     $telField = 'tel_fax_home';
                 }
                 
             // HOME
-            } elseif ($property['TYPE']->has('home')) {
+            } elseif ($typeParameter->has('home')) {
                 $telField = 'tel_home';
                 
             // WORK
-            } elseif ($property['TYPE']->has('work')) {
+            } elseif ($typeParameter->has('work')) {
                 $telField = 'tel_work';
             }
         } else {
@@ -367,6 +377,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
      */
     protected function _fromTine20ModelAddGeoData(Tinebase_Record_Abstract $record, \Sabre\VObject\Component $card)
     {
+        /** @var Addressbook_Model_Contact $record */
         if ($record->adr_one_lat && $record->adr_one_lon) {
             $card->add('GEO', array($record->adr_one_lat, $record->adr_one_lon));
             
@@ -383,6 +394,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
      */
     protected function _fromTine20ModelAddBirthday(Tinebase_Record_Abstract $record, \Sabre\VObject\Component $card)
     {
+        /** @var Addressbook_Model_Contact $record */
         if ($record->bday instanceof Tinebase_DateTime) {
             $date = clone $record->bday;
             $date->setTimezone(Tinebase_Core::getUserTimezone());
@@ -394,10 +406,10 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
     /**
      * parse categories from Tine20 model to VCard and attach it to VCard $card
      *
-     * @param Tinebase_Record_Abstract &$_record
+     * @param Tinebase_Record_Abstract $record
      * @param Sabre\VObject\Component $card
      */
-        protected function _fromTine20ModelAddCategories(Tinebase_Record_Abstract &$record, Sabre\VObject\Component $card)
+        protected function _fromTine20ModelAddCategories(Tinebase_Record_Abstract $record, Sabre\VObject\Component $card)
         {
             if (!isset($record->tags)) {
                 // If the record has not been populated yet with tags, let's try to get them all and update the record
@@ -438,6 +450,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
      */
     protected function _fromTine20ModelRequiredFields(Tinebase_Record_Abstract $record)
     {
+        /** @var Addressbook_Model_Contact $record */
         $version = Tinebase_Application::getInstance()->getApplicationByName('Addressbook')->version;
         
         $card = new \Sabre\VObject\Component\VCard(array(
index 9658ad6..5b5ede2 100644 (file)
@@ -43,7 +43,7 @@ class Addressbook_Convert_Contact_VCard_Factory
      *
      * @param   string $_backend
      * @param   string $_version
-     * @return  Addressbook_Convert_Contact_VCard_Interface
+     * @return  Addressbook_Convert_Contact_VCard_Abstract
      */
     static public function factory($_backend, $_version = null)
     {
index 0a896da..06c8f83 100644 (file)
@@ -49,7 +49,7 @@ class Addressbook_Convert_List_Json extends Tinebase_Convert_Json
             $contacts = Addressbook_Controller_Contact::getInstance()->getMultiple($contactIds);
         }
         foreach ($records as $list) {
-            if (isset($record->memberroles)) {
+            if (isset($list->memberroles)) {
                 foreach ($list->memberroles as $memberrole) {
                     $contact = $contacts->getById($memberrole->contact_id);
                     if ($contact) {
index fe2a779..a3bc686 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Export
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Alexander Stintzing <a.stintzing@metaways.de>
- * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2014-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
  * @subpackage  Export
  *
  */
-class Addressbook_Export_Doc extends Tinebase_Export_Richtext_Doc {
-    /**
-     * @var string application name of this export class
-     */
-    protected $_applicationName = 'Addressbook';
-    
-    /**
-     * the record model
-     *
-     * @var string
-     */
-    protected $_modelName = 'Contact';
-    
+class Addressbook_Export_Doc extends Tinebase_Export_Richtext_Doc
+{
     protected $_defaultExportname = 'adb_default_doc';
 
+    /**
+     * @param Tinebase_Record_RecordSet $_records
+     * @throws Tinebase_Exception_NotImplemented
+     */
     public function processIteration($_records)
     {
         $record = $_records->getFirstRecord();
@@ -48,7 +41,7 @@ class Addressbook_Export_Doc extends Tinebase_Export_Richtext_Doc {
     /**
      * returns a formal salutation
      *
-     * @param Tinebase_Record_Interface $record
+     * @param Tinebase_Record_Interface $resolved
      * @return string
      */
     protected function _getSalutation($resolved)
@@ -68,7 +61,7 @@ class Addressbook_Export_Doc extends Tinebase_Export_Richtext_Doc {
     /**
      * returns a short salutation
      *
-     * @param Tinebase_Record_Interface $record
+     * @param Tinebase_Record_Interface $resolved
      * @return string
      */
     protected function _getShortSalutation($resolved)
index 304245d..1d0a1a8 100644 (file)
@@ -125,6 +125,7 @@ class Addressbook_Export_Pdf extends Tinebase_Export_Pdf
             $tmpPath = tempnam(Tinebase_Core::getTempDir(), 'tine20_tmp_gd');
             $tmpPath .= $tineImage->getImageExtension();
             file_put_contents($tmpPath, $tineImage->blob);
+            /** @var Zend_PDF_Image $contactPhoto */
             $contactPhoto = Zend_Pdf_Image::imageWithPath($tmpPath);
             unlink($tmpPath);
         } catch (Exception $e) {
index 2149888..45dfb7e 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Addressbook
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2008-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
@@ -143,8 +143,11 @@ class Addressbook_Frontend_ActiveSync extends ActiveSync_Frontend_Abstract imple
     }
     
     /**
-     * (non-PHPdoc)
      * @see ActiveSync_Frontend_Abstract::toSyncrotonModel()
+     *
+     * @param Addressbook_Model_Contact $entry
+     * @param array $options
+     * @return Syncroton_Model_Contact
      */
     public function toSyncrotonModel($entry, array $options = array())
     {
@@ -212,7 +215,8 @@ class Addressbook_Frontend_ActiveSync extends ActiveSync_Frontend_Abstract imple
     /**
      * convert contact from xml to Addressbook_Model_Contact
      *
-     * @param SimpleXMLElement $_data
+     * @param Syncroton_Model_IEntry $data
+     * @param $entry
      * @return Addressbook_Model_Contact
      */
     public function toTineModel(Syncroton_Model_IEntry $data, $entry = null)
index abe0d46..7ef5b5f 100644 (file)
@@ -9,7 +9,7 @@ use Sabre\VObject;
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Cornelius Weiss <c.weiss@metaways.de>
- * @copyright   Copyright (c) 2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2013-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -37,7 +37,7 @@ class Addressbook_Frontend_CardDAV_AllContacts extends Sabre\DAV\Collection impl
     
     public function __construct($_userId)
     {
-        $this->_user = $_userId instanceof Tinebase_Model_FullUser ? $_userId : Tinebase_User::getInstance()->get($_userId);
+        $this->_user = $_userId instanceof Tinebase_Model_FullUser ? $_userId : Tinebase_User::getInstance()->getUserById($_userId, 'Tinebase_Model_FullUser');
         $this->_containerName = Tinebase_Translation::getTranslation('Addressbook')->_('All Contacts');
     }
     
@@ -85,22 +85,23 @@ class Addressbook_Frontend_CardDAV_AllContacts extends Sabre\DAV\Collection impl
      * The contents of the new file must be a valid VCARD
      *
      * @param  string    $name
-     * @param  resource  $vcardData
+     * @param  resource  $vobjectData
      * @return string    the etag of the record
      */
     public function createFile($name, $vobjectData = null)
     {
-        $objectClass = 'Addressbook_Frontend_WebDAV_Contact';
-        
         $container = Tinebase_Container::getInstance()->getDefaultContainer('Addressbook_Model_Contact', $this->_user);
-        $object = $objectClass::create($container, $name, $vobjectData);
+        $object = Addressbook_Frontend_WebDAV_Contact::create($container, $name, $vobjectData);
     
         return $object->getETag();
     }
-    
+
     /**
-     * (non-PHPdoc)
      * @see Sabre\DAV\Collection::getChild()
+     *
+     * @param string $_name
+     * @return Addressbook_Frontend_WebDAV_Contact
+     * @throws \Sabre\DAV\Exception\NotFound
      */
     public function getChild($_name)
     {
@@ -125,9 +126,8 @@ class Addressbook_Frontend_CardDAV_AllContacts extends Sabre\DAV\Collection impl
         }
         
         $container = Tinebase_Container::getInstance()->getContainerById($object->container_id);
-        $objectClass = 'Addressbook_Frontend_WebDAV_Contact';
     
-        return new $objectClass($container, $object);
+        return new Addressbook_Frontend_WebDAV_Contact($container, $object);
     }
     
    
index bbcdff6..e253abe 100644 (file)
@@ -4,7 +4,7 @@
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2008-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  */
 
@@ -48,9 +48,111 @@ class Addressbook_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
                 'addressbookId' => 'only export contcts of the given addressbook',
                 'tagId'         => 'only export contacts having the given tag'
             )
+        ),
+        'syncbackends' => array(
+            'description'   => 'Syncs all contacts to the sync backends',
+            'params'        => array(),
         )
     );
-    
+
+    public function syncbackends($_opts)
+    {
+        $sqlBackend = new Addressbook_Backend_Sql();
+        $controller = Addressbook_Controller_Contact::getInstance();
+        $syncBackends = $controller->getSyncBackends();
+
+        foreach ($sqlBackend->getAll() as $contact) {
+            $oldRecordBackendIds = $contact->syncBackendIds;
+            if (is_string($oldRecordBackendIds)) {
+                $oldRecordBackendIds = explode(',', $contact->syncBackendIds);
+            } else {
+                $oldRecordBackendIds = array();
+            }
+
+            $updateSyncBackendIds = false;
+            
+            foreach($syncBackends as $backendId => $backendArray)
+            {
+                if (isset($backendArray['filter'])) {
+                    $oldACL = $controller->doContainerACLChecks(false);
+
+                    $filter = new Addressbook_Model_ContactFilter($backendArray['filter']);
+                    $filter->addFilter(new Addressbook_Model_ContactIdFilter(
+                        array('field' => $contact->getIdProperty(), 'operator' => 'equals', 'value' => $contact->getId())
+                    ));
+
+                    // record does not match the filter, attention searchCount returns a STRING! "1"...
+                    if ($controller->searchCount($filter) != 1) {
+
+                        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                            . ' record did not match filter of syncBackend "' . $backendId . '"');
+
+                        // record is stored in that backend, so we remove it from there
+                        if (in_array($backendId, $oldRecordBackendIds)) {
+
+                            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                                . ' deleting record from syncBackend "' . $backendId . '"');
+
+                            try {
+                                $backendArray['instance']->delete($contact);
+
+                                $contact->syncBackendIds = trim(preg_replace('/(^|,)' . $backendId . '($|,)/', ',', $contact->syncBackendIds), ',');
+
+                                $updateSyncBackendIds = true;
+                            } catch (Exception $e) {
+                                Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not delete record from sync backend "' .
+                                    $backendId . '": ' . $e->getMessage());
+                                Tinebase_Exception::log($e, false);
+                            }
+                        }
+
+                        $controller->doContainerACLChecks($oldACL);
+
+                        continue;
+                    }
+                    $controller->doContainerACLChecks($oldACL);
+                }
+
+                // if record is in this syncbackend, update it
+                if (in_array($backendId, $oldRecordBackendIds)) {
+
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                        . ' update record in syncBackend "' . $backendId . '"');
+
+                    try {
+                        $backendArray['instance']->update($contact);
+                    } catch (Exception $e) {
+                        Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not update record in sync backend "' .
+                            $backendId . '": ' . $e->getMessage());
+                        Tinebase_Exception::log($e, false);
+                    }
+
+                    // else create it
+                } else {
+
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                        . ' create record in syncBackend "' . $backendId . '"');
+
+                    try {
+                        $backendArray['instance']->create($contact);
+
+                        $contact->syncBackendIds = (empty($contact->syncBackendIds)?'':$contact->syncBackendIds . ',') . $backendId;
+
+                        $updateSyncBackendIds = true;
+                    } catch (Exception $e) {
+                        Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' could not create record in sync backend "' .
+                            $backendId . '": ' . $e->getMessage());
+                        Tinebase_Exception::log($e, false);
+                    }
+                }
+            }
+
+            if (true === $updateSyncBackendIds) {
+                $sqlBackend->updateSyncBackendIds($contact->getId(), $contact->syncBackendIds);
+            }
+        }
+    }
+
     /**
      * import contacts
      *
@@ -66,11 +168,11 @@ class Addressbook_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
      * 
      * NOTE: exports contacts in container id 1 by default. id needs to be changed in the code.
      *
-     * @param Zend_Console_Getopt $_opts
+     * //@ param Zend_Console_Getopt $_opts
      * 
      * @todo allow to pass container id (and maybe more filter options) as param
      */
-    public function export($_opts)
+    public function export(/*$_opts*/)
     {
         $containerId = 1;
         
@@ -82,66 +184,14 @@ class Addressbook_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
         
         $csvExporter->generate();
     }
-    
-    /**
-     * create sample data
-     * 
-     * @param Zend_Console_Getopt $_opts
-     */
-    public function sampledata($_opts)
-    {
-        echo 'importing data ...';
-        include '/var/www/tine20/Addressbook/sampledata.php';
-        $controller = Addressbook_Controller_Contact::getInstance();
-        $contact = array();
-        
-        for ($i=0; $i<10; $i++) {
-            
-            // create a company
-            $contact['org_name'] = $sampledata['companyNames'][array_rand($sampledata['companyNames'])] . ' ' .
-                                   $sampledata['companyDesc'][array_rand($sampledata['companyDesc'])] . ' ' .
-                                   $sampledata['companyFrom'][array_rand($sampledata['companyFrom'])];
-                                   
-            $randCompNumber = array_rand($sampledata['companyPlz']);
-            $contact['adr_one_street']     = $sampledata['companyStreet'][array_rand($sampledata['companyStreet'])];
-            $contact['adr_one_postalcode'] = $sampledata['companyPlz'][$randCompNumber];
-            $contact['adr_one_locality']   = $sampledata['companyOrt'][$randCompNumber];
-            
-            for ($j=0; $j<10; $j++) {
-                // create person
-                $contact['tel_work']           = $sampledata['companyDialcode'][$randCompNumber] . rand(2456, 871234);
-                $contact['tel_fax']            = $contact['tel_work'] . '9';
-                $contact['tel_cell']           = $sampledata['mobileDialcode'][array_rand($sampledata['mobileDialcode'])] . rand(245634, 87123224);
-                $contact['role']               = $sampledata['position'][array_rand($sampledata['position'])];
-                
-                $randNameNumber = array_rand($sampledata['personFirstName']);
-                $contact['n_given']            = $sampledata['personFirstName'][$randNameNumber];
-                // todo: generate salutation even maile / odd femail
-                $contact['n_family']            = $sampledata['personLastName'][array_rand($sampledata['personLastName'])];
-                
-                $randPersNumber = array_rand($sampledata['personPlz']);
-                $contact['adr_two_street']     = $sampledata['personStreet'][array_rand($sampledata['personStreet'])];
-                $contact['adr_two_postalcode'] = $sampledata['personPlz'][$randPersNumber];
-                $contact['adr_two_locality']   = $sampledata['personOrt'][$randPersNumber];
-                
-                $contact['tel_home']           = $sampledata['personDialcode'][$randPersNumber] . rand(2456, 871234);
-                $contact['tel_cell_private']   = $sampledata['mobileDialcode'][array_rand($sampledata['mobileDialcode'])] . rand(245634, 87123224);
-                
-                $contact['container_id'] = 133;
-                $contactObj = new Addressbook_Model_Contact($contact, true);
-                
-                print_r($contactObj->toArray());
-                
-                $controller->create($contactObj);
-            }
-        }
-    }
 
     /**
      * remove autogenerated contacts
-     * 
+     *
      * @param Zend_Console_Getopt $opts
-     * 
+     *
+     * @throws Addressbook_Exception
+     * @throws Tinebase_Exception_InvalidArgument
      * @todo use OR filter for different locales
      */
     public function removeAutogeneratedContacts($opts)