Merge branch '2016.11' into 2016.11-develop
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 1 Dec 2016 17:05:50 +0000 (18:05 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 1 Dec 2016 17:05:50 +0000 (18:05 +0100)
287 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/JsonTest.php
tests/tine20/Addressbook/LdapSyncTest.php [new file with mode: 0644]
tests/tine20/AllTests.php
tests/tine20/ExampleApplication/JsonTest.php
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/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/TestCase.php
tests/tine20/Timetracker/ControllerTest.php
tests/tine20/Tinebase/ApplicationTest.php
tests/tine20/Tinebase/GroupTest.php
tests/tine20/Tinebase/Log/FormatterTest.php
tests/tine20/Tinebase/User/LdapTest.php
tests/tine20/Tinebase/User/SqlTest.php
tine20/ActiveSync/Controller/SyncDevices.php
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/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/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/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/Calendar/Controller/Event.php
tine20/Calendar/Controller/EventNotifications.php
tine20/Calendar/Controller/MSEventFacade.php
tine20/Crm/Controller/Lead.php
tine20/ExampleApplication/Model/ExampleRecord.php
tine20/ExampleApplication/Setup/setup.xml
tine20/Expressodriver/Controller/Node.php
tine20/Expressomail/Controller/Account.php
tine20/Expressomail/Controller/Folder.php
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/css/Felamimail.css
tine20/Felamimail/js/GridPanel.js
tine20/Felamimail/js/Model.js
tine20/Felamimail/translations/de.po
tine20/Felamimail/translations/en.po
tine20/Felamimail/translations/template.pot
tine20/Filemanager/Controller/Node.php
tine20/Filemanager/js/NodeEditDialog.js
tine20/Filemanager/js/NodeGridPanel.js
tine20/Filemanager/js/NodeTreePanel.js
tine20/Filemanager/translations/template.pot
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/Release9.php
tine20/Sales/Controller/Invoice.php
tine20/Sales/Controller/NumberableAbstract.php
tine20/Sales/Frontend/Json.php
tine20/Setup/Backend/Interface.php
tine20/Setup/Controller.php
tine20/Setup/Initialize.php
tine20/Setup/Update/Abstract.php
tine20/Setup/js/ConfigManagerPanel.js
tine20/Timetracker/Controller/Timesheet.php
tine20/Timetracker/Model/Timeaccount.php
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/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/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.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/FileSystem.php
tine20/Tinebase/FileSystem/RecordAttachments.php
tine20/Tinebase/Frontend/Http/Abstract.php
tine20/Tinebase/Frontend/Json/Abstract.php
tine20/Tinebase/ImageHelper.php
tine20/Tinebase/Import/Abstract.php
tine20/Tinebase/Import/Interface.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/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/Relations.php
tine20/Tinebase/Server/Json.php
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/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/js/Application.js
tine20/Tinebase/js/MainMenu.js
tine20/Tinebase/js/common.js
tine20/Tinebase/js/ux/file/BrowsePlugin.js
tine20/Tinebase/js/widgets/container/ContainerSelect.js
tine20/Tinebase/js/widgets/container/TreePanel.js
tine20/Tinebase/js/widgets/customfields/Field.js
tine20/Voipmanager/Backend/Asterisk/SipPeer.php
tine20/Voipmanager/Backend/Asterisk/Voicemail.php
tine20/composer.json
tine20/composer.lock
tine20/library/Console/Daemon.php

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 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 d1daa07..c0b84a1 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 ece137f..b654993 100644 (file)
@@ -49,22 +49,37 @@ 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']);
         
         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
@@ -104,7 +119,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 55d4ad5..0e059f4 100644 (file)
@@ -7,16 +7,11 @@ use Sabre\DAV;
  *
  * @package     Felamimail
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2009-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
 /**
- * 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 *********************************/
@@ -1148,9 +1146,164 @@ 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 "ssss" at Oct 12, 2016 4:00:00 PM');
+        $this->assertContains('Event details', $emlNode['message']['body'], print_r($emlNode['message'], 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..83320c9
--- /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 "ssss" 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..6cbd622 100644 (file)
@@ -1030,7 +1030,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 +1322,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;
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 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 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);
         }
     }
 }
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'
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');
         
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 3ec0ee1..72338b4 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 40c2397..dbdf500 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,39 +494,69 @@ 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)
     {
+        /** @var Addressbook_Model_Contact $_record */
+        /** @var Addressbook_Model_Contact $_oldRecord */
+
         // do update of geo data only if one of address field changed
         $addressDataChanged = FALSE;
         foreach ($this->_addressFields as $field) {
@@ -350,22 +589,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);
     }
     
     /**
@@ -594,6 +838,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) {
@@ -632,4 +877,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 47afd88..18bd9d9 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':
@@ -266,38 +274,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 {
@@ -368,6 +378,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));
             
@@ -384,6 +395,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());
@@ -395,10 +407,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
@@ -439,6 +451,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..bcc3ce5 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)
  *
  */
 
@@ -32,6 +32,10 @@ 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 +52,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 +72,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 aad16ce..13751a0 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)
index 37ea7ab..8425541 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -34,9 +34,10 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
      */
     public static function resolveImages(Tinebase_Record_RecordSet $_records)
     {
+        /** @var Tinebase_Record_Interface $record */
         foreach($_records as &$record) {
             if($record['jpegphoto'] == '1') {
-                $record['jpegphoto'] = Tinebase_Model_Image::getImageUrl('Addressbook', $record->__get('id'), '');
+                $record['jpegphoto'] = Tinebase_Model_Image::getImageUrl('Addressbook', $record->getId(), '');
             }
         }
     }
@@ -124,17 +125,18 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
             }
          }
          return array("results" => $results, "totalcount" => $lists["totalcount"]+$contacts["totalcount"]);
-    } 
-    
+    }
+
     /**
      * return autocomplete suggestions for a given property and value
-     * 
+     *
      * @todo have spechial controller/backend fns for this
      * @todo move to abstract json class and have tests
      *
-     * @param  string   $property
-     * @param  string   $startswith
+     * @param  string $property
+     * @param  string $startswith
      * @return array
+     * @throws Tasks_Exception_UnexpectedValue
      */
     public function autoCompleteContactProperty($property, $startswith)
     {
@@ -241,6 +243,51 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     {
         return $this->_save($recordData, Addressbook_Controller_ListRole::getInstance(), 'ListRole');
     }
+    
+    /**
+     * get one industry identified by $id
+     *
+     * @param string $id
+     * @return array
+     */
+    public function getIndustry($id)
+    {
+        return $this->_get($id, Addressbook_Controller_Industry::getInstance());
+    }
+    
+    /**
+     * Search for industries matching given arguments
+     *
+     * @param  array $filter
+     * @param  array $paging
+     * @return array
+     */
+    public function searchIndustrys($filter, $paging)
+    {
+        return $this->_search($filter, $paging, Addressbook_Controller_Industry::getInstance(), 'Addressbook_Model_IndustryFilter');
+    }
+    
+    /**
+     * delete multiple industries
+     *
+     * @param array $ids list of listId's to delete
+     * @return array
+     */
+    public function deleteIndustrys($ids)
+    {
+        return $this->_delete($ids, Addressbook_Controller_Industry::getInstance());
+    }
+    
+    /**
+     * save industry
+     *
+     * @param $recordData
+     * @return array
+     */
+    public function saveIndustry($recordData)
+    {
+        return $this->_save($recordData, Addressbook_Controller_Industry::getInstance(), 'Industry');
+    }
 
     /**
      * save one contact
index 7828ab9..6969857 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Frontend
  * @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)
  */
 
 /**
@@ -18,8 +18,9 @@
 class Addressbook_Frontend_WebDAV extends Tinebase_WebDav_Collection_AbstractContainerTree
 {
     /**
-     * (non-PHPdoc)
      * @see Tinebase_WebDav_Collection_AbstractContainerTree::getChild()
+     * @param string $name
+     * @return Tinebase_WebDav_Container_Abstract|Tinebase_WebDav_Collection_AbstractContainerTree|Tinebase_Frontend_WebDAV_RecordCollection
      */
     public function getChild($name)
     {
@@ -36,7 +37,7 @@ class Addressbook_Frontend_WebDAV extends Tinebase_WebDav_Collection_AbstractCon
      */
     public function getChildren()
     {
-        list ($client, $version) = Addressbook_Convert_Contact_VCard_Factory::getUserAgent();
+        list ($client/*, $version*/) = Addressbook_Convert_Contact_VCard_Factory::getUserAgent();
         
         if (count($this->_getPathParts()) === 2 && in_array($client, array(Addressbook_Convert_Contact_VCard_Factory::CLIENT_MACOSX))) {
             $children[] = $this->getChild(Addressbook_Frontend_CardDAV_AllContacts::NAME);
@@ -46,4 +47,4 @@ class Addressbook_Frontend_WebDAV extends Tinebase_WebDav_Collection_AbstractCon
         
         return parent::getChildren();
     }
-}
+}
\ No newline at end of file
index fef2ff9..f28a837 100644 (file)
@@ -8,7 +8,7 @@ use \Sabre\DAV;
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2011-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -40,14 +40,15 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
     protected $_vcard;
     
     /**
-     * @var Addressbook_Convert_Contact_VCard_Interface
+     * @var Addressbook_Convert_Contact_VCard_Abstract
      */
     protected $_converter;
-    
+
     /**
-     * Constructor 
-     * 
-     * @param  string|Addressbook_Model_Contact  $_contact  the id of a contact or the contact itself 
+     * Constructor
+     *
+     * @param Tinebase_Model_Container $_container
+     * @param  string|Addressbook_Model_Contact $_contact the id of a contact or the contact itself
      */
     public function __construct(Tinebase_Model_Container $_container, $_contact = null) 
     {
@@ -58,14 +59,17 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
         
         $this->_converter = Addressbook_Convert_Contact_VCard_Factory::factory($backend, $version);
     }
-    
+
     /**
      * this function creates a Addressbook_Model_Contact and stores it in the database
-     * 
+     *
      * @todo the header handling does not belong here. It should be moved to the DAV_Server class when supported
-     * 
-     * @param  Tinebase_Model_Container  $container
-     * @param  stream|string           $vcardData
+     *
+     * @param  Tinebase_Model_Container $container
+     * @param  string $name
+     * @param  resource|string $vcardData
+     * @return Addressbook_Frontend_WebDAV_Contact
+     * @throws DAV\Exception\Forbidden
      */
     public static function create(Tinebase_Model_Container $container, $name, $vcardData)
     {
@@ -91,12 +95,12 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
         
         return $card;
     }
-    
+
     /**
      * Deletes the card
      *
-     * @return void
-     * 
+     * @throws DAV\Exception\Forbidden
+     * @throws Exception
      * @see Calendar_Frontend_WebDAV_Event::delete()
      */
     public function delete() 
@@ -121,7 +125,7 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
     /**
      * Returns the VCard-formatted object 
      * 
-     * @return stream
+     * @return resource
      */
     public function get() 
     {
@@ -153,7 +157,7 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
     public function getOwner() 
     {
         return null;
-        return $this->addressBookInfo['principaluri'];
+        //return $this->addressBookInfo['principaluri'];
     }
 
     /**
@@ -180,13 +184,13 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
      *      be updated. 
      * 
      * @todo add the real logic
-     * @return array 
+     * @return array|null
      */
     public function getACL() 
     {
         return null;
         
-        return array(
+        /*return array(
             array(
                 'privilege' => '{DAV:}read',
                 'principal' => $this->addressBookInfo['principaluri'],
@@ -197,7 +201,7 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
                 'principal' => $this->addressBookInfo['principaluri'],
                 'protected' => true,
             ),
-        );
+        );*/
 
     }
     
@@ -225,7 +229,7 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
     /**
      * Returns the last modification date as a unix timestamp
      *
-     * @return time
+     * @return string
      */
     public function getLastModified() 
     {
@@ -242,12 +246,13 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
     {
         return strlen($this->_getVCard());
     }
-    
+
     /**
      * Updates the VCard-formatted object
      *
      * @param string $cardData
-     * @return void
+     * @throws DAV\Exception\Forbidden
+     * @return string
      */
     public function put($cardData) 
     {
@@ -271,24 +276,25 @@ class Addressbook_Frontend_WebDAV_Contact extends Sabre\DAV\File implements Sabr
         
         return $this->getETag();
     }
-    
+
     /**
      * Updates the ACL
      *
-     * This method will receive a list of new ACE's. 
-     * 
-     * @param array $acl 
-     * @return void
+     * This method will receive a list of new ACE's.
+     *
+     * @param array $acl
+     * @throws DAV\Exception\MethodNotAllowed
      */
     public function setACL(array $acl) 
     {
         throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
     }
-    
+
     /**
      * return Addressbook_Model_Contact and convert contact id to model if needed
-     * 
+     *
      * @return Addressbook_Model_Contact
+     * @throws DAV\Exception\Forbidden
      */
     public function getRecord()
     {
index d360d17..e414fd8 100644 (file)
@@ -9,7 +9,7 @@ use Sabre\CardDAV;
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2011-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -61,3 +61,45 @@ class Addressbook_Frontend_WebDAV_Container extends Tinebase_WebDav_Container_Ab
         return $response;
     }
 }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
index aef860e..0c44a41 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Import
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schuele <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -14,6 +14,8 @@
  * 
  * @package     Addressbook
  * @subpackage  Import
+ *
+ * @property Addressbook_Controller_Contact     $_controller    protected property!
  */
 class Addressbook_Import_Csv extends Tinebase_Import_Csv_Abstract
 {
@@ -54,7 +56,7 @@ class Addressbook_Import_Csv extends Tinebase_Import_Csv_Abstract
             // TODO make this setting overwritable via import definition/options
             $this->_controller->setGeoDataForContacts(FALSE);
         }
-        
+
         // get container id from default container if not set
         if (empty($this->_options['container_id'])) {
             $defaultContainer = $this->_controller->getDefaultAddressbook();
index 6ee8356..9786969 100644 (file)
@@ -19,6 +19,8 @@ require_once 'vcardphp/vcard.php';
  * @package     Addressbook
  * @subpackage  Import
  * @see ftp://ftp.rfc-editor.org/in-notes/rfc2426.txt
+ *
+ * @property Addressbook_Controller_Contact     $_controller    protected property!
  */
 class Addressbook_Import_VCard extends Tinebase_Import_Abstract
 {
@@ -65,8 +67,9 @@ class Addressbook_Import_VCard extends Tinebase_Import_Abstract
 
     /**
      * constructs a new importer from given config
-     * 
+     *
      * @param array $_options
+     * @throws Tinebase_Exception_InvalidArgument
      */
     public function __construct(array $_options = array())
     {
@@ -131,8 +134,7 @@ class Addressbook_Import_VCard extends Tinebase_Import_Abstract
     protected function _getRawData(&$_resource) 
     {
         $card = new VCard();
-        
-        $result = FALSE;
+
         while ($card->parse($_resource)) {
             if ($card->getProperty('N')) {
                 return $card;
@@ -148,7 +150,6 @@ class Addressbook_Import_VCard extends Tinebase_Import_Abstract
      * do the mapping and replacements
      *
      * @param VCard $card
-     * @param array $_headline [optional]
      * @return array
      * 
      * @todo split this into smaller parts
index 3353e49..72d89d4 100644 (file)
  * 
  * @package     Addressbook
  * @subpackage  Model
- * @property    string account_id                 id of associated user
- * @property    string adr_one_countryname        name of the country the contact lives in
- * @property    string adr_one_locality           locality of the contact
- * @property    string adr_one_postalcode         postalcode belonging to the locality
- * @property    string adr_one_region             region the contact lives in
- * @property    string adr_one_street             street where the contact lives
- * @property    string adr_one_street2            street2 where contact lives
- * @property    string adr_two_countryname        second home/country where the contact lives
- * @property    string adr_two_locality           second locality of the contact
- * @property    string adr_two_postalcode         ostalcode belonging to second locality
- * @property    string adr_two_region             second region the contact lives in
- * @property    string adr_two_street             second street where the contact lives
- * @property    string adr_two_street2            second street2 where the contact lives
- * @property    string assistent                  name of the assistent of the contact
- * @property    datetime bday                     date of birth of contact
- * @property    integer container_id              id of container
- * @property    string email                      the email address of the contact
- * @property    string email_home                 the private email address of the contact
- * @property    blob jpegphoto                    photo of the contact
- * @property    string n_family                   surname of the contact
- * @property    string n_fileas                   display surname, name
- * @property    string n_fn                       the full name
- * @property    string n_given                    forename of the contact
- * @property    string n_middle                   middle name of the contact
- * @property    string note                       notes of the contact
- * @property    string n_prefix
- * @property    string n_suffix
- * @property    string org_name                   name of the company the contact works at
- * @property    string org_unit
- * @property    string role                       type of role of the contact
- * @property    string tel_assistent              phone number of the assistent
- * @property    string tel_car
- * @property    string tel_cell                   mobile phone number
- * @property    string tel_cell_private           private mobile number
- * @property    string tel_fax                    number for calling the fax
- * @property    string tel_fax_home               private fax number
- * @property    string tel_home                   telephone number of contact's home
- * @property    string tel_pager                  contact's pager number
- * @property    string tel_work                   contact's office phone number
- * @property    string title                      special title of the contact
- * @property    string type                       type of contact
- * @property    string url                        url of the contact
- * @property    string url_home                   private url of the contact
+ *
+ * @property    string $account_id                 id of associated user
+ * @property    string $adr_one_countryname        name of the country the contact lives in
+ * @property    string $adr_one_locality           locality of the contact
+ * @property    string $adr_one_postalcode         postalcode belonging to the locality
+ * @property    string $adr_one_region             region the contact lives in
+ * @property    string $adr_one_street             street where the contact lives
+ * @property    string $adr_one_street2            street2 where contact lives
+ * @property    string $adr_one_lon
+ * @property    string $adr_one_lat
+ * @property    string $adr_two_countryname        second home/country where the contact lives
+ * @property    string $adr_two_locality           second locality of the contact
+ * @property    string $adr_two_postalcode         ostalcode belonging to second locality
+ * @property    string $adr_two_region             second region the contact lives in
+ * @property    string $adr_two_street             second street where the contact lives
+ * @property    string $adr_two_street2            second street2 where the contact lives
+ * @property    string $adr_two_lon
+ * @property    string $adr_two_lat
+ * @property    string $assistent                  name of the assistent of the contact
+ * @property    datetime $bday                     date of birth of contact
+ * @property    integer $container_id              id of container
+ * @property    string $email                      the email address of the contact
+ * @property    string $email_home                 the private email address of the contact
+ * @property    string $jpegphoto                    photo of the contact
+ * @property    string $n_family                   surname of the contact
+ * @property    string $n_fileas                   display surname, name
+ * @property    string $n_fn                       the full name
+ * @property    string $n_given                    forename of the contact
+ * @property    string $n_middle                   middle name of the contact
+ * @property    string $note                       notes of the contact
+ * @property    string $n_prefix
+ * @property    string $n_suffix
+ * @property    string $org_name                   name of the company the contact works at
+ * @property    string $org_unit
+ * @property    string $role                       type of role of the contact
+ * @property    string $tel_assistent              phone number of the assistent
+ * @property    string $tel_car
+ * @property    string $tel_cell                   mobile phone number
+ * @property    string $tel_cell_private           private mobile number
+ * @property    string $tel_fax                    number for calling the fax
+ * @property    string $tel_fax_home               private fax number
+ * @property    string $tel_home                   telephone number of contact's home
+ * @property    string $tel_pager                  contact's pager number
+ * @property    string $tel_work                   contact's office phone number
+ * @property    string $title                      special title of the contact
+ * @property    string $type                       type of contact
+ * @property    string $url                        url of the contact
+ * @property    string $url_home                   private url of the contact
  */
 class Addressbook_Model_Contact extends Tinebase_Record_Abstract
 {
@@ -114,8 +119,9 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
      * @var array
      */
     protected static $_resolveForeignIdFields = array(
-        'Tinebase_Model_User' => array('created_by', 'last_modified_by'),
-        'recursive'           => array('attachments' => 'Tinebase_Model_Tree_Node'),
+        'Tinebase_Model_User'        => array('created_by', 'last_modified_by'),
+        'Addressbook_Model_Industry' => array('industry'),
+        'recursive'                  => array('attachments' => 'Tinebase_Model_Tree_Node'),
     );
     
     /**
@@ -230,6 +236,8 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
             array('InArray', array(self::CONTACTTYPE_USER, self::CONTACTTYPE_CONTACT)),
         ),
         'paths'                         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'industry'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'syncBackendIds'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
     );
     
     /**
@@ -317,8 +325,9 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
     }
     
     /**
-     * (non-PHPdoc)
-     * @see Tinebase/Record/Tinebase_Record_Abstract#setFromArray($_data)
+     * @see Tinebase_Record_Abstract::setFromArray
+     *
+     * @param array $_data            the new data to set
      */
     public function setFromArray(array $_data)
     {
@@ -372,6 +381,8 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
      * Overwrites the __set Method from Tinebase_Record_Abstract
      * Also sets n_fn and n_fileas when org_name, n_given or n_family should be set
      * @see Tinebase_Record_Abstract::__set()
+     * @param string $_name of property
+     * @param mixed $_value of property
      */
     public function __set($_name, $_value) {
         
index 8a745d6..198b09e 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Addressbook
  * @subpackage  Filter
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2012-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
@@ -26,7 +26,6 @@ class Addressbook_Model_ContactDisabledFilter extends Tinebase_Model_Filter_Bool
     public function appendFilterSql($_select, $_backend)
     {
         $db        = $_backend->getAdapter();
-        $dbCommand = $_backend->getDbCommand();
 
         // prepare value
         $value = $this->_value ? 1 : 0;
@@ -60,4 +59,4 @@ class Addressbook_Model_ContactDisabledFilter extends Tinebase_Model_Filter_Bool
             $_select->where($where);
         }
     }
-}
+}
\ No newline at end of file
index 25da4fa..2e1360e 100644 (file)
@@ -142,5 +142,12 @@ class Addressbook_Model_ContactFilter extends Tinebase_Model_Filter_FilterGroup
         )),
         'salutation'           => array('filter' => 'Tinebase_Model_Filter_Text'),
         //'bday'               => array('filter' => 'Tinebase_Model_Filter_Date'),
+        'industry'             => array(
+            'filter' => 'Tinebase_Model_Filter_ForeignId',
+            'options' => array(
+                    'filtergroup' => 'Addressbook_Model_IndustryFilter',
+                    'controller' => 'Addressbook_Controller_Industry'
+            )
+        ),
     );
 }
diff --git a/tine20/Addressbook/Model/Industry.php b/tine20/Addressbook/Model/Industry.php
new file mode 100644 (file)
index 0000000..5b4b577
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * Model of a industry
+ * 
+ * @package Addressbook
+ */
+class Addressbook_Model_Industry extends Tinebase_Record_Simple
+{
+    /**
+     * application the record belongs to
+     *
+     * @var string
+     */
+    protected $_application = 'Addressbook';
+}
diff --git a/tine20/Addressbook/Model/IndustryFilter.php b/tine20/Addressbook/Model/IndustryFilter.php
new file mode 100644 (file)
index 0000000..f94e5c6
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Addressbook_Model_IndustryFilter
+ * 
+ * @package     Addressbook
+ * @subpackage  Filter
+ */
+class Addressbook_Model_IndustryFilter extends Tinebase_Model_Filter_FilterGroup
+{
+    /**
+     * @var string class name of this filter group
+     *      this is needed to overcome the static late binding
+     *      limitation in php < 5.3
+     */
+    protected $_className = 'Addressbook_Model_IndustryFilter';
+    
+    /**
+     * @var string application of this filter group
+     */
+    protected $_applicationName = 'Addressbook';
+    
+    /**
+     * @var string name of model this filter group is designed for
+     */
+    protected $_modelName = 'Addressbook_Model_Industry';
+    
+    /**
+     * @var array filter model fieldName => definition
+     */
+    protected $_filterModel = array(
+        'id'                    => array(
+            'filter' => 'Tinebase_Model_Filter_Id'
+        ),
+        'query'                => array(
+            'filter' => 'Tinebase_Model_Filter_Query', 
+            'options' => array('fields' => array('name'))
+        ),
+    );
+}
index f5b5d60..74e421c 100644 (file)
@@ -5,20 +5,22 @@
  * @package     Addressbook
  * @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)
  */
 
 /**
  * class to hold addressbook list data
- * 
- * @property    id        
- * @property    container_id
- * @property    name            
- * @property    description        
- * @property    member
- * @property    email             
- * @property    type            type of list
+ *
  * @package     Addressbook
+ *
+ * @property    string      $id
+ * @property    string      $container_id
+ * @property    string      $name
+ * @property    string      $description
+ * @property    array       $members
+ * @property    array       $memberroles
+ * @property    string      $email
+ * @property    string      $type                 type of list
  */
 class Addressbook_Model_List extends Tinebase_Record_Abstract
 {
@@ -95,7 +97,6 @@ class Addressbook_Model_List extends Tinebase_Record_Abstract
         ),
         'list_type'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'group_id'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
-        'tags'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'emails'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'memberroles'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
 
index 5cfda96..ed3dd1c 100644 (file)
@@ -45,3 +45,5 @@ class Addressbook_Model_ListHiddenFilter extends Tinebase_Model_Filter_Bool
         }
     }
 }
+
+
index a3d2a2c..af480e0 100644 (file)
@@ -32,6 +32,8 @@ class Addressbook_Setup_Initialize extends Setup_Initialize
      */
     protected function _initialize(Tinebase_Model_Application $_application, $_options = null)
     {
+        parent::createInitialRights($_application);
+
         $initialAdminUserOptions = $this->_parseInitialAdminUserOptions($_options);
         
         if (Tinebase_User::getInstance() instanceof Tinebase_User_Interface_SyncAble) {
@@ -53,21 +55,19 @@ class Addressbook_Setup_Initialize extends Setup_Initialize
         
         parent::_initialize($_application, $_options);
     }
-    
+
     /**
-     * init/create user contacts
+     * create inital rights
+     *
+     * @param Tinebase_Application $application
+     * @return void
      */
-    protected function _initializeUserContacts()
+    public static function createInitialRights(Tinebase_Model_Application $_application)
     {
-        foreach (Tinebase_User::getInstance()->getFullUsers() as $fullUser) {
-            $fullUser->container_id = $this->_getInternalAddressbook()->getId();
-            $contact = Admin_Controller_User::getInstance()->createOrUpdateContact($fullUser);
-            
-            $fullUser->contact_id = $contact->getId();
-            Tinebase_User::getInstance()->updateUser($fullUser);
-        }
+        // we do nothing to work our way through the jungle here
+        // we call parent::createInitialRights at the time we like it in _initialize
     }
-    
+
     /**
      * returns internal addressbook
      * 
index 3c8905d..a938d44 100644 (file)
@@ -344,4 +344,124 @@ class Addressbook_Setup_Update_Release9 extends Setup_Update_Abstract
 
         $this->setApplicationVersion('Addressbook', '9.9');
     }
+
+    /**
+     * add addressbook_industry table and column
+     *
+     * @return void
+     */
+    public function update_9()
+    {
+        if ($this->getTableVersion('addressbook_industry') < 1) {
+            $table = Setup_Backend_Schema_Table_Factory::factory('String', '
+            <table>
+                <name>addressbook_industry</name>
+                <engine>InnoDB</engine>
+                <charset>utf8</charset>
+                <version>1</version>
+                <declaration>
+                    <field>
+                        <name>id</name>
+                        <type>text</type>
+                        <length>40</length>
+                        <notnull>true</notnull>
+                    </field>
+                    <field>
+                        <name>name</name>
+                        <type>text</type>
+                        <length>256</length>
+                        <notnull>false</notnull>
+                    </field>
+                    <field>
+                        <name>description</name>
+                        <type>text</type>
+                        <notnull>false</notnull>
+                    </field>
+                    <field>
+                        <name>created_by</name>
+                        <type>text</type>
+                        <length>40</length>
+                    </field>
+                    <field>
+                        <name>creation_time</name>
+                        <type>datetime</type>
+                    </field>
+                    <field>
+                        <name>last_modified_by</name>
+                        <type>text</type>
+                        <length>40</length>
+                    </field>
+                    <field>
+                        <name>last_modified_time</name>
+                        <type>datetime</type>
+                    </field>
+                    <field>
+                        <name>is_deleted</name>
+                        <type>boolean</type>
+                        <default>false</default>
+                    </field>
+                    <field>
+                        <name>deleted_by</name>
+                        <type>text</type>
+                        <length>40</length>
+                    </field>
+                    <field>
+                        <name>deleted_time</name>
+                        <type>datetime</type>
+                    </field>
+                    <field>
+                        <name>seq</name>
+                        <type>integer</type>
+                        <notnull>true</notnull>
+                        <default>0</default>
+                    </field>
+                    <index>
+                        <name>id</name>
+                        <primary>true</primary>
+                        <field>
+                            <name>id</name>
+                        </field>
+                    </index>
+                </declaration>
+            </table>
+            ');
+            $this->createTable('addressbook_industry', $table, 'Addressbook');
+        }
+        
+        if ($this->getTableVersion('addressbook') == 20) {
+            $declaration = new Setup_Backend_Schema_Field_Xml('
+                <field>
+                    <name>industry</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>false</notnull>
+                </field>');
+            $this->_backend->addCol('addressbook', $declaration);
+            $this->setTableVersion('addressbook', 21);
+        }
+        
+        $this->setApplicationVersion('Addressbook', '9.10');
+    }
+
+    /**
+     * update to 9.11
+     *
+     * add multiple sync backends / ldap implementation
+     */
+    public function update_10()
+    {
+        if ($this->getTableVersion('addressbook') < 21) {
+            $declaration = new Setup_Backend_Schema_Field_Xml(
+                '<field>
+                    <name>syncBackendIds</name>
+                    <type>text</type>
+                    <length>16000</length>
+                </field>');
+            $this->_backend->addCol('addressbook', $declaration);
+
+            $this->setTableVersion('addressbook', 21);
+        }
+
+        $this->setApplicationVersion('Addressbook', '9.11');
+    }
 }
index 53bf5b2..df61fb0 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Addressbook</name>
-    <version>9.9</version>
+    <version>9.11</version>
     <order>10</order>
     <depends>
         <application>Admin</application>
@@ -11,7 +11,7 @@
             <name>addressbook</name>
             <engine>InnoDB</engine>
             <charset>utf8</charset>
-            <version>20</version>
+            <version>21</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <notnull>false</notnull>
                 </field>
                 <field>
+                    <name>industry</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>false</notnull>
+                </field>
+                <field>
                     <name>created_by</name>
                     <type>text</type>
                     <length>40</length>
                     <notnull>true</notnull>
                     <default>contact</default>
                 </field>
+                <field>
+                    <name>syncBackendIds</name>
+                    <type>text</type>
+                    <length>16000</length>
+                </field>
                 <index>
                     <name>id</name>
                     <primary>true</primary>
                 </index>
             </declaration>
         </table>
+                <table>
+            <name>addressbook_industry</name>
+            <engine>InnoDB</engine>
+            <charset>utf8</charset>
+            <version>1</version>
+            <declaration>
+                <field>
+                    <name>id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>name</name>
+                    <type>text</type>
+                    <length>256</length>
+                    <notnull>false</notnull>
+                </field>
+                <field>
+                    <name>description</name>
+                    <type>text</type>
+                    <notnull>false</notnull>
+                </field>
+                <field>
+                    <name>created_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>creation_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>last_modified_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>last_modified_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>is_deleted</name>
+                    <type>boolean</type>
+                    <default>false</default>
+                </field>
+                <field>
+                    <name>deleted_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>deleted_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>seq</name>
+                    <type>integer</type>
+                    <notnull>true</notnull>
+                    <default>0</default>
+                </field>
+                <index>
+                    <name>id</name>
+                    <primary>true</primary>
+                    <field>
+                        <name>id</name>
+                    </field>
+                </index>
+            </declaration>
+        </table>
     </tables>
     <defaultRecords>
         <record>
index 3b2c032..4579369 100755 (executable)
@@ -86,6 +86,41 @@ Tine.Addressbook.Application = Ext.extend(Tine.Tinebase.Application, {
                 }
             }
         );
+        
+        Tine.CoreData.Manager.registerGrid(
+            'adb_industries',
+            Tine.widgets.grid.GridPanel,
+            {
+                recordClass: Tine.Addressbook.Model.Industry,
+                app: this,
+                initialLoadAfterRender: false,
+                // TODO move this to a generic place
+                gridConfig: {
+                    autoExpandColumn: 'name',
+                    columns: [{
+                        id: 'id',
+                        header: this.i18n._("ID"),
+                        width: 150,
+                        sortable: true,
+                        dataIndex: 'id',
+                        hidden: true
+                    }, {
+                        id: 'name',
+                        header: this.i18n._("Name"),
+                        width: 300,
+                        sortable: true,
+                        dataIndex: 'name'
+                    }, {
+                        id: 'description',
+                        header: this.i18n._("Description"),
+                        width: 300,
+                        sortable: true,
+                        dataIndex: 'description',
+                        hidden: true
+                    }]
+                }
+            }
+        );
     }
 });
 
index d237035..e972a6b 100644 (file)
@@ -82,6 +82,10 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                             xtype: 'panel',
                             layout: 'hbox',
                             align: 'stretch',
+                            plugins: [{
+                                ptype: 'ux.itemregistry',
+                                key:   'Tine.Addressbook.editDialog.northPanel'
+                            }],
                             items: [{
                                 flex: 1,
                                 xtype: 'columnform',
@@ -134,7 +138,8 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                                     fieldLabel: this.app.i18n._('Unit'),
                                     name: 'org_unit',
                                     maxLength: 64
-                                }]/* move to seperate tab, [{
+                                }
+                                ]/* move to seperate tab, [{
                                     columnWidth: .4,
                                     fieldLabel: this.app.i18n._('Suffix'),
                                     name:'n_suffix'
@@ -157,13 +162,21 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                         }, {
                             xtype: 'columnform',
                             items: [[
+                                !Tine.Tinebase.appMgr.get('Addressbook').featureEnabled('featureIndustry') ?
                                 {
                                     columnWidth: 0.64,
                                     xtype: 'combo',
                                     fieldLabel: this.app.i18n._('Display Name'),
                                     name: 'n_fn',
                                     disabled: true
-                                }, {
+                                } :
+                                (
+                                    new Tine.Addressbook.IndustrySearchCombo({
+                                    fieldLabel: this.app.i18n._('Industry'),
+                                    columnWidth: 0.64,
+                                    name: 'industry'
+                                    })
+                                ), {
                                      columnWidth: 0.36,
                                      fieldLabel: this.app.i18n._('Job Title'),
                                      name: 'title',
@@ -181,6 +194,10 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                         region: 'center',
                         title: this.app.i18n._('Contact Information'),
                         autoScroll: true,
+                        plugins: [{
+                            ptype: 'ux.itemregistry',
+                            key:   'Tine.Addressbook.editDialog.centerPanel'
+                        }],
                         items: [{
                             xtype: 'columnform',
                             items: [[{
@@ -267,6 +284,10 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                         defaults: {
                             frame: true
                         },
+                        plugins: [{
+                            ptype: 'ux.itemregistry',
+                            key:   'Tine.Addressbook.editDialog.southPanel'
+                        }],
                         items: [{
                             title: this.app.i18n._('Company Address'),
                             xtype: 'columnform',
@@ -340,6 +361,10 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                     header: false,
                     margins: '0 5 0 5',
                     border: true,
+                    plugins: [{
+                        ptype: 'ux.itemregistry',
+                        key:   'Tine.Addressbook.editDialog.eastPanel'
+                    }],
                     items: [
                         new Ext.Panel({
                             // @todo generalise!
index 764b224..0bc3c89 100644 (file)
@@ -271,7 +271,7 @@ Tine.Addressbook.ContactGridPanel.countryRenderer = function(data) {
  * Statically constructs the columns used to represent a contact. Reused by ListMemberGridPanel + ListMemberRoleGridPanel
  */
 Tine.Addressbook.ContactGridPanel.getBaseColumns = function(i18n) {
-    return [
+    var columns = [
         { id: 'type', header: i18n._('Type'), dataIndex: 'type', width: 30, renderer: Tine.Addressbook.ContactGridPanel.contactTypeRenderer.createDelegate(this), hidden: false },
         { id: 'tags', header: i18n._('Tags'), dataIndex: 'tags', width: 50, renderer: Tine.Tinebase.common.tagsRenderer, sortable: false, hidden: false  },
         { id: 'salutation', header: i18n._('Salutation'), dataIndex: 'salutation', renderer: Tine.Tinebase.widgets.keyfield.Renderer.get('Addressbook', 'contactSalutation') },
@@ -321,6 +321,11 @@ Tine.Addressbook.ContactGridPanel.getBaseColumns = function(i18n) {
         { id: 'bday', header: i18n._('Birthday'), dataIndex: 'bday', renderer: Tine.Tinebase.common.dateRenderer },
         { id: 'memberroles', header: i18n._('List Roles'), dataIndex: 'memberroles', renderer: Tine.Addressbook.ListMemberRoleRenderer }
     ]
+    if (Tine.Tinebase.appMgr.get('Addressbook').featureEnabled('featureIndustry')) {
+        columns.push({ id: 'industry', header: i18n._('Industry'), dataIndex: 'industry', renderer: Tine.Tinebase.common.foreignRecordRenderer});
+    }
+    
+    return columns;
 };
 
 /**
diff --git a/tine20/Addressbook/js/IndustryEditDialog.js b/tine20/Addressbook/js/IndustryEditDialog.js
new file mode 100644 (file)
index 0000000..3abc729
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/*global Ext, Tine*/
+
+Ext.ns('Tine.Addressbook');
+
+/**
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.ListRoleEditDialog
+ * @extends     Tine.widgets.dialog.EditDialog
+ * Addressbook Edit Dialog <br>
+ *
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+Tine.Addressbook.IndustryEditDialog = Ext.extend(Tine.widgets.dialog.SimpleRecordEditDialog, {
+    windowNamePrefix: 'IndustryEditWindow_',
+    appName: 'Addressbook',
+    recordClass: Tine.Addressbook.Model.Industry
+});
+
+/**
+ * Opens a new contact edit dialog window
+ *
+ * @return {Ext.ux.Window}
+ */
+Tine.Addressbook.IndustryEditDialog.openWindow = function (config) {
+    var id = (config.record && config.record.id) ? config.record.id : 0;
+    var window = Tine.WindowFactory.getWindow({
+        width: 400,
+        height: 300,
+        name: Tine.Addressbook.IndustryEditDialog.prototype.windowNamePrefix + id,
+        contentPanelConstructor: 'Tine.Addressbook.IndustryEditDialog',
+        contentPanelConstructorConfig: config
+    });
+    return window;
+};
diff --git a/tine20/Addressbook/js/IndustrySearchCombo.js b/tine20/Addressbook/js/IndustrySearchCombo.js
new file mode 100644 (file)
index 0000000..b0b7a71
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Tine 2.0
+ * Addressbook industry combo box
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+Ext.ns('Tine.Addressbook');
+
+/**
+ * Contract selection combo box
+ * 
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.IndustrySearchCombo
+ * @extends     Ext.form.ComboBox
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>log
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ * @param       {Object} config
+ * @constructor
+ * Create a new Tine.Addressbook.IndustrySearchCombo
+ */
+Tine.Addressbook.IndustrySearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPickerComboBox, {
+    
+    initComponent: function(){
+        this.recordClass = Tine.Addressbook.Model.Industry;
+
+        Tine.Addressbook.IndustrySearchCombo.superclass.initComponent.call(this);
+
+        this.displayField = 'name';
+        this.sortBy = 'name';
+    }
+});
+
+Tine.widgets.form.RecordPickerManager.register('Addressbook', 'Industry', Tine.Addressbook.IndustrySearchCombo);
\ No newline at end of file
index b5131fe..89d7150 100644 (file)
@@ -77,7 +77,8 @@ Tine.Addressbook.Model.ContactArray = Tine.Tinebase.Model.genericFields.concat([
     {name: 'attachments', omitDuplicateResolving: true},
     {name: 'paths', omitDuplicateResolving: true},
     {name: 'type', omitDuplicateResolving: true},
-    {name: 'memberroles', omitDuplicateResolving: true}
+    {name: 'memberroles', omitDuplicateResolving: true},
+    {name: 'industry', omitDuplicateResolving: true}
 ]);
 
 /**
@@ -151,7 +152,7 @@ Tine.Addressbook.Model.Contact.getFilterModel = function() {
     
     var typeStore = [['contact', app.i18n._('Contact')], ['user', app.i18n._('User Account')]];
     
-    return [
+    var filters = [
         {label: i18n._('Quick Search'),                                                      field: 'query',              operators: ['contains']},
         {filtertype: 'tine.widget.container.filtermodel', app: app, recordClass: Tine.Addressbook.Model.Contact},
         {filtertype: 'addressbook.listMember', app: app},
@@ -193,6 +194,15 @@ Tine.Addressbook.Model.Contact.getFilterModel = function() {
             defaultOperator: 'in'
         }
     ];
+    
+    if (Tine.Tinebase.appMgr.get('Addressbook').featureEnabled('featureIndustry')) {
+        filters.push({filtertype: 'foreignrecord', 
+            app: app,
+            foreignRecordClass: Tine.Addressbook.Model.Industry,
+            ownField: 'industry'
+        });
+    }
+    return filters;
 };
 
 /**
@@ -333,7 +343,7 @@ Tine.Addressbook.Model.ListRole = Tine.Tinebase.data.Record.create([
     appName: 'Addressbook',
     modelName: 'ListRole',
     titleProperty: 'name',
-    // ngettext('List Role', List Roles', n); gettext('List Roles');
+    // ngettext('List Role', 'List Roles', n); gettext('List Roles');
     recordName: 'List Role',
     recordsName: 'List Roles'
 });
@@ -350,3 +360,34 @@ Tine.Addressbook.Model.ListRole.getFilterModel = function() {
         {label: i18n._('Quick search'),       field: 'query',              operators: ['contains']}
     ];
 };
+
+/**
+
+/**
+ * Industry model
+ */
+Tine.Addressbook.Model.Industry = Tine.Tinebase.data.Record.create([
+    {name: 'id'},
+    {name: 'name'},
+    {name: 'description'}
+], {
+    appName: 'Addressbook',
+    modelName: 'Industry',
+    titleProperty: 'name',
+    // ngettext('Industry', 'Industries', n); gettext('Industries');
+    recordName: 'Industry',
+    recordsName: 'Industries'
+});
+
+/**
+ * get filtermodel of Industry model
+ *
+ * @namespace Tine.Addressbook.Model
+ * @static
+ * @return {Array} filterModel definition
+ */
+Tine.Addressbook.Model.Industry.getFilterModel = function() {
+    return [
+        {label: i18n._('Quick search'),       field: 'query',              operators: ['contains']}
+    ];
+};
index b223666..1253c39 100644 (file)
@@ -1,4 +1,4 @@
-# 
+#
 # Translators:
 # Björn Balazs <transifex@lazs.de>, 2013
 # Juergen Heine <transifex.com@sysdef.de>, 2013
@@ -12,17 +12,16 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Tine 2.0\n"
 "POT-Creation-Date: 2008-05-17 22:12+0100\n"
-"PO-Revision-Date: 2016-08-11 14:49+0000\n"
+"PO-Revision-Date: 2016-10-27 11:31+0100\n"
 "Last-Translator: sstamer <s.stamer@metaways.de>\n"
 "Language-Team: German (http://www.transifex.com/tine20/tine20/language/de/)\n"
+"Language: de\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: de\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Poedit-Country: GB\n"
-"X-Poedit-Language: en\n"
 "X-Poedit-SourceCharset: utf-8\n"
+"X-Generator: Poedit 1.5.4\n"
 
 #: Export/Pdf.php:37
 msgid "Business Contact Data"
@@ -68,7 +67,7 @@ msgstr "URL"
 msgid "Role"
 msgstr "Funktion"
 
-#: Export/Pdf.php:76 js/ContactEditDialog.js:147 js/Model.js:33
+#: Export/Pdf.php:76 js/ContactEditDialog.js:165 js/Model.js:33
 #: js/ContactGrid.js:295
 msgid "Room"
 msgstr "Raum"
@@ -85,7 +84,7 @@ msgstr "Telefon des Assistenten"
 msgid "Private Contact Data"
 msgstr "Private Kontaktdaten"
 
-#: Export/Pdf.php:88 js/ContactEditDialog.js:300 js/Model.js:43
+#: Export/Pdf.php:88 js/ContactEditDialog.js:326 js/Model.js:43
 #: js/Model.js:179 js/Model.js:180 js/Model.js:181 js/Model.js:182
 #: js/Model.js:183
 msgid "Private Address"
@@ -115,33 +114,33 @@ msgstr "URL (Zuhause)"
 msgid "Other Data"
 msgstr "Andere Daten"
 
-#: Export/Pdf.php:114 js/ContactEditDialog.js:174 js/Model.js:26
+#: Export/Pdf.php:114 js/ContactEditDialog.js:192 js/Model.js:26
 #: js/Model.js:173 js/ContactGrid.js:321
 msgid "Birthday"
 msgstr "Geburtstag"
 
-#: Export/Pdf.php:117 js/ContactEditDialog.js:168 js/Model.js:30
+#: Export/Pdf.php:117 js/ContactEditDialog.js:186 js/Model.js:30
 #: js/Model.js:168 js/ContactGrid.js:293
 msgid "Job Title"
 msgstr "Berufs-Bezeichnung"
 
-#: Export/Doc.php:59
+#: Export/Doc.php:63
 msgid "Dear Mister"
 msgstr "Sehr geehrter Herr"
 
-#: Export/Doc.php:61
+#: Export/Doc.php:65
 msgid "Dear Miss"
 msgstr "Sehr geehrte Dame"
 
-#: Export/Doc.php:63
+#: Export/Doc.php:67
 msgid "Dear"
 msgstr "Sehr geehrte Damen und Herren"
 
-#: Export/Doc.php:79
+#: Export/Doc.php:83
 msgid "Mister"
 msgstr "Herr"
 
-#: Export/Doc.php:81
+#: Export/Doc.php:85
 msgid "Misses"
 msgstr "Frau"
 
@@ -150,15 +149,20 @@ msgstr "Frau"
 msgid "%s's personal addressbook"
 msgstr "%ss persönliches Adressbuch"
 
-#: Controller.php:184 js/Model.js:232 js/ListGridDetailsPanel.js:57
+#: Controller.php:184 js/Model.js:240 js/ListGridDetailsPanel.js:57
 #: js/ListGridDetailsPanel.js:83
 msgid "Lists"
 msgstr "Gruppen"
 
-#: Controller.php:193 js/ContactGrid.js:322 js/ListRoleGridPanel.js:50
+#: Controller.php:193 js/Model.js:338 js/ContactGrid.js:322
+#: js/ListRoleGridPanel.js:50
 msgid "List Roles"
 msgstr "Gruppenrollen"
 
+#: Controller.php:201 js/Model.js:369
+msgid "Industries"
+msgstr "Branchen"
+
 #: Acl/Rights.php:122
 msgid "manage shared addressbooks"
 msgstr "Gemeinsame Adressbücher verwalten"
@@ -191,6 +195,12 @@ msgstr "Verwalte Listen Rollen in den Stammdaten"
 msgid "View, create, delete or update list roles in CoreData application"
 msgstr ""
 
+#: Model/Contact.php:557 js/ContactEditDialog.js:145 js/Model.js:369
+msgid "Industry"
+msgid_plural "Industries"
+msgstr[0] "Branche"
+msgstr[1] "Branchen"
+
 #: js/ListSearchCombo.js:27
 msgid "Search for system groups ..."
 msgstr "Suche nach Systemgruppen ..."
@@ -210,9 +220,10 @@ msgid_plural "Contacts"
 msgstr[0] "Kontakt"
 msgstr[1] "Kontakte"
 
-#: js/ListRoleMemberFilterModel.js:37 js/Model.js:330
+#: js/ListRoleMemberFilterModel.js:37 js/Model.js:338
+#, fuzzy
 msgid "List Role"
-msgid_plural ", n); gettext("
+msgid_plural "List Roles"
 msgstr[0] "Gruppenrolle"
 msgstr[1] "Gruppenrollen"
 
@@ -232,171 +243,174 @@ msgstr "Karte"
 msgid "Personal Information"
 msgstr "Persönliche Informationen"
 
-#: js/ContactEditDialog.js:92 js/Model.js:29 js/ContactGrid.js:277
+#: js/ContactEditDialog.js:96 js/Model.js:29 js/Model.js:190
+#: js/ContactGrid.js:277
 msgid "Salutation"
 msgstr "Anrede"
 
-#: js/ContactEditDialog.js:108 js/Model.js:22 js/Model.js:161
+#: js/ContactEditDialog.js:112 js/Model.js:22 js/Model.js:161
 #: js/ContactGrid.js:285
 msgid "Title"
 msgstr "Titel"
 
-#: js/ContactEditDialog.js:113 js/Model.js:20 js/Model.js:162
+#: js/ContactEditDialog.js:117 js/Model.js:20 js/Model.js:162
 #: js/ContactGrid.js:288
 msgid "First Name"
 msgstr "Vorname"
 
-#: js/ContactEditDialog.js:118 js/Model.js:21 js/Model.js:164
+#: js/ContactEditDialog.js:122 js/Model.js:21 js/Model.js:164
 #: js/ContactGrid.js:286
 msgid "Middle Name"
 msgstr "zweiter Vorname"
 
-#: js/ContactEditDialog.js:123 js/Model.js:19 js/Model.js:163
+#: js/ContactEditDialog.js:127 js/Model.js:19 js/Model.js:163
 #: js/ContactGrid.js:287
 msgid "Last Name"
 msgstr "Nachname"
 
-#: js/ContactEditDialog.js:129 js/Model.js:27 js/Model.js:165
-#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:291 Config.php:102
+#: js/ContactEditDialog.js:133 js/Model.js:27 js/Model.js:165
+#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:291 Config.php:110
 #: Setup/Update/Release5.php:196
 msgid "Company"
 msgstr "Firma"
 
-#: js/ContactEditDialog.js:134 js/Model.js:28 js/Model.js:166
+#: js/ContactEditDialog.js:138 js/Model.js:28 js/Model.js:166
 #: js/ContactGrid.js:292
 msgid "Unit"
 msgstr "Abteilung"
 
-#: js/ContactEditDialog.js:139 js/Model.js:23
+#: js/ContactEditDialog.js:157 js/Model.js:23
 msgid "Suffix"
 msgstr "Namenszusatz"
 
-#: js/ContactEditDialog.js:143 js/Model.js:31 js/Model.js:172
+#: js/ContactEditDialog.js:161 js/Model.js:31 js/Model.js:172
 #: js/ContactGrid.js:294
 msgid "Job Role"
 msgstr "Funktion"
 
-#: js/ContactEditDialog.js:163 js/Model.js:24 js/ContactGrid.js:290
+#: js/ContactEditDialog.js:181 js/Model.js:24 js/ContactGrid.js:290
 msgid "Display Name"
 msgstr "Angezeigter Name"
 
-#: js/ContactEditDialog.js:182
+#: js/ContactEditDialog.js:200
 msgid "Contact Information"
 msgstr "Kontakt-Informationen"
 
-#: js/ContactEditDialog.js:187 js/Model.js:51 js/Model.js:167
+#: js/ContactEditDialog.js:209 js/Model.js:51 js/Model.js:167
 #: js/ContactGridDetailsPanel.js:139 js/ContactGridDetailsPanel.js:163
 #: js/ContactGrid.js:307
 msgid "Phone"
 msgstr "Telefon"
 
-#: js/ContactEditDialog.js:192 js/Model.js:52
+#: js/ContactEditDialog.js:214 js/Model.js:52
 #: js/ContactGridDetailsPanel.js:140 js/ContactGridDetailsPanel.js:164
 #: js/ContactGrid.js:308
 msgid "Mobile"
 msgstr "Handy"
 
-#: js/ContactEditDialog.js:197 js/Model.js:53
+#: js/ContactEditDialog.js:219 js/Model.js:53
 #: js/ContactGridDetailsPanel.js:141 js/ContactGridDetailsPanel.js:165
 #: js/ContactGrid.js:309
 msgid "Fax"
 msgstr "Fax"
 
-#: js/ContactEditDialog.js:202 js/Model.js:57 js/ContactGrid.js:312
+#: js/ContactEditDialog.js:224 js/Model.js:57 js/ContactGrid.js:312
 msgid "Phone (private)"
 msgstr "Telefon (privat)"
 
-#: js/ContactEditDialog.js:207 js/Model.js:59 js/ContactGrid.js:314
+#: js/ContactEditDialog.js:229 js/Model.js:59 js/ContactGrid.js:314
 msgid "Mobile (private)"
 msgstr "Handy (privat)"
 
-#: js/ContactEditDialog.js:212 js/Model.js:58 js/ContactGrid.js:313
+#: js/ContactEditDialog.js:234 js/Model.js:58 js/ContactGrid.js:313
 msgid "Fax (private)"
 msgstr "Fax (privat)"
 
-#: js/ContactEditDialog.js:217 js/Model.js:62 js/Model.js:170
+#: js/ContactEditDialog.js:239 js/Model.js:62 js/Model.js:170
 #: js/ContactGridDetailsPanel.js:142 js/ContactGridDetailsPanel.js:166
 msgid "E-Mail"
 msgstr "E-Mail"
 
-#: js/ContactEditDialog.js:223 js/Model.js:63
+#: js/ContactEditDialog.js:245 js/Model.js:63
 msgid "E-Mail (private)"
 msgstr "E-Mail (privat)"
 
-#: js/ContactEditDialog.js:230 js/Model.js:64
+#: js/ContactEditDialog.js:252 js/Model.js:64
 #: js/ContactGridDetailsPanel.js:144 js/ContactGridDetailsPanel.js:168
 #: js/ContactGrid.js:316
 msgid "Web"
 msgstr "Internet"
 
-#: js/ContactEditDialog.js:271 js/Model.js:34 js/Model.js:174 js/Model.js:175
+#: js/ContactEditDialog.js:297 js/Model.js:34 js/Model.js:174 js/Model.js:175
 #: js/Model.js:176 js/Model.js:177 js/Model.js:178
 msgid "Company Address"
 msgstr "Geschäftliche Anschrift"
 
-#: js/ContactEditDialog.js:274 js/ContactEditDialog.js:303 js/Model.js:174
+#: js/ContactEditDialog.js:300 js/ContactEditDialog.js:329 js/Model.js:174
 #: js/Model.js:179 js/ContactGrid.js:296
 msgid "Street"
 msgstr "Straße"
 
-#: js/ContactEditDialog.js:278 js/ContactEditDialog.js:307
+#: js/ContactEditDialog.js:304 js/ContactEditDialog.js:333
 msgid "Street 2"
 msgstr "Straße 2"
 
-#: js/ContactEditDialog.js:282 js/ContactEditDialog.js:311 js/Model.js:175
+#: js/ContactEditDialog.js:308 js/ContactEditDialog.js:337 js/Model.js:175
 #: js/Model.js:180 js/ContactGrid.js:298
 msgid "Region"
 msgstr "Region"
 
-#: js/ContactEditDialog.js:286 js/ContactEditDialog.js:315 js/Model.js:176
+#: js/ContactEditDialog.js:312 js/ContactEditDialog.js:341 js/Model.js:176
 #: js/Model.js:181
 msgid "Postal Code"
 msgstr "Postleitzahl"
 
-#: js/ContactEditDialog.js:290 js/ContactEditDialog.js:319 js/Model.js:177
+#: js/ContactEditDialog.js:316 js/ContactEditDialog.js:345 js/Model.js:177
 #: js/Model.js:182 js/ContactGrid.js:297
 msgid "City"
 msgstr "Ort"
 
-#: js/ContactEditDialog.js:295 js/ContactEditDialog.js:324 js/Model.js:178
+#: js/ContactEditDialog.js:321 js/ContactEditDialog.js:350 js/Model.js:178
 #: js/Model.js:183 js/ContactGrid.js:300
 msgid "Country"
 msgstr "Land"
 
-#: js/ContactEditDialog.js:346 js/Addressbook.js:80 js/Model.js:68
-#: js/Model.js:169 js/Model.js:253 js/ListEditDialog.js:118
+#: js/ContactEditDialog.js:376 js/Addressbook.js:80 js/Addressbook.js:115
+#: js/Model.js:68 js/Model.js:169 js/Model.js:261 js/ListEditDialog.js:118
 msgid "Description"
 msgstr "Beschreibung"
 
-#: js/ContactEditDialog.js:360 js/ListEditDialog.js:132
+#: js/ContactEditDialog.js:390 js/ListEditDialog.js:132
 msgid "Enter description"
 msgstr "Bitte geben Sie eine Beschreibung an"
 
-#: js/ContactEditDialog.js:398
+#: js/ContactEditDialog.js:428
 msgid "Export as pdf"
 msgstr "Als PDF exportieren"
 
-#: js/ContactEditDialog.js:405 js/ContactEditDialog.js:449
+#: js/ContactEditDialog.js:435 js/ContactEditDialog.js:479
 msgid "Parse address"
 msgstr "Adresse einlesen"
 
-#: js/ContactEditDialog.js:441
+#: js/ContactEditDialog.js:471
 msgid "Paste address"
 msgstr "Adresse einfügen"
 
-#: js/ContactEditDialog.js:441
+#: js/ContactEditDialog.js:471
 msgid "Please paste an address or a URI to a vcard that should be parsed:"
-msgstr "Bitte fügen Sie eine Adresse oder URI zu einer vcard ein, die eingelesen werden soll:"
+msgstr ""
+"Bitte fügen Sie eine Adresse oder URI zu einer vcard ein, die eingelesen "
+"werden soll:"
 
-#: js/ContactEditDialog.js:467
+#: js/ContactEditDialog.js:497
 msgid "Failed to parse address!"
 msgstr "Einlesen der Adresse fehlgeschlagen!"
 
-#: js/ContactEditDialog.js:467
+#: js/ContactEditDialog.js:497
 msgid "The address could not be read."
 msgstr "Die Adresse konnte nicht gelesen werden."
 
-#: js/ContactEditDialog.js:484
+#: js/ContactEditDialog.js:514
 msgid "End token mode"
 msgstr "Beende Merkmalsmodus"
 
@@ -404,18 +418,19 @@ msgstr "Beende Merkmalsmodus"
 msgid "New Contact"
 msgstr "Neuer Kontakt"
 
-#: js/Addressbook.js:32 js/Model.js:101 js/Model.js:236 js/Model.js:287
+#: js/Addressbook.js:32 js/Model.js:101 js/Model.js:244 js/Model.js:295
 msgid "Addressbook"
 msgid_plural "Addressbooks"
 msgstr[0] "Adressbuch"
 msgstr[1] "Adressbücher"
 
-#: js/Addressbook.js:67 js/ListRoleGridPanel.js:58
+#: js/Addressbook.js:67 js/Addressbook.js:102 js/ListRoleGridPanel.js:58
 msgid "ID"
 msgstr "ID"
 
-#: js/Addressbook.js:74 js/Model.js:19 js/Model.js:252 js/ListGrid.js:102
-#: js/ListEditDialog.js:83 js/ListEditDialog.js:166 js/ListRoleGridPanel.js:65
+#: js/Addressbook.js:74 js/Addressbook.js:109 js/Model.js:19 js/Model.js:260
+#: js/ListGrid.js:102 js/ListEditDialog.js:83 js/ListEditDialog.js:166
+#: js/ListRoleGridPanel.js:65
 msgid "Name"
 msgstr "Name"
 
@@ -495,11 +510,11 @@ msgstr "Webseite (Privat)"
 msgid "Contacts"
 msgstr "Kontakte"
 
-#: js/Model.js:101 js/Model.js:236 js/Model.js:287
+#: js/Model.js:101 js/Model.js:244 js/Model.js:295
 msgid "Addressbooks"
 msgstr "Adressbücher"
 
-#: js/Model.js:154 js/Model.js:246
+#: js/Model.js:154 js/Model.js:254
 msgid "User Account"
 msgstr "Benutzerkonto"
 
@@ -511,39 +526,39 @@ msgstr "Schnellsuche"
 msgid "Type"
 msgstr "Typ"
 
-#: js/Model.js:185 js/Model.js:254
+#: js/Model.js:185 js/Model.js:262
 msgid "Last Modified Time"
 msgstr "Zuletzt geändert"
 
-#: js/Model.js:186 js/Model.js:255
+#: js/Model.js:186 js/Model.js:263
 msgid "Last Modified By"
 msgstr "Zuletzt geändert von"
 
-#: js/Model.js:187 js/Model.js:256
+#: js/Model.js:187 js/Model.js:264
 msgid "Creation Time"
 msgstr "Erstelldatum"
 
-#: js/Model.js:188 js/Model.js:257
+#: js/Model.js:188 js/Model.js:265
 msgid "Created By"
 msgstr "Angelegt von"
 
-#: js/Model.js:232 js/Model.js:246 js/ListGridDetailsPanel.js:86
+#: js/Model.js:240 js/Model.js:254 js/ListGridDetailsPanel.js:86
 msgid "List"
 msgid_plural "Lists"
 msgstr[0] "Gruppe"
 msgstr[1] "Gruppen"
 
-#: js/Model.js:249 js/Model.js:314 js/Model.js:344
+#: js/Model.js:257 js/Model.js:322 js/Model.js:352 js/Model.js:383
 msgid "Quick search"
 msgstr "Schnellsuche"
 
-#: js/Model.js:283
+#: js/Model.js:291
 msgid "Email Address"
 msgid_plural "Email Addresses"
 msgstr[0] "E-mail Adresse"
 msgstr[1] "E-mail Adressen"
 
-#: js/Model.js:283
+#: js/Model.js:291
 msgid "Email Addresses"
 msgstr "E-mail Adressen"
 
@@ -715,7 +730,7 @@ msgstr "Suche nach Benutzern ..."
 msgid "Search for Contacts ..."
 msgstr "Suche nach Kontakten ..."
 
-#: Controller/Contact.php:340
+#: Controller/Contact.php:348
 msgid "Uploaded new contact image."
 msgstr "Ein neues Kontaktbild wurde hochgeladen."
 
@@ -745,7 +760,9 @@ msgstr "Kontakt ODS-Export Konfiguration"
 
 #: Preference.php:90
 msgid "Use this configuration for the contact ODS export."
-msgstr "Hiermit können Sie die zu verwendende Kontakt ODS-Export Konfiguration einstellen."
+msgstr ""
+"Hiermit können Sie die zu verwendende Kontakt ODS-Export Konfiguration "
+"einstellen."
 
 #: Preference.php:93
 msgid "Contacts XLS export configuration"
@@ -753,92 +770,105 @@ msgstr "Kontakt XLS-Export Konfiguration"
 
 #: Preference.php:94
 msgid "Use this configuration for the contact XLS export."
-msgstr "Hiermit können Sie die zu verwendende XLS-Export Konfiguration einstellen."
+msgstr ""
+"Hiermit können Sie die zu verwendende XLS-Export Konfiguration einstellen."
 
 #: Preference.php:165
 msgid "default"
 msgstr "Standard"
 
-#: Frontend/Cli.php:166
+#: Frontend/Cli.php:114
 msgid ""
 "This contact has been automatically added by the system as an event attender"
-msgstr "Dieser Kontakt wurde automatisch vom System als Termin Teilnehmer hinzugefügt."
+msgstr ""
+"Dieser Kontakt wurde automatisch vom System als Termin Teilnehmer "
+"hinzugefügt."
 
 #: Frontend/CardDAV/AllContacts.php:41
 msgid "All Contacts"
 msgstr "Alle Kontakte"
 
-#: Config.php:59
+#: Config.php:67
 msgid "Enabled Features"
 msgstr "Feature aktivieren"
 
-#: Config.php:61
+#: Config.php:69
 msgid "Enabled Features in Calendar Application."
 msgstr "Feature in der Kalender Applikation aktivieren."
 
-#: Config.php:68
+#: Config.php:76
 msgid "Addressbook List View"
 msgstr "Adressbuch Listenansicht"
 
-#: Config.php:69
+#: Config.php:77
 msgid "Shows an additional view for lists inside the addressbook)"
 msgstr ""
 
-#: Config.php:77
+#: Config.php:85
 msgid "Contact duplicate check fields"
 msgstr "Kontaktfelder für den Dublettencheck"
 
-#: Config.php:79
+#: Config.php:87
 msgid ""
-"These fields are checked when a new contact is created. If a record with the"
-" same data in the fields is found, a duplicate exception is thrown."
-msgstr "Diese Felder werden überprüft, wenn ein neuer Kontakt erstellt wird. Falls ein Eintrag mit den gleichen Daten in diesen Feldern angelegt wird, wird eine Fehlermeldung angezeit, auf die der Benutzer reagieren kann."
+"These fields are checked when a new contact is created. If a record with the "
+"same data in the fields is found, a duplicate exception is thrown."
+msgstr ""
+"Diese Felder werden überprüft, wenn ein neuer Kontakt erstellt wird. Falls "
+"ein Eintrag mit den gleichen Daten in diesen Feldern angelegt wird, wird "
+"eine Fehlermeldung angezeit, auf die der Benutzer reagieren kann."
 
-#: Config.php:91
+#: Config.php:99
 msgid "Contact salutations available"
 msgstr "Verfügbare Kontaktanreden"
 
-#: Config.php:93
+#: Config.php:101
 msgid ""
 "Possible contact salutations. Please note that additional values might "
 "impact other Addressbook systems on export or syncronisation."
-msgstr "Mögliche Kontaktanreden. Bitte beachten Sie, dass sich zusätzliche Werte beim Export oder der Synchronisation auf andere Systeme auswirken."
+msgstr ""
+"Mögliche Kontaktanreden. Bitte beachten Sie, dass sich zusätzliche Werte "
+"beim Export oder der Synchronisation auf andere Systeme auswirken."
 
-#: Config.php:100 Setup/Update/Release5.php:194
+#: Config.php:108 Setup/Update/Release5.php:194
 msgid "Mr"
 msgstr "Herr"
 
-#: Config.php:101 Setup/Update/Release5.php:195
+#: Config.php:109 Setup/Update/Release5.php:195
 msgid "Ms"
 msgstr "Frau"
 
-#: Config.php:108
+#: Config.php:116
 msgid "Parsing rules for addresses"
 msgstr "Analyseregeln für Adressen"
 
-#: Config.php:110
+#: Config.php:118
 msgid "Path to a XML file with address parsing rules."
 msgstr "Pfad zu einer XML-Datei mit den Analyseregeln für Adressen."
 
-#: Config.php:118
+#: Config.php:126
 msgid "List types available"
 msgstr "Verfügbare Listen Typen"
 
-#: Config.php:120
+#: Config.php:128
 msgid "List types available."
 msgstr "Verfügbare Listen Typen."
 
-#: Config.php:127
+#: Config.php:135
 msgid "Department"
 msgstr "Abteilung"
 
-#: Config.php:128
+#: Config.php:136
 msgid "Mailing list"
 msgstr "Mailingliste"
 
+#: Config.php:141
+msgid "Use Nominatim during contact import"
+msgstr ""
+
 #: Import/definitions/adb_outlook2007_de_import_csv.xml:10
 msgid "Import CSV formated contacts from Outlook 2007 German address book"
-msgstr "Importiere CSV formatierte Kontakte aus einem Outlook 2007 Deutsch Adressbuch"
+msgstr ""
+"Importiere CSV formatierte Kontakte aus einem Outlook 2007 Deutsch Adressbuch"
 
 #: Import/definitions/adb_outlook2007_de_import_csv.xml:15
 msgid "CSV Outlook 2007 German"
@@ -868,11 +898,14 @@ msgstr "Importliste (###CURRENTDATE###)"
 msgid ""
 "Contacts imported on ###CURRENTDATE### at ###CURRENTTIME### by "
 "###USERFULLNAME###"
-msgstr "Kontakte importiert am ###CURRENTDATE### um ###CURRENTTIME### von ###USERFULLNAME###"
+msgstr ""
+"Kontakte importiert am ###CURRENTDATE### um ###CURRENTTIME### von "
+"###USERFULLNAME###"
 
 #: Import/definitions/adb_outlook_import_csv.xml:10
 msgid "Import CSV formated contacts from Exchange / Outlook address book"
-msgstr "Importiere CSV formatierte Kontakte aus einem Exchange / Outlook Adressbuch"
+msgstr ""
+"Importiere CSV formatierte Kontakte aus einem Exchange / Outlook Adressbuch"
 
 #: Import/definitions/adb_outlook_import_csv.xml:14
 msgid "Contact CSV import from Outlook address book"
@@ -898,7 +931,7 @@ msgstr "Importiere vCard formatierte Kontakte"
 msgid "Contact VCARD import"
 msgstr "VCARD-Import für Kontakte"
 
-#: Setup/setup.xml:866
+#: Setup/setup.xml:936
 msgid "Internal Contacts"
 msgstr "Interne Kontakte"
 
index effc0f3..fdac643 100644 (file)
@@ -13,450 +13,465 @@ msgstr ""
 "X-Poedit-SourceCharset: utf-8\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 
-#: Frontend/CardDAV/AllContacts.php:41
-msgid "All Contacts"
-msgstr "All Contacts"
+#: Export/Pdf.php:37
+msgid "Business Contact Data"
+msgstr "Business Contact Data"
 
-#: Frontend/Cli.php:166
-msgid ""
-"This contact has been automatically added by the system as an event attender"
-msgstr ""
-"This contact has been automatically added by the system as an event attender"
+#: Export/Pdf.php:39
+msgid "Organisation / Unit"
+msgstr "Organisation / Unit"
 
-#: Controller/Contact.php:330
-msgid "Uploaded new contact image."
-msgstr "Uploaded new contact image."
+#: Export/Pdf.php:44
+msgid "Business Address"
+msgstr "Business Address"
+
+#: Export/Pdf.php:52 js/ContactGrid.js:306
+msgid "Email"
+msgstr "Email"
+
+#: Export/Pdf.php:55
+msgid "Telephone Work"
+msgstr "Telephone Work"
+
+#: Export/Pdf.php:58
+msgid "Telephone Cellphone"
+msgstr "Telephone Cellphone"
+
+#: Export/Pdf.php:61
+msgid "Telephone Car"
+msgstr "Telephone Car"
+
+#: Export/Pdf.php:64
+msgid "Telephone Fax"
+msgstr "Telephone Fax"
+
+#: Export/Pdf.php:67
+msgid "Telephone Page"
+msgstr "Telephone Page"
+
+#: Export/Pdf.php:70
+msgid "URL"
+msgstr "URL"
+
+#: Export/Pdf.php:73
+msgid "Role"
+msgstr "Role"
+
+#: Export/Pdf.php:76 js/ContactEditDialog.js:165 js/Model.js:33
+#: js/ContactGrid.js:295
+msgid "Room"
+msgstr "Room"
+
+#: Export/Pdf.php:79
+msgid "Assistant"
+msgstr "Assistant"
+
+#: Export/Pdf.php:82
+msgid "Assistant Telephone"
+msgstr "Assistant Telephone"
+
+#: Export/Pdf.php:86
+msgid "Private Contact Data"
+msgstr "Private Contact Data"
 
-#: Acl/Rights.php:105
+#: Export/Pdf.php:88 js/ContactEditDialog.js:326 js/Model.js:43
+#: js/Model.js:179 js/Model.js:180 js/Model.js:181 js/Model.js:182
+#: js/Model.js:183
+msgid "Private Address"
+msgstr "Private Address"
+
+#: Export/Pdf.php:96
+msgid "Email Home"
+msgstr "Email Home"
+
+#: Export/Pdf.php:99
+msgid "Telephone Home"
+msgstr "Telephone Home"
+
+#: Export/Pdf.php:102
+msgid "Telephone Cellphone Private"
+msgstr "Telephone Cellphone Private"
+
+#: Export/Pdf.php:105
+msgid "Telephone Fax Home"
+msgstr "Telephone Fax Home"
+
+#: Export/Pdf.php:108
+msgid "URL Home"
+msgstr "URL Home"
+
+#: Export/Pdf.php:112
+msgid "Other Data"
+msgstr "Other Data"
+
+#: Export/Pdf.php:114 js/ContactEditDialog.js:192 js/Model.js:26
+#: js/Model.js:173 js/ContactGrid.js:321
+msgid "Birthday"
+msgstr "Birthday"
+
+#: Export/Pdf.php:117 js/ContactEditDialog.js:186 js/Model.js:30
+#: js/Model.js:168 js/ContactGrid.js:293
+msgid "Job Title"
+msgstr "Job Title"
+
+#: Export/Doc.php:63
+msgid "Dear Mister"
+msgstr "Dear Mister"
+
+#: Export/Doc.php:65
+msgid "Dear Miss"
+msgstr "Dear Miss"
+
+#: Export/Doc.php:67
+msgid "Dear"
+msgstr "Dear"
+
+#: Export/Doc.php:83
+msgid "Mister"
+msgstr "Mister"
+
+#: Export/Doc.php:85
+msgid "Misses"
+msgstr "Misses"
+
+#: Controller.php:129
+#, python-format
+msgid "%s's personal addressbook"
+msgstr "%s's personal addressbook"
+
+#: Controller.php:184 js/Model.js:240 js/ListGridDetailsPanel.js:57
+#: js/ListGridDetailsPanel.js:83
+msgid "Lists"
+msgstr "Lists"
+
+#: Controller.php:193 js/Model.js:338 js/ContactGrid.js:322
+#: js/ListRoleGridPanel.js:50
+msgid "List Roles"
+msgstr "List Roles"
+
+#: Controller.php:201 js/Model.js:369
+msgid "Industries"
+msgstr "Industries"
+
+#: Acl/Rights.php:122
 msgid "manage shared addressbooks"
 msgstr "manage shared addressbooks"
 
-#: Acl/Rights.php:106
+#: Acl/Rights.php:123
 msgid "Create new shared addressbook folders"
 msgstr "Create new shared addressbook folders"
 
-#: Acl/Rights.php:109
+#: Acl/Rights.php:126
 msgid "manage shared addressbook favorites"
 msgstr "manage shared addressbook favorites"
 
-#: Acl/Rights.php:110
+#: Acl/Rights.php:127
 msgid "Create or update shared addressbook favorites"
 msgstr "Create or update shared addressbook favorites"
 
-#: Setup/setup.xml:654
-msgid "Internal Contacts"
-msgstr "Internal Contacts"
-
-#: Setup/Initialize.php:68 Setup/Update/Release5.php:194
-msgid "Mr"
-msgstr "Mr"
+#: Acl/Rights.php:130
+msgid "Manage lists in CoreData"
+msgstr "Manage lists in CoreData"
 
-#: Setup/Initialize.php:69 Setup/Update/Release5.php:195
-msgid "Ms"
-msgstr "Ms"
+#: Acl/Rights.php:131
+msgid "View, create, delete or update lists in CoreData application"
+msgstr "View, create, delete or update lists in CoreData application"
 
-#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
-#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/ContactGrid.js:117 js/Model.js:27
-#: js/Model.js:144 js/ContactGridDetailsPanel.js:131
-msgid "Company"
-msgstr "Company"
+#: Acl/Rights.php:134
+msgid "Manage list roles in CoreData"
+msgstr "Manage list roles in CoreData"
 
-#: Setup/Initialize.php:140 Setup/Update/Release3.php:37
-msgid "All contacts I have read grants for"
-msgstr "All contacts I have read grants for"
+#: Acl/Rights.php:135
+msgid "View, create, delete or update list roles in CoreData application"
+msgstr "View, create, delete or update list roles in CoreData application"
 
-#: Setup/Initialize.php:145
-msgid "My company"
-msgstr "My company"
+#: Model/Contact.php:557 js/ContactEditDialog.js:145 js/Model.js:369
+msgid "Industry"
+msgid_plural "Industries"
+msgstr[0] "Industry"
+msgstr[1] "Industries"
 
-#: Setup/Initialize.php:146
-msgid "All coworkers in my company"
-msgstr "All coworkers in my company"
+#: js/ListSearchCombo.js:27
+msgid "Search for system groups ..."
+msgstr "Search for system groups ..."
 
-#: Setup/Initialize.php:159
-msgid "My contacts"
-msgstr "My contacts"
+#: js/ListSearchCombo.js:28
+msgid "Search for groups ..."
+msgstr "Search for groups ..."
 
-#: Setup/Initialize.php:160
-msgid "All contacts in my Addressbooks"
-msgstr "All contacts in my Addressbooks"
+#: js/CardDAVContainerPropertiesHookField.js:35
+msgid "CardDAV URL"
+msgstr "CardDAV URL"
 
-#: Setup/Initialize.php:172
-msgid "Last modified by me"
-msgstr "Last modified by me"
+#: js/ContactFilterModel.js:35 js/Model.js:97 js/Model.js:154
+#: js/ContactGrid.js:255
+msgid "Contact"
+msgid_plural "Contacts"
+msgstr[0] "Contact"
+msgstr[1] "Contacts"
 
-#: Setup/Initialize.php:173
-msgid "All contacts that I have last modified"
-msgstr "All contacts that I have last modified"
+#: js/ListRoleMemberFilterModel.js:37 js/Model.js:338
+msgid "List Role"
+msgid_plural "List Roles"
+msgstr[0] "List Role"
+msgstr[1] "List Roles"
 
-#: js/Addressbook.js:22
-msgid "New Contact"
-msgstr "New Contact"
+#: js/ListMemberRoleGridPanel.js:37 js/ListRoleGridPanel.js:71
+msgid "Members"
+msgstr "Members"
 
-#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
-msgid "Addressbook"
-msgid_plural "Addressbooks"
-msgstr[0] "Addressbook"
-msgstr[1] "Addressbooks"
+#: js/ListMemberRoleGridPanel.js:207
+msgid "Remove Member"
+msgstr "Remove Member"
 
-#: js/ContactEditDialog.js:41 js/ContactEditDialog.js:50 js/MapPanel.js:40
+#: js/ContactEditDialog.js:42 js/ContactEditDialog.js:51 js/MapPanel.js:40
 msgid "Map"
 msgstr "Map"
 
-#: js/ContactEditDialog.js:79
+#: js/ContactEditDialog.js:80
 msgid "Personal Information"
 msgstr "Personal Information"
 
-#: js/ContactEditDialog.js:91 js/ContactGrid.js:103 js/Model.js:29
+#: js/ContactEditDialog.js:96 js/Model.js:29 js/Model.js:190
+#: js/ContactGrid.js:277
 msgid "Salutation"
 msgstr "Salutation"
 
-#: js/ContactEditDialog.js:107 js/ContactGrid.js:111 js/Model.js:22
-#: js/Model.js:140
+#: js/ContactEditDialog.js:112 js/Model.js:22 js/Model.js:161
+#: js/ContactGrid.js:285
 msgid "Title"
 msgstr "Title"
 
-#: js/ContactEditDialog.js:112 js/ContactGrid.js:114 js/Model.js:20
-#: js/Model.js:141
+#: js/ContactEditDialog.js:117 js/Model.js:20 js/Model.js:162
+#: js/ContactGrid.js:288
 msgid "First Name"
 msgstr "First Name"
 
-#: js/ContactEditDialog.js:117 js/ContactGrid.js:112 js/Model.js:21
-#: js/Model.js:143
+#: js/ContactEditDialog.js:122 js/Model.js:21 js/Model.js:164
+#: js/ContactGrid.js:286
 msgid "Middle Name"
 msgstr "Middle Name"
 
-#: js/ContactEditDialog.js:122 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/ContactGrid.js:113 js/Model.js:19
-#: js/Model.js:142
+#: js/ContactEditDialog.js:127 js/Model.js:19 js/Model.js:163
+#: js/ContactGrid.js:287
 msgid "Last Name"
 msgstr "Last Name"
 
-#: js/ContactEditDialog.js:133 js/ContactGrid.js:118 js/Model.js:28
-#: js/Model.js:145
+#: js/ContactEditDialog.js:133 js/Model.js:27 js/Model.js:165
+#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:291 Config.php:110
+#: Setup/Update/Release5.php:196
+msgid "Company"
+msgstr "Company"
+
+#: js/ContactEditDialog.js:138 js/Model.js:28 js/Model.js:166
+#: js/ContactGrid.js:292
 msgid "Unit"
 msgstr "Unit"
 
-#: js/ContactEditDialog.js:138 js/Model.js:23
+#: js/ContactEditDialog.js:157 js/Model.js:23
 msgid "Suffix"
 msgstr "Suffix"
 
-#: js/ContactEditDialog.js:142 js/ContactGrid.js:120 js/Model.js:31
-#: js/Model.js:151
+#: js/ContactEditDialog.js:161 js/Model.js:31 js/Model.js:172
+#: js/ContactGrid.js:294
 msgid "Job Role"
 msgstr "Job Role"
 
-#: js/ContactEditDialog.js:146 js/ContactGrid.js:121 js/Model.js:33
-#: Export/Pdf.php:76
-msgid "Room"
-msgstr "Room"
-
-#: js/ContactEditDialog.js:162 js/ContactGrid.js:116 js/Model.js:24
+#: js/ContactEditDialog.js:181 js/Model.js:24 js/ContactGrid.js:290
 msgid "Display Name"
 msgstr "Display Name"
 
-#: js/ContactEditDialog.js:167 js/ContactGrid.js:119 js/Model.js:30
-#: js/Model.js:147 Export/Pdf.php:117
-msgid "Job Title"
-msgstr "Job Title"
-
-#: js/ContactEditDialog.js:173 js/ContactGrid.js:147 js/Model.js:26
-#: js/Model.js:152 Export/Pdf.php:114
-msgid "Birthday"
-msgstr "Birthday"
-
-#: js/ContactEditDialog.js:181
+#: js/ContactEditDialog.js:200
 msgid "Contact Information"
 msgstr "Contact Information"
 
-#: js/ContactEditDialog.js:186 js/ContactGrid.js:133 js/Model.js:51
-#: js/Model.js:146 js/ContactGridDetailsPanel.js:139
-#: js/ContactGridDetailsPanel.js:163
+#: js/ContactEditDialog.js:209 js/Model.js:51 js/Model.js:167
+#: js/ContactGridDetailsPanel.js:139 js/ContactGridDetailsPanel.js:163
+#: js/ContactGrid.js:307
 msgid "Phone"
 msgstr "Phone"
 
-#: js/ContactEditDialog.js:191 js/ContactGrid.js:134 js/Model.js:52
+#: js/ContactEditDialog.js:214 js/Model.js:52
 #: js/ContactGridDetailsPanel.js:140 js/ContactGridDetailsPanel.js:164
+#: js/ContactGrid.js:308
 msgid "Mobile"
 msgstr "Mobile"
 
-#: js/ContactEditDialog.js:196 js/ContactGrid.js:135 js/Model.js:53
+#: js/ContactEditDialog.js:219 js/Model.js:53
 #: js/ContactGridDetailsPanel.js:141 js/ContactGridDetailsPanel.js:165
+#: js/ContactGrid.js:309
 msgid "Fax"
 msgstr "Fax"
 
-#: js/ContactEditDialog.js:201 js/ContactGrid.js:138 js/Model.js:57
+#: js/ContactEditDialog.js:224 js/Model.js:57 js/ContactGrid.js:312
 msgid "Phone (private)"
 msgstr "Phone (private)"
 
-#: js/ContactEditDialog.js:206 js/ContactGrid.js:140 js/Model.js:59
+#: js/ContactEditDialog.js:229 js/Model.js:59 js/ContactGrid.js:314
 msgid "Mobile (private)"
 msgstr "Mobile (private)"
 
-#: js/ContactEditDialog.js:211 js/ContactGrid.js:139 js/Model.js:58
+#: js/ContactEditDialog.js:234 js/Model.js:58 js/ContactGrid.js:313
 msgid "Fax (private)"
 msgstr "Fax (private)"
 
-#: js/ContactEditDialog.js:216 js/Model.js:62 js/Model.js:149
+#: js/ContactEditDialog.js:239 js/Model.js:62 js/Model.js:170
 #: js/ContactGridDetailsPanel.js:142 js/ContactGridDetailsPanel.js:166
 msgid "E-Mail"
 msgstr "E-Mail"
 
-#: js/ContactEditDialog.js:222 js/Model.js:63
+#: js/ContactEditDialog.js:245 js/Model.js:63
 msgid "E-Mail (private)"
 msgstr "E-Mail (private)"
 
-#: js/ContactEditDialog.js:229 js/ContactGrid.js:142 js/Model.js:64
+#: js/ContactEditDialog.js:252 js/Model.js:64
 #: js/ContactGridDetailsPanel.js:144 js/ContactGridDetailsPanel.js:168
+#: js/ContactGrid.js:316
 msgid "Web"
 msgstr "Web"
 
-#: js/ContactEditDialog.js:270 js/Model.js:34 js/Model.js:153 js/Model.js:154
-#: js/Model.js:155 js/Model.js:156 js/Model.js:157
+#: js/ContactEditDialog.js:297 js/Model.js:34 js/Model.js:174 js/Model.js:175
+#: js/Model.js:176 js/Model.js:177 js/Model.js:178
 msgid "Company Address"
 msgstr "Company Address"
 
-#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302
-#: js/ContactGrid.js:122 js/Model.js:153 js/Model.js:158
+#: js/ContactEditDialog.js:300 js/ContactEditDialog.js:329 js/Model.js:174
+#: js/Model.js:179 js/ContactGrid.js:296
 msgid "Street"
 msgstr "Street"
 
-#: js/ContactEditDialog.js:277 js/ContactEditDialog.js:306
+#: js/ContactEditDialog.js:304 js/ContactEditDialog.js:333
 msgid "Street 2"
 msgstr "Street 2"
 
-#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310
-#: js/ContactGrid.js:124 js/Model.js:154 js/Model.js:159
+#: js/ContactEditDialog.js:308 js/ContactEditDialog.js:337 js/Model.js:175
+#: js/Model.js:180 js/ContactGrid.js:298
 msgid "Region"
 msgstr "Region"
 
-#: js/ContactEditDialog.js:285 js/ContactEditDialog.js:314 js/Model.js:155
-#: js/Model.js:160
+#: js/ContactEditDialog.js:312 js/ContactEditDialog.js:341 js/Model.js:176
+#: js/Model.js:181
 msgid "Postal Code"
 msgstr "Postal Code"
 
-#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318
-#: js/ContactGrid.js:123 js/Model.js:156 js/Model.js:161
+#: js/ContactEditDialog.js:316 js/ContactEditDialog.js:345 js/Model.js:177
+#: js/Model.js:182 js/ContactGrid.js:297
 msgid "City"
 msgstr "City"
 
-#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323
-#: js/ContactGrid.js:126 js/Model.js:157 js/Model.js:162
+#: js/ContactEditDialog.js:321 js/ContactEditDialog.js:350 js/Model.js:178
+#: js/Model.js:183 js/ContactGrid.js:300
 msgid "Country"
 msgstr "Country"
 
-#: js/ContactEditDialog.js:299 js/Model.js:43 js/Model.js:158 js/Model.js:159
-#: js/Model.js:160 js/Model.js:161 js/Model.js:162 Export/Pdf.php:88
-msgid "Private Address"
-msgstr "Private Address"
-
-#: js/ContactEditDialog.js:344 js/Model.js:68 js/Model.js:148
+#: js/ContactEditDialog.js:376 js/Addressbook.js:80 js/Addressbook.js:115
+#: js/Model.js:68 js/Model.js:169 js/Model.js:261 js/ListEditDialog.js:118
 msgid "Description"
 msgstr "Description"
 
-#: js/ContactEditDialog.js:358
+#: js/ContactEditDialog.js:390 js/ListEditDialog.js:132
 msgid "Enter description"
 msgstr "Enter description"
 
-#: js/ContactEditDialog.js:402
+#: js/ContactEditDialog.js:428
 msgid "Export as pdf"
 msgstr "Export as pdf"
 
-#: js/ContactEditDialog.js:410 js/ContactEditDialog.js:499
+#: js/ContactEditDialog.js:435 js/ContactEditDialog.js:479
 msgid "Parse address"
 msgstr "Parse address"
 
-#: js/ContactEditDialog.js:434 js/ContactEditDialog.js:456
-#, python-brace-format
-msgid "Either {0} or {1} must be given"
-msgstr "Either {0} or {1} must be given"
-
-#: js/ContactEditDialog.js:491
+#: js/ContactEditDialog.js:471
 msgid "Paste address"
 msgstr "Paste address"
 
-#: js/ContactEditDialog.js:491
+#: js/ContactEditDialog.js:471
 msgid "Please paste an address or a URI to a vcard that should be parsed:"
 msgstr "Please paste an address or a URI to a vcard that should be parsed:"
 
-#: js/ContactEditDialog.js:517
+#: js/ContactEditDialog.js:497
 msgid "Failed to parse address!"
 msgstr "Failed to parse address!"
 
-#: js/ContactEditDialog.js:517
+#: js/ContactEditDialog.js:497
 msgid "The address could not be read."
 msgstr "The address could not be read."
 
-#: js/ContactEditDialog.js:530
+#: js/ContactEditDialog.js:514
 msgid "End token mode"
 msgstr "End token mode"
 
-#: js/ContactGrid.js:101 js/Model.js:163
-msgid "Type"
-msgstr "Type"
+#: js/Addressbook.js:22
+msgid "New Contact"
+msgstr "New Contact"
 
-#: js/ContactGrid.js:102
-msgid "Tags"
-msgstr "Tags"
+#: js/Addressbook.js:32 js/Model.js:101 js/Model.js:244 js/Model.js:295
+msgid "Addressbook"
+msgid_plural "Addressbooks"
+msgstr[0] "Addressbook"
+msgstr[1] "Addressbooks"
 
-#: js/ContactGrid.js:115
-msgid "Full Name"
-msgstr "Full Name"
+#: js/Addressbook.js:67 js/Addressbook.js:102 js/ListRoleGridPanel.js:58
+msgid "ID"
+msgstr "ID"
 
-#: js/ContactGrid.js:125
-msgid "Postalcode"
-msgstr "Postalcode"
+#: js/Addressbook.js:74 js/Addressbook.js:109 js/Model.js:19 js/Model.js:260
+#: js/ListGrid.js:102 js/ListEditDialog.js:83 js/ListEditDialog.js:166
+#: js/ListRoleGridPanel.js:65
+msgid "Name"
+msgstr "Name"
 
-#: js/ContactGrid.js:127
-msgid "Street (private)"
-msgstr "Street (private)"
+#: js/ListMemberFilterModel.js:37
+msgid "Member of List"
+msgstr "Member of List"
 
-#: js/ContactGrid.js:128
-msgid "City (private)"
-msgstr "City (private)"
+#: js/Model.js:34
+msgid "Street (Company Address)"
+msgstr "Street (Company Address)"
 
-#: js/ContactGrid.js:129
-msgid "Region (private)"
-msgstr "Region (private)"
+#: js/Model.js:35
+msgid "Street 2 (Company Address)"
+msgstr "Street 2 (Company Address)"
 
-#: js/ContactGrid.js:130
-msgid "Postalcode (private)"
-msgstr "Postalcode (private)"
+#: js/Model.js:36
+msgid "City (Company Address)"
+msgstr "City (Company Address)"
 
-#: js/ContactGrid.js:131
-msgid "Country (private)"
-msgstr "Country (private)"
+#: js/Model.js:37
+msgid "Region (Company Address)"
+msgstr "Region (Company Address)"
 
-#: js/ContactGrid.js:132 Export/Pdf.php:52
-msgid "Email"
-msgstr "Email"
+#: js/Model.js:38
+msgid "Postal Code (Company Address)"
+msgstr "Postal Code (Company Address)"
 
-#: js/ContactGrid.js:136
-msgid "Car phone"
-msgstr "Car phone"
+#: js/Model.js:39
+msgid "Country (Company Address)"
+msgstr "Country (Company Address)"
 
-#: js/ContactGrid.js:137
-msgid "Pager"
-msgstr "Pager"
+#: js/Model.js:40
+msgid "Longitude (Company Address)"
+msgstr "Longitude (Company Address)"
 
-#: js/ContactGrid.js:141
-msgid "Email (private)"
-msgstr "Email (private)"
+#: js/Model.js:41
+msgid "Latitude (Company Address)"
+msgstr "Latitude (Company Address)"
 
-#: js/ContactGrid.js:143
-msgid "URL (private)"
-msgstr "URL (private)"
+#: js/Model.js:43
+msgid "Street (Private Address)"
+msgstr "Street (Private Address)"
 
-#: js/ContactGrid.js:144
-msgid "Note"
-msgstr "Note"
+#: js/Model.js:44
+msgid "Street 2 (Private Address)"
+msgstr "Street 2 (Private Address)"
 
-#: js/ContactGrid.js:145
-msgid "Timezone"
-msgstr "Timezone"
+#: js/Model.js:45
+msgid "City (Private Address)"
+msgstr "City (Private Address)"
 
-#: js/ContactGrid.js:146
-msgid "Geo"
-msgstr "Geo"
-
-#: js/ContactGrid.js:157 js/ContactGrid.js:158 js/ContactGrid.js:159
-#, python-brace-format
-msgid "Export {0}"
-msgid_plural "Export {0}"
-msgstr[0] "Export {0}"
-msgstr[1] "Export {0}"
-
-#: js/ContactGrid.js:168
-msgid "Export as PDF"
-msgstr "Export as PDF"
-
-#: js/ContactGrid.js:175
-msgid "Export as CSV"
-msgstr "Export as CSV"
-
-#: js/ContactGrid.js:182
-msgid "Export as ODS"
-msgstr "Export as ODS"
-
-#: js/ContactGrid.js:189
-msgid "Export as XLS"
-msgstr "Export as XLS"
-
-#: js/ContactGrid.js:196
-msgid "Export as DOC"
-msgstr "Export as DOC"
-
-#: js/ContactGrid.js:203
-msgid "Export as ..."
-msgstr "Export as ..."
-
-#: js/ContactGrid.js:215
-msgid "Import contacts"
-msgstr "Import contacts"
-
-#: js/ContactGrid.js:304
-msgid "Contact of a user account"
-msgstr "Contact of a user account"
-
-#: js/ContactGrid.js:304 js/Model.js:95 js/Model.js:134
-#: js/ContactFilterModel.js:35
-msgid "Contact"
-msgid_plural "Contacts"
-msgstr[0] "Contact"
-msgstr[1] "Contacts"
-
-#: js/CardDAVContainerPropertiesHookField.js:35
-msgid "CardDAV URL"
-msgstr "CardDAV URL"
-
-#: js/ListMemberFilterModel.js:37
-msgid "Member of List"
-msgstr "Member of List"
-
-#: js/Model.js:19
-msgid "Name"
-msgstr "Name"
-
-#: js/Model.js:34
-msgid "Street (Company Address)"
-msgstr "Street (Company Address)"
-
-#: js/Model.js:35
-msgid "Street 2 (Company Address)"
-msgstr "Street 2 (Company Address)"
-
-#: js/Model.js:36
-msgid "City (Company Address)"
-msgstr "City (Company Address)"
-
-#: js/Model.js:37
-msgid "Region (Company Address)"
-msgstr "Region (Company Address)"
-
-#: js/Model.js:38
-msgid "Postal Code (Company Address)"
-msgstr "Postal Code (Company Address)"
-
-#: js/Model.js:39
-msgid "Country (Company Address)"
-msgstr "Country (Company Address)"
-
-#: js/Model.js:43
-msgid "Street (Private Address)"
-msgstr "Street (Private Address)"
-
-#: js/Model.js:44
-msgid "Street 2 (Private Address)"
-msgstr "Street 2 (Private Address)"
-
-#: js/Model.js:45
-msgid "City (Private Address)"
-msgstr "City (Private Address)"
-
-#: js/Model.js:46
-msgid "Region (Private Address)"
-msgstr "Region (Private Address)"
+#: js/Model.js:46
+msgid "Region (Private Address)"
+msgstr "Region (Private Address)"
 
 #: js/Model.js:47
 msgid "Postal Code (Private Address)"
@@ -478,47 +493,102 @@ msgstr "Private Communication"
 msgid "Web (private)"
 msgstr "Web (private)"
 
-#: js/Model.js:95 js/ContactGridDetailsPanel.js:62
+#: js/Model.js:97 js/ContactGridDetailsPanel.js:62
 msgid "Contacts"
 msgstr "Contacts"
 
-#: js/Model.js:99 js/Model.js:208
+#: js/Model.js:101 js/Model.js:244 js/Model.js:295
 msgid "Addressbooks"
 msgstr "Addressbooks"
 
-#: js/Model.js:134
+#: js/Model.js:154 js/Model.js:254
 msgid "User Account"
 msgstr "User Account"
 
-#: js/Model.js:137
+#: js/Model.js:157
 msgid "Quick Search"
 msgstr "Quick Search"
 
-#: js/Model.js:164
+#: js/Model.js:184 js/ListGrid.js:100 js/ContactGrid.js:275
+msgid "Type"
+msgstr "Type"
+
+#: js/Model.js:185 js/Model.js:262
 msgid "Last Modified Time"
 msgstr "Last Modified Time"
 
-#: js/Model.js:165
+#: js/Model.js:186 js/Model.js:263
 msgid "Last Modified By"
 msgstr "Last Modified By"
 
-#: js/Model.js:166
+#: js/Model.js:187 js/Model.js:264
 msgid "Creation Time"
 msgstr "Creation Time"
 
-#: js/Model.js:167
+#: js/Model.js:188 js/Model.js:265
 msgid "Created By"